<?xml version="1.0" encoding="utf-8"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><language>en</language><title>Blog posts by Jonatan Dahl</title> <link>https://world.optimizely.com/blogs/jonatan-dahl/</link><description></description><ttl>60</ttl><generator>Optimizely World</generator><item> <title>Checkbox tree Dojo widget</title>            <link>https://world.optimizely.com/blogs/jonatan-dahl/dates/2016/10/checkbox-tree---custom-property-dojo-widget/</link>            <description>&lt;p&gt;Hi,&lt;/p&gt;
&lt;p&gt;Due to a customer request, to display selectable hierarchical data in an editor, I finally wrote this post.&lt;br /&gt;Unfortunately there are no editors for selectable tree data (except for the category picker which requires a category list property) in Episerver.&lt;/p&gt;
&lt;p&gt;More specifically the request was to be able to select multiple units from an organization tree, located in a separate database.&lt;br /&gt;So below is my solution, a checkbox tree dojo editor for hierarchical data...&lt;/p&gt;
&lt;p&gt;&lt;em&gt;Since I can&amp;acute;t show their organization tree, below is an example for namespaces:&lt;/em&gt;&lt;/p&gt;
&lt;h2&gt;&lt;img src=&quot;/link/3dce407a033e4a378b1065a63ee47b91.aspx&quot; alt=&quot;Image Untitled.png&quot; /&gt;&lt;/h2&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;h2&gt;Editor descriptor&lt;/h2&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[EditorDescriptorRegistration(TargetType = typeof(IEnumerable&amp;lt;int&amp;gt;), UIHint = Global.UIHint.CheckBoxTreeEditor)]
public class CheckBoxTreeEditorDescriptor : EditorDescriptor
{
    public override void ModifyMetadata(ExtendedMetadata metadata, IEnumerable&amp;lt;Attribute&amp;gt; attributes)
    {
        ClientEditingClass = &quot;mysite/editors/CheckBoxTreeEditor/checkBoxTreeEditor&quot;;
        base.ModifyMetadata(metadata, attributes);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Selection factory&lt;/h2&gt;
&lt;p&gt;The selection factory is responsible for feeding data to the dojo widget. Since the SelectItem class isn&amp;acute;t sufficient we create a new class called TreeNodeSelectItem.&lt;br /&gt;The HasChildren property is implemented to be able to tell of a node has any children. This is optional but you will get a plus sign icon in front of each unexpanded node unless you supply a way to determine if it has any children.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class TreeNodeSelectItem : SelectItem
{
    public int Id { get; set; }
    public int? ParentId { get; set; }
    public bool HasChildren { get; set; }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Below you see an example of a selection factory for an organizational unit tree.&lt;br /&gt;The dijit tree can only have one root node and since the unit tree data used in this example has several root nodes &lt;br /&gt;we have to create a dummy root node and attach the unit data root nodes to it. &lt;br /&gt;For this example the HasChildren property is set using a linq query against the retrieved units.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;public class UnitTreeSelectionFactory : ISelectionFactory
{
    public IEnumerable&amp;lt;ISelectItem&amp;gt; GetSelections(ExtendedMetadata metadata)
    {
        var unitService = ServiceLocator.Current.GetInstance&amp;lt;IUnitService&amp;gt;();
        var items = new List&amp;lt;TreeNodeSelectItem&amp;gt;();
        var units = unitService.GetAll();

        // Dummy root
        items.Add(new TreeNodeSelectItem
        {
            Id = -1,
            HasChildren = units.Any(),
            Value = -1
        });

        foreach (var unit in units)
        {
             var item = new TreeNodeSelectItem
             {
                 Id = unit.Id,
                 ParentId = unit.ParentId != null ? unit.ParentId : items[0].Id,
                 Text = unit.Name,
                 Value = unit.Id,
                 HasChildren = units.Exists(x =&amp;gt; x.ParentId == unit.Id)
             };

             items.Add(item);
        }

        return items;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;EPiServer property&lt;/h2&gt;
&lt;p&gt;For storing the unit ids I use a PropertyDefinitionTypePlugin called PropertyIntegerList that stores the ids (IEnumerable&amp;lt;int&amp;gt;) as a string, based on this solution:&lt;br /&gt; &lt;a href=&quot;http://www.patrickvankleef.com/2015/02/03/episerver-custom-property-in-dojo/&quot;&gt;http://www.patrickvankleef.com/2015/02/03/episerver-custom-property-in-dojo/&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;The PropertyList&amp;lt;T&amp;gt; might be a cleaner solution but it is still in beta.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;[Display(Name = &quot;Enhet&quot;)
[UIHint(Global.UIHint.CheckBoxTreeEditor)]
[ClientEditor(SelectionFactoryType = typeof(UnitTreeSelectionFactory))]
[BackingType(typeof(PropertyIntegerList))]
public virtual IEnumerable&amp;lt;int&amp;gt; UnitIds { get; set; }
&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Dojo widget template&lt;/h2&gt;
&lt;p&gt;checkBoxTreeEditor.html&lt;/p&gt;
&lt;pre class=&quot;language-html&quot;&gt;&lt;code&gt;&amp;lt;div class=&quot;dijitInline checkbox-tree-editor-widget&quot;&amp;gt;
    &amp;lt;div data-dojo-attach-point=&quot;container&quot; id=&quot;container&quot;&amp;gt;
    &amp;lt;/div&amp;gt;
&amp;lt;/div&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Dojo widget CSS&lt;/h2&gt;
&lt;p&gt;checkBoxTreeEditor.css&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;.checkbox-tree-editor-widget {
    width: 580px;
}
.checkbox-tree-editor-widget .dijitTreeContent img {
    display: none;
}
.checkbox-tree-editor-widget .dijitTreeLabel .tree-checkbox {
    margin: 0 7px 0 5px;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;Dojo widget&lt;/h2&gt;
&lt;p&gt;Below is the empty shell of the dojo widget, the methods are described further down&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;define([
    &quot;dojo/_base/declare&quot;,
    &quot;dojo/aspect&quot;,
    &quot;dojo/_base/array&quot;,
    &quot;dojo/_base/connect&quot;,
    &quot;dojo/_base/lang&quot;,
    &quot;dijit/registry&quot;,
    &quot;dojo/query&quot;,
    &quot;dojo/store/Memory&quot;,
    &quot;dijit/tree/ObjectStoreModel&quot;,
    &quot;dijit/Tree&quot;,
    &quot;dijit/_Widget&quot;,
    &quot;dijit/_TemplatedMixin&quot;,
    &quot;dijit/form/CheckBox&quot;,
    &quot;dojo/text!./checkBoxTreeEditor.html&quot;,
    &#39;xstyle/css!./checkBoxTreeEditor.css&#39;
], function (
    declare,
    aspect,
    array,
    connect,
    lang,
    registry,
    query,
    Memory,
    ObjectStoreModel,
    Tree,
    _Widget,
    _TemplatedMixin,
    CheckBox,
    template
) {
    return declare(&quot;mysite/editors/CheckBoxTreeEditor/checkBoxTreeEditor&quot;, [
        _Widget,
        _TemplatedMixin
    ],
        {
            templateString: template,
            value: null,
            postCreate: function () {
                this.inherited(arguments);
                var main = this;
            },
            _refreshDescendantCount: function (container) {
            },
            _getDescendantsCheckCountByItemId: function (itemId) {
            },
            _updateValue: function (value, checked) {
            },
            _setValueAttr: function (value) {
            },
            _refreshTree: function (container) {
            },
            _valueIsSet: function () {
            },
            _valueHasItems: function () {
            }
        });
    });&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;postCreate:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;So to be able to view hierarchical data we use the dijit.Tree. As described in the documentation is consists of the following components: Data store -&amp;gt; Model -&amp;gt; Tree&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Data store:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Since the depth of the tree I&amp;acute;m using isn&amp;acute;t that deep I use a Memory store which means that I load all the data at once. It&amp;acute;s possible to lazy load the tree by using another type of store such as &amp;ldquo;dojo/store/JsonRest&amp;rdquo;.&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;var store = new Memory({
     data: this.selections,
     idProperty: &quot;id&quot;,
     getChildren: function (object) {
         return this.query({ parentId: object.id });
     }
});&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;I set the store data to &amp;ldquo;this.selections&amp;rdquo; which is the TreeNodeSelectItems supplied by the SelectionFactory. The &amp;ldquo;idProperty&amp;rdquo; specifies which property of the TreeNodeSelectItem that represents the item id. The &amp;ldquo;getChildren&amp;rdquo; method is responsible for returning the children of an item.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Model:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;var model = new ObjectStoreModel({
    store: store,
    query: { id: -1 },
    labelAttr: &quot;text&quot;,
    mayHaveChildren: function (item) {
         return item.hasChildren;
    }
});
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We create the model and specify our memory store.&lt;br /&gt;&lt;strong&gt; query:&lt;/strong&gt; Specifies how to find the root node (which in this case is id=-1). &lt;br /&gt;&lt;strong&gt; labelAttr:&lt;/strong&gt; Property that holds the text to display&lt;br /&gt;&lt;strong&gt; mayHaveChildren:&lt;/strong&gt; If this is not specified all nodes will initially be prefixed with a &amp;ldquo;expand-icon&amp;rdquo; regardless of whether they have children or not.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Tree:&lt;/strong&gt;&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;var tree = new Tree({
    model: model,
    persist: false,
    showRoot: false,
    _createTreeNode: function (args) {
        var tnode = new Tree._TreeNode(args);
        tnode.labelNode.innerHTML = args.label;
               
        var cb = new CheckBox({
            value: args.item.value,
            class: &quot;tree-checkbox&quot;
        });
        
        cb.placeAt(tnode.labelNode, &quot;first&quot;);

        connect.connect(cb, &quot;onClick&quot;, function () {
            var treeNode = dijit.getEnclosingWidget(this.domNode.parentNode);
            main._updateValue(treeNode.item.value, this.checked);
        });
         
        return tnode;
    },
    onClick: function (item, node, event) {
        if (event.target.type != &#39;checkbox&#39;)
            this._onExpandoClick({ node: node });
    },
    onOpen: function (item, node, event) {
        main._refreshTree(node.containerNode);
        node.labelNode.childNodes[1].nodeValue = node.item.text;
        main._refreshDescendantCount(node.containerNode);
    },
    onClose: function (item, node, event) {
        node.labelNode.childNodes[1].nodeValue = node.item.text;

        if (main._valueHasItems()) {
            var count = main._getDescendantsCheckCountByItemId(node.item.id);

            if (count &amp;gt; 0)
                node.labelNode.childNodes[1].nodeValue += &quot; (&quot; + count + &quot;)&quot;;
        }
    }
});

this.container.appendChild(tree.domNode);
tree.startup();
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;We create the tree and specify the model.&lt;br /&gt;&lt;strong&gt; _createTreeNode:&lt;/strong&gt; Adds a CheckBox to each tree node and creates an onClick event for each CheckBox.&lt;br /&gt;&lt;strong&gt;onClick:&lt;/strong&gt; Node click event, open/close the node unless the actual checkbox is clicked&lt;br /&gt;&lt;strong&gt;onOpen:&lt;/strong&gt; Node open event&lt;br /&gt;&lt;strong&gt;onClose:&lt;/strong&gt; Node close event&lt;/p&gt;
&lt;p&gt;Finally we add the tree to our container.&lt;/p&gt;
&lt;h3&gt;&lt;strong&gt;_refreshDescendantCount:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Refreshes descendant count for all visible leaf nodes&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_refreshDescendantCount: function (container) {
    var main = this;

    if (this._valueHasItems()) {
        dojo.query(&quot;.dijitTreeNode&quot;, container).forEach(function (node) {
            var nodeItem = dijit.getEnclosingWidget(node);

            if (nodeItem.labelNode.childNodes[1] != null)
                nodeItem.labelNode.childNodes[1].nodeValue = nodeItem.item.text;

            if (nodeItem.isExpandable &amp;amp;&amp;amp; !nodeItem.isExpanded) {
                var count = main._getDescendantsCheckCountByItemId(nodeItem.item.id);
    
                if (count &amp;gt; 0)
                    nodeItem.labelNode.childNodes[1].nodeValue += &quot; (&quot; + count + &quot;)&quot;;
            }
        });
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;_getDescendantsCheckCountByItemId:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Recursive function that returns descendant check count for a specific item&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_getDescendantsCheckCountByItemId: function (itemId) {
    var count = 0;

    var children = this.selections.filter(function (x) {
        return x.parentId == itemId;
    });

    for (var i = 0, len = children.length; i &amp;lt; len; i++) {
        if (this.value.indexOf(children[i].id) != -1)
            count++;

        count += this._getDescendantsCheckCountByItemId(children[i].id);
    }

    return count;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;_updateValue:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Adds or removes a value from the value property&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_updateValue: function (value, checked) {
    var values = this.value;

    if (values == null)
        values = [];

    if (checked == true) {
        if (values.indexOf(value) == -1)
            values.push(value);
    }
    else {
        var index = values.indexOf(value);

        if (index &amp;gt; -1)
            values.splice(index, 1);
    }

    if (values.length == 0)
        values = null;

    // Sets value and notifies watchers
    this._set(&#39;value&#39;, values);

    // Notifies EPiServer that the property value has changed
    this.onChange(values);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;_setValueAttr:&lt;/strong&gt;&lt;/h3&gt;
&lt;p&gt;Property setter for the value variable, only called initially and if value is not null&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_setValueAttr: function (value) {
    var main = this;
    var values = value;

    if (!lang.isArray(values)) {
        values = null;
    }
    else {
        // Removes values not matching a select item
        values = array.filter(values, function (currentValue) {
        var matchingSelectItems = array.filter(main.selections, function (selectItem) {
            return selectItem.value == currentValue;
        });

        return matchingSelectItems.length &amp;gt; 0;
    });

    if (values.length == 0)
        values = null;
    }

    // Sets value and notifies watchers
    this._set(&#39;value&#39;, values);

    this._refreshTree(this.container);
    this._refreshDescendantCount(this.container);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;strong&gt;_refreshTree:&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Checks or unchecks visible checkboxes under specified container&lt;/p&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_refreshTree: function (container) {
    var main = this;

    if (this._valueIsSet()) {
        dojo.query(&quot;.tree-checkbox&quot;, container).forEach(function (node) {
            var checkbox = dijit.getEnclosingWidget(node);

            if (array.indexOf(main.value, checkbox.value) != -1) {
                checkbox.set(&#39;checked&#39;, true);
            }
            else {
                checkbox.set(&#39;checked&#39;, false);
            }
        });
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;_valueIsSet:&lt;/strong&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_valueIsSet: function() {
    return (this.value != null);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3&gt;&lt;strong&gt;_valueHasItems:&lt;/strong&gt;&lt;/h3&gt;
&lt;pre class=&quot;language-csharp&quot;&gt;&lt;code&gt;_valueHasItems: function() {
    return (this._valueIsSet() &amp;amp;&amp;amp; this.value.length &amp;gt; 0);
}&lt;/code&gt;&lt;/pre&gt;</description>            <guid>https://world.optimizely.com/blogs/jonatan-dahl/dates/2016/10/checkbox-tree---custom-property-dojo-widget/</guid>            <pubDate>Sat, 17 Dec 2016 03:07:50 GMT</pubDate>           <category>Blog post</category></item></channel>
</rss>