# HG changeset patch # User Dino Kosic <44305363+kraskrom@users.noreply.github.com> # Date 1733135599 -3600 # Node ID 4b2de1a0fbf9ce02e3341dc7f3fb35369530255c # Parent c325749651d1162223ce156db6eef44007e903bd Extend HMI:JsonTable, create Edit CSV POU, create example, add some doc (#41) * Extend Jsontable widget, create Edit CSV POU, create example for both * Add doc for HMI:Image and HMI:JsonTable * Expand example to include HMI:Image diff -r c325749651d1 -r 4b2de1a0fbf9 doc/svghmi/widgets.rst --- a/doc/svghmi/widgets.rst Thu Nov 28 14:46:50 2024 +0100 +++ b/doc/svghmi/widgets.rst Mon Dec 02 11:33:19 2024 +0100 @@ -50,13 +50,39 @@ ----------- +HMI:Image +--------- +| It is an SVG Image element with label +| ``HMI:Image:variable`` +| where ``variable`` contains the HTTP GET path to the image it should display. + + HMI:Input --------- -HMI:Jsontable +HMI:JsonTable ------------- - +| It is a SVG (group) element with label +| ``HMI:JsonTable:path@notify_var@range_var@position_var@visible_var@filter_var`` +| where: +* ``path`` is HTTP POST path used to fetch JSON list response +* ``range_var`` is a variable containing number of elements in a list +* ``position_var`` is a variable containing index of the first element from the list shown in the table +* ``visible_var`` is a variable with number of elements to be displayed in the table +* ``filter_var`` is a variable containing the string which is posted in a request for JSON response, and it's used to filter the results +| On render request, the widget does a POST request to the path. That request contains all the variables listed above. +| Handler for the request should be written in such manner that it returns a JSON list containing defined number of elements. +| Elements are objects with keys and values defined by a user. +| SVG element itself contains another element group ``data``. All of the elements in that group are also groups, with name ``[i]`` where ``i`` goes from 0 to ``visible_var - 1``. Only the last one is actually created, others are just copy of it. +| Elements from ``data`` can be placed in such manner to depict table rows. +| Elements in ``[i]`` can emulate columns. They can be: +* HMI:TextStyleList (label dictates the style and text content) +* Image (label dictates path to the image) +* Other SVG elements +| Any of the above elements can have label with ``onClick[acknowledge]=var`` which means that on a click on such element, the same POST request is invoked, but among ``options`` posted now there is a variable ``onClick[acknowledge]`` and its value is ``var`` +| Beside ``data`` element, there can also be ``action_reset`` group element with similar behavior as stated above: click invokes POST with ``action_reset`` among ``options``. +| If it's needed to display images in a table, and those images should be loaded dynamically, one may use GET handler to load and return appropriate image. HMI:Jump -------- diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/beremiz.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/beremiz.xml Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,5 @@ + + + + + diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/plc.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/plc.xml Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,337 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FILENAME + + + + + + + CSV_ROWIDX + + + + + + + CSV_COLIDX + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + CSV_ACK + + + + + + + + + + + + + CSV_RES + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + FILENAME + + + + + + + CSV_ROWIDX + + + + + + + CSV_COLIDX + + + + + + + CONTENT + + + + + + + SAVE + + + + + + + + + + + SEC_ACK + + + + + + + + + + + + + SEC_RES + + + + + + + + + + + + + + + + + + diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/project_files/beremiz.png Binary file exemples/svghmi_csv_json_img_table/project_files/beremiz.png has changed diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/project_files/file.png Binary file exemples/svghmi_csv_json_img_table/project_files/file.png has changed diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/project_files/folder.png Binary file exemples/svghmi_csv_json_img_table/project_files/folder.png has changed diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/project_files/test1.csv --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/project_files/test1.csv Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,4 @@ +id,value +1,First +2,Second + diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/project_files/test2.csv --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/project_files/test2.csv Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,4 @@ +id,name,value +1,x,15 +2,11,11 + diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/py_ext_0@py_ext/baseconfnode.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/py_ext_0@py_ext/baseconfnode.xml Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,2 @@ + + diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/py_ext_0@py_ext/pyfile.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/py_ext_0@py_ext/pyfile.xml Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,144 @@ + + + + + + + + + 0 and extra[0] != "": + fFiles = [fl for fl in FileList if extra[0] in fl] + else: + fFiles = FileList[:] + new_range = len(fFiles) + delta = new_range - visible + new_position = 0 if delta <= 0 else delta if old_position > delta else old_position + new_visible = new_range if delta <= 0 else visible + + visible_files = [] + for desc in fFiles[new_position:new_position + new_visible]: + visible_files.append(desc) + + return new_range, new_position, visible_files + + +]]> + + + + + + + + + + + + + + diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/baseconfnode.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/baseconfnode.xml Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,2 @@ + + diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/confnode.xml --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/confnode.xml Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,2 @@ + + diff -r c325749651d1 -r 4b2de1a0fbf9 exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/svghmi.svg --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/exemples/svghmi_csv_json_img_table/svghmi_0@svghmi/svghmi.svg Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,1802 @@ + + + +image/svg+xmlnumber7418529630Esc+/-information.QWERTYUIOPASDFGHJKLZXCVBNM.:;,123456789-0+EscCapsLockCapsLocktextShiftShiftShiftShiftinformationLists of itemsHMI:TextStylListLists of itemsHMI:ListUpdatereset88888888blahblahHMI:JsonTablerangepositionfilterIn this example, JsonTable widget is used as a list of CSV files.JSON data is exchanged with python code in py_ext_0 using HTTP POSTHMI:JsonTable888Row#Column#Content: + +value diff -r c325749651d1 -r 4b2de1a0fbf9 py_ext/pous.xml --- a/py_ext/pous.xml Thu Nov 28 14:46:50 2024 +0100 +++ b/py_ext/pous.xml Mon Dec 02 11:33:19 2024 +0100 @@ -1657,6 +1657,810 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 'CSVWrInt("' + + + + + + + FILE_NAME + + + + + + + '",' + + + + + + + ROW + + + + + + + ',' + + + + + + + COLUMN + + + + + + + ',"' + + + + + + + CONTENT + + + + + + + '",' + + + + + + + SAVE + + + + + + + ')' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OLDCODE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + OLDCODE + + + + + + + OLDCODE + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + pyext_csv_update + + + + + + + + + + + + + ACK + + + + + + + + + + + + + RESULT + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1 + + + + + + + '#' + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff -r c325749651d1 -r 4b2de1a0fbf9 py_ext/py_ext.py --- a/py_ext/py_ext.py Thu Nov 28 14:46:50 2024 +0100 +++ b/py_ext/py_ext.py Mon Dec 02 11:33:19 2024 +0100 @@ -24,7 +24,6 @@ # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. - import os from POULibrary import POULibrary from py_ext.PythonFileCTNMixin import PythonFileCTNMixin @@ -46,7 +45,7 @@ if data is None: data = list() try: - csvfile = open(fname, 'rb') + csvfile = open(fname, 'rt', encoding='utf-8') except IOError: return "#FILE_NOT_FOUND" try: @@ -55,12 +54,12 @@ reader = csv.reader(csvfile, dialect) for row in reader: data.append(row) - except csv.Error: + except csv.Error as e: return "#CSV_ERROR" finally: csvfile.close() csv_int_files[fname] = data - + try: row = data[rowidx] except IndexError: @@ -83,14 +82,14 @@ if entry is None: data = dict() try: - csvfile = open(fname, 'rb') + csvfile = open(fname, 'rt', encoding='utf-8') except IOError: return "#FILE_NOT_FOUND" try: dialect = csv.Sniffer().sniff(csvfile.read(1024)) csvfile.seek(0) reader = csv.reader(csvfile, dialect) - headers = dict([(name, index) for index, name in enumerate(reader.next()[1:])]) + headers = dict([(name, index) for index, name in enumerate(reader.__next__()[1:])]) for row in reader: data[row[0]] = row[1:] except csv.Error: @@ -100,7 +99,7 @@ csv_str_files[fname] = (headers, data) else: headers, data = entry - + try: row = data[rowname] except KeyError: @@ -116,6 +115,56 @@ except IndexError: return "#COL_NOT_FOUND" + +csv_int_files = {} +def CSVWrInt(fname, rowidx, colidx, content, save): + \"\"\" + Update value at row/column pointed by integer indexes + Assumes data starts at first row and first column, no headers. + \"\"\" + if save > 0: + global csv_int_files + data = csv_int_files.get(fname, None) + if data is None: + data = list() + try: + csvfile = open(fname, 'rt', encoding='utf-8') + except IOError: + return "#FILE_NOT_FOUND" + try: + dialect = csv.Sniffer().sniff(csvfile.read(1024)) + csvfile.seek(0) + reader = csv.reader(csvfile, dialect) + for row in reader: + data.append(row) + except csv.Error as e: + return "#CSV_ERROR" + finally: + csvfile.close() + csv_int_files[fname] = data + + try: + row = data[rowidx] + except IndexError: + return "#ROW_NOT_FOUND" + + try: + row[colidx] = content + except IndexError: + return "#COL_NOT_FOUND" + + wfile = open(fname, 'rt+', encoding='utf-8') + wdialect = csv.Sniffer().sniff(wfile.read(1024)) + wfile.seek(0) + writer = csv.writer(wfile, wdialect) + for row in data: + writer.writerow(row) + wfile.truncate() + wfile.close() + + return "#SUCCESS" + + def pyext_csv_reload(): global csv_int_files, csv_str_files csv_int_files.clear() @@ -123,6 +172,7 @@ """ + class PythonLibrary(POULibrary): def GetLibraryPath(self): return paths.AbsNeighbourFile(__file__, "pous.xml") @@ -157,8 +207,7 @@ ("runtime_00_pyext.py", open(runtimefile_path, "rb"))) - class PythonFile(PythonFileCTNMixin): def GetIconName(self): - return "Pyfile" + return "Pyfile" \ No newline at end of file diff -r c325749651d1 -r 4b2de1a0fbf9 svghmi/analyse_widget.xslt --- a/svghmi/analyse_widget.xslt Thu Nov 28 14:46:50 2024 +0100 +++ b/svghmi/analyse_widget.xslt Mon Dec 02 11:33:19 2024 +0100 @@ -546,6 +546,20 @@ where to find HMI_NODEs whose HMI_CLASS is class_name + + + + + + If Image widget is a svg:image element, then href content is replaced by + + value of given variable. + + + + Image display + + @@ -589,7 +603,7 @@ - Documentation to be written. see svghmi exemple. + Documentation to be written. see svghmi example. diff -r c325749651d1 -r 4b2de1a0fbf9 svghmi/gen_index_xhtml.xslt --- a/svghmi/gen_index_xhtml.xslt Thu Nov 28 14:46:50 2024 +0100 +++ b/svghmi/gen_index_xhtml.xslt Mon Dec 02 11:33:19 2024 +0100 @@ -4816,6 +4816,76 @@ } + + + + + + If Image widget is a svg:image element, then href content is replaced by + + value of given variable. + + + + Image display + + + + class + ImageWidget + extends Widget{ + + frequency = 5; + + dispatch(value, oldval, index) { + + if (index == 0) { + + this.given_url = value; + + this.ready = true; + + this.request_animate(); + + } + + } + + } + + + + + + + + + /disabled + + + + + + + given_url: "", + + ready: false, + + animate: function(){ + + this.element.setAttribute('href', this.given_url); + + }, + + + + init: function() { + + this.animate(); + + }, + + @@ -5042,7 +5112,7 @@ - Documentation to be written. see svghmi exemple. + Documentation to be written. see svghmi example. @@ -5247,7 +5317,7 @@ - JsonTable : missplaced '=' or inconsistent names in Json data expressions. + JsonTable : misplaced '=' or inconsistent names in Json data expressions. @@ -5320,7 +5390,7 @@ - Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignement following value expression in label. + Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignment following value expression in label. { @@ -5352,6 +5422,16 @@ + + + + id(" + + ").setAttribute('href', String( + + )); + + @@ -10142,8 +10222,6 @@ let widget = hmi_widgets[id]; - if(widget.curr_value != undefined) return; - widget.do_init(); }); diff -r c325749651d1 -r 4b2de1a0fbf9 svghmi/widget_image.ysl2 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/svghmi/widget_image.ysl2 Mon Dec 02 11:33:19 2024 +0100 @@ -0,0 +1,35 @@ +// widget_image.ysl2 + +widget_desc("Image") { + longdesc + || + If Image widget is a svg:image element, then href content is replaced by + value of given variable. + || + + shortdesc > Image display +} + +widget_class("Image") + || + frequency = 5; + dispatch(value, oldval, index) { + if (index == 0) { + this.given_url = value; + this.ready = true; + this.request_animate(); + } + } + || + +widget_defs("Image") { + | given_url: "", + | ready: false, + | animate: function(){ + | this.element.setAttribute('href', this.given_url); + | }, + | + | init: function() { + | this.animate(); + | }, +} diff -r c325749651d1 -r 4b2de1a0fbf9 svghmi/widget_jsontable.ysl2 --- a/svghmi/widget_jsontable.ysl2 Thu Nov 28 14:46:50 2024 +0100 +++ b/svghmi/widget_jsontable.ysl2 Mon Dec 02 11:33:19 2024 +0100 @@ -6,7 +6,7 @@ Send given variables as POST to http URL argument, spread returned JSON in SVG sub-elements of "data" labeled element. - Documentation to be written. see svghmi exemple. + Documentation to be written. see svghmi example. || shortdesc > Http POST variables, spread JSON back @@ -113,7 +113,7 @@ param "label"; // compute javascript expressions to access JSON data - // desscribed in given svg element's "label" + // described in given svg element's "label" // knowing that parent element already has given "expressions". choose { @@ -128,7 +128,7 @@ when "contains($suffix,'=')" { const "name", "substring-before($suffix,'=')"; if "$expr/@name[. != $name]" - error > JsonTable : missplaced '=' or inconsistent names in Json data expressions. + error > JsonTable : misplaced '=' or inconsistent names in Json data expressions. attrib "name" value "$name"; attrib "content" > «$expr/@content»«substring-after($suffix,'=')» } @@ -176,7 +176,7 @@ when "count($from_textstylelist) > 0" { const "content_expr", "$expressions/expression[2]/@content"; if "string-length($content_expr) = 0 or $expressions/expression[2]/@name != 'textContent'" - error > Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignement following value expression in label. + error > Clones (svg:use) in JsonTable Widget pointing to a HMI:TextStyleList widget or item must have a "textContent=.someVal" assignment following value expression in label. | { | let elt = id("«@id»"); | elt.textContent = String(«$content_expr»); @@ -189,8 +189,13 @@ } } - -// only labels comming from Json widget are counted in +template "svg:image", mode="json_table_elt_render" { + param "expressions"; + const "value_expr", "$expressions/expression[1]/@content"; + | id("«@id»").setAttribute('href', String(«$value_expr»)); +} + +// only labels coming from Json widget are counted in def "func:filter_non_widget_label" { param "elt"; param "widget_elts";