Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Support
Keyboard shortcuts
?
Submit feedback
Contribute to GitLab
Sign in / Register
Toggle navigation
erp5
Project overview
Project overview
Details
Activity
Releases
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Issues
0
Issues
0
List
Boards
Labels
Milestones
Merge Requests
0
Merge Requests
0
Analytics
Analytics
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Commits
Issue Boards
Open sidebar
Aurélien Vermylen
erp5
Commits
dfb62b97
Commit
dfb62b97
authored
Dec 12, 2017
by
Tomáš Peterka
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[hal_json+renderjs] Implement "stat" line for ListBox
parent
f4eb48d9
Changes
18
Expand all
Hide whitespace changes
Inline
Side-by-side
Showing
18 changed files
with
773 additions
and
193 deletions
+773
-193
bt5/erp5_hal_json_style/SkinTemplateItem/portal_skins/erp5_hal_json_style/ERP5Document_getHateoas.py
...rtal_skins/erp5_hal_json_style/ERP5Document_getHateoas.py
+66
-2
bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.py
...ateItem/portal_skins/erp5_ui_test/FooModule_statMethod.py
+12
-0
bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.xml
...teItem/portal_skins/erp5_ui_test/FooModule_statMethod.xml
+66
-0
bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.py
...eItem/portal_skins/erp5_ui_test/FooModule_statQuantity.py
+6
-0
bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.xml
...Item/portal_skins/erp5_ui_test/FooModule_statQuantity.xml
+66
-0
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.js
...athTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.js
+26
-20
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.xml
...thTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.xml
+2
-2
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.html
...ateItem/web_page_module/rjs_gadget_erp5_listbox_html.html
+37
-18
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.xml
...lateItem/web_page_module/rjs_gadget_erp5_listbox_html.xml
+2
-2
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js
...emplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js
+172
-99
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.xml
...mplateItem/web_page_module/rjs_gadget_erp5_listbox_js.xml
+2
-2
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.css
...emplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.css
+20
-14
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.xml
...emplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.xml
+2
-2
bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/erp5css.less.txt
...teItem/portal_skins/erp5_web_renderjs_ui/erp5css.less.txt
+31
-32
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.xml
...ortal_tests/renderjs_ui_listbox_zuite/testStatColumns.xml
+58
-0
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.zpt
...ortal_tests/renderjs_ui_listbox_zuite/testStatColumns.zpt
+73
-0
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.xml
...portal_tests/renderjs_ui_listbox_zuite/testStatMethod.xml
+58
-0
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.zpt
...portal_tests/renderjs_ui_listbox_zuite/testStatMethod.zpt
+74
-0
No files found.
bt5/erp5_hal_json_style/SkinTemplateItem/portal_skins/erp5_hal_json_style/ERP5Document_getHateoas.py
View file @
dfb62b97
...
@@ -46,10 +46,26 @@ MARKER = []
...
@@ -46,10 +46,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
):
...
@@ -669,6 +685,8 @@ def renderField(traversed_document, field, form, value=None, meta_type=None, key
...
@@ -669,6 +685,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
(
...
@@ -1456,8 +1474,54 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
...
@@ -1456,8 +1474,54 @@ def calculateHateoas(is_portal=None, is_site_root=None, traversed_document=None,
contents_item
[
select
]
=
getAttrFromAnything
(
search_result
,
select
,
property_getter
,
property_hasser
,
{
'brain'
:
search_result
})
contents_item
[
select
]
=
getAttrFromAnything
(
search_result
,
select
,
property_getter
,
property_hasser
,
{
'brain'
:
search_result
})
# endfor select
# endfor select
contents_list
.
append
(
contents_item
)
contents_list
.
append
(
contents_item
)
result_dict
[
'_embedded'
][
'contents'
]
=
contents_list
result_dict
[
'_embedded'
][
'contents'
]
=
ensureSerializable
(
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
selection_name
=
source_field
.
get_value
(
'selection_name'
)
if
selection_name
and
'selection_name'
not
in
catalog_kw
:
catalog_kw
[
'selection_name'
]
=
selection_name
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
)(
**
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
())(
**
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'
]
=
ensureSerializable
(
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'
:
...
...
bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.py
0 → 100644
View file @
dfb62b97
"""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
,
]
bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statMethod.xml
0 → 100644
View file @
dfb62b97
<?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>
bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.py
0 → 100644
View file @
dfb62b97
counter
=
0
for
value
in
context
.
contentValues
():
counter
=
counter
+
int
(
value
.
getQuantity
())
return
counter
bt5/erp5_ui_test/SkinTemplateItem/portal_skins/erp5_ui_test/FooModule_statQuantity.xml
0 → 100644
View file @
dfb62b97
<?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_statQuantity
</string>
</value>
</item>
<item>
<key>
<string>
portal_type
</string>
</key>
<value>
<string>
Python Script
</string>
</value>
</item>
</dictionary>
</pickle>
</record>
</ZopeData>
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.js
View file @
dfb62b97
...
@@ -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
};
};
});
});
})
})
...
...
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_jio_js.xml
View file @
dfb62b97
...
@@ -230,7 +230,7 @@
...
@@ -230,7 +230,7 @@
</item>
</item>
<item>
<item>
<key>
<string>
serial
</string>
</key>
<key>
<string>
serial
</string>
</key>
<value>
<string>
9
47.45414.13002.1005
2
</string>
</value>
<value>
<string>
9
63.59331.40212.5543
2
</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>
1
449753994.81
</float>
<float>
1
512454358.33
</float>
<string>
UTC
</string>
<string>
UTC
</string>
</tuple>
</tuple>
</state>
</state>
...
...
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.html
View file @
dfb62b97
...
@@ -45,14 +45,14 @@
...
@@ -45,14 +45,14 @@
<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
}}
...
@@ -63,7 +63,7 @@
...
@@ -63,7 +63,7 @@
<
/a
>
<
/a
>
{{
/
if
}}
{{
/
if
}}
{{
else
}}
{{
else
}}
<
a
href
=
"
{{href}}
"
class
=
"
ui-link
"
>
{{
tex
t
}}
<
/a
>
<
a
href
=
"
{{href}}
"
class
=
"
ui-link
"
>
{{
defaul
t
}}
<
/a
>
{{
/
if
}}
{{
/
if
}}
<
/td
>
<
/td
>
{{
/
each
}}
{{
/
each
}}
...
@@ -82,7 +82,7 @@
...
@@ -82,7 +82,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
>
...
@@ -92,7 +92,7 @@
...
@@ -92,7 +92,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
}}
...
@@ -103,7 +103,7 @@
...
@@ -103,7 +103,7 @@
<
/a
>
<
/a
>
{{
/
if
}}
{{
/
if
}}
{{
else
}}
{{
else
}}
<
a
href
=
"
{{href}}
"
class
=
"
ui-link
"
>
{{
tex
t
}}
<
/a
>
<
a
href
=
"
{{href}}
"
class
=
"
ui-link
"
>
{{
defaul
t
}}
<
/a
>
{{
/
if
}}
{{
/
if
}}
<
/td
>
<
/td
>
{{
/
each
}}
{{
/
each
}}
...
@@ -121,19 +121,38 @@
...
@@ -121,19 +121,38 @@
<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
}}
{{
#
if
@
first
}}
Total
{{
/if}
}
{{
/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
>
...
@@ -153,11 +172,11 @@
...
@@ -153,11 +172,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}}
"
>
...
...
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_html.xml
View file @
dfb62b97
...
@@ -234,7 +234,7 @@
...
@@ -234,7 +234,7 @@
</item>
</item>
<item>
<item>
<key>
<string>
serial
</string>
</key>
<key>
<string>
serial
</string>
</key>
<value>
<string>
96
2.24395.32903.29832
</string>
</value>
<value>
<string>
96
3.63278.21548.22971
</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>
15
06526285.9
</float>
<float>
15
12617871.97
</float>
<string>
UTC
</string>
<string>
UTC
</string>
</tuple>
</tuple>
</state>
</state>
...
...
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js
View file @
dfb62b97
This diff is collapsed.
Click to expand it.
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.xml
View file @
dfb62b97
...
@@ -236,7 +236,7 @@
...
@@ -236,7 +236,7 @@
</item>
</item>
<item>
<item>
<key>
<string>
serial
</string>
</key>
<key>
<string>
serial
</string>
</key>
<value>
<string>
963.
40734.12928.52855
</string>
</value>
<value>
<string>
963.
61737.34616.6621
</string>
</value>
</item>
</item>
<item>
<item>
<key>
<string>
state
</string>
</key>
<key>
<string>
state
</string>
</key>
...
@@ -254,7 +254,7 @@
...
@@ -254,7 +254,7 @@
</tuple>
</tuple>
<state>
<state>
<tuple>
<tuple>
<float>
151
1430200.52
</float>
<float>
151
2556744.93
</float>
<string>
UTC
</string>
<string>
UTC
</string>
</tuple>
</tuple>
</state>
</state>
...
...
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.css
View file @
dfb62b97
...
@@ -1279,24 +1279,30 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button {
...
@@ -1279,24 +1279,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
;
}
}
}
}
...
@@ -1316,7 +1322,6 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button {
...
@@ -1316,7 +1322,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
)
{
...
@@ -1385,41 +1390,42 @@ div[data-gadget-scope='erp5_searchfield'] div.search_parsed_value button {
...
@@ -1385,41 +1390,42 @@ 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
:last-of-type
{
.document_table
nav
a
:last-of-type
{
margin-right
:
0
;
margin-right
:
0
;
}
}
.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
;
}
}
@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
;
}
}
}
}
.document_table
table
tfoot
.ui-controlgroup-controls
a
::before
{
.document_table
nav
a
::before
{
margin-right
:
6pt
;
margin-right
:
6pt
;
}
}
@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
;
}
}
...
...
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_nojqm_css.xml
View file @
dfb62b97
...
@@ -242,7 +242,7 @@
...
@@ -242,7 +242,7 @@
</item>
</item>
<item>
<item>
<key>
<string>
serial
</string>
</key>
<key>
<string>
serial
</string>
</key>
<value>
<string>
963.
49524.2871.19182
</string>
</value>
<value>
<string>
963.
54869.55137.61115
</string>
</value>
</item>
</item>
<item>
<item>
<key>
<string>
state
</string>
</key>
<key>
<string>
state
</string>
</key>
...
@@ -260,7 +260,7 @@
...
@@ -260,7 +260,7 @@
</tuple>
</tuple>
<state>
<state>
<tuple>
<tuple>
<float>
1512
052879.86
</float>
<float>
1512
455490.33
</float>
<string>
UTC
</string>
<string>
UTC
</string>
</tuple>
</tuple>
</state>
</state>
...
...
bt5/erp5_web_renderjs_ui/SkinTemplateItem/portal_skins/erp5_web_renderjs_ui/erp5css.less.txt
View file @
dfb62b97
...
@@ -1476,9 +1476,10 @@ div[data-gadget-scope='erp5_searchfield'] {
...
@@ -1476,9 +1476,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;
...
@@ -1518,7 +1519,6 @@ div[data-gadget-scope='erp5_searchfield'] {
...
@@ -1518,7 +1519,6 @@ div[data-gadget-scope='erp5_searchfield'] {
@media @desktop, @tablet {
@media @desktop, @tablet {
a {
a {
display: block;
display: block;
padding: @half-margin-size;
}
}
}
}
...
@@ -1598,48 +1598,47 @@ div[data-gadget-scope='erp5_searchfield'] {
...
@@ -1598,48 +1598,47 @@ div[data-gadget-scope='erp5_searchfield'] {
}
}
}
}
}
}
} /* end-of tbody, tfoot*/
} /* end-of table */
nav {
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 {
padding: @margin-size;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: @radius;
margin-right: @margin-size;
&:last-of-type {
margin-right: 0;
}
tfoot .ui-controlgroup-controls {
&:hover, &:active {
display: flex;
background-color: @colorblocklinkbackground;
padding-top: @margin-size;
}
border-top: 2px solid rgba(0, 0, 0, 0.14902);
span {
@media @smartphone {
opacity: .3;
.hide_text(@width: initial);
flex: 2;
text-align: right;
}
}
a {
padding: @margin-size;
border: 1px solid rgba(0, 0, 0, 0.14);
border-radius: @radius;
&::before {
margin-right: @margin-size;
margin-right: @margin-size;
&:last-of-type {
margin-right: 0;
}
&:hover, &:active {
background-color: @colorblocklinkbackground;
}
@media @smartphone {
@media @smartphone {
.hide_text(@width: initial);
float: left;
}
text-indent: @margin-size;
&::before {
margin-right: @margin-size;
@media @smartphone {
float: left;
text-indent: @margin-size;
}
}
}
}
}
}
}
}
}
}
}
...
...
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.xml
0 → 100644
View file @
dfb62b97
<?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>
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatColumns.zpt
0 → 100644
View file @
dfb62b97
<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
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.xml
0 → 100644
View file @
dfb62b97
<?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>
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testStatMethod.zpt
0 → 100644
View file @
dfb62b97
<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
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment