Search code examples
dotvvm

Dotvvm - fire custom binding handler after DOM elements are rendered


Lets say I want to customize css classes of treeview component.

HTML code for <li> expand. As we can see, 1st <li> got class bp-state-expanded. When its collapsed, I would like to have bp-class-collapsed class instead of bp-state-expanded for my styling purpose.

<li class="bp-item bp-state-focused bp-state-expanded" data-bind="css: $bpControl.getItemStyle($data)">
    <!-- ko if: HasCategories -->
    <a role="button" class="bp-expand">
    <i class="bp-icon fa fa-plus-square-o"></i></a>
    <a role="button" class="bp-collapse"><i class="bp-icon fa fa-minus-square-o"></i></a><!-- /ko --><label>
        <!-- ko if: $bpControl.canBeChecked($data) --><!-- /ko -->
        <i class="fa fa-circle-o"></i>
        <span><!-- ko text: Name -->Jazyky<!-- /ko --></span>
    </label><!-- ko template: { name: $findTemplateId("levelTemplate"), data: (AssignedToMenuItem) } --><!-- ko if: $data && $data.length -->
    <ul class="bp-list" data-bind="foreach: $data">

    </ul><!-- /ko --><!-- /ko -->
</li>

For my testing purpose I made this simple script where I want to store children DOM elements into variable. When I refresh page and my script is fired, treeview elements are not yet rendered.

I tried to use afterRender in data-binding but without any success. Is there a way how I can fire this handler after all DOM elements are rendered so i can manipulate with css classes?

ko.bindingHandlers["test"] = {
init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {

    var data = bindingContext.$data.AdminMenuList();
    var parentDOM = element.children;
    ko.utils.arrayForEach(data, function (item) {
        if (item().HasCategories() == true){
            console.log(item().Name());
        }           
    });
}};

My data-binding in business pack treeview

<bp:TreeView DataSource="{value: AdminMenuList}"
         SelectedValues="{value: AdminMenuSelectedList}"
         ItemKeyBinding="{value: Id}"
         ItemHasChildrenBinding="{value: HasCategories}"
         ItemChildrenBinding="{value: AssignedToMenuItem}"
         Changed="{command: RedirectTo()}"
         Validation.Enabled="false"
         data-bind="test: {afterRender: test }">
<i class="fa fa-circle-o"></i>
<span>{{value: Name}}</span>

UPDATE

For now I solved that using timeout function, but not sure if this is good aproach.

enter image description here

I got 3 types of li. I will try to explain each type.

Type 1. => Its expandable element and the reason why I need bp-class-collapsed is that, because I dont want to influence li type 2,3 with :not selector and I need here + icon.

Type 2. => Its classic li with redirect so i need o icon here. If I will use :not selector here, it will influence also this type.

Type 3. => Same as type 2, but its nested li.

I hope its better explained on this sample.


Solution

  • It's not supported to apply custom CSS classes directly on nodes. But we will add the requested CSS class in next release. For now, you can modify our API to do what you want.

    let TreeView = DotVVM.BusinessPack.Controls.TreeView,
    originalGetItemStyle = TreeView.prototype.getItemStyle;
    
    TreeView.prototype.getItemStyle = function(item) {
        let style = originalGetItemStyle.call(this, item);
        style["expandable"] = this.hasChildren(item);
        return style;
    };
    

    Please beware this is our internal API and it may change in future releases. In most cases, you should use your CSS classes inside the ItemTemplate.