Combine Knockout and Isotope

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

HTML

<script src="http://aknuds1.github.io/knockout-isotope/javascripts/knockout-latest.debug.js"></script>
<script src="http://aknuds1.github.io/knockout-isotope/javascripts/knockout-isotope.js"></script>
<script src="http://isotope.metafizzy.co/jquery.isotope.min.js"></script>
<h1>Knockout Isotope Binding Demo</h1>
<p>This is a demonstration of the <a href="https://github.com/aknuds1/knockout-isotope">Knockout-Isotope</a> binding for <a href="http://knockoutjs.com/">Knockout</a>, which visualizes Knockout observableArrays through <a href="http://isotope.metafizzy.co/index.html">Isotope</a>.
</p>

<p>Click on an item to remove it or click in the item container to add a
    new item</p>
<div id="container" data-bind="isotope: { data: items, isotopeOptions: getOptions }, click: addItem">
    <div data-bind="text: text, click: $parent.removeItem, clickBubble: false"></div>
</div>

CSS

body {
   margin:20px 20px;
}

h1 {
    font-size: 24px;
    font-weight: bold;
    margin-bottom: 12px;
}
#container {
    margin-top: 20px;
    border: 1px dashed;
    border-radius: 4px;
}

p + p {
    margin-top: 10px;
}

.isotope-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(parent) {
    var value, self = this,
        found, i;
    for (value = 0; value < parent.items().length; ++value) {
        found = false;
        for (i in parent.items()) {
            var item = parent.items()[i];
            if (item.value() == value) {
                found = true;
                break;
            }
        }
        if (!found) {
            break;
        }
    }
    this.value = ko.observable(value);

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

var ViewModel = function () {
    var self = this;
    self.items = ko.observableArray();
    self.items.push(new ItemModel(self));
    self.items.push(new ItemModel(self));

    this.removeItem = function (item) {
        self.items.remove(item);
    };

    this.addItem = function () {
        self.items.push(new ItemModel(self));
    };

    // Knockout callback for getting Isotope options
    this.getOptions = function () {
        return {
            masonry: {
                columnWidth: 210
            }
        };
    };
};

ko.applyBindings(new ViewModel("Test"));