Search code examples
javascriptarraysknockout.jsko.observablearray

Removing Self From observableArray in knockoutJS


I'm using knockoutjs to create a treeview of divisions. Next to each node will be three buttons: 1)New child(applies to the node it's next to 2) Remove(This removes the node it's next to, and 3) Copy, which copies the node and all it's children and creates a new node under the parent.

I've got the New button down, and now I'm working on the remove button. I can't seem to get it to work, and instead of doing anything useful it just refreshes the entire page. Here's the code:

View:

<h2>Skill & Weight Divisions</h2>
        <span data-bind="text: tournamentname"></span><button data-bind="click: addDivision"><img src="new.png"/></button>
        <ul data-bind="template: { name: 'divisionTemplate', foreach: divisions }"></ul>

Template:

<script id="divisionTemplate" type="text/html">
   <li data-bind="style: {'background-color':color}">
       <input data-bind="value: name"/><button data-bind="click: addDivision"><img src="new.png"/></button><button data-bind="click: $parent.removeDivision"><img src="remove.png"/></button><button data-bind="click: $parent.copyDivision"><img src="copy.png"/></button>
       <ul data-bind="template: { 'if': children, name: 'divisionTemplate', foreach: children }"></ul>
    </li>       
</script>

View Model and appropriate helper function:

function division(id, name, filter, children) {
        this.id = ko.observable(id);
        this.name = ko.observable(name);
        this.filter = ko.observable(filter)
        if(children){
            this.children = ko.observableArray(children);   
        }else{
            this.children = ko.observableArray();   
        }
        this.addDivision = function(){
            this.children.push(new division("", "", ""));   
        }
        this.removeDivision = function(division){
            this.children.remove(division);
        }
        this.copyDivision = function(division){
            this.children.push(division);   
        }
        this.color = randColor();
    };
    function tournamentViewModel(){
        var self= this;
        self.tournamentname = ko.observable('NO NAME YET');
        self.districts = ko.observableArray([new district('Provo',1),new district('Salt Lake City',2),new district('St. George',3)]);
        self.district = ko.observable(self.districts()[0]);
        self.regions = ko.observableArray([new region('Utah',1),new region('Idaho',2)]);
        self.region = ko.observable(self.regions()[0]);
        self.location = ko.observable('WHEREVER YOU WANT');
        self.eventdate = ko.observable('');
        self.startTime = ko.observable('');
        self.image = ko.observable();
        self.flyer = ko.computed(function(){
            var flyerHTML = '<span style="text-align:center;padding:10px;"><h1>'+self.tournamentname()+'</h1><img src="'+self.image()+'"/><br/>';
            flyerHTML += 'District: ' + self.district().districtName + ' Region: ' + self.region().regionName+'<br><br>';
            flyerHTML += '<h2>WHEN: '+self.eventdate()+' '+self.startTime()+'</h2>';
            flyerHTML += '<h2>WHERE: '+self.location()+'</h2>';
            flyerHTML += '<img src="http://maps.googleapis.com/maps/api/staticmap?center='+encodeURI(self.location())+'&zoom=12&size=200x200&markers=color:blue%7Clabel:S%7C'+encodeURI(self.location())+'&maptype=roadmap&sensor=false"/>';
            return flyerHTML;
        }, self);
        self.clearImage = function(){
            self.image(''); 
        }
        self.tournamentID = ko.computed(function(){return 't_'+self.district()+'_'+self.region()+'_'+self.eventdate()}, self);
        self.pricingStructures = ko.observableArray([new pricingStructure(3,2.99), new pricingStructure(1,1.99)]);
        self.removePricingStructure = function(pricingStructure){
            self.pricingStructures.remove(pricingStructure); 
        }
        self.addPricingStructure = function(){
            self.pricingStructures.push(new pricingStructure("", ""));  
        }
        self.promoCodes = ko.observableArray();
        self.promoTypes = ['%','$'];
        self.removePromoCode = function(promoCode){
            self.promoCodes.remove(promoCode); 
        }
        self.addPromoCode = function(){
            self.promoCodes.push(new promoCode("", ""));    
        }
        self.divisions = ko.observableArray([new division(1, "Men","",[new division(2,"Gi"), new division(3,"No-Gi")])]);
        self.addDivision = function(){
            self.divisions.push(new division("", "", ""));  
        }

    }
    ko.applyBindings(new tournamentViewModel());

My main question in all of this is this: Is there a way to access an object's parent array in order to remove that very object from the array? Thanks in advance for the help!

EDIT: Here's a jsFiddle: http://jsfiddle.net/eqY7Z/ However it doesn't seem to be working at all there. If you guys can't get it going, I'll include the link to my site where it's being hosted so you can take a good look at it.


Solution

  • I took your idea and made a working fiddle, which behaves exactly how you described. I didn't want to try to sort yours out, sorry. It had a lot of stuff not directly related to your problem, and this solution is general enough that other people should be able to use it. If you need help adapting it, let me know.

    One thing to make note of is the clone function. Your copy function isn't deep, and will result in multiple nodes pointing to the same object. If you were to update a nodes value, it would propagate to its clones. Knockout provides a handy deep copy + unwrap observables with ko.toJS. Super useful.

    The JS:

    var Node = function(name, children) {
        var self = this;
        self.name = ko.observable(name || 'NewNode');
        self.children = ko.observableArray(
        ko.utils.arrayMap(children || [], function(i) {
            return new Node(i.name, i.children);
        }));
        self.newChild = function() {
            self.children.push(new Node());
        };
        self.removeNode = function(node) {
            self.children.remove(node);
        };
        self.copyNode = function(node) {
            var cloneNode = ko.toJS(node);
            self.children.push(new Node(cloneNode.name, cloneNode.children));
        };
    };
    
    //Example data removed for brevity, see fiddle
    ko.applyBindings(new Node(data.name, data.children));​
    

    HTML:

    <button data-bind="click: newChild">NewNode</button>
    <ul data-bind="template: { name: 'treeTemplate', foreach: children}">
    </ul>
    
    <script id="treeTemplate" type="text/html">
        <li>
            <input data-bind="value: name" />
            <button data-bind="click: newChild">New Child</button>
            <button data-bind="click: $parent.removeNode">Remove Node</button>
            <button data-bind="click: $parent.copyNode">Copy Node</button>
            <ul data-bind="template: { name: 'treeTemplate', foreach: children}"></ul>
        </li>
    </script>
    ​