0

I want to build my own listView with selectedItem and Itemsource.

I have started a jsFiddle http://jsfiddle.net/andersb79/53xRL/1/

Can you please help me in the right direction about how I should build this. I havn't seen a bindingHanlder that do this.

What I want is something like this.

data-bind="myListView : { items : comments, selectedItem : selectedComment}"
Steve Greatrex
  • 15,789
  • 5
  • 59
  • 73
user1199595
  • 406
  • 2
  • 7
  • 16

1 Answers1

1

In your custom binding you need to:

  1. Create an element for each of the items in the list
  2. Append that element to the parent
  3. Add a click handler to each element that sets the selectedItem on the original view model

I've also highlighted the current selected item for clarity.

ko.bindingHandlers.myListView = {
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),

            //get the list of items
            items = value.items(),
            //get a reference to the selected item observable
            selectedItem = value.selectedItem,
            //get a jQuery reference to the element
            $element = $(element),
            //get the currently selected item
            currentSelected = selectedItem();

        //clear the parent of any existing children
        $element.html("");

        for (var index = 0; index < items.length; index++) {
            (function() {
                //get the list of items
                var item = ko.utils.unwrapObservable(items[index]),

                    //create a child element with a click handler
                    $childElement = $("<li>")
                    .text(item.id() + " " + item.text())
                    .click(function() {
                        //remove selected class on all siblings
                        $(this).siblings().removeClass("selected");
                        //add selected class on this item
                        $(this).addClass("selected");
                        //set the observable 'selected item' property on the source view model
                        selectedItem(item);
                    });

                //add the selected class if this item is the current selected item
                if (item == currentSelected) {
                    $childElement.addClass("selected");
                }

                //add the child to the parent
                $element.append($childElement);
            })();
        }

    }
};

Here's a working example

Note: As I am using the update instead of the init method it will work when updating the list of comments

Update

If you want to use the contents of the original DIV as the template for each created item, you need two additional steps:

  1. Grab the original content of the element when initialized and use that in place of the hard-coded content above
  2. Apply bindings to the created element before adding it to the DOM

        ko.bindingHandlers.myListView = {
    init: function(element) {
        var $element = $(element),
            originalContent = $element.html();
    
        $element.data("original-content", originalContent);
        return { controlsDescendantBindings: true }
    },
    update: function(element, valueAccessor) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
    
            //get the list of items
            items = value.items(),
            //get a reference to the selected item observable
            selectedItem = value.selectedItem,
            //get a jQuery reference to the element
            $element = $(element),
            //get the currently selected item
            currentSelected = selectedItem(),
            //get the current content of the element
            elementContent = $element.data("original-content");
    
        $element.html("");
    
        for (var index = 0; index < items.length; index++) {
            (function() {
                //get the list of items
                var item = ko.utils.unwrapObservable(items[index]),
    
                    //create a child element with a click handler
                    $childElement = $(elementContent)
                    .click(function() {
                        //remove selected class on all siblings
                        $(this).siblings().removeClass("selected");
                        //add selected class on this item
                        $(this).addClass("selected");
                        //set the observable 'selected item' property on the source view model
                        selectedItem(item);
                    });
    
                ko.applyBindings(item, $childElement[0]);
    
                //add the selected class if this item is the current selected item
                if (item == currentSelected) {
                    $childElement.addClass("selected");
                }
    
                //add the child to the parent
                $element.append($childElement);
            })();
        }
    
    }
    

    };

Note that we have to prevent the content of the div being processed by knockout by returning { controlsDescendantBindings: true } from our init method.

Here is an updated example

Steve Greatrex
  • 15,789
  • 5
  • 59
  • 73
  • Whoo thanks a lot. But what if I want to be abel to have code inside the div that is the comment, or a template.
    On the page I want code here that is a Comment. so I can do data-bind="text:comment" here
    – user1199595 Jun 14 '12 at 11:56
  • Great help. Cant you hook up and use the foreach binding inside the bindinghandler to take advantage of the template binding also so if i would have it like this. myListView : {Items : Comment,template : 'myTemplate',selectedItem : selectedItem} And not have a for loop and instead have a knockout foreach. Is it possible? Is it better ? – user1199595 Jun 14 '12 at 12:37
  • It is possible and arguably better as we are slightly re-writing the knockout functionality here – Steve Greatrex Jun 14 '12 at 12:49
  • In your example how can i get $parent to be the viewModel inside the div so I can do a remove and update, now in the div the $parent is a comment also. – user1199595 Jun 14 '12 at 12:57
  • For this given solution, if I wanted to add a custom method after the items has rendered in the DOM how would I go about doing this? Seems like it should be in the update part of the method, but what event should I subscribe to? This is associated with this other [question:](http://stackoverflow.com/questions/17331621/refresh-masonry-layout-after-knockoutjs-databound-template-is-inserted-into-a-co) – BigDubb Oct 31 '13 at 02:36
  • @BigDubb what are you trying to do after the items are rendered? You can probably just invoke your custom method immediately in the `update` handler – Steve Greatrex Oct 31 '13 at 08:17