diff --git a/jpcore/template.py b/jpcore/template.py
index bcd487af..f00c8b36 100644
--- a/jpcore/template.py
+++ b/jpcore/template.py
@@ -3,8 +3,6 @@
@author: wf
"""
-
-
class Context:
"""
legacy context handler, encapsulates context
@@ -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}\n{self.as_script_srcs(indent)}"
+ html+=f"{indent}\n"
+ return html
+
+ def as_script_src(self,file_name:str,indent:str=" "):
+ src= f"{indent}\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
@@ -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)
diff --git a/justpy/__init__.py b/justpy/__init__.py
index 0a93beca..82fbe557 100644
--- a/justpy/__init__.py
+++ b/justpy/__init__.py
@@ -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"
diff --git a/justpy/templates/js/justpy_core.js b/justpy/templates/js/justpy_core.js
index 0b33672a..165ccaf9 100644
--- a/justpy/templates/js/justpy_core.js
+++ b/justpy/templates/js/justpy_core.js
@@ -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;
@@ -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;
@@ -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);
+ }
+ }
}
\ No newline at end of file
diff --git a/justpy/templates/main.html b/justpy/templates/main.html
index d63ac391..4d8d03c4 100644
--- a/justpy/templates/main.html
+++ b/justpy/templates/main.html
@@ -1,25 +1,8 @@
{%- for file_name in options.component_file_list %}
{%- endfor %}
-
-{%- for file_name in ["justpy_core","event_handler","html_component","quasar_component",
- "chartjp","aggrid","iframejp","deckgl","altairjp","plotlyjp","bokehjp","katexjp","editorjp"] %}
-
-{%- endfor %}
-
\ No newline at end of file
diff --git a/tests/test_template.py b/tests/test_template.py
index 9b915c7c..24a3e0d0 100644
--- a/tests/test_template.py
+++ b/tests/test_template.py
@@ -6,17 +6,14 @@
from tests.basetest import Basetest
from jpcore.template import Context
-
class TestTemplate(Basetest):
"""
Tests template handling
"""
-
- def test_javascript(self):
- """
- test javascript generation
- """
- context_dict = {
+
+ def setUp(self, debug=False, profile=True):
+ Basetest.setUp(self, debug=debug, profile=profile)
+ self.context_dict = {
"html": "",
"justpy_dict": '[{"attrs": {}, "id": null, "vue_type": "html_component", '
'"show": true, "events": [], "event_modifiers": {}, "classes": '
@@ -61,14 +58,30 @@ def test_javascript(self):
"request": None,
"use_websockets": "true",
}
- context_obj = Context(context_dict)
- js = context_obj.as_javascript()
+ self.context_obj = Context(self.context_dict)
+
+ def test_html(self):
+ """
+ test html generation
+ """
+ html=self.context_obj.as_html_lines()
+ print(html)
+
+ def test_javascript(self):
+ """
+ test javascript generation
+ """
+ js = self.context_obj.as_javascript_constructor()
debug = True
if debug:
print(js)
for param in [
"window",
+ "page_id",
"title",
+ "use_websockets",
+ "redirect",
+ "display_url",
"page_ready",
"result_ready",
"reload_interval",