I show a tree defined by list tags. This tree may be arbitrary large and may have arbitrary depth. Therefore it may just show too much information to be useful. So I set out to reduce this information in a sensible way.
In order to show only the first level, I offer a button. The same button will restore the whole tree if collapsed. The button calls a JavaScript function which will list all items to be hidden, then I cycle through that list to hide the items (resp. show them again if hidden). This mechanism works well and is not dependent on the number of items.
The length of this operation obviously depends on the number of items to be shown or hidden. For small numbers the result of the operation is immediately, so there is no need for visual feedback. 35 items take about 2 sec on my machine, which I think is still acceptable. But 1000 items take about 10 sec which is too much without a visual feedback.
So I tried several strategies.
The first was a modal box with a message to be shown at the beginning of the operation, eventually a second box at the end (DONE). This does not work as (a) the last (or only) modal is shown (b) after the operation which is useless. Why is it not shown before as this function is called before the rearrangement operations? Anyway, the user is confused as he has no idea what is going on during the operation.
Next I tried the shadow approach. Switch the background color of the tree to gray to signal inertia, then do the operations, then switch back to normal. Same thing, both background-switching operations are executed at once after the operations are done. How come?
Finally I implemented a progress bar. Again, this mechanism, although called at each step of the operations, which should give a really nice progress bar with a thousand steps, is visually done in one swoop at the end of the operations.
The core of my code is as follows:
// various attempts to get a visual feedback before the next operation starts
const list = document.getElementsByClassName('_fsl'); // level > 1
for (var i = 0; i < list.length; i++) {
// check each element and manipulate visiblity
}
Next I tried querySelectorAll:
document.querySelectorAll('._fsl').forEach(function(el) {
var style = el.style.display || 'block';
el.style.display = style == 'block'
? 'none' // hide
: 'block'; // show
})
This works about twice as fast, but my main problem is not solved. The visual feedback is given after the fact either way.
There does not seem to be a way around looping, and this looping operation is done first for a reason unknown to me. JavaScript is an interpreting language, so it should process each function in a linear manner.
As I run out of ideas, I ask for advice. Is what I try to achieve impossible to do? At least with my approach?
Any ideas to resolve this problem?
Later ...
I found a solution for strategy 1, but I don't understand why this works. By trial and error I implemented separate functions and wrapped the hide/show-mechanism into a setTimeout setting. With 1015 items, I can see that the message comes first and the result of the manipulation later. Without this setTimeout trick it is the other way around.
show_msg(); // shows first, finally
setTimeout(function() { // magic
hide_show_elements()
}, (1 * 10));
switch_button_img(); // visual hint
Still, when I add show_msg_done() at the end I get this last message only instead of the initial show_msg(). Really confusing. How to understand that? No progress with strategies 2 and 3.
With 3431 items I get 16 sec for hiding and 25 sec for unhiding. show_msg uses the modal box which uses setTimeout, too, but shows as long as hide_show_elements runs, no matter which time value is set for the modal box itself. The time used probably also depends on the complexity of the tree, not only on the number of items, but I don't want to dig into that.
The result is pleasing, so I guess my journey was successful. I'd like to understand what is happening here, though.
Here is some more code, I hope it helps:
PHP
$nodes_cnt = $this->_get_nodes_cnt();
# find out how many nodes are to be changed
$msg = $nodes_cnt > 25
? wp_lg_str('vx_collapse_takes_time')
# raw text in the language in use
: '';
$msg = str_replace('NUMNODES', $nodes_cnt, $msg);
# fill in number
$time = $nodes_cnt > 250
? 0.5
: 1.5;
# for many nodes the message will stay long enough to be read
Javascript
// the modal box
function success_msg(msg, t = 3.5) {
$('nbox_c').innerHTML = msg; // define msg
$('nbox').style.display = 'block'; // show msg
setTimeout(function(){ // hide msg
$('nbox').style.display = 'none';
}, t * 1000)
} // function success_msg(msg)
function show_msg() { // use the modal box
var msg = '".$msg."';
var time= '0.1';
if (msg) {
success_msg(msg, time);
}
} // show_msg()
// the work horse
function hide_show_elements() {
document.querySelectorAll('._fsl').forEach(function(el) {
// _fsl is class name of nodes to be affected
//el.style.backgroundColor = 'orange';
// an attempt for strategy 2
var style = el.style.display || 'block';
el.style.display = style == 'block'
? 'none' // hide
: 'block'; // show
})
} // hide_show_elements()
Raw data
The base is a MySQL table with columns: id, label, parent. This table is transformed by PHP to a tree, i.e. a multidimensional array. This array is then transformed to a HTML string with tags ul, li. According to hints ul is used to hide or show branches.
HTML
I found a flaw in my HTML implementation and managed to create a much simpler solution. I hoped my timing results would be better, but they werde not. The browser obviously did not care about the flaw.
The code looks primitive and handmade, but it works.
First I cycle through my tree data $this->_ar_tree (multidimensional array) and build an array with all sub_1 nodes at level 1 having a sub array:
function _get_ar_sub_keys_level_1() {
foreach($this->_ar_tree['parent_0']['sub_1'] as $key => $val){
# parent_0 is root level, sub_1 first level
if (!wp_sp($key, 'sub_')) {
# we don't have sub nodes
continue;
}
# collect sub nodes at sub_1 to be hidden
$this->_ar_sub_keys_level_1[] = $key;
} # done foreach
} # _get_ar_sub_keys_level_1()
Next I take the HTML string $res which is a multidimensional list of lists and split this string at <ul to inspect and augment those lists which belong to $this->_ar_sub_keys_level_1 with a class tag using a comment of form <!-- vp:26/1:pv --> where the first number is the node number and the second the parent node, using a custom helper function wp_tag_part:
function _add_level_1_class($res) {
$ar = explode('<ul', $res); # list of lists
foreach($ar as $key => $val){
$parent_sublevel = wp_tag_part($val, '/', ':pv'); # cut between / and :pv
if ($parent_sublevel < 2) { # too small
continue;
}
if (!in_array("sub_$parent_sublevel", $this->_ar_sub_keys_level_1)) {
continue; # too deep
}
$ar[$key] = ' class="_fsl"' . $val; # mark this one
} # done foreach
$str = implode('<ul', $ar); # regain string
return $str;
The hide/show mechanism is done by JavaScript as given above based on class _fsl.
Test results
The time taken by the chrome browser to process a tree with the sample 6165 nodes and 252 level 1 sub nodes is slightly faster now: less then 2 sec for hiding and about 5 sec for showing.
With another sample with 4912 nodes and 12 level 1 sub nodes I get about the same values. In a different language this sample has 9853 nodes and 12 level 1 sub nodes which take 5 / 13. These numbers are generated with Opera and aroused curiosity for more input and different browsers, i.e Chrome.
A test produced a popup after some seconds: Page unresponsive, then went on normal. Very unpleasant, but interesting. So I tested with more browsers. Ur and Firefox seemed to be the fastest.
Surprisingly, they produced different results at various attempts. E.g. I could not reproduce the fine showing value for Ur, I reloaded the page and restartet the browser to no avail. The popup seems to appear after 15 sec.
Although most browsers use the same engine, they will not use same version.
The following table shows my investigation:
| sample | nodes | marked | hiding s | showing s | browser | popup |
|---|---|---|---|---|---|---|
| 1624 | 24 | 2 | < 0.1 | < 0.1 | Opera | no |
| 445 | 321 | 35 | < 0.5 | < 1 | Opera | no |
| 520 | 6165 | 252 | 2 | 5 | Opera | no |
| 7428 a | 4912 | 12 | 2 | 5 | Opera | no |
| 7428 b | 9853 | 12 | 5 | 19 | Opera | no |
| 7428 c | 22869 | 12 | 7 | 23 | Opera | no |
| 7428 c | 22869 | 12 | 4 | 13/26 | Ur | no, yes twice |
| 7428 c | 22869 | 12 | 5 | 8/12/15 | Firefox | no |
| 7428 c | 22869 | 12 | 9 | 10/28 | Vivaldi | no |
| 7428 c | 22869 | 12 | 6 | 31 | Chrome | yes, twice |
| 7428 c | 22869 | 12 | 6 | 23 | Edge | yes |
These results should be treated with caution. I find it remarkable to get different results on repeat (Firefox, Vivaldi). Maybe some other background activity on Windows has influence on performance. Also I noticed that faster browsers show the visual hint fast, others do take undue time.