Skip to content

Commit

Permalink
moves javascript code from main.html to justpy_core.js and uses Conte…
Browse files Browse the repository at this point in the history
…xt class to generate html and javascript code

to improve #479
  • Loading branch information
WolfgangFahl committed Sep 8, 2022
1 parent 9687caa commit cf0a752
Show file tree
Hide file tree
Showing 5 changed files with 344 additions and 252 deletions.
81 changes: 72 additions & 9 deletions jpcore/template.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@
@author: wf
"""


class Context:
"""
legacy context handler, encapsulates context
Expand All @@ -19,19 +17,87 @@ def __init__(self, context_dict: dict):
"""
self.context_dict = context_dict
self.page_options = PageOptions(context_dict.get("page_options", {}))
self.use_websockets_js=self.context_dict.get("use_websockets","true")
self.page_id_js=context_dict["page_id"]
self.title_js=self.get_js_option("title", "JustPy")
self.redirect_js=self.get_js_option("redirect","")
self.display_url_js=self.get_js_option("display_url","")
justpy_dict_js=str(self.context_dict.get("justpy_dict","[]"))
self.justpy_dict_js=justpy_dict_js.replace('</' + 'script>', '</" + "script>')

def get_js_option(self,key,default_value):
"""
get the page_option with the given key as javascript using the
default value in case the value is not set or none
"""
js_option=self.page_options.page_options_dict.get(key,default_value)
if js_option is None:
js_option=default_value
return js_option

def as_html_lines(self,indent:str=" "):
"""
generate the html lines for justpy to work
"""
html=self.as_script_src("justpy_core")
html+=f"""{indent}<script>
{indent} var page_id = {self.page_id_js};
{indent} var use_websockets = {self.use_websockets_js};
{indent} var justpyComponents = {self.justpy_dict_js};
"""
html+=self.as_javascript_constructor(indent+" ")
html+=f"\n{indent}</script>\n{self.as_script_srcs(indent)}"
html+=f"{indent}<script>\n{self.as_javascript_setup(indent)}\n{indent}</script>\n"
return html

def as_script_src(self,file_name:str,indent:str=" "):
src= f"{indent}<script src='/templates/js/{file_name}.js'></script>\n"
return src

def as_script_srcs(self,indent:str=" "):
"""
generate a list of javascript files to be imported
"""
srcs = ""
for file_name in [
"event_handler",
"html_component",
"quasar_component",
"chartjp",
"aggrid",
"iframejp",
"deckgl",
"altairjp",
"plotlyjp",
"bokehjp",
"katexjp",
"editorjp",
]:
srcs +=self.as_script_src(file_name,indent)
return srcs

def as_javascript_setup(self,indent):
"""
generate the java script setup code
"""
js=f"{indent} justpy_core.setup();"
return js

def as_javascript(self):
def as_javascript_constructor(self,indent:str=" "):
"""
generate my initial JavaScript
"""
title = self.page_options.get_title()
debug = str(self.page_options.get_debug()).lower()
page_ready = str(self.page_options.get_page_ready()).lower()
result_ready = str(self.page_options.get_result_ready()).lower()
reload_interval_ms = self.page_options.get_reload_interval_ms()
javascript = f"""let justpy_core=new JustpyCore(
javascript = f"""{indent}let justpy_core=new JustpyCore(
this, // window
'{title}', // title
page_id, // page_id
'{self.title_js}', // title
use_websockets, // use_websockets
'{self.redirect_js}', // redirect
'{self.display_url_js}', // display_url
{page_ready}, // page_ready
{result_ready}, // result_ready
{reload_interval_ms}, // reload_interval
Expand All @@ -49,9 +115,6 @@ def __init__(self, page_options_dict: dict):
self.page_options_dict = page_options_dict
self.events = page_options_dict["events"]

def get_title(self):
return self.page_options_dict.get("title", "JustPy")

def get_debug(self):
return self.page_options_dict.get("debug", False)

Expand Down
2 changes: 1 addition & 1 deletion justpy/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""JustPy is an object-oriented, component based, high-level Python Web Framework that requires no front-end programming"""
from .justpy import *

__version__ = "0.7.2"
__version__ = "0.8.0"
249 changes: 248 additions & 1 deletion justpy/templates/js/justpy_core.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@
* Legacy global variables
*/
var comp_dict = {}; // Hold components for running methods
var websocket_id = '';
var websocket_ready = false;
var web_socket_closed = false;

// the global app object
var app1=null; // will be initialized with a Vue component in justpy_core.setup()
var msg=null; // declare msg - beware aggrid also does this!
var socket=null;

// @TODO make configurable and put into object oriented part
let reload_timeout = 2000;
Expand Down Expand Up @@ -78,9 +85,17 @@
class JustpyCore {

// create a JustpyCore instance
constructor(window,title,page_ready,result_ready,reload_interval_ms,debug) {
constructor(window,page_id,title,use_websockets,redirect,display_url,page_ready,result_ready,reload_interval_ms,debug) {
this.window=window;
this.page_id=page_id;
this.setTitle(title);
this.use_websockets=use_websockets;
if (redirect) {
location.href = redirect;
}
if (display_url) {
window.history.pushState("", "", display_url);
}
this.page_ready=page_ready;
this.result_ready=result_ready;
this.reload_interval_ms=reload_interval_ms;
Expand All @@ -93,4 +108,236 @@ class JustpyCore {
this.title=title;
// pass
}

// setup the core functionality
setup() {
if (this.use_websockets) {
this.setupWebSocket();
} else {
this.setupNoWebSocket();
}
this.createApp1();
}

// create the global app1 Vue object
createApp1() {
app1 = new Vue({
el: '#components',
data: {
justpyComponents: justpyComponents
},
render: function (h) {
var comps = [];
for (var i = 0; i < this.justpyComponents.length; i++) {
if (this.justpyComponents[i].show) {
comps.push(h(this.justpyComponents[i].vue_type, {
props: {
jp_props: this.justpyComponents[i]
}
}))
}
}
return h('div', {}, comps);
}
});
}

// prepare WebSocket handling
setupWebSocket() {
console.log(location.protocol + ' Domain: ' + document.domain);
if (location.protocol === 'https:') {
var protocol_string = 'wss://'
} else {
protocol_string = 'ws://'
}
var ws_url = protocol_string + document.domain;
if (location.port) {
ws_url += ':' + location.port;
}
socket = new WebSocket(ws_url);

socket.addEventListener('open', function (event) {
console.log('Websocket opened');
socket.send(JSON.stringify({'type': 'connect', 'page_id': page_id}));
});

socket.addEventListener('error', function (event) {
reload_site();
});

socket.addEventListener('close', function (event) {
console.log('Websocket closed');
web_socket_closed = true;
reload_site()
});

socket.addEventListener('message', function (event) {
msg = JSON.parse(event.data);
if (justpy_core.debug) {
console.log('Message received from server ', msg);
console.log(event);
}
let e = {};
switch (msg.type) {
case 'page_update':
if (msg.page_options.redirect) {
location.href = msg.page_options.redirect;
break;
}
if (msg.page_options.open) {
window.open(msg.page_options.open, '_blank');
}
if (msg.page_options.display_url !== null)
window.history.pushState("", "", msg.page_options.display_url);
document.title = msg.page_options.title;
if (msg.page_options.favicon) {
var link = document.querySelector("link[rel*='icon']") || document.createElement('link');
link.type = 'image/x-icon';
link.rel = 'shortcut icon';
if (msg.page_options.favicon.startsWith('http')) {
link.href = msg.page_options.favicon;
} else {
link.href = '{{ url_for(options.static_name, path='/') }}' + msg.page_options.favicon;
}
document.getElementsByTagName('head')[0].appendChild(link);
}

app1.justpyComponents = msg.data;

break;
case 'page_mode_update':
Quasar.Dark.set(msg.dark);
break;
case 'websocket_update':
websocket_id = msg.data;
websocket_ready = true;
if (justpy_core.page_ready) {
e = {
'event_type': 'page_ready',
'visibility': document.visibilityState,
'page_id': page_id,
'websocket_id': websocket_id
};
send_to_server(e, 'page_event', false);
}
break;
case 'component_update':
// update just specific component on the page
comp_replace(msg.data, app1.justpyComponents);
break;
case 'run_javascript':
// let js_result = eval(msg.data);
let js_result;

function eval_success() {
e = {
'event_type': 'result_ready',
'visibility': document.visibilityState,
'page_id': page_id,
'websocket_id': websocket_id,
'request_id': msg.request_id,
'result': js_result //JSON.stringify(js_result)
};
if (justpy_core.result_ready) {
if (msg.send) send_to_server(e, 'page_event', false);
}
}

const jsPromise = (new Promise(function () {
js_result = eval(msg.data);
})).then(eval_success());

break;
case 'run_method':
// await websocket.send_json({'type': 'run_method', 'data': command, 'id': self.id})
eval('comp_dict[' + msg.id + '].' + msg.data);
break;
case 'draw_crosshair':
// Remove previous crosshairs
for (let i = 0; i < crosshairs.length; i++) {
crosshairs[i].destroy();
}
crosshairs = [];
for (let i = 0; i < msg.data.length; i++) {
const m = msg.data[i];
const chart_index = document.getElementById(m.id.toString()).getAttribute('data-highcharts-chart');
const chart = Highcharts.charts[chart_index];
const point = chart.series[m.series].data[m.point];
draw_crosshair(chart, chart.series[m.series], point);
}
break;
case 'select_point':
for (let i = 0; i < msg.data.length; i++) {
const m = msg.data[i];
const chart_index = document.getElementById(m.id.toString()).getAttribute('data-highcharts-chart');
const chart = Highcharts.charts[chart_index];
chart.series[m.series].data[m.point].select();
}
break;
case 'chart_update':
const chart = cached_graph_def['chart' + msg.id];
chart.update(msg.data);
break;
case 'tooltip_update':
// https://github.com/highcharts/highcharts/issues/6824
var chart_on = cached_graph_def['chart' + msg.id];

if (chart_on.options.tooltip.split) {
chart_on.tooltip.tt.attr({
text: msg.data[0]
});

var j = 1;
for (let i = 0; i < chart_on.tooltip.chart.series.length; i++) {
if (chart_on.tooltip.chart.series[i].visible &&
(chart_on.tooltip.chart.series[i].tt != null)) {
chart_on.tooltip.chart.series[i].tt.attr({
text: msg.data[j++]
});

}
}

} else {
chart_on.tooltip.label.attr({
text: msg.data
});
}
break;
default: {

}
}
});
}

setupNoWebSocket() {
window.addEventListener('beforeunload', function (event) {
e = {
'event_type': 'beforeunload',
'page_id': page_id,
};
send_to_server(e);
});
}

// setup the reload interval
setupReloadInterval() {
if (this.reload_interval_ms >0) {
setInterval(function () {
$.ajax({
type: "POST",
url: "/zzz_justpy_ajax",
data: JSON.stringify({
'type': 'event',
'event_data': {'event_type': 'page_update', 'page_id': this.page_id}
}),
success: function (msg) {
if (msg) app1.justpyComponents = msg.data;
},
dataType: 'json'
});
}, this.reload_interval_ms);
}
}
}
Loading

0 comments on commit cf0a752

Please sign in to comment.