Best practice: Knockout observableArray inplace Add/Edit

The scenario is familiar: you have an observableArray in the viewModel that you want to be able to push new items to, or perhaps modify existing elements. There are just tons of code out there how to do so, for example knockout official example pages.

However, by mimicking that pattern, the viewModel quickly turns into a huge chunk of pointer to “active” elements, methods for “start edit”, “stop edit”. In the project I’m working, we’re going to have three different list with this functionality and it would just be such an repetitive work and overly complex code.

Just to make things worse, let’s imagine you want an in place editable table, like this guy wants to. If you look at his markup, there is just too much happening there with brackets, dubble equal signs,… Ugh, from my point of view the markup is the most important thing to get understandable and that’s not it.

Here’s the problem

Meet the Offers, an array with stuff that I’m offering. Yep, you guessed right it is an observableArray. As an admin, I want to be able to add/remove/edit Offers in place.

What you might have seen around is that an offer is created from a function call like so:

function Offer(id,name, shortName){
     this.id = ko.observable(id);
     this.name = ko.observable(name);
 }

Somewhere in your markup you end up with a

<input type="button" data-bind="click:$edit">

That must correlate to method on your viewModel

var viewModel = new function () {
    var self = this;
    self.Offers = ko.observableArray([]);
    self.selectedOffer = ko.observable();

    self.edit = function (item) {
        self.selectedOffer(item);
    };
}

But, hang on you also want to show an input text field if you edit the current item, as opposed to a div/span if you don’t edit. So you need some more methods that I am only going to name:

  • isOfferActive (used in the foreach in markup to evaluate what to show)
  • saveOffer (used to set selectedOffer to null so that none is edited)

Here’s the solution

So what we want to do is putting some of the “is active” “is inactive” logic on the element itself (rather than a comparing method on the viewModel $root). In the light of what I wrote earlier about the module pattern and jasmine testing, I ended up with this class:

var Offer = (function () {
    var activeOffer = function() {};

    //CONSTRUCTOR
    var offer = function(name, productMasterId, active) {

        var self = this;
        activeOffer = active;
        self.Name = ko.observable(name);
        self.Id = ko.observable(productMasterId);

        active(this);
        self.IsActive = ko.computed(function () {
            return self == active();
        });

        self.IsNotActive = ko.computed(function () {
            return self != active();
        });
    };

    var activateOffer = function () {
        activeOffer(this);
    };

    var deactivateOffer = function () {
        activeOffer(null);
    };

    offer.prototype = {
        constructor: offer,
        activateOffer: activateOffer,
        deactivateOffer: deactivateOffer
    };

    return offer;
}())

What’s so great with this? Well, it it takes a pointer to an observable that is considered to be the placeholder for the “current” selected Offer. The offer is then responsible to either set itself as active (offer.activateOffer()) or deactivate (offer.deactiveOffer()). Note that this observable does not need to be attached to the viewModel, actually this is how i do the add:

var viewModel = new function () {

    var activeUnlockedOffer = ko.observable(""); //we do not even need to add this to the viewModel

    this.Offers = ko.observableArray();

    this.addOffer = function () {
        var offer = new Offer("", "", activeUnlockedOffer);
        viewModel.Offers.push(offer);
    };

    this.removeOffer = function (data) {
        viewModel.Offers.remove(data);
    };
}

As you can see the activeUnlockedOffer is just a local variable. No need to pollute the viewModel with that. The only additional methods in my viewModel is the add/remove – which I think suits there (it should not be at the Offer anyways).

Perk number two, the markup will be easier to read, look at this:

<button title="Edit" data-bind="click: $data.activateOffer, visible: $data.IsNotActive" />
<button title="Save" data-bind="click: $data.deactivateOffer, visible: $data.IsActive" />

Another sweet thing with this solution is that when I add another list off offers, let’s call it OffersOnSales I can decide if I should allow one editable row in the two tables (in that case I would initialize all offers with the same observable), or if I want to allow the editing of one OfferOnSale and one normal Offer (in that case I would initialize the different groups with different observables).

And… yes, there’s a jsFiddle to it. Happy coding!

Typeahead from Twitter Bootstrap data-bound in Knockout.js

After writing about Knockout.js and Twitter Bootstrap‘s Popover and Tooltip, it feels just about right that I continue to rant about how to get the two frameworks to get along. This time, it’s Typehead‘s time to get examined.

In general I like how Twitter Bootstrap works with the data-attributes when describing what I’m expecting from it, such as data-title for the title of a popover, or data-toggle for how the collapse function will disappear/reappear.

For Typeahead, I found it a bit frustrating that the data-source, that is used to populate the typeahead list with options, must be decleared as a list like so:

<input type="text" data-source="
[&quot;Alabama&quot;,&quot;Alaska&quot;,&quot;Arizona&quot;
,&quot;Arkansas&quot;,&quot;California&quot;,&quot;Colorado&quot;
,&quot;Connecticut&quot;,&quot;Delaware&quot;,&quot;Florida&quot;
,&quot;Georgia&quot;,&quot;Hawaii&quot;,&quot;Idaho&quot;,&quot;
Illinois&quot;,&quot;Indiana&quot;,&quot;Iowa&quot;,&quot;
Kansas&quot;,&quot;Kentucky&quot;,&quot;Louisiana&quot;,&quot;
Maine&quot;,&quot;Maryland&quot;,&quot;Massachusetts&quot;,&quot
;Michigan&quot;,&quot;Minnesota&quot;,&quot;Mississippi&quot;
,&quot;Missouri&quot;,&quot;Montana&quot;,&quot;Nebraska&quot;
,&quot;Nevada&quot;,&quot;New Hampshire&quot;,&quot;New Jersey&quot;
,&quot;New Mexico&quot;,&quot;New York&quot;,&quot;North Dakota&quot;
,&quot;North Carolina&quot;,&quot;Ohio&quot;,&quot;Oklahoma&quot;
,&quot;Oregon&quot;,&quot;Pennsylvania&quot;,&quot;Rhode Island&quot;
,&quot;South Carolina&quot;,&quot;South Dakota&quot;
,&quot;Tennessee&quot;,&quot;Texas&quot;,&quot;Utah&quot;
,&quot;Vermont&quot;,&quot;Virginia&quot;,&quot;Washington&quot;
,&quot;West Virginia&quot;,&quot;Wisconsin&quot;,&quot;Wyoming&quot;]"
 data-items="4" data-provide="typeahead" style="margin: 0 auto;">
(Example from Twitter Bootstraps).

That kind of code just makes the source very unreadable. Therefore,when I wrote the custom data-binding in Knockout, I added the possibility to describe the datasource as an ko.observableArray/javascript variable/objects property.Here’s what you would write if you had an observable array named colors that you wanted to use as data source for your typeahead:

<input type="text" data-bind="typeahead: colors"/>

Imagine that you had some list that is not part of your model (maybe it is not important to keep track on the list):

<script>
var allColors = ["Red", "Green", "Yellow", "Black"];
</script>

The databinding is described just the same, but with singel citation signs like this:

 <input type="text" data-bind="typeahead: 'allColors '"/>

Similarly, if you have an javascript object and you want one if its properties as datasource:

<script>
var myComplexObject = {
    animals: ["pig", "pony", "dog", "cat", "cow", "chicken", "fish", "fox"]
};
</script>

You just have to write:

<input type="text" data-bind="typeahead: 'myComplexObject.animals'"/>

Here’s the custom biding code, which can be tested in the fiddle.

ko.bindingHandlers.typeahead = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var typeaheadSource; // <-- this is where our typeahead options will be stored in
        //this is the parameter that you pass from you data-bind expression in the mark-up
        var passedValueFromMarkup = ko.utils.unwrapObservable(valueAccessor());
        if (passedValueFromMarkup instanceof Array) typeaheadSource = passedValueFromMarkup;
        else {
            // if the name contains '.', then we expect it to be a property in an object such as myLists.listOfCards
            var splitedName = passedValueFromMarkup.split('.');
            var result = window[splitedName[0]];
            $.each($(splitedName).slice(1, splitedName.length), function(iteration, name) {
                result = result[name];
            });

            // if we find any array in the JsVariable, then use that as source, otherwise init without any specific source and hope that it is defined from attributes
            if (result != null && result.length > 0) {
                typeaheadSource = result;
            }

        }
        if (typeaheadSource == null) $(element).typeahead();
        else {
            $(element).typeahead({
                source: typeaheadSource
            });
        }

    },
};

Popover from Twitter Bootstrap with Knockout.js observable

I explained earlier how to get Twitter Bootstrap‘s neat Tooltip feature to work with Knockout.js observables. For my current project I wanted to be able to edit the value of my observable property from within a Twitter Bootstrap Popover.

My goal was to be able to keep as much of the bootstrap functionality intact(e.g. the data-attributes should work) and also be able to combine tooltip databinding with other knockout bindings. In a nutshell, I wanted to be able to write something like

<span data-placement="bottom" data-title="write new text to update" data-bind="popover: '#editTemplate', text:exampleText"></span>

Where

  • #editTemplate is the Css Selector for a html-template that contains the desired popover content
  • exampleText is, just like when it comes to any other Knockout binding – just a property on the ViewModel.
  • data-placement and data-title are attributes that Twitter Bootstrap uses to render the Popover

I found this to be a bit tricky, since the popover content will typically be appended to the DOM after the ko.applyBindings(viewModel) is called. Therefore, the data-bindings will not be noticed by Knockout and the binding will not take place.

Luckely, it is quite a pleasure to extend Knockout with custom bindings. Here’s the binding that I wrote:

ko.bindingHandlers.popover = {
        init: function (element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
            var cssSelectorForPopoverTemplate = ko.utils.unwrapObservable(valueAccessor());
            var popOverTemplate = "<div id='my-knockout-popver'>" + $(cssSelectorForPopoverTemplate).html() + "</div>";
            $(element).popover({ content: popOverTemplate, html:true, trigger: 'manual' });

            $(element).click(function() {
                $(this).popover('toggle');
                var thePopover = document.getElementById("my-knockout-popver");
                ko.applyBindings(viewModel, thePopover);
            });  
        },
    };

There are a few things that can be made more nicely, like creating the surrounding <div> using jQuery’s wrap(). A limitation with this implementation is that the popover is toggled by click. This can easerly be solved by adding a data-trigger atttriute in the HTML and in the js method look at the element to see if it has that attribute and if so change the click-listener to a hover-listener etc.

 

The JsFiddel is found here.

Tooltip from Twitter Bootstrap with Knockout.js observable

Working with Twitter Bootstrap in combination with Knockout.js can be cumbersome, as  they are not out of the box compatible.

So… The other day, I wanted to display some value from my viewmodel on a twitter bootstrap tooltip.

What I wanted to be able to do is something like

<a href="#" data-bind="tooltip: info">this</a>

The best way to get both frameworks to get along nicely is to write custom databindings for Knockout.js, that takes care of the bootstrapping of the bootstraper :)

It turned out that extending Knockout.js is really simple. Without further ado, I give you: the Tooltip Custom Binding:

ko.bindingHandlers.tooltip = {
    init: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
        var valueUnwrapped = ko.utils.unwrapObservable(valueAccessor());
        $(element).tooltip({
            title: valueUnwrapped
        });
    },
};

To get this working, simply add this code somewhere before you databind you model. For a working example, go to this fiddler.