Combine Knockout, Isotope and Bootstrap

Make Isotope layout elements as they are added to or removed from a ko.observableArray.

by aknuds1

HTML

<script src="http://isotope.metafizzy.co/jquery.isotope.min.js"></script>
<script src="https://github.com/downloads/SteveSanderson/knockout/knockout-2.2.0.debug.js"></script>
<link rel="stylesheet" href="http://twitter.github.com/bootstrap/assets/css/bootstrap.css">
<script src="http://underscorejs.org/underscore.js"></script>
<div class="container-fluid">
    <div class="row-fluid">
        <div class="span9">
             <h1>Knockout + Isotope + Bootstrap Demo</h1>

            <p>Click on an item to remove it or click in the item container to add a
                new item</p>
        </div>
    </div>
    <div id="row" class="row">
        <div class="span9">
            <div id="container" class="hide" data-bind="template: {foreach: items, afterAdd: afterAdd, beforeRemove: beforeRemove, afterMove: afterMove}, click: addItem">
                <div class="item show" data-bind="text: text, click: $parent.removeItem, clickBubble: false"></div>
            </div>
        </div>
    </div>
</div>

CSS

h1 {
    font-size: 24px;
    font-weight: bold;
    margin-bottom: 4px;
}
#container {
    margin-top: 20px;
    border: 1px dashed;
    height: 600px;
}
.item {
    background-color: blue;
    border: none;
    width: 200px;
    height: 150px;
    text-align: center;
    line-height: 150px;
    margin: 0;
}
.item {
    border-radius: 4px;
    background-color: blue;
    border: none;
    width: 200px;
    height: 150px;
    text-align: center;
    line-height: 150px;
    margin-bottom: 10px;
}
/**** Isotope Filtering ****/
 .isotope-item {
    z-index: 2;
}
.isotope-hidden.isotope-item {
    pointer-events: none;
    z-index: 1;
}
/**** Isotope CSS3 transitions ****/
 .isotope, .isotope .isotope-item {
    -webkit-transition-duration: 0.8s;
    -moz-transition-duration: 0.8s;
    -ms-transition-duration: 0.8s;
    -o-transition-duration: 0.8s;
    transition-duration: 0.8s;
}
.isotope {
    -webkit-transition-property: height, width;
    -moz-transition-property: height, width;
    -ms-transition-property: height, width;
    -o-transition-property: height, width;
    transition-property: height, width;
}
.isotope .isotope-item {
    -webkit-transition-property: -webkit-transform, opacity;
    -moz-transition-property: -moz-transform, opacity;
    -ms-transition-property: -ms-transform, opacity;
    -o-transition-property: -o-transform, opacity;
    transition-property: transform, opacity;
}
/**** disabling Isotope CSS3 transitions ****/
 .isotope.no-transition, .isotope.no-transition .isotope-item, .isotope .isotope-item.no-transition {
    -webkit-transition-duration: 0s;
    -moz-transition-duration: 0s;
    -ms-transition-duration: 0s;
    -o-transition-duration: 0s;
    transition-duration: 0s;
}

JavaScript

function ItemModel(value, parent) {
    if (value === undefined) {
        throw new Error("value must be defined");
    }
    if (parent === undefined) {
        throw new Error("parent must be defined");
    }
    var self = this;
    this.value = ko.observable(value);

    this.text = ko.computed(function () {
        return "Item " + self.value();
    });
}

var ViewModel = function () {
    var self = this,
        itemDb = [
            [new ItemModel(1, self), new ItemModel(2, self)],
            [new ItemModel(2, self), new ItemModel(3, self)],
            [new ItemModel(1, self), new ItemModel(2, self)]
        ],
        itemIndex = 0;
    self.items = ko.observableArray();
    self.haveInitialized = ko.observable(false);

    this.afterAdd = function (elem) {
        // Node type 1 means the element node itself, we may get notifications for sub-nodes,
        // e.g. text
        if (elem.nodeType != 1) {
            return;
        }
        $('#container').isotope('reloadItems');
        $('#container').isotope('reLayout');
        //$('#container').isotope({
          //  'sortBy': 'value'
        //});
    };
    
    this.afterMove = function (elem) {
        if (elem.nodeType != 1) {
            return;
        }
        
        console.log('Element moved');
        $('#container').isotope('reloadItems');
        $('#container').isotope('reLayout');
        //console.log($('#container').children());
    }

    this.beforeRemove = function (elem) {
        var $elem = $(elem);
        // Node type 1 means the element node itself, we may get notifications for sub-nodes,
        // e.g. text
        if (elem.nodeType != 1) {
            return;
        }
        console.log('Removing element ' + $elem.text());
        $('#container').isotope('remove', $elem);
    };

    this.removeItem = function (item, event) {
        self.items.remove(item);
        return false;
    };

    this.addItem = function () {
        var found = false,
           ...