Difference between revisions of "Extension talk:Treeview5.php"

From Organic Design wiki
(Todo: can have multiple roots)
(Change source-code blocks to standard format)
 
(10 intermediate revisions by 2 users not shown)
Line 4: Line 4:
 
== Parameters ==
 
== Parameters ==
 
*'''root:''' The content of the root node. Later this will also affect how sub-trees work
 
*'''root:''' The content of the root node. Later this will also affect how sub-trees work
*'''id:''' A tree must be given an identifier if you want it's state to be persistent
+
*'''id:''' A tree must be given a unique identifier if you want it's state to be persistent
 
*'''openlevels:''' not implemented in 5.x yet
 
*'''openlevels:''' not implemented in 5.x yet
 +
*'''open:''' equals '''top''' | '''bottom'''
 +
*'''close:''' equals '''top''' | '''bottom'''
  
 
== Livelets ==
 
== Livelets ==
The new tree doesn't currently work when included via AJAX because the code in the response is not executed. The livelets code should be updated to execute script tags within responses.
+
There are a couple of issues with the the new tree view with respect to [[Livelets]] which we discuss the details of here.
 +
 
 +
=== The document.write problem ===
 +
The first issue which has been resolved in the treeview code concerns the ''document.write'' statement used in the JavaScript code which is recommended in the ''dTree'' documentation for rendering the tree after its nodes have all been defined.
 +
 
 +
A ''dTree'' is composed of JavaScript statements which execute as the page loads, so when the tree is rendered with a ''document.write'' statement, it's converted to a string (by the ''dTree'' object's ''toString'' method being executed) and then appended to the page at that point. This is fine under normal page load circumstances, but if the tree happens to be included within an AJAX request which populates some arbitrary part of the page after the page has loaded, then the page-appending behaviour of ''document.write'' will not achieve the desired result since it will append the tree to the end of the page rather than insert it at the location of the executing code. To get round this Treeview5 surrounds the script in a ''div'' element which is identified by an ''id'' attribute. The tree can then be converted to a string and written into the ''div'''s ''innerHTML'' property, using a statement similar to the following:
 +
<source lang="js">
 +
document.getElementById('{$id}').innerHTML = {$id}.toString();
 +
</source>
 +
 
 +
=== The script execution problem ===
 +
The second problem cannot be solved by adjusting the Treeview code, it requires changes to be made to the Livelets extension itself. The problem is that when an article is retrieved via AJAX request instead of as a normal browser page load, the &lt;script&gt; elements will not get executed. This problem has not shown previously because Treeview versions prior to 5 were based on HTML tables containing ''onClick'' events rather than being composed completed of JavaScript. To solve this, the Livelet extension must execute a function on receipt of its content rather than just sending it straight to the ''innerHTML'' property of its target element. The function must populate the target as usual, and then execute the content of all the ''script'' contained within it.
  
 
== How it works ==
 
== How it works ==
Line 16: Line 29:
  
 
Here is the wikitext of an example tree which includes two transcluded tree's
 
Here is the wikitext of an example tree which includes two transcluded tree's
 +
{|
 +
! Source || Rendered Tree
 +
|-
 +
|
 
<pre>
 
<pre>
 +
{{#tree:|open=top|close=top|
 
*[[toplevel]]
 
*[[toplevel]]
 
**bar
 
**bar
Line 25: Line 43:
 
**[[Baz]]
 
**[[Baz]]
 
***{{:Bar}}
 
***{{:Bar}}
 +
}}
 
</pre>
 
</pre>
 +
|valign=top|{{#tree:|open=top|close=top|
 +
*[[toplevel]]
 +
**bar
 +
***baz
 +
*foo
 +
**[[Bar]]
 +
***{{:Foo}}
 +
**[[Baz]]
 +
***{{:Bar}}
 +
}}
 +
|}
  
 
The ''expandTree'' method (the ''#tree'' parser-function hook) then converts all the matched rows into the following general structure:
 
The ''expandTree'' method (the ''#tree'' parser-function hook) then converts all the matched rows into the following general structure:
Line 38: Line 68:
 
After the resulting formatted-row structure has been fully parsed by the wiki parser, the content portions will have been converted into HTML and the information portions remain unchanged. Below is the HTML source of the above example after parsing and printed at the start of the ''renderTree'' method (the ''ParserAfterTidy'' hook).
 
After the resulting formatted-row structure has been fully parsed by the wiki parser, the content portions will have been converted into HTML and the information portions remain unchanged. Below is the HTML source of the above example after parsing and printed at the start of the ''renderTree'' method (the ''ParserAfterTidy'' hook).
 
<pre>
 
<pre>
1tv47cb2fd61165f-47cb2fd6dadf4-0-<a href="/wiki/index.php?title=Toplevl&amp;action=edit" class="new" title="Toplevl">toplevl</a>-2tv47cb2fd61165f
+
1tv47cb2fd61165f-47cb2fd6dadf4-0-<a href="/wiki/index.php?title=Toplevel&amp;action=edit" class="new" title="Toplevl">toplevel</a>-2tv47cb2fd61165f
 
1tv47cb2fd61165f-47cb2fd6dadf4-1-bar-2tv47cb2fd61165f
 
1tv47cb2fd61165f-47cb2fd6dadf4-1-bar-2tv47cb2fd61165f
 
1tv47cb2fd61165f-47cb2fd6dadf4-2-baz-2tv47cb2fd61165f
 
1tv47cb2fd61165f-47cb2fd6dadf4-2-baz-2tv47cb2fd61165f
Line 59: Line 89:
  
 
The following regular expression is used to create an array, ''$matches'', each item of which contains a list of ''(,$id,$depth,,$icon,$item)'' extracted from the formatted rows in the page.
 
The following regular expression is used to create an array, ''$matches'', each item of which contains a list of ''(,$id,$depth,,$icon,$item)'' extracted from the formatted rows in the page.
{{code|<php>
+
<source lang="php">
 
preg_match_all("/1$u-(.+?)-([0-9]+)-({$u}3(.+?){$u}4)?(.*?)(?=[12]$u)/",$text,$matches,PREG_SET_ORDER)
 
preg_match_all("/1$u-(.+?)-([0-9]+)-({$u}3(.+?){$u}4)?(.*?)(?=[12]$u)/",$text,$matches,PREG_SET_ORDER)
</php>}}
+
</source>
  
 
Following this extraction of row information into ''$matches'' is a loop that builds a new list called ''$rows'' which contains more detailed information than ''$matches'', and adjusts the depth values to account for transcluded sub-tree's. The ''$rows'' array is a list of ''($id,$depth,$icon,$item,$start)''. ''$id'' is the id of each root-tree (sub-tree's are not distinct from their root trees in the final rendered trees), ''$start'' is a boolean indicating whether or not the row is the first in a new root tree.
 
Following this extraction of row information into ''$matches'' is a loop that builds a new list called ''$rows'' which contains more detailed information than ''$matches'', and adjusts the depth values to account for transcluded sub-tree's. The ''$rows'' array is a list of ''($id,$depth,$icon,$item,$start)''. ''$id'' is the id of each root-tree (sub-tree's are not distinct from their root trees in the final rendered trees), ''$start'' is a boolean indicating whether or not the row is the first in a new root tree.
Line 72: Line 102:
 
=== Root tree's and sub-tree's ===
 
=== Root tree's and sub-tree's ===
 
One of the difficulties with this extension has been in assessing from within a parser-function whether or not it's at the root level in terms of transclusion-depth. The new version doesn't require voodoo to determine this, it instead does the following pattern match on all the formatted rows in the page after wiki-parsing is complete (in the ''ParserAfterTidy'' hook).
 
One of the difficulties with this extension has been in assessing from within a parser-function whether or not it's at the root level in terms of transclusion-depth. The new version doesn't require voodoo to determine this, it instead does the following pattern match on all the formatted rows in the page after wiki-parsing is complete (in the ''ParserAfterTidy'' hook).
{{code|<php>
+
<source lang="php">
 
if (!preg_match_all("/-1$u-(.+?)-/",$text,$subs)) $subs = array(1 => array());
 
if (!preg_match_all("/-1$u-(.+?)-/",$text,$subs)) $subs = array(1 => array());
</php>}}
+
</source>
 
This results in an array called ''$subs[1]'' which contains all the tree id's which are not transcluded. It does this by checking which  
 
This results in an array called ''$subs[1]'' which contains all the tree id's which are not transcluded. It does this by checking which  
 
rows have a preceding minus symbol, since top level ones will all be preceded by a ''p'' tag or a line start (see the example output of formatted rows above). An id is at root level if it is not in the ''$subs[1]'' array. This way is not as robust as it could be, and is also more computationally intensive than it could be.
 
rows have a preceding minus symbol, since top level ones will all be preceded by a ''p'' tag or a line start (see the example output of formatted rows above). An id is at root level if it is not in the ''$subs[1]'' array. This way is not as robust as it could be, and is also more computationally intensive than it could be.
 +
 +
== CSS ==
 +
The dTree javascript library automatically makes links for the text beside folders. This is overridden if you explicitly set an external or internal link within the tree.
 +
It would be nice to distinguish the link colour between MediaWiki's links and the links automatically created beside folders.
 +
Below is the css that comes with dtree.
 +
 +
<source lang="css">
 +
/*--------------------------------------------------|
 +
| dTree 2.05 | www.destroydrop.com/javascript/tree/ |
 +
|---------------------------------------------------|
 +
| Copyright (c) 2002-2003 Geir Landrö              |
 +
|--------------------------------------------------*/
 +
 +
.dtree {
 +
font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
 +
font-size: 11px;
 +
color: #666;
 +
white-space: nowrap;
 +
}
 +
.dtree img {
 +
border: 0px;
 +
vertical-align: middle;
 +
}
 +
.dtree a {
 +
color: #333;
 +
text-decoration: none;
 +
}
 +
.dtree a.node, .dtree a.nodeSel {
 +
white-space: nowrap;
 +
padding: 1px 2px 1px 2px;
 +
}
 +
.dtree a.node:hover, .dtree a.nodeSel:hover {
 +
color: #333;
 +
text-decoration: underline;
 +
}
 +
.dtree a.nodeSel {
 +
background-color: #c0d2ec;
 +
}
 +
.dtree .clip {
 +
overflow: hidden;
 +
}
 +
</source>
  
 
== Todo ==
 
== Todo ==
Line 84: Line 156:
 
*Add '''open all'''/'''close all''' functionality
 
*Add '''open all'''/'''close all''' functionality
 
*Implement  openlevels arg as treeview maintains persistence now
 
*Implement  openlevels arg as treeview maintains persistence now
** ''I think the javascript method ''openTo(1, true);'' handles this - ''see [http://www.destroydrop.com/javascripts/tree/example/ Code generator]''
+
** ''I think the javascript method '''openTo(1, true);''' handles this - see [http://www.destroydrop.com/javascripts/tree/example/ Code generator] source for an example''
 +
*'''root=name''' arg does not allow wikitext e.g. root=[[User:Nad]]
 
*Allow rootless trees
 
*Allow rootless trees
 
*Get roots working properly for transcluded subtrees - ''looks like dtree must have at least one root node to work''
 
*Get roots working properly for transcluded subtrees - ''looks like dtree must have at least one root node to work''

Latest revision as of 18:11, 22 May 2015

Info.svg This talk page pertains specifically to the development of this extension. For more general discussion about bugs and usage etc, please refer to the mediawiki.org talk page at MW:Extension talk:Tree view

Treeview5.1 is a new approach to the core recursive bullet-list code which doesn't require any voodoo, and should be compatible with all MediaWiki versions including 1.12.

Parameters

  • root: The content of the root node. Later this will also affect how sub-trees work
  • id: A tree must be given a unique identifier if you want it's state to be persistent
  • openlevels: not implemented in 5.x yet
  • open: equals top | bottom
  • close: equals top | bottom

Livelets

There are a couple of issues with the the new tree view with respect to Livelets which we discuss the details of here.

The document.write problem

The first issue which has been resolved in the treeview code concerns the document.write statement used in the JavaScript code which is recommended in the dTree documentation for rendering the tree after its nodes have all been defined.

A dTree is composed of JavaScript statements which execute as the page loads, so when the tree is rendered with a document.write statement, it's converted to a string (by the dTree object's toString method being executed) and then appended to the page at that point. This is fine under normal page load circumstances, but if the tree happens to be included within an AJAX request which populates some arbitrary part of the page after the page has loaded, then the page-appending behaviour of document.write will not achieve the desired result since it will append the tree to the end of the page rather than insert it at the location of the executing code. To get round this Treeview5 surrounds the script in a div element which is identified by an id attribute. The tree can then be converted to a string and written into the div's innerHTML property, using a statement similar to the following:

document.getElementById('{$id}').innerHTML = {$id}.toString();

The script execution problem

The second problem cannot be solved by adjusting the Treeview code, it requires changes to be made to the Livelets extension itself. The problem is that when an article is retrieved via AJAX request instead of as a normal browser page load, the <script> elements will not get executed. This problem has not shown previously because Treeview versions prior to 5 were based on HTML tables containing onClick events rather than being composed completed of JavaScript. To solve this, the Livelet extension must execute a function on receipt of its content rather than just sending it straight to the innerHTML property of its target element. The function must populate the target as usual, and then execute the content of all the script contained within it.

How it works

The initial problem was how to write a parser-function which could generate a tree which could be composed of other transcluded tree's (i.e. parser-functions which are recursively executed forming a larger whole structure together). This seemed impossible since there are no MediaWiki properties available in the environment which reveal the current depth that the parser-function expansion is operating at.

But after thinking about the problem more closely, it turns out that the tree can be constructed without depth information by converting each tree row into a new format containing depth and tree id information which is protected from wiki-parsing. This information can then be converted into tree JavaScript after the parser has finished in the ParserAfterTidy hook.

Here is the wikitext of an example tree which includes two transcluded tree's

Source Rendered Tree
{{#tree:|open=top|close=top|
*[[toplevel]]
**bar
***baz
*foo
**[[Bar]]
***{{:Foo}}
**[[Baz]]
***{{:Bar}}
}}

The expandTree method (the #tree parser-function hook) then converts all the matched rows into the following general structure:

1{uniq}-{id}-{depth}-{item}-2{uniq}

Where uniq is a unique string representing treeview's in general, id is a unique string representing the current tree, depth is the depth of the current row with respect to the current tree root, and item is the wikitext within the current row. Each row is converted to the new structured format by the private formatRow method. The information is "protected" from the parser merely by the fact that it contains no markup characters, but this could be made more robust later.

Notes about the format used

  • If the same article is transcluded more than once it may not get processed again after the first time. This caching effect occurs at an internal level within the parser and is not affected by globals or parser properties. This means that information relating to specific items or sub-tree's could be repeated across transcluded instances of the same article within one or more trees in the page. This is not a problem in the case of the tree-id because only id's of root trees will be used in the final rendered tree.
  • There a potential problem which hasn't been tested yet, with a sub-tree being transcluded twice in consecutive nodes. The tree id is also used to determine when a new tree is starting (by checking if id has changed), so the repeated id problem arising from multiple adjacent transclusion would prevent the loop from knowing about the transition from one of the similar sub-trees to the next.
  • The actual pattern used in the current version is slightly different than the one shown here due to subsequent bug fixes. For example, the dashes used to separate the fields in the formatted rows have been changed to \x7F's. The examples shown are easier to read than the new ones, so they've been left as is. They still demonstrate the process used to render the tree in the current version.

After the resulting formatted-row structure has been fully parsed by the wiki parser, the content portions will have been converted into HTML and the information portions remain unchanged. Below is the HTML source of the above example after parsing and printed at the start of the renderTree method (the ParserAfterTidy hook).

1tv47cb2fd61165f-47cb2fd6dadf4-0-<a href="/wiki/index.php?title=Toplevel&action=edit" class="new" title="Toplevl">toplevel</a>-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6dadf4-1-bar-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6dadf4-2-baz-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6dadf4-0-foo-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6dadf4-1-<a href="/Bar" title="Bar">Bar</a>-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6dadf4-2-1tv47cb2fd61165f-47cb2fd6da03a-0-<a href="/Foo" title="Foo">Foo</a>-2tv47cb2fd61165f-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6da03a-1-bar-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6da03a-2-baz-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6da03a-0-foo-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6da03a-1-biz-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6da03a-2-1tv47cb2fd61165f-47cb2fd6d9c40-0-<a href="/Bar" title="Bar">bar</a>-2tv47cb2fd61165f-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6d9c40-1-baz2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6d9c40-2-biz2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6da03a-1-<a href="/wiki/index.php?title=Baz&action=edit" class="new" title="Baz">Baz</a>-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6dadf4-1-<a href="/wiki/index.php?title=Baz&action=edit" class="new" title="Baz">Baz</a>-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6dadf4-2-1tv47cb2fd61165f-47cb2fd6d9c40-0-<a href="/Bar" title="Bar">bar</a>-2tv47cb2fd61165f-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6d9c40-1-baz-2tv47cb2fd61165f
1tv47cb2fd61165f-47cb2fd6d9c40-2-biz2tv47cb2fd61165f

The following regular expression is used to create an array, $matches, each item of which contains a list of (,$id,$depth,,$icon,$item) extracted from the formatted rows in the page.

preg_match_all("/1$u-(.+?)-([0-9]+)-({$u}3(.+?){$u}4)?(.*?)(?=[12]$u)/",$text,$matches,PREG_SET_ORDER)

Following this extraction of row information into $matches is a loop that builds a new list called $rows which contains more detailed information than $matches, and adjusts the depth values to account for transcluded sub-tree's. The $rows array is a list of ($id,$depth,$icon,$item,$start). $id is the id of each root-tree (sub-tree's are not distinct from their root trees in the final rendered trees), $start is a boolean indicating whether or not the row is the first in a new root tree.

The main dTree JavaScript can then be constructed by looping through the $rows list and converting ordered depth-based information to the parent/child format required by dTree. The indexes of each row within the $rows array are used as id's for the individual dTree nodes.

Adjusting depth to account for sub-tree transclusion

The depth item in the $matches array is directly derived from the number of asterisks defined at the beginning of the row's wikitext. In the $rows array, the depth item has been adjusted to account for the depth of the tree at the point the sub-tree is transcluded, so that it can continue within-ward naturally. This adjustment is done by using the $depths array to associate each tree id with the current depth at each row where a new tree-id is encountered. The depths from $matches are added to the associated $depths values and stored in $rows.

Root tree's and sub-tree's

One of the difficulties with this extension has been in assessing from within a parser-function whether or not it's at the root level in terms of transclusion-depth. The new version doesn't require voodoo to determine this, it instead does the following pattern match on all the formatted rows in the page after wiki-parsing is complete (in the ParserAfterTidy hook).

if (!preg_match_all("/-1$u-(.+?)-/",$text,$subs)) $subs = array(1 => array());

This results in an array called $subs[1] which contains all the tree id's which are not transcluded. It does this by checking which rows have a preceding minus symbol, since top level ones will all be preceded by a p tag or a line start (see the example output of formatted rows above). An id is at root level if it is not in the $subs[1] array. This way is not as robust as it could be, and is also more computationally intensive than it could be.

CSS

The dTree javascript library automatically makes links for the text beside folders. This is overridden if you explicitly set an external or internal link within the tree. It would be nice to distinguish the link colour between MediaWiki's links and the links automatically created beside folders. Below is the css that comes with dtree.

/*--------------------------------------------------|
| dTree 2.05 | www.destroydrop.com/javascript/tree/ |
|---------------------------------------------------|
| Copyright (c) 2002-2003 Geir Landrö               |
|--------------------------------------------------*/

.dtree {
	font-family: Verdana, Geneva, Arial, Helvetica, sans-serif;
	font-size: 11px;
	color: #666;
	white-space: nowrap;
}
.dtree img {
	border: 0px;
	vertical-align: middle;
}
.dtree a {
	color: #333;
	text-decoration: none;
}
.dtree a.node, .dtree a.nodeSel {
	white-space: nowrap;
	padding: 1px 2px 1px 2px;
}
.dtree a.node:hover, .dtree a.nodeSel:hover {
	color: #333;
	text-decoration: underline;
}
.dtree a.nodeSel {
	background-color: #c0d2ec;
}
.dtree .clip {
	overflow: hidden;
}

Todo

Note.svg Note: See Code generator for example generation including various javascript options available
  • Need to get inline images working - for simplicity it would be nice to hook into the img directory that comes with dDree
    • I think inline and othe images defined by the user should work off wiki uploaded images and only use dTree images as defaults - the same way the $wgTreeviewImages array works by allowing specified images to override the dTree defaults. The dTree/img folder should not be changed
  • Add open all/close all functionality
  • Implement openlevels arg as treeview maintains persistence now
    • I think the javascript method openTo(1, true); handles this - see Code generator source for an example
  • root=name arg does not allow wikitext e.g. root=User:Nad
  • Allow rootless trees
  • Get roots working properly for transcluded subtrees - looks like dtree must have at least one root node to work
    • I've tested this and it doesn't need a root (or rather, it can have a number of root nodes)

See also