From 8fdc1a1599d51a3828b88f67dde15e25cb8bba48 Mon Sep 17 00:00:00 2001 From: Tomas Peterka <tomas.peterka@nexedi.com> Date: Tue, 9 Jan 2018 18:22:19 +0100 Subject: [PATCH] [hal_json+renderjs] Implement "stat" line for ListBox /reviewed-on https://lab.nexedi.com/nexedi/erp5/merge_requests/528 --- .../ERP5Document_getHateoas.py | 69 ++++- .../erp5_ui_test/FooModule_statMethod.py | 12 + .../erp5_ui_test/FooModule_statMethod.xml | 66 +++++ .../erp5_ui_test/FooModule_statQuantity.py | 6 + .../erp5_ui_test/FooModule_statQuantity.xml | 66 +++++ .../web_page_module/rjs_gadget_erp5_jio_js.js | 46 +-- .../rjs_gadget_erp5_jio_js.xml | 4 +- .../rjs_gadget_erp5_listbox_html.html | 59 ++-- .../rjs_gadget_erp5_listbox_html.xml | 4 +- .../rjs_gadget_erp5_listbox_js.js | 262 +++++++++++------- .../rjs_gadget_erp5_listbox_js.xml | 10 +- .../rjs_gadget_erp5_nojqm_css.css | 38 ++- .../erp5_web_renderjs_ui/erp5css.less.txt | 53 ++-- .../testEmptyListboxWithStat.xml | 58 ++++ .../testEmptyListboxWithStat.zpt | 47 ++++ .../testHideItem.zpt | 15 - .../testStatColumns.xml | 58 ++++ .../testStatColumns.zpt | 73 +++++ .../testStatMethod.xml | 58 ++++ .../testStatMethod.zpt | 74 +++++ .../testStatMissing.xml | 58 ++++ .../testStatMissing.zpt | 53 ++++ .../Zuite_CommonTemplateForRenderjsUi.zpt | 2 +- 23 files changed, 989 insertions(+), 202 deletions(-) create mode 100644 bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.py create mode 100644 bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.xml create mode 100644 bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.py create mode 100644 bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.xml create mode 100644 bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testEmptyListboxWithStat.xml create mode 100644 bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testEmptyListboxWithStat.zpt create mode 100644 bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.xml create mode 100644 bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.zpt create mode 100644 bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.xml create mode 100644 bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.zpt create mode 100644 bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMissing.xml create mode 100644 bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMissing.zpt diff --git a/bt5/erp5_hal_json_style/SkinTemplateItem/portal_skins/erp5_hal_json_style/ERP5Document_getHateoas.py b/bt5/erp5_hal_json_style/SkinTemplateItem/portal_skins/erp5_hal_json_style/ERP5Document_getHateoas.py index 797b7bea1b..09cb3709d0 100644 --- a/bt5/erp5_hal_json_style/SkinTemplateItem/portal_skins/erp5_hal_json_style/ERP5Document_getHateoas.py +++ b/bt5/erp5_hal_json_style/SkinTemplateItem/portal_skins/erp5_hal_json_style/ERP5Document_getHateoas.py @@ -47,10 +47,26 @@ MARKER = [] if REQUEST is None: REQUEST = context.REQUEST - # raise Unauthorized + if response is None: response = REQUEST.RESPONSE + +def toBasicTypes(obj): + """Ensure that obj contains only basic types.""" + if obj is None: + return obj + if isinstance(obj, (bool, int, float, long, str, unicode)): + return obj + if isinstance(obj, (tuple, list)): + return [toBasicTypes(x) for x in obj] + try: + return {toBasicTypes(key): toBasicTypes(obj[key]) for key in obj} + except: + log('Cannot convert {!s} to basic types {!s}'.format(type(obj), obj), level=100) + return obj + + # http://stackoverflow.com/a/13105359 def byteify(string): if isinstance(string, dict): @@ -684,6 +700,8 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key "lines": lines, "default_params": default_params, "list_method": list_method_name, + "show_stat": field.get_value('stat_method') != "" or len(field.get_value('stat_columns')) > 0, + "show_count": field.get_value('count_method') != "", "query": url_template_dict["jio_search_template"] % { "query": make_query({ "query": sql_catalog.buildQuery( @@ -1478,6 +1496,55 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, contents_list.append(contents_item) result_dict['_embedded']['contents'] = contents_list + # Compute statistics if the search issuer was ListBox + # or in future if the stats (SUM) are required by JIO call + source_field_meta_type = source_field.meta_type if source_field is not None else "" + if source_field_meta_type == "ProxyField": + source_field_meta_type = source_field.getRecursiveTemplateField().meta_type + + if source_field is not None and source_field_meta_type == "ListBox": + contents_stat_list = [] + # in case the search was issued by listbox we can provide results of + # stat_method and count_method back to the caller + # XXX: we should check whether they asked for it + stat_method = source_field.get_value('stat_method') + stat_columns = source_field.get_value('stat_columns') + # support only selection_name for stat methods because any `selection` is deprecated + # and should be removed + # Romain wants full backward compatibility so putting `selection` back in parameters + selection_name = source_field.get_value('selection_name') + if selection_name and 'selection_name' not in catalog_kw: + catalog_kw['selection_name'] = selection_name + catalog_kw['selection'] = context.getPortalObject().portal_selections.getSelectionFor(selection_name, REQUEST) + + contents_stat = {} + if len(stat_columns) > 0: + # prefer stat per column (follow original ListBox.py implementation) + for stat_name, stat_script in stat_columns: + contents_stat[stat_name] = getattr(traversed_document, stat_script)(REQUEST=REQUEST, **catalog_kw) + contents_stat_list.append(contents_stat) + elif stat_method != "" and stat_method.getMethodName() != list_method: + # general stat_method is second in priority list - should return dictionary or list of dictionaries + # where all "fields" should be accessible by their "select" name (no "listbox_" prefix) + stat_method_result = getattr(traversed_document, stat_method.getMethodName())(REQUEST=REQUEST, **catalog_kw) + # stat method can return simple dictionary or subscriptable object thus we put it into one-item list + if stat_method_result is not None and not isinstance(stat_method_result, (list, tuple)): + stat_method_result = [stat_method_result, ] + contents_stat_list = toBasicTypes(stat_method_result) or [] + + for contents_stat in contents_stat_list: + for key, value in contents_stat.items(): + if key in editable_field_dict: + contents_stat[key] = renderField( + traversed_document, editable_field_dict[key], listbox_form, value, key=editable_field_dict[key].id + '__sum') + + if len(contents_stat_list) > 0: + result_dict['_embedded']['sum'] = contents_stat_list + + # We should cleanup the selection if it exists in catalog params BUT + # we cannot because it requires escalated Permission.'modifyPortal' so + # the correct solution would be to ReportSection.popReport but unfortunately + # we don't have it anymore because we are asynchronous return result_dict elif mode == 'form': diff --git a/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.py b/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.py new file mode 100644 index 0000000000..a2f361717c --- /dev/null +++ b/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.py @@ -0,0 +1,12 @@ +"""Compute stats from actual Foo Lines on a Foo object""" +column_list = ['getQuantity', 'id'] +result = {c: 0.0 for c in column_list} + +for line in context.contentValues(portal_type="Foo"): + for column in column_list: + value = getattr(line, column) + if callable(value): + value = value() + result[column] = result[column] + float(value) + +return [result, ] diff --git a/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.xml b/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.xml new file mode 100644 index 0000000000..efa6d726e9 --- /dev/null +++ b/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.xml @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="Python Script" module="erp5.portal_type"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>Script_magic</string> </key> + <value> <int>3</int> </value> + </item> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_container</string> </key> + <value> <string>container</string> </value> + </item> + <item> + <key> <string>name_context</string> </key> + <value> <string>context</string> </value> + </item> + <item> + <key> <string>name_m_self</string> </key> + <value> <string>script</string> </value> + </item> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>_params</string> </key> + <value> <string>**kwargs</string> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>FooModule_statMethod</string> </value> + </item> + <item> + <key> <string>portal_type</string> </key> + <value> <string>Python Script</string> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.py b/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.py new file mode 100644 index 0000000000..7aad111814 --- /dev/null +++ b/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.py @@ -0,0 +1,6 @@ +counter = 0 + +for value in context.contentValues(): + counter = counter + int(value.getQuantity()) + +return counter diff --git a/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.xml b/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.xml new file mode 100644 index 0000000000..b0d7234476 --- /dev/null +++ b/bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.xml @@ -0,0 +1,66 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="Python Script" module="erp5.portal_type"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>Script_magic</string> </key> + <value> <int>3</int> </value> + </item> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_container</string> </key> + <value> <string>container</string> </value> + </item> + <item> + <key> <string>name_context</string> </key> + <value> <string>context</string> </value> + </item> + <item> + <key> <string>name_m_self</string> </key> + <value> <string>script</string> </value> + </item> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>_params</string> </key> + <value> <string>selection, **kwargs</string> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>FooModule_statQuantity</string> </value> + </item> + <item> + <key> <string>portal_type</string> </key> + <value> <string>Python Script</string> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.js b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.js index 6a2a8c0915..f8e12b7184 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.js +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.js @@ -178,27 +178,33 @@ ] ) .push(function (catalog_json) { - var data = catalog_json._embedded.contents, - count = data.length, - k, - uri, - item, - result = []; - for (k = 0; k < count; k += 1) { - item = data[k]; - uri = new URI(item._links.self.href); - delete item._links; - result.push({ - id: uri.segment(2), - doc: {}, - value: item - }); - } + var data = catalog_json._embedded.contents || [], + summary = catalog_json._embedded.sum || [], + count = catalog_json._embedded.count; return { - data: { - rows: result, - total_rows: result.length - } + "data": { + "rows": data.map(function (item) { + var uri = new URI(item._links.self.href); + delete item._links; + return { + "id": uri.segment(2), + "doc": {}, + "value": item + }; + }), + "total_rows": data.length + }, + "sum": { + "rows": summary.map(function (item, index) { + return { + "id": '/#summary' + index, // this is obviously wrong. @Romain help please! + "doc": {}, + "value": item + }; + }), + "total_rows": summary.length + }, + "count": count }; }); }) diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.xml index 17adcde780..80760bcd12 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.xml +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.xml @@ -230,7 +230,7 @@ </item> <item> <key> <string>serial</string> </key> - <value> <string>947.45414.13002.10052</string> </value> + <value> <string>963.59331.40212.55432</string> </value> </item> <item> <key> <string>state</string> </key> @@ -248,7 +248,7 @@ </tuple> <state> <tuple> - <float>1449753994.81</float> + <float>1512454358.33</float> <string>UTC</string> </tuple> </state> diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.html b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.html index 85e34c32cf..0c1e25f79c 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.html +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.html @@ -60,18 +60,18 @@ </thead> </table> </script> - + <script id="listbox-hidden-tbody-template" type="text/x-handlebars-template"> <table> <tbody class="tbody"> - {{#each body_value}} + {{#each row_list}} <tr> {{#if ../show_anchor}} <th> <a class="ui-link ui-btn ui-corner-all ui-icon-carat-r ui-btn-icon-notext" href="{{jump}}"></a> </th> {{/if}} - {{#each tr_value}} + {{#each cell_list}} <td> {{#if type}} {{#if editable}} @@ -82,7 +82,7 @@ </a> {{/if}} {{else}} - <a href="{{href}}" class="ui-link">{{text}}</a> + <a href="{{href}}" class="ui-link">{{default}}</a> {{/if}} </td> {{/each}} @@ -101,7 +101,7 @@ <script id="listbox-show-tbody-template" type="text/x-handlebars-template"> <table> <tbody class="tbody"> - {{#each body_value}} + {{#each row_list}} <tr> {{#if ../show_anchor}} <th> @@ -111,7 +111,7 @@ <td> <input value="{{value}}" type="checkbox" checked="true" class="hide_element"> </td> - {{#each tr_value}} + {{#each cell_list}} <td> {{#if type}} {{#if editable}} @@ -122,7 +122,7 @@ </a> {{/if}} {{else}} - <a href="{{href}}" class="ui-link">{{text}}</a> + <a href="{{href}}" class="ui-link">{{default}}</a> {{/if}} </td> {{/each}} @@ -140,19 +140,40 @@ <script id="listbox-tfoot-template" type="text/x-handlebars-template"> <table> <tfoot class="ui-bar-inherit tfoot"> - <th colspan="{{colspan}}"> - <div class="ui-controlgroup ui-controlgroup-horizontal ui-corner-all ui-paging-menu"> - <div class="ui-controlgroup-controls"> - <a class="{{previous_classname}}" data-i18n="Previous" href="{{previous_url}}">Previous</a> - <a class="{{next_classname}}" data-i18n="Next" href="{{next_url}}">Next</a> - <span class="ui-btn ui-disabled" data-i18n="{{record}}">{{record}}</span> - </div> - </div> - </th> + {{#each row_list}} + <tr> + {{#if ../show_anchor}} + <td>Total</td> + {{/if}} + {{#each cell_list}} + <td> + {{#if type}} + <div class="editable_div" data-column="{{column}}" data-line="{{line}}"></div> + {{else}} + {{#if default}} + {{default}} + {{else}} + {{#unless ../../show_anchor }} + {{#if @first}} + Total + {{/if}} + {{/unless}} + {{/if}} + {{/if}} + </td> + {{/each}} + </tr> + {{/each}} </tfoot> </table> </script> + <script id="listbox-nav-template" type="text/x-handlebars-template"> + <a class="{{previous_classname}}" data-i18n="Previous" href="{{previous_url}}">Previous</a> + <a class="{{next_classname}}" data-i18n="Next" href="{{next_url}}">Next</a> + <span class="ui-disabled ui-right" data-i18n="{{record}}">{{record}}</span> + </script> + <script id="listbox-template" type="text/x-handlebars-template"> <div class="ui-table-header ui-header ui-bar-c ui-corner-all"> <h1 data-i18n="{{title}}" class="ui-title ui-override-theme">{{title}}<span> <span class="listboxloader ui-icon-spinner ui-btn-icon-left"></span></span></h1> @@ -165,11 +186,11 @@ <tbody></tbody> <tfoot class="ui-bar-inherit tfoot"></tfoot> </table> + <nav></nav> </div> </script> - - - + + <script id="error-message-template" type="text/x-handlebars-template"> <div class="ui-listbox-error"> <a class="ui-btn ui-corner-all ui-btn-inline" href="{{reset_url}}"> diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.xml index e06ae3c88d..4029320e53 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.xml +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.xml @@ -234,7 +234,7 @@ </item> <item> <key> <string>serial</string> </key> - <value> <string>964.27340.60822.54681</string> </value> + <value> <string>964.45882.29366.36147</string> </value> </item> <item> <key> <string>state</string> </key> @@ -252,7 +252,7 @@ </tuple> <state> <tuple> - <float>1514393372.0</float> + <float>1515514305.55</float> <string>UTC</string> </tuple> </state> diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js index c5bfa03cc8..d32384ad7c 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js @@ -28,6 +28,11 @@ .innerHTML, listbox_tfoot_template = Handlebars.compile(listbox_tfoot_source), + listbox_nav_source = gadget_klass.__template_element + .getElementById("listbox-nav-template") + .innerHTML, + listbox_nav_template = Handlebars.compile(listbox_nav_source), + listbox_source = gadget_klass.__template_element .getElementById("listbox-template") .innerHTML, @@ -41,88 +46,80 @@ loading_class_list = ['ui-icon-spinner', 'ui-btn-icon-left'], disabled_class = 'ui-disabled'; + function renderSubField(gadget, element, sub_field_json) { + sub_field_json.editable = sub_field_json.editable && gadget.state.editable; + return gadget.declareGadget( + 'gadget_erp5_label_field.html', + { + element: element, + scope: sub_field_json.key + } + ) + .push(function (cell_gadget) { + gadget.props.cell_gadget_list.push(cell_gadget); + return cell_gadget.render({ + field_type: sub_field_json.type, + field_json: sub_field_json, + label: false + }); + }); + } - function renderEditableField(gadget, element, column_list) { + function renderEditableField(gadget, element, column_list, field_table) { var i, promise_list = [], - uid_value_dict = {}, - uid_value, column, line, element_list = element.querySelectorAll(".editable_div"); - gadget.props.listbox_uid_dict = {}; - gadget.props.cell_gadget_list = []; - function renderSubCell(element, sub_field_json) { - sub_field_json.editable = sub_field_json.editable && gadget.state.editable; // XXX - return gadget.declareGadget('gadget_erp5_label_field.html', {element: element, scope: sub_field_json.key}) - .push(function (cell_gadget) { - gadget.props.cell_gadget_list.push(cell_gadget); - return cell_gadget.render({ - field_type: sub_field_json.type, - field_json: sub_field_json, - label: false - }); - }); - } + for (i = 0; i < element_list.length; i += 1) { column = element_list[i].getAttribute("data-column"); line = element_list[i].getAttribute("data-line"); - if (gadget.props.listbox_uid_dict.key === undefined) { - gadget.props.listbox_uid_dict.key = gadget.state.allDocs_result.data.rows[line].value["listbox_uid:list"].key; - gadget.props.listbox_uid_dict.value = [gadget.state.allDocs_result.data.rows[line].value["listbox_uid:list"].value]; - uid_value_dict[gadget.state.allDocs_result.data.rows[line].value["listbox_uid:list"].value] = null; - } else { - uid_value = gadget.state.allDocs_result.data.rows[line].value["listbox_uid:list"].value; - if (!uid_value_dict.hasOwnProperty(uid_value)) { - uid_value_dict[uid_value] = null; - gadget.props.listbox_uid_dict.value.push(uid_value); - } - } - promise_list.push(renderSubCell(element_list[i], - gadget.state.allDocs_result.data.rows[line].value[column_list[column][0]] || "")); + + promise_list.push(renderSubField( + gadget, + element_list[i], + field_table[line].cell_list[column] || "" + )); } return RSVP.all(promise_list); } + /**Put resulting `row_list` into `template` together with necessary gadget.state parameters. - function renderListboxTbody(gadget, template, body_value) { - var tmp, + First, it removes all similar containers from within the table! Currently it is tricky + to have multiple tbody/thead/tfoot elements! Feel free to refactor. + + Example call: renderTablePart(gadget, compiled_template, row_list, "tbody"); + **/ + function renderTablePart(gadget, template, row_list, container_name) { + var container, column_list = JSON.parse(gadget.state.column_list_json); return gadget.translateHtml(template( { - "body_value": body_value, + "row_list": row_list, "show_anchor": gadget.state.show_anchor, "column_list": column_list } )) - .push(function (my_html) { - tmp = document.createElement("tbody"); - tmp.innerHTML = my_html; - return renderEditableField(gadget, tmp, column_list); + .push(function (table_part_html) { + container = document.createElement(container_name); + container.innerHTML = table_part_html; + return renderEditableField(gadget, container, column_list, row_list); }) .push(function () { var table = gadget.element.querySelector("table"), - tbody = table.querySelector("tbody"); - table.removeChild(tbody); - table.appendChild(tmp); + old_container = table.querySelector(container_name); + if (old_container) { + table.replaceChild(container, old_container); + } else { + table.appendChild(container); + } + return table; }); } - - function renderListboxTfoot(gadget, foot) { - return gadget.translateHtml(listbox_tfoot_template( - { - "colspan": foot.colspan, - "previous_classname": foot.previous_classname, - "previous_url": foot.previous_url, - "record": foot.record, - "next_classname": foot.next_classname, - "next_url": foot.next_url - } - )); - } - /** Clojure to ease finding in lists of lists by the first item **/ function hasSameFirstItem(a) { return function (b) { @@ -137,7 +134,10 @@ // Init local properties .ready(function () { this.props = { + // holds references to all editable sub-fields cell_gadget_list: [], + // ERP5 needs listbox_uid:list with UIDs of editable sub-documents + // so it can search for them in REQUEST.form under <field.id>_<sub-document.uid> listbox_uid_dict: {} }; }) @@ -272,6 +272,9 @@ sort_list_json: JSON.stringify(result_list[1] || field_json.sort.map(jioize_sort)), show_anchor: field_json.show_anchor, + show_stat: field_json.show_stat, + show_count: field_json.show_count, + line_icon: field_json.line_icon, query: field_json.query, query_string: query_string, @@ -438,6 +441,8 @@ }); } + /* Function `fetchLineContent` calls changeState({"allDocs_result": JIO.allDocs()}) + so this if gets re-evaluated later with allDocs_result defined. */ if (gadget.state.allDocs_result === undefined) { // Trigger line content calculation result_queue @@ -453,7 +458,6 @@ } else if ((modification_dict.hasOwnProperty('show_line_selector')) || (modification_dict.hasOwnProperty('allDocs_result'))) { - // Render the listbox content result_queue .push(function () { @@ -463,7 +467,7 @@ counter; column_list = JSON.parse(gadget.state.column_list_json); - + // for actual allDocs_result structure see ref:gadget_erp5_jio.js if (lines === 0) { lines = allDocs_result.data.total_rows; counter = allDocs_result.data.total_rows; @@ -471,7 +475,7 @@ counter = Math.min(allDocs_result.data.total_rows, lines); } sort_list = JSON.parse(gadget.state.sort_list_json); - + // Every line points to a sub-document so we need those links for (i = 0; i < counter; i += 1) { promise_list.push( gadget.getUrlFor({ @@ -492,31 +496,52 @@ return RSVP.all(promise_list); }) - .push(function (result_list) { - var value, - body_value = [], - tr_value = [], - tmp_url, - listbox_tbody_template; + .push(function (line_link_list) { + var row_list = [], + value, + cell_list, + listbox_tbody_template, + setNonEditable = function (cell) {cell.editable = false; }; + // reset list of UIDs of editable sub-documents + gadget.props.listbox_uid_dict = { + key: undefined, + value: [] + }; + // clear list of previous sub-gadgets + gadget.props.cell_gadget_list = []; for (i = 0; i < counter; i += 1) { - tmp_url = result_list[i]; - tr_value = []; + cell_list = []; for (j = 0; j < column_list.length; j += 1) { value = allDocs_result.data.rows[i].value[column_list[j][0]] || ""; - tr_value.push({ - "type": value.type, - "editable": value.editable && gadget.state.editable, - "href": tmp_url, - "text": value, - "line": i, - "column": j - }); + // value can be simply just a value in case of non-editable field + // thus we construct "field_json" manually and insert the value in "default" + if (value.constructor !== Object) { + value = { + 'editable': 0, + 'default': value + }; + } + value.href = line_link_list[i]; + value.editable = value.editable && gadget.state.editable; + value.line = i; + value.column = j; + cell_list.push(value); + } + // note row's editable UID into gadget.props.listbox_uid_dict if exists to send it back to ERP5 + // together with ListBox data. The listbox_uid_dict has quite surprising structure {key: <key>, value: <uid-array>} + if (allDocs_result.data.rows[i].value['listbox_uid:list'] !== undefined) { + gadget.props.listbox_uid_dict.key = allDocs_result.data.rows[i].value['listbox_uid:list'].key; + gadget.props.listbox_uid_dict.value.push(allDocs_result.data.rows[i].value['listbox_uid:list'].value); + // we could come up with better name than "value" for almost everything ^^ + } else { + // if the document does not have listbox_uid:list then no gadget should be editable + cell_list.forEach(setNonEditable); } - body_value.push({ - "value": allDocs_result.data.rows[i].value.uid, - "jump": tmp_url, - "tr_value": tr_value, + row_list.push({ + "uid": allDocs_result.data.rows[i].value.uid, + "jump": line_link_list[i], + "cell_list": cell_list, "line_icon": gadget.state.line_icon }); } @@ -527,7 +552,7 @@ listbox_tbody_template = listbox_hidden_tbody_template; } - return renderListboxTbody(gadget, listbox_tbody_template, body_value); + return renderTablePart(gadget, listbox_tbody_template, row_list, "tbody"); }) .push(function () { var prev_param = {}, @@ -551,31 +576,70 @@ }) .push(function (url_list) { - var foot = {}; - foot.colspan = column_list.length + gadget.state.show_anchor + - (gadget.state.line_icon ? 1 : 0) + gadget.state.show_line_selector; - foot.previous_classname = "ui-btn ui-icon-carat-l ui-btn-icon-left responsive ui-first-child"; - foot.previous_url = url_list[0]; - foot.next_classname = "ui-btn ui-icon-carat-r ui-btn-icon-right responsive ui-last-child"; - foot.next_url = url_list[1]; + var record, + previous_url = url_list[0], + next_url = url_list[1], + previous_classname = "ui-btn ui-icon-carat-l ui-btn-icon-left responsive ui-first-child", + next_classname = "ui-btn ui-icon-carat-r ui-btn-icon-right responsive ui-last-child"; + if ((gadget.state.begin_from === 0) && (counter === 0)) { - foot.record = variable.translated_no_record; + record = variable.translated_no_record; } else if ((allDocs_result.data.rows.length <= lines) && (gadget.state.begin_from === 0)) { - foot.record = counter + " " + variable.translated_records; + record = counter + " " + variable.translated_records; } else { - foot.record = variable.translated_records + " " + (((gadget.state.begin_from + lines) / lines - 1) * lines + 1) + " - " + (((gadget.state.begin_from + lines) / lines - 1) * lines + counter); + record = variable.translated_records + " " + (((gadget.state.begin_from + lines) / lines - 1) * lines + 1) + " - " + (((gadget.state.begin_from + lines) / lines - 1) * lines + counter); } if (gadget.state.begin_from === 0) { - foot.previous_classname += " ui-disabled"; + previous_classname += " ui-disabled"; } if (allDocs_result.data.rows.length <= lines) { - foot.next_classname += " ui-disabled"; + next_classname += " ui-disabled"; } - return renderListboxTfoot(gadget, foot); + return gadget.translateHtml( + listbox_nav_template({ + "previous_classname": previous_classname, + "previous_url": previous_url, + "record": record, + "next_classname": next_classname, + "next_url": next_url + }) + ) + .push(function (listbox_nav_html) { + gadget.element.querySelector('nav').innerHTML = listbox_nav_html; + }); }) - .push(function (my_html) { - gadget.element.querySelector(".tfoot").innerHTML = my_html; + .push(function () { + var result_sum = (gadget.state.allDocs_result.sum || {}).rows || [], // render summary footer if available + summary = result_sum.map(function (row, row_index) { + var row_editability = row['listbox_uid:list'] !== undefined; + return { + "uid": 'summary' + row_index, + "cell_list": column_list.map(function (col_name, col_index) { + var field_json = row.value[col_name[0]] || ""; + if (field_json.constructor !== Object) { + field_json = {'default': field_json, 'editable': 0}; + } + field_json.editable = field_json.editable && row_editability; + field_json.column = col_index; + field_json.line = row_index; + return field_json; + }) + }; + }), + element; + + if (counter === 0) { + // do not render footer (summary) when no data in Listbox because it is ugly + element = gadget.element.querySelector("table tfoot tr"); + if (element !== null) { + element.remove(); + } + return null; + } + return renderTablePart(gadget, listbox_tfoot_template, summary, "tfoot"); + }) + .push(function () { var loading_element_classList = gadget.element.querySelector(".listboxloader").classList; loading_element_classList.remove.apply(loading_element_classList, loading_class_list); }); @@ -616,7 +680,8 @@ var gadget = this, select_list = [], - limit_options, + limit_options = [], + aggregation_option_list = [], column_list = JSON.parse(gadget.state.column_list_json), i; @@ -631,6 +696,12 @@ limit_options = [gadget.state.begin_from, gadget.state.lines + 1]; } + if (gadget.state.show_stat === true) { + aggregation_option_list.push("sum"); + } + if (gadget.state.show_count === true) { + aggregation_option_list.push("count"); + } return gadget.jio_allDocs({ // XXX Not jIO compatible, but until a better api is found... @@ -638,6 +709,7 @@ "query": gadget.state.query_string, "limit": limit_options, "select_list": select_list, + // "aggregation": aggregation_option_list "sort_on": JSON.parse(gadget.state.sort_list_json) }) .push(function (result) { @@ -647,7 +719,7 @@ }, function (error) { // do not crash interface if allDocs fails - //this will catch all error, not only search criteria invalid error + // this will catch all error, not only search criteria invalid error if (error instanceof RSVP.CancellationError) { throw error; } diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.xml b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.xml index 950e8869f2..d9f492a851 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.xml +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.xml @@ -75,12 +75,6 @@ <none/> </value> </item> - <item> - <key> <string>content_type</string> </key> - <value> - <none/> - </value> - </item> <item> <key> <string>default_reference</string> </key> <value> <string>gadget_erp5_field_listbox.js</string> </value> @@ -242,7 +236,7 @@ </item> <item> <key> <string>serial</string> </key> - <value> <string>964.28739.16428.44868</string> </value> + <value> <string>964.47502.56518.25890</string> </value> </item> <item> <key> <string>state</string> </key> @@ -260,7 +254,7 @@ </tuple> <state> <tuple> - <float>1514478789.32</float> + <float>1515602869.96</float> <string>UTC</string> </tuple> </state> diff --git a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.css b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.css index 0f80fd26d2..dbea631c60 100644 --- a/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.css +++ b/bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.css @@ -1305,24 +1305,30 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button { .document_table table { width: 100%; text-align: left; + /* end-of tbody, tfoot*/ } .document_table table th, .document_table table td { vertical-align: middle; + padding: 3pt; } -.document_table table thead { +.document_table table thead, +.document_table table tfoot { background-color: #0E81C2; color: #FFFFFF; } -.document_table table thead a { +.document_table table thead a, +.document_table table tfoot a { color: #FFFFFF; text-decoration: underline; } -.document_table table thead tr th { +.document_table table thead tr th, +.document_table table tfoot tr th { padding: 6pt 3pt; } @media not screen and (min-width: 45em) { - .document_table table thead { + .document_table table thead, + .document_table table tfoot { display: none; } } @@ -1342,7 +1348,6 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button { @media not screen and (max-width: 85em), only screen and (min-width: 45em) and (max-width: 85em) { .document_table table tbody a { display: block; - padding: 3pt; } } @media not screen and (min-width: 45em) { @@ -1411,41 +1416,46 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button { content: " ~ "; } } -.document_table table tfoot .ui-controlgroup-controls { +.document_table nav { display: flex; padding-top: 6pt; border-top: 2px solid rgba(0, 0, 0, 0.14902); } -.document_table table tfoot .ui-controlgroup-controls span { +.document_table nav span { opacity: .3; flex: 2; text-align: right; + float: right; } -.document_table table tfoot .ui-controlgroup-controls a { +.document_table nav a { padding: 6pt; border: 1px solid rgba(0, 0, 0, 0.14); border-radius: 0.325em; margin-right: 6pt; } -.document_table table tfoot .ui-controlgroup-controls a::before { +.document_table nav a::before { margin-right: 6pt; } -.document_table table tfoot .ui-controlgroup-controls a:hover, -.document_table table tfoot .ui-controlgroup-controls a:active { +.document_table nav a:hover, +.document_table nav a:active { background-color: #e0e0e0; } -.document_table table tfoot .ui-controlgroup-controls a:last-of-type { +.document_table nav a:last-of-type { margin-right: 0; } +.document_table nav a:hover, +.document_table nav a:active { + background-color: #e0e0e0; +} @media not screen and (min-width: 45em) { - .document_table table tfoot .ui-controlgroup-controls a { + .document_table nav a { overflow: hidden; text-indent: -9999px; white-space: nowrap; } } @media not screen and (min-width: 45em) { - .document_table table tfoot .ui-controlgroup-controls a::before { + .document_table nav a::before { float: left; text-indent: 6pt; } diff --git a/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/erp5css.less.txt b/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/erp5css.less.txt index e129e603a7..70ae43683d 100644 --- a/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/erp5css.less.txt +++ b/bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/erp5css.less.txt @@ -1523,9 +1523,10 @@ div[data-gadget-scope='erp5_searchfield'] { th, td { // line-height: 1.5em; vertical-align: middle; + padding: @half-margin-size; } - thead { + thead, tfoot { background-color: @colorsubheaderbackground; color: @white; @@ -1565,7 +1566,6 @@ div[data-gadget-scope='erp5_searchfield'] { @media @desktop, @tablet { a { display: block; - padding: @half-margin-size; } } @@ -1646,40 +1646,43 @@ div[data-gadget-scope='erp5_searchfield'] { } } } + } + nav { + display: flex; + padding-top: @margin-size; + border-top: 2px solid rgba(0, 0, 0, 0.14902); - tfoot .ui-controlgroup-controls { - display: flex; - padding-top: @margin-size; - border-top: 2px solid rgba(0, 0, 0, 0.14902); + span { + opacity: .3; + flex: 2; + text-align: right; + float: right; + } + a { + .button(); - span { - opacity: .3; - flex: 2; - text-align: right; + margin-right: @margin-size; + &:last-of-type { + margin-right: 0; } - a { - .button(); - margin-right: @margin-size; - &:last-of-type { - margin-right: 0; - } + &:hover, &:active { + background-color: @colorblocklinkbackground; + } - @media @smartphone { - .hide_text(@width: initial); - } + @media @smartphone { + .hide_text(@width: initial); + } - &::before { + &::before { - @media @smartphone { - float: left; - text-indent: @margin-size; - } + @media @smartphone { + float: left; + text-indent: @margin-size; } } } - } } diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testEmptyListboxWithStat.xml b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testEmptyListboxWithStat.xml new file mode 100644 index 0000000000..2fc3ac7b12 --- /dev/null +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testEmptyListboxWithStat.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>content_type</string> </key> + <value> <string>text/html</string> </value> + </item> + <item> + <key> <string>expand</string> </key> + <value> <int>0</int> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>testEmptyListboxWithStat</string> </value> + </item> + <item> + <key> <string>output_encoding</string> </key> + <value> <string>utf-8</string> </value> + </item> + <item> + <key> <string>title</string> </key> + <value> <unicode></unicode> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testEmptyListboxWithStat.zpt b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testEmptyListboxWithStat.zpt new file mode 100644 index 0000000000..59a80ead79 --- /dev/null +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testEmptyListboxWithStat.zpt @@ -0,0 +1,47 @@ +<html xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal"> +<!-- +Ensure empty listbox does not show stat line even though it receives stat data. +--> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>Test RenderJS UI ListBox No Stat Line on No Data</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">Test RenderJS UI ListBox No Stat Line on No Data</td></tr> +</thead><tbody> +<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> + +<!-- Clean Up --> +<tr><td>open</td> + <td>${base_url}/foo_module/ListBoxZuite_reset</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Reset Successfully.</td><td></td></tr> + +<!-- Shortcut for full renderjs url --> +<tr><td>store</td> + <td>${base_url}/web_site_module/renderjs_runner</td> + <td>renderjs_url</td></tr> + +<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" /> + +<!-- Let's set up the default sort correctly: id | ASC --> +<tr><td>open</td> + <td>${base_url}/FooModule_viewFooList/listbox/ListBox_setPropertyList?field_stat_method=FooModule_statMethod</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Set Successfully.</td><td></td></tr> + +<tr><td>open</td> + <td>${renderjs_url}/#/foo_module</td><td></td></tr> +<tr><td>waitForElementPresent</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/span[@data-i18n="No records"]</td><td></td></tr> + +<tr><td>assertElementNotPresent</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tfoot/tr</td> + <td></td></tr> + +</tbody></table> +</body> +</html> \ No newline at end of file diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testHideItem.zpt b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testHideItem.zpt index b2c1ee59c8..0606092e08 100644 --- a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testHideItem.zpt +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testHideItem.zpt @@ -128,11 +128,6 @@ <td>//thead/tr/th[3]</td> <td>Quantity</td> </tr> -<tr> - <td>assertElementPresent</td> - <td>//tfoot/tr/th[@colspan="3"]</td> - <td></td> -</tr> <tr> @@ -169,11 +164,6 @@ <td>//thead/tr/th[4]</td> <td>Quantity</td> </tr> -<tr> - <td>assertElementPresent</td> - <td>//tfoot/tr/th[@colspan="4"]</td> - <td></td> -</tr> <!-- Line checkbox --> @@ -233,11 +223,6 @@ <td>//thead/tr/th[3]</td> <td>Quantity</td> </tr> -<tr> - <td>assertElementPresent</td> - <td>//tfoot/tr/th[@colspan="3"]</td> - <td></td> -</tr> <!-- only one element present --> <tr> diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.xml b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.xml new file mode 100644 index 0000000000..f48a19ffe3 --- /dev/null +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>content_type</string> </key> + <value> <string>text/html</string> </value> + </item> + <item> + <key> <string>expand</string> </key> + <value> <int>0</int> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>testStatColumns</string> </value> + </item> + <item> + <key> <string>output_encoding</string> </key> + <value> <string>utf-8</string> </value> + </item> + <item> + <key> <string>title</string> </key> + <value> <unicode></unicode> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.zpt b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.zpt new file mode 100644 index 0000000000..6bb81377d9 --- /dev/null +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.zpt @@ -0,0 +1,73 @@ +<html xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal"> +<!-- +Ensure Stat Column methods are executed correctly and result displayed in tfoot element of the listbox table. + +- if anchor, then text "Total" is present +- columns which are not present in Stat Columns do not display any data +--> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>Test RenderJS ListBox Stat Columns</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">Test RenderJS ListBox Stat Columns</td></tr> +</thead><tbody> +<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> + +<!-- Clean Up --> +<tr><td>open</td> + <td>${base_url}/foo_module/ListBoxZuite_reset</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Reset Successfully.</td><td></td></tr> + +<!-- Shortcut for full renderjs url --> +<tr><td>store</td> + <td>${base_url}/web_site_module/renderjs_runner</td> + <td>renderjs_url</td></tr> + +<!-- Create Foo objects with IDs 0-9 --> +<tr><td>open</td> + <td>${base_url}/foo_module/FooModule_createObjects?start:int=1&num:int=2</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Created Successfully.</td><td></td></tr> + +<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" /> + +<!-- Let's set up stat column property on listbox --> +<tr><td>open</td> + <td>${base_url}/FooModule_viewFooList/listbox/ListBox_setPropertyList?field_stat_columns=getQuantity+%7C+FooModule_statQuantity+%0A+title+%7C+FooModule_statTitle</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Set Successfully.</td><td></td></tr> + +<tr><td>open</td> + <td>${renderjs_url}/#/foo_module</td><td></td></tr> +<tr><td>waitForElementPresent</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/span[@data-i18n="2 Records"]</td><td></td></tr> + +<tr><td>store</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table</td> + <td>listbox_table</td></tr> + +<!-- Default sort on ID column has to be ASCENDING --> +<tr><td>assertFloat</td> + <td>${listbox_table}/tbody/tr[1]/td[3]/a</td> + <td>9</td></tr> +<tr><td>assertFloat</td> + <td>${listbox_table}/tbody/tr[2]/td[3]/a</td> + <td>8</td></tr> +<tr><td>assertText</td><!-- This tests that "Total" appears when first column has no stat defined --> + <td>${listbox_table}/tfoot/tr[1]/td[1]</td> + <td>Total</td></tr> +<tr><td>assertText</td><!-- Test multiple Stat Columns --> + <td>${listbox_table}/tfoot/tr[1]/td[2]</td> + <td>Foos</td></tr> +<tr><td>assertFloat</td> + <td>${listbox_table}/tfoot/tr[1]/td[3]</td> + <td>17</td></tr> + +</tbody></table> +</body> +</html> \ No newline at end of file diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.xml b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.xml new file mode 100644 index 0000000000..dcd6f40d26 --- /dev/null +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>content_type</string> </key> + <value> <string>text/html</string> </value> + </item> + <item> + <key> <string>expand</string> </key> + <value> <int>0</int> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>testStatMethod</string> </value> + </item> + <item> + <key> <string>output_encoding</string> </key> + <value> <string>utf-8</string> </value> + </item> + <item> + <key> <string>title</string> </key> + <value> <unicode></unicode> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.zpt b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.zpt new file mode 100644 index 0000000000..3e1a3666d4 --- /dev/null +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.zpt @@ -0,0 +1,74 @@ +<html xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal"> +<!-- +Ensure stat_method gets executed and result displayed in tfoot element of the listbox table. + +- if anchor, then text "Total" is present +- columns for which stat_method does not return any data remain empty +--> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>Test RenderJS UI ListBox Stat Method</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">Test RenderJS UI ListBox Stat Method</td></tr> +</thead><tbody> +<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> + +<!-- Clean Up --> +<tr><td>open</td> + <td>${base_url}/foo_module/ListBoxZuite_reset</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Reset Successfully.</td><td></td></tr> + +<!-- Shortcut for full renderjs url --> +<tr><td>store</td> + <td>${base_url}/web_site_module/renderjs_runner</td> + <td>renderjs_url</td></tr> + +<!-- Create Foo objects with IDs 0-9 --> +<tr><td>open</td> + <td>${base_url}/foo_module/FooModule_createObjects?start:int=1&num:int=3</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Created Successfully.</td><td></td></tr> + +<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" /> + +<!-- Let's set up the default sort correctly: id | ASC --> +<tr><td>open</td> + <td>${base_url}/FooModule_viewFooList/listbox/ListBox_setPropertyList?field_stat_method=FooModule_statMethod</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Set Successfully.</td><td></td></tr> + +<tr><td>open</td> + <td>${renderjs_url}/#/foo_module</td><td></td></tr> +<tr><td>waitForElementPresent</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/span[@data-i18n="3 Records"]</td><td></td></tr> + +<tr><td>store</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table</td> + <td>listbox_table</td></tr> + +<!-- Default sort on ID column has to be ASCENDING --> +<tr><td>assertFloat</td> + <td>${listbox_table}/tbody/tr[1]/td[3]/a</td> + <td>9</td></tr> +<tr><td>assertFloat</td> + <td>${listbox_table}/tbody/tr[2]/td[3]/a</td> + <td>8</td></tr> +<tr><td>assertFloat</td> + <td>${listbox_table}/tbody/tr[3]/td[3]/a</td> + <td>7</td></tr> +<tr><td>assertFloat</td><!-- This tests that "Total" does not appear when first column has stat defined --> + <td>${listbox_table}/tfoot/tr[1]/td[1]</td> + <td>6</td></tr> +<tr><td>assertFloat</td> + <td>${listbox_table}/tfoot/tr[1]/td[3]</td> + <td>24</td></tr> + + +</tbody></table> +</body> +</html> \ No newline at end of file diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMissing.xml b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMissing.xml new file mode 100644 index 0000000000..cde2def16c --- /dev/null +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMissing.xml @@ -0,0 +1,58 @@ +<?xml version="1.0"?> +<ZopeData> + <record id="1" aka="AAAAAAAAAAE="> + <pickle> + <global name="ZopePageTemplate" module="Products.PageTemplates.ZopePageTemplate"/> + </pickle> + <pickle> + <dictionary> + <item> + <key> <string>_bind_names</string> </key> + <value> + <object> + <klass> + <global name="NameAssignments" module="Shared.DC.Scripts.Bindings"/> + </klass> + <tuple/> + <state> + <dictionary> + <item> + <key> <string>_asgns</string> </key> + <value> + <dictionary> + <item> + <key> <string>name_subpath</string> </key> + <value> <string>traverse_subpath</string> </value> + </item> + </dictionary> + </value> + </item> + </dictionary> + </state> + </object> + </value> + </item> + <item> + <key> <string>content_type</string> </key> + <value> <string>text/html</string> </value> + </item> + <item> + <key> <string>expand</string> </key> + <value> <int>0</int> </value> + </item> + <item> + <key> <string>id</string> </key> + <value> <string>testStatMissing</string> </value> + </item> + <item> + <key> <string>output_encoding</string> </key> + <value> <string>utf-8</string> </value> + </item> + <item> + <key> <string>title</string> </key> + <value> <unicode></unicode> </value> + </item> + </dictionary> + </pickle> + </record> +</ZopeData> diff --git a/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMissing.zpt b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMissing.zpt new file mode 100644 index 0000000000..ea6584cd7a --- /dev/null +++ b/bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMissing.zpt @@ -0,0 +1,53 @@ +<html xmlns:tal="http://xml.zope.org/namespaces/tal" + xmlns:metal="http://xml.zope.org/namespaces/metal"> +<!-- +Ensure no stat line is displayed when no stat_method and no stat_columns are defined. +--> +<head> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8"> +<title>Test RenderJS UI ListBox Stat Missing</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">Test RenderJS UI ListBox Stat Missing</td></tr> +</thead><tbody> +<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/init" /> + +<!-- Clean Up --> +<tr><td>open</td> + <td>${base_url}/foo_module/ListBoxZuite_reset</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Reset Successfully.</td><td></td></tr> + +<!-- Shortcut for full renderjs url --> +<tr><td>store</td> + <td>${base_url}/web_site_module/renderjs_runner</td> + <td>renderjs_url</td></tr> + +<!-- Create Foo objects with IDs 0-9 --> +<tr><td>open</td> + <td>${base_url}/foo_module/FooModule_createObjects?start:int=1&num:int=3</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Created Successfully.</td><td></td></tr> + +<tal:block metal:use-macro="here/Zuite_CommonTemplate/macros/wait_for_activities" /> + +<!-- Let's set up the default sort correctly: id | ASC --> +<tr><td>open</td> + <td>${base_url}/FooModule_viewFooList/listbox/ListBox_setPropertyList</td><td></td></tr> +<tr><td>assertTextPresent</td> + <td>Set Successfully.</td><td></td></tr> + +<tr><td>open</td> + <td>${renderjs_url}/#/foo_module</td><td></td></tr> +<tr><td>waitForElementPresent</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav/span[@data-i18n="3 Records"]</td><td></td></tr> + +<tr><td>assertElementNotPresent</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//table/tfoot/tr</td> + <td></td></tr> + +</tbody></table> +</body> +</html> \ No newline at end of file diff --git a/bt5/erp5_web_renderjs_ui_test/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui_test/Zuite_CommonTemplateForRenderjsUi.zpt b/bt5/erp5_web_renderjs_ui_test/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui_test/Zuite_CommonTemplateForRenderjsUi.zpt index b0d0593a1e..ca8bb820e4 100644 --- a/bt5/erp5_web_renderjs_ui_test/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui_test/Zuite_CommonTemplateForRenderjsUi.zpt +++ b/bt5/erp5_web_renderjs_ui_test/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui_test/Zuite_CommonTemplateForRenderjsUi.zpt @@ -247,7 +247,7 @@ </tr> <tr> <td>waitForElementPresent</td> - <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//tfoot//span[contains(@data-i18n, "Records")]</td> + <td>//div[contains(@data-gadget-url, 'gadget_erp5_field_listbox.html')]//nav//span[contains(@data-i18n, "Records")]</td> <td></td> </tr> <tr> -- 2.30.9