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
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Analytics
Analytics
CI / CD
Repository
Value Stream
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
Laurent S
erp5
Commits
53d26c09
Commit
53d26c09
authored
Dec 18, 2017
by
Tomáš Peterka
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
[renderjs_ui] ListBox asynchronous data fetching locks other ListBox operations
parent
b2edbc2f
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
260 additions
and
37 deletions
+260
-37
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
+49
-33
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_global_js.js
.../PathTemplateItem/web_page_module/rjs_gadget_global_js.js
+93
-0
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_global_js.xml
...PathTemplateItem/web_page_module/rjs_gadget_global_js.xml
+2
-2
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testFastSave.xml
...m/portal_tests/renderjs_ui_listbox_zuite/testFastSave.xml
+58
-0
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testFastSave.zpt
...m/portal_tests/renderjs_ui_listbox_zuite/testFastSave.zpt
+56
-0
No files found.
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.js
View file @
53d26c09
/*jslint indent: 2, maxerr: 3, nomen: true */
/*jslint indent: 2, maxerr: 3, nomen: true */
/*global window, document, rJS, URI, RSVP,
/*global window, document, rJS, URI, RSVP,
SimpleQuery, ComplexQuery, Query, Handlebars, console, QueryFactory*/
SimpleQuery, ComplexQuery, Query, Handlebars, console, QueryFactory,
lockGadgetInQueue, unlockGadgetInQueue, unlockGadgetInFailedQueue */
(
function
(
window
,
document
,
rJS
,
URI
,
RSVP
,
(
function
(
window
,
document
,
rJS
,
URI
,
RSVP
,
SimpleQuery
,
ComplexQuery
,
Query
,
Handlebars
,
console
,
QueryFactory
)
{
SimpleQuery
,
ComplexQuery
,
Query
,
Handlebars
,
console
,
QueryFactory
,
lockGadgetInQueue
,
unlockGadgetInQueue
,
unlockGadgetInFailedQueue
)
{
"
use strict
"
;
"
use strict
"
;
var
gadget_klass
=
rJS
(
window
),
var
gadget_klass
=
rJS
(
window
),
listbox_thead_source
=
gadget_klass
.
__template_element
listbox_thead_source
=
gadget_klass
.
__template_element
...
@@ -414,7 +416,6 @@
...
@@ -414,7 +416,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
()
{
...
@@ -599,8 +600,12 @@
...
@@ -599,8 +600,12 @@
"
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
)
})
})
// Lock gadget to show that state is not consistent thus any other
// operation must wait
.
push
(
lockGadgetInQueue
(
gadget
))
.
push
(
function
(
result
)
{
.
push
(
function
(
result
)
{
return
gadget
.
changeState
({
return
gadget
.
changeState
({
allDocs_result
:
result
allDocs_result
:
result
...
@@ -608,7 +613,7 @@
...
@@ -608,7 +613,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
;
}
}
...
@@ -616,40 +621,50 @@
...
@@ -616,40 +621,50 @@
return
gadget
.
changeState
({
return
gadget
.
changeState
({
has_error
:
true
has_error
:
true
});
});
});
})
// gadget mutex is necessary because part of ListBox is rendered later
// via cancellable `fetchLineList` thus no other operation (eg. getContent) must be
// performed on ListBox before it is fully rendered
// unlock mutex in success and fail branch
.
push
(
unlockGadgetInQueue
(
gadget
),
unlockGadgetInFailedQueue
(
gadget
));
})
})
.
declareMethod
(
"
getContent
"
,
function
(
options
)
{
.
declareMethod
(
"
getContent
"
,
function
(
options
)
{
var
form_gadget
=
this
,
var
gadget
=
this
;
k
,
field_gadget
,
count
=
form_gadget
.
props
.
cell_gadget_list
.
length
,
data
=
{},
queue
=
new
RSVP
.
Queue
();
function
extendData
(
field_data
)
{
var
key
;
for
(
key
in
field_data
)
{
if
(
field_data
.
hasOwnProperty
(
key
))
{
data
[
key
]
=
field_data
[
key
];
}
}
}
for
(
k
=
0
;
k
<
count
;
k
+=
1
)
{
return
new
RSVP
.
Queue
()
field_gadget
=
form_gadget
.
props
.
cell_gadget_list
[
k
];
// lock gadget to forbid sub-field modification while getting their values
// XXX Hack until better defined
.
push
(
lockGadgetInQueue
(
gadget
))
if
(
field_gadget
.
getContent
!==
undefined
)
{
queue
.
push
(
field_gadget
.
getContent
.
bind
(
field_gadget
,
options
))
.
push
(
extendData
);
}
}
return
queue
.
push
(
function
()
{
.
push
(
function
()
{
data
[
form_gadget
.
props
.
listbox_uid_dict
.
key
]
=
form_gadget
.
props
.
listbox_uid_dict
.
value
;
// gather content from all sub-gadgets
return
RSVP
.
all
(
gadget
.
props
.
cell_gadget_list
.
map
(
function
(
subgadget
)
{
if
(
subgadget
.
getContent
!==
undefined
)
{
return
subgadget
.
getContent
(
options
);
}
return
{};
}
)
);
})
.
push
(
function
(
field_value_list
)
{
var
data
=
{};
// will contain all subfields values
field_value_list
.
forEach
(
function
(
field_value
)
{
// imprint the content (object with possible multiple keys!) into `data`
var
key
;
for
(
key
in
field_value
)
{
if
(
field_value
.
hasOwnProperty
(
key
))
{
data
[
key
]
=
field_value
[
key
];
}
}
});
// gadget.props.listbox_uid_dict.value is an array of UIDs of all editable documents
data
[
gadget
.
props
.
listbox_uid_dict
.
key
]
=
gadget
.
props
.
listbox_uid_dict
.
value
;
return
data
;
return
data
;
});
})
// unlock passes through its argument
.
push
(
unlockGadgetInQueue
(
gadget
),
unlockGadgetInFailedQueue
(
gadget
));
})
})
.
onEvent
(
'
click
'
,
function
(
evt
)
{
.
onEvent
(
'
click
'
,
function
(
evt
)
{
...
@@ -738,4 +753,5 @@
...
@@ -738,4 +753,5 @@
});
});
}(
window
,
document
,
rJS
,
URI
,
RSVP
,
}(
window
,
document
,
rJS
,
URI
,
RSVP
,
SimpleQuery
,
ComplexQuery
,
Query
,
Handlebars
,
console
,
QueryFactory
));
SimpleQuery
,
ComplexQuery
,
Query
,
Handlebars
,
console
,
QueryFactory
,
lockGadgetInQueue
,
unlockGadgetInQueue
,
unlockGadgetInFailedQueue
));
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_erp5_listbox_js.xml
View file @
53d26c09
...
@@ -236,7 +236,7 @@
...
@@ -236,7 +236,7 @@
</item>
</item>
<item>
<item>
<key>
<string>
serial
</string>
</key>
<key>
<string>
serial
</string>
</key>
<value>
<string>
96
3.40734.12928.52855
</string>
</value>
<value>
<string>
96
4.19650.6129.64921
</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
3931714.4
</float>
<string>
UTC
</string>
<string>
UTC
</string>
</tuple>
</tuple>
</state>
</state>
...
...
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_global_js.js
View file @
53d26c09
...
@@ -126,4 +126,97 @@
...
@@ -126,4 +126,97 @@
});
});
};
};
/** Internal function to prepare gadget to hold a mutex */
function
ensureLockable
(
gadget
)
{
if
(
gadget
.
props
===
undefined
)
{
gadget
.
props
=
{};
}
// waiting_line is container of mutexes which already blocks some Promise
if
(
gadget
.
props
.
waiting_line
===
undefined
)
{
gadget
.
props
.
waiting_line
=
[];
}
}
/** Synchronously lock gadget and return previous lock's promise.
If used in a Queue (@see lockGadgetInQueue) it blocks when acquiring the lock.
*/
window
.
lockGadget
=
function
(
gadget
)
{
var
ahead_of_me
;
ensureLockable
(
gadget
);
// step in line
gadget
.
props
.
waiting_line
.
push
(
RSVP
.
defer
());
if
(
gadget
.
props
.
waiting_line
.
length
>=
2
)
{
// wait for the promise ahead of me
ahead_of_me
=
gadget
.
props
.
waiting_line
[
gadget
.
props
.
waiting_line
.
length
-
2
].
promise
;
}
else
{
ahead_of_me
=
RSVP
.
resolve
();
}
// return previous lock's Promise to postpone execution
return
ahead_of_me
;
};
/** Lock gadget as a step in RSVP.Queue waiting for previous lock to unlock.
Use in RSVP.Queue to block execution until manually called `unlockGadget`.
Both lock/unlockGadget pass through any value in RSVP.Queue manner.
Pass through any value. Not re-throwing errors in fail branch!
Example:
new RSVP.Queue()
.push(function () {return some_value;})
.push(lockGadgetInQueue(gadget))
.push(function (some_value) {return someWork(some_value);})
.push(unlockGadgetInQueue(gadget));
*/
window
.
lockGadgetInQueue
=
function
(
gadget
)
{
// return function to be used in RSVP.Queue
return
function
(
pass_through
)
{
return
new
RSVP
.
Queue
()
.
push
(
function
()
{
return
window
.
lockGadget
(
gadget
);
})
.
push
(
function
()
{
return
pass_through
;
});
};
};
/** Synchronously unlock gadget by resolving props.mutex.promise.
That promise is most likely blocking some RSVP.Queue or is then-ed on another
Promise.
*/
window
.
unlockGadget
=
function
(
gadget
)
{
if
(
gadget
.
props
===
undefined
||
gadget
.
props
.
waiting_line
===
undefined
||
gadget
.
props
.
waiting_line
.
length
===
0
)
{
throw
new
Error
(
"
Gadget
"
+
gadget
+
"
has not been locked yet!
"
);
}
gadget
.
props
.
waiting_line
.
shift
().
resolve
();
};
/** Unlock gadget without blocking as a step in RSVP.Queue.
Pass through any value. Not re-throwing errors in fail branch!
For example @see lockGadgetInQueue.
*/
window
.
unlockGadgetInQueue
=
function
(
gadget
)
{
return
function
(
pass_through
)
{
window
.
unlockGadget
(
gadget
);
return
pass_through
;
};
};
/** Unlock gadget without blocking and throw any argument received. */
window
.
unlockGadgetInFailedQueue
=
function
(
gadget
)
{
return
function
(
error
)
{
window
.
unlockGadget
(
gadget
);
throw
error
;
};
};
}(
window
,
RSVP
,
FileReader
));
}(
window
,
RSVP
,
FileReader
));
\ No newline at end of file
bt5/erp5_web_renderjs_ui/PathTemplateItem/web_page_module/rjs_gadget_global_js.xml
View file @
53d26c09
...
@@ -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.51167.64410.14796
</string>
</value>
<value>
<string>
9
64.18755.54967.20718
</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
450099422.01
</float>
<float>
1
513931649.44
</float>
<string>
UTC
</string>
<string>
UTC
</string>
</tuple>
</tuple>
</state>
</state>
...
...
bt5/erp5_web_renderjs_ui_test/PathTemplateItem/portal_tests/renderjs_ui_listbox_zuite/testFastSave.xml
0 → 100644
View file @
53d26c09
<?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>
testFastSave
</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/testFastSave.zpt
0 → 100644
View file @
53d26c09
<html
xmlns:tal=
"http://xml.zope.org/namespaces/tal"
xmlns:metal=
"http://xml.zope.org/namespaces/metal"
>
<head>
<meta
http-equiv=
"Content-Type"
content=
"text/html; charset=UTF-8"
>
<title>
Test ListBox Fast Save
</title>
</head>
<body>
<table
cellpadding=
"1"
cellspacing=
"1"
border=
"1"
>
<thead>
<tr><td
rowspan=
"1"
colspan=
"3"
>
Test fast saving without enough time to render listbox data
</td></tr>
</thead><tbody>
<tal:block
metal:use-macro=
"here/PTZuite_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>
<!-- Create Foo objects with IDs 0-9 -->
<tr><td>
open
</td>
<td>
${base_url}/foo_module/FooModule_createObjects?start:int=1
&
num:int=3
&
create_line:int=1
</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"
/>
<!-- Shortcut for full renderjs url -->
<tr><td>
store
</td>
<td>
${base_url}/web_site_module/renderjs_runner
</td>
<td>
renderjs_url
</td></tr>
<tr><td>
open
</td>
<td>
${renderjs_url}/#/foo_module/1?editable=1
</td><td></td></tr>
<tr><td>
waitForElementPresent
</td>
<td>
//input[@name="field_my_quantity"]
</td><td></td></tr>
<tr><td>
type
</td>
<td>
field_my_quantity
</td>
<td>
1.00
</td></tr>
<tr><td>
click
</td>
<td>
//div[@data-gadget-scope='header']//button[@data-i18n='Save']
</td><td></td></tr>
<tr><td>
waitForElementPresent
</td>
<td>
//button[text()="Input data has errors."]
</td><td></td></tr>
<tr><td>
waitForElementPresent
</td>
<td>
//div[@data-gadget-scope="field_my_quantity"]//span[text()='The number you input has too large precision.']
</td><td></td></tr>
<tr><td>
type
</td>
<td>
field_my_quantity
</td>
<td>
1000000000.0
</td></tr>
<tal:block
metal:use-macro=
"here/Zuite_CommonTemplateForRenderjsUi/macros/save"
/>
<tr><td>
verifyValue
</td>
<td>
field_my_quantity
</td>
<td>
1000000000.0
</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