Commit b058c73e authored by Tomáš Peterka's avatar Tomáš Peterka

[hal_json+renderjs] Implement "stat" line for ListBox

parent 973a5075
...@@ -47,10 +47,26 @@ MARKER = [] ...@@ -47,10 +47,26 @@ MARKER = []
if REQUEST is None: if REQUEST is None:
REQUEST = context.REQUEST REQUEST = context.REQUEST
# raise Unauthorized
if response is None: if response is None:
response = REQUEST.RESPONSE 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 # http://stackoverflow.com/a/13105359
def byteify(string): def byteify(string):
if isinstance(string, dict): if isinstance(string, dict):
...@@ -684,6 +700,8 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key ...@@ -684,6 +700,8 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
"lines": lines, "lines": lines,
"default_params": default_params, "default_params": default_params,
"list_method": list_method_name, "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": url_template_dict["jio_search_template"] % {
"query": make_query({ "query": make_query({
"query": sql_catalog.buildQuery( "query": sql_catalog.buildQuery(
...@@ -1478,6 +1496,55 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None, ...@@ -1478,6 +1496,55 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
contents_list.append(contents_item) contents_list.append(contents_item)
result_dict['_embedded']['contents'] = contents_list 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 return result_dict
elif mode == 'form': elif mode == 'form':
......
"""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, ]
<?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>
counter = 0
for value in context.contentValues():
counter = counter + int(value.getQuantity())
return counter
<?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>
...@@ -178,27 +178,33 @@ ...@@ -178,27 +178,33 @@
] ]
) )
.push(function (catalog_json) { .push(function (catalog_json) {
var data = catalog_json._embedded.contents, var data = catalog_json._embedded.contents || [],
count = data.length, summary = catalog_json._embedded.sum || [],
k, count = catalog_json._embedded.count;
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
});
}
return { return {
data: { "data": {
rows: result, "rows": data.map(function (item) {
total_rows: result.length 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
}; };
}); });
}) })
......
...@@ -230,7 +230,7 @@ ...@@ -230,7 +230,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>947.45414.13002.10052</string> </value> <value> <string>963.59331.40212.55432</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -248,7 +248,7 @@ ...@@ -248,7 +248,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1449753994.81</float> <float>1512454358.33</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -60,18 +60,18 @@ ...@@ -60,18 +60,18 @@
</thead> </thead>
</table> </table>
</script> </script>
<script id="listbox-hidden-tbody-template" type="text/x-handlebars-template"> <script id="listbox-hidden-tbody-template" type="text/x-handlebars-template">
<table> <table>
<tbody class="tbody"> <tbody class="tbody">
{{#each body_value}} {{#each row_list}}
<tr> <tr>
{{#if ../show_anchor}} {{#if ../show_anchor}}
<th> <th>
<a class="ui-link ui-btn ui-corner-all ui-icon-carat-r ui-btn-icon-notext" href="{{jump}}"></a> <a class="ui-link ui-btn ui-corner-all ui-icon-carat-r ui-btn-icon-notext" href="{{jump}}"></a>
</th> </th>
{{/if}} {{/if}}
{{#each tr_value}} {{#each cell_list}}
<td> <td>
{{#if type}} {{#if type}}
{{#if editable}} {{#if editable}}
...@@ -82,7 +82,7 @@ ...@@ -82,7 +82,7 @@
</a> </a>
{{/if}} {{/if}}
{{else}} {{else}}
<a href="{{href}}" class="ui-link">{{text}}</a> <a href="{{href}}" class="ui-link">{{default}}</a>
{{/if}} {{/if}}
</td> </td>
{{/each}} {{/each}}
...@@ -101,7 +101,7 @@ ...@@ -101,7 +101,7 @@
<script id="listbox-show-tbody-template" type="text/x-handlebars-template"> <script id="listbox-show-tbody-template" type="text/x-handlebars-template">
<table> <table>
<tbody class="tbody"> <tbody class="tbody">
{{#each body_value}} {{#each row_list}}
<tr> <tr>
{{#if ../show_anchor}} {{#if ../show_anchor}}
<th> <th>
...@@ -111,7 +111,7 @@ ...@@ -111,7 +111,7 @@
<td> <td>
<input value="{{value}}" type="checkbox" checked="true" class="hide_element"> <input value="{{value}}" type="checkbox" checked="true" class="hide_element">
</td> </td>
{{#each tr_value}} {{#each cell_list}}
<td> <td>
{{#if type}} {{#if type}}
{{#if editable}} {{#if editable}}
...@@ -122,7 +122,7 @@ ...@@ -122,7 +122,7 @@
</a> </a>
{{/if}} {{/if}}
{{else}} {{else}}
<a href="{{href}}" class="ui-link">{{text}}</a> <a href="{{href}}" class="ui-link">{{default}}</a>
{{/if}} {{/if}}
</td> </td>
{{/each}} {{/each}}
...@@ -140,19 +140,40 @@ ...@@ -140,19 +140,40 @@
<script id="listbox-tfoot-template" type="text/x-handlebars-template"> <script id="listbox-tfoot-template" type="text/x-handlebars-template">
<table> <table>
<tfoot class="ui-bar-inherit tfoot"> <tfoot class="ui-bar-inherit tfoot">
<th colspan="{{colspan}}"> {{#each row_list}}
<div class="ui-controlgroup ui-controlgroup-horizontal ui-corner-all ui-paging-menu"> <tr>
<div class="ui-controlgroup-controls"> {{#if ../show_anchor}}
<a class="{{previous_classname}}" data-i18n="Previous" href="{{previous_url}}">Previous</a> <td>Total</td>
<a class="{{next_classname}}" data-i18n="Next" href="{{next_url}}">Next</a> {{/if}}
<span class="ui-btn ui-disabled" data-i18n="{{record}}">{{record}}</span> {{#each cell_list}}
</div> <td>
</div> {{#if type}}
</th> <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> </tfoot>
</table> </table>
</script> </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"> <script id="listbox-template" type="text/x-handlebars-template">
<div class="ui-table-header ui-header ui-bar-c ui-corner-all"> <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> <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 @@ ...@@ -165,11 +186,11 @@
<tbody></tbody> <tbody></tbody>
<tfoot class="ui-bar-inherit tfoot"></tfoot> <tfoot class="ui-bar-inherit tfoot"></tfoot>
</table> </table>
<nav></nav>
</div> </div>
</script> </script>
<script id="error-message-template" type="text/x-handlebars-template"> <script id="error-message-template" type="text/x-handlebars-template">
<div class="ui-listbox-error"> <div class="ui-listbox-error">
<a class="ui-btn ui-corner-all ui-btn-inline" href="{{reset_url}}"> <a class="ui-btn ui-corner-all ui-btn-inline" href="{{reset_url}}">
......
...@@ -234,7 +234,7 @@ ...@@ -234,7 +234,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>964.27340.60822.54681</string> </value> <value> <string>964.45882.29366.36147</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -252,7 +252,7 @@ ...@@ -252,7 +252,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1514393372.0</float> <float>1515514305.55</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -28,6 +28,11 @@ ...@@ -28,6 +28,11 @@
.innerHTML, .innerHTML,
listbox_tfoot_template = Handlebars.compile(listbox_tfoot_source), 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 listbox_source = gadget_klass.__template_element
.getElementById("listbox-template") .getElementById("listbox-template")
.innerHTML, .innerHTML,
...@@ -41,88 +46,80 @@ ...@@ -41,88 +46,80 @@
loading_class_list = ['ui-icon-spinner', 'ui-btn-icon-left'], loading_class_list = ['ui-icon-spinner', 'ui-btn-icon-left'],
disabled_class = 'ui-disabled'; 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, var i,
promise_list = [], promise_list = [],
uid_value_dict = {},
uid_value,
column, column,
line, line,
element_list = element.querySelectorAll(".editable_div"); 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) { for (i = 0; i < element_list.length; i += 1) {
column = element_list[i].getAttribute("data-column"); column = element_list[i].getAttribute("data-column");
line = element_list[i].getAttribute("data-line"); 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; promise_list.push(renderSubField(
gadget.props.listbox_uid_dict.value = [gadget.state.allDocs_result.data.rows[line].value["listbox_uid:list"].value]; gadget,
uid_value_dict[gadget.state.allDocs_result.data.rows[line].value["listbox_uid:list"].value] = null; element_list[i],
} else { field_table[line].cell_list[column] || ""
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]] || ""));
} }
return RSVP.all(promise_list); return RSVP.all(promise_list);
} }
/**Put resulting `row_list` into `template` together with necessary gadget.state parameters.
function renderListboxTbody(gadget, template, body_value) { First, it removes all similar containers from within the table! Currently it is tricky
var tmp, 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); column_list = JSON.parse(gadget.state.column_list_json);
return gadget.translateHtml(template( return gadget.translateHtml(template(
{ {
"body_value": body_value, "row_list": row_list,
"show_anchor": gadget.state.show_anchor, "show_anchor": gadget.state.show_anchor,
"column_list": column_list "column_list": column_list
} }
)) ))
.push(function (my_html) { .push(function (table_part_html) {
tmp = document.createElement("tbody"); container = document.createElement(container_name);
tmp.innerHTML = my_html; container.innerHTML = table_part_html;
return renderEditableField(gadget, tmp, column_list); return renderEditableField(gadget, container, column_list, row_list);
}) })
.push(function () { .push(function () {
var table = gadget.element.querySelector("table"), var table = gadget.element.querySelector("table"),
tbody = table.querySelector("tbody"); old_container = table.querySelector(container_name);
table.removeChild(tbody); if (old_container) {
table.appendChild(tmp); 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 **/ /** Clojure to ease finding in lists of lists by the first item **/
function hasSameFirstItem(a) { function hasSameFirstItem(a) {
return function (b) { return function (b) {
...@@ -137,7 +134,10 @@ ...@@ -137,7 +134,10 @@
// Init local properties // Init local properties
.ready(function () { .ready(function () {
this.props = { this.props = {
// holds references to all editable sub-fields
cell_gadget_list: [], 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: {} listbox_uid_dict: {}
}; };
}) })
...@@ -272,6 +272,9 @@ ...@@ -272,6 +272,9 @@
sort_list_json: JSON.stringify(result_list[1] || field_json.sort.map(jioize_sort)), sort_list_json: JSON.stringify(result_list[1] || field_json.sort.map(jioize_sort)),
show_anchor: field_json.show_anchor, show_anchor: field_json.show_anchor,
show_stat: field_json.show_stat,
show_count: field_json.show_count,
line_icon: field_json.line_icon, line_icon: field_json.line_icon,
query: field_json.query, query: field_json.query,
query_string: query_string, query_string: query_string,
...@@ -438,6 +441,8 @@ ...@@ -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) { if (gadget.state.allDocs_result === undefined) {
// Trigger line content calculation // Trigger line content calculation
result_queue result_queue
...@@ -453,7 +458,6 @@ ...@@ -453,7 +458,6 @@
} else if ((modification_dict.hasOwnProperty('show_line_selector')) || } else if ((modification_dict.hasOwnProperty('show_line_selector')) ||
(modification_dict.hasOwnProperty('allDocs_result'))) { (modification_dict.hasOwnProperty('allDocs_result'))) {
// Render the listbox content // Render the listbox content
result_queue result_queue
.push(function () { .push(function () {
...@@ -463,7 +467,7 @@ ...@@ -463,7 +467,7 @@
counter; counter;
column_list = JSON.parse(gadget.state.column_list_json); column_list = JSON.parse(gadget.state.column_list_json);
// for actual allDocs_result structure see ref:gadget_erp5_jio.js
if (lines === 0) { if (lines === 0) {
lines = allDocs_result.data.total_rows; lines = allDocs_result.data.total_rows;
counter = allDocs_result.data.total_rows; counter = allDocs_result.data.total_rows;
...@@ -471,7 +475,7 @@ ...@@ -471,7 +475,7 @@
counter = Math.min(allDocs_result.data.total_rows, lines); counter = Math.min(allDocs_result.data.total_rows, lines);
} }
sort_list = JSON.parse(gadget.state.sort_list_json); 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) { for (i = 0; i < counter; i += 1) {
promise_list.push( promise_list.push(
gadget.getUrlFor({ gadget.getUrlFor({
...@@ -492,31 +496,52 @@ ...@@ -492,31 +496,52 @@
return RSVP.all(promise_list); return RSVP.all(promise_list);
}) })
.push(function (result_list) { .push(function (line_link_list) {
var value, var row_list = [],
body_value = [], value,
tr_value = [], cell_list,
tmp_url, listbox_tbody_template,
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) { for (i = 0; i < counter; i += 1) {
tmp_url = result_list[i]; cell_list = [];
tr_value = [];
for (j = 0; j < column_list.length; j += 1) { for (j = 0; j < column_list.length; j += 1) {
value = allDocs_result.data.rows[i].value[column_list[j][0]] || ""; value = allDocs_result.data.rows[i].value[column_list[j][0]] || "";
tr_value.push({ // value can be simply just a value in case of non-editable field
"type": value.type, // thus we construct "field_json" manually and insert the value in "default"
"editable": value.editable && gadget.state.editable, if (value.constructor !== Object) {
"href": tmp_url, value = {
"text": value, 'editable': 0,
"line": i, 'default': value
"column": j };
}); }
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({ row_list.push({
"value": allDocs_result.data.rows[i].value.uid, "uid": allDocs_result.data.rows[i].value.uid,
"jump": tmp_url, "jump": line_link_list[i],
"tr_value": tr_value, "cell_list": cell_list,
"line_icon": gadget.state.line_icon "line_icon": gadget.state.line_icon
}); });
} }
...@@ -527,7 +552,7 @@ ...@@ -527,7 +552,7 @@
listbox_tbody_template = listbox_hidden_tbody_template; 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 () { .push(function () {
var prev_param = {}, var prev_param = {},
...@@ -551,31 +576,70 @@ ...@@ -551,31 +576,70 @@
}) })
.push(function (url_list) { .push(function (url_list) {
var foot = {}; var record,
foot.colspan = column_list.length + gadget.state.show_anchor + previous_url = url_list[0],
(gadget.state.line_icon ? 1 : 0) + gadget.state.show_line_selector; next_url = url_list[1],
foot.previous_classname = "ui-btn ui-icon-carat-l ui-btn-icon-left responsive ui-first-child"; previous_classname = "ui-btn ui-icon-carat-l ui-btn-icon-left responsive ui-first-child",
foot.previous_url = url_list[0]; next_classname = "ui-btn ui-icon-carat-r ui-btn-icon-right responsive ui-last-child";
foot.next_classname = "ui-btn ui-icon-carat-r ui-btn-icon-right responsive ui-last-child";
foot.next_url = url_list[1];
if ((gadget.state.begin_from === 0) && (counter === 0)) { 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)) { } 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 { } 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) { if (gadget.state.begin_from === 0) {
foot.previous_classname += " ui-disabled"; previous_classname += " ui-disabled";
} }
if (allDocs_result.data.rows.length <= lines) { 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) { .push(function () {
gadget.element.querySelector(".tfoot").innerHTML = my_html; 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; var loading_element_classList = gadget.element.querySelector(".listboxloader").classList;
loading_element_classList.remove.apply(loading_element_classList, loading_class_list); loading_element_classList.remove.apply(loading_element_classList, loading_class_list);
}); });
...@@ -616,7 +680,8 @@ ...@@ -616,7 +680,8 @@
var gadget = this, var gadget = this,
select_list = [], select_list = [],
limit_options, limit_options = [],
aggregation_option_list = [],
column_list = JSON.parse(gadget.state.column_list_json), column_list = JSON.parse(gadget.state.column_list_json),
i; i;
...@@ -631,6 +696,12 @@ ...@@ -631,6 +696,12 @@
limit_options = [gadget.state.begin_from, gadget.state.lines + 1]; 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({ return gadget.jio_allDocs({
// XXX Not jIO compatible, but until a better api is found... // XXX Not jIO compatible, but until a better api is found...
...@@ -638,6 +709,7 @@ ...@@ -638,6 +709,7 @@
"query": gadget.state.query_string, "query": gadget.state.query_string,
"limit": limit_options, "limit": limit_options,
"select_list": select_list, "select_list": select_list,
// "aggregation": aggregation_option_list
"sort_on": JSON.parse(gadget.state.sort_list_json) "sort_on": JSON.parse(gadget.state.sort_list_json)
}) })
.push(function (result) { .push(function (result) {
...@@ -647,7 +719,7 @@ ...@@ -647,7 +719,7 @@
}, function (error) { }, function (error) {
// do not crash interface if allDocs fails // 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) { if (error instanceof RSVP.CancellationError) {
throw error; throw error;
} }
......
...@@ -75,12 +75,6 @@ ...@@ -75,12 +75,6 @@
<none/> <none/>
</value> </value>
</item> </item>
<item>
<key> <string>content_type</string> </key>
<value>
<none/>
</value>
</item>
<item> <item>
<key> <string>default_reference</string> </key> <key> <string>default_reference</string> </key>
<value> <string>gadget_erp5_field_listbox.js</string> </value> <value> <string>gadget_erp5_field_listbox.js</string> </value>
...@@ -242,7 +236,7 @@ ...@@ -242,7 +236,7 @@
</item> </item>
<item> <item>
<key> <string>serial</string> </key> <key> <string>serial</string> </key>
<value> <string>964.28739.16428.44868</string> </value> <value> <string>964.47502.56518.25890</string> </value>
</item> </item>
<item> <item>
<key> <string>state</string> </key> <key> <string>state</string> </key>
...@@ -260,7 +254,7 @@ ...@@ -260,7 +254,7 @@
</tuple> </tuple>
<state> <state>
<tuple> <tuple>
<float>1514478789.32</float> <float>1515602869.96</float>
<string>UTC</string> <string>UTC</string>
</tuple> </tuple>
</state> </state>
......
...@@ -1305,24 +1305,30 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button { ...@@ -1305,24 +1305,30 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button {
.document_table table { .document_table table {
width: 100%; width: 100%;
text-align: left; text-align: left;
/* end-of tbody, tfoot*/
} }
.document_table table th, .document_table table th,
.document_table table td { .document_table table td {
vertical-align: middle; vertical-align: middle;
padding: 3pt;
} }
.document_table table thead { .document_table table thead,
.document_table table tfoot {
background-color: #0E81C2; background-color: #0E81C2;
color: #FFFFFF; color: #FFFFFF;
} }
.document_table table thead a { .document_table table thead a,
.document_table table tfoot a {
color: #FFFFFF; color: #FFFFFF;
text-decoration: underline; text-decoration: underline;
} }
.document_table table thead tr th { .document_table table thead tr th,
.document_table table tfoot tr th {
padding: 6pt 3pt; padding: 6pt 3pt;
} }
@media not screen and (min-width: 45em) { @media not screen and (min-width: 45em) {
.document_table table thead { .document_table table thead,
.document_table table tfoot {
display: none; display: none;
} }
} }
...@@ -1342,7 +1348,6 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button { ...@@ -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) { @media not screen and (max-width: 85em), only screen and (min-width: 45em) and (max-width: 85em) {
.document_table table tbody a { .document_table table tbody a {
display: block; display: block;
padding: 3pt;
} }
} }
@media not screen and (min-width: 45em) { @media not screen and (min-width: 45em) {
...@@ -1411,41 +1416,46 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button { ...@@ -1411,41 +1416,46 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button {
content: " ~ "; content: " ~ ";
} }
} }
.document_table table tfoot .ui-controlgroup-controls { .document_table nav {
display: flex; display: flex;
padding-top: 6pt; padding-top: 6pt;
border-top: 2px solid rgba(0, 0, 0, 0.14902); border-top: 2px solid rgba(0, 0, 0, 0.14902);
} }
.document_table table tfoot .ui-controlgroup-controls span { .document_table nav span {
opacity: .3; opacity: .3;
flex: 2; flex: 2;
text-align: right; text-align: right;
float: right;
} }
.document_table table tfoot .ui-controlgroup-controls a { .document_table nav a {
padding: 6pt; padding: 6pt;
border: 1px solid rgba(0, 0, 0, 0.14); border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: 0.325em; border-radius: 0.325em;
margin-right: 6pt; margin-right: 6pt;
} }
.document_table table tfoot .ui-controlgroup-controls a::before { .document_table nav a::before {
margin-right: 6pt; margin-right: 6pt;
} }
.document_table table tfoot .ui-controlgroup-controls a:hover, .document_table nav a:hover,
.document_table table tfoot .ui-controlgroup-controls a:active { .document_table nav a:active {
background-color: #e0e0e0; background-color: #e0e0e0;
} }
.document_table table tfoot .ui-controlgroup-controls a:last-of-type { .document_table nav a:last-of-type {
margin-right: 0; margin-right: 0;
} }
.document_table nav a:hover,
.document_table nav a:active {
background-color: #e0e0e0;
}
@media not screen and (min-width: 45em) { @media not screen and (min-width: 45em) {
.document_table table tfoot .ui-controlgroup-controls a { .document_table nav a {
overflow: hidden; overflow: hidden;
text-indent: -9999px; text-indent: -9999px;
white-space: nowrap; white-space: nowrap;
} }
} }
@media not screen and (min-width: 45em) { @media not screen and (min-width: 45em) {
.document_table table tfoot .ui-controlgroup-controls a::before { .document_table nav a::before {
float: left; float: left;
text-indent: 6pt; text-indent: 6pt;
} }
......
...@@ -1523,9 +1523,10 @@ div[data-gadget-scope='erp5_searchfield'] { ...@@ -1523,9 +1523,10 @@ div[data-gadget-scope='erp5_searchfield'] {
th, td { th, td {
// line-height: 1.5em; // line-height: 1.5em;
vertical-align: middle; vertical-align: middle;
padding: @half-margin-size;
} }
thead { thead, tfoot {
background-color: @colorsubheaderbackground; background-color: @colorsubheaderbackground;
color: @white; color: @white;
...@@ -1565,7 +1566,6 @@ div[data-gadget-scope='erp5_searchfield'] { ...@@ -1565,7 +1566,6 @@ div[data-gadget-scope='erp5_searchfield'] {
@media @desktop, @tablet { @media @desktop, @tablet {
a { a {
display: block; display: block;
padding: @half-margin-size;
} }
} }
...@@ -1646,40 +1646,43 @@ div[data-gadget-scope='erp5_searchfield'] { ...@@ -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 { span {
display: flex; opacity: .3;
padding-top: @margin-size; flex: 2;
border-top: 2px solid rgba(0, 0, 0, 0.14902); text-align: right;
float: right;
}
a {
.button();
span { margin-right: @margin-size;
opacity: .3; &:last-of-type {
flex: 2; margin-right: 0;
text-align: right;
} }
a {
.button();
margin-right: @margin-size; &:hover, &:active {
&:last-of-type { background-color: @colorblocklinkbackground;
margin-right: 0; }
}
@media @smartphone { @media @smartphone {
.hide_text(@width: initial); .hide_text(@width: initial);
} }
&::before { &::before {
@media @smartphone { @media @smartphone {
float: left; float: left;
text-indent: @margin-size; text-indent: @margin-size;
}
} }
} }
} }
} }
} }
......
<?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>
<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
...@@ -128,11 +128,6 @@ ...@@ -128,11 +128,6 @@
<td>//thead/tr/th[3]</td> <td>//thead/tr/th[3]</td>
<td>Quantity</td> <td>Quantity</td>
</tr> </tr>
<tr>
<td>assertElementPresent</td>
<td>//tfoot/tr/th[@colspan="3"]</td>
<td></td>
</tr>
<tr> <tr>
...@@ -169,11 +164,6 @@ ...@@ -169,11 +164,6 @@
<td>//thead/tr/th[4]</td> <td>//thead/tr/th[4]</td>
<td>Quantity</td> <td>Quantity</td>
</tr> </tr>
<tr>
<td>assertElementPresent</td>
<td>//tfoot/tr/th[@colspan="4"]</td>
<td></td>
</tr>
<!-- Line checkbox --> <!-- Line checkbox -->
...@@ -233,11 +223,6 @@ ...@@ -233,11 +223,6 @@
<td>//thead/tr/th[3]</td> <td>//thead/tr/th[3]</td>
<td>Quantity</td> <td>Quantity</td>
</tr> </tr>
<tr>
<td>assertElementPresent</td>
<td>//tfoot/tr/th[@colspan="3"]</td>
<td></td>
</tr>
<!-- only one element present --> <!-- only one element present -->
<tr> <tr>
......
<?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>
<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&amp;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
<?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>
<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&amp;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
<?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>
<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&amp;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
...@@ -247,7 +247,7 @@ ...@@ -247,7 +247,7 @@
</tr> </tr>
<tr> <tr>
<td>waitForElementPresent</td> <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> <td></td>
</tr> </tr>
<tr> <tr>
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment