2

I often create custom binding handlers that changes the DOM. But I sometimes I stumble on a situation where I want to put that kind of code inside the subscribe handler.

MyModel =
{
    this.name = ko.observable();

    this.name.subscribe(function()
    {
        // is it correct to do these kind of calls inside a model?
        $.ajax(
        {
            url: "... url that creates customer on server ...",
            success: function()
            {
                $(".container_element").noty(
                {
                    text: "Customer created!"
                }); 
            }
        });
    });
}

Noty is a jQuery plugin that pops up alert messages, and since Noty in fact changes the DOM, what would be the recommended design pattern?

Jeroen
  • 60,696
  • 40
  • 206
  • 339
Adrian Rosca
  • 6,922
  • 9
  • 40
  • 57

1 Answers1

4

It's usually a red flag if you feel like you want to do DOM manipulation inside View Models (with some exceptions, most notably the beforeRender & friends callbacks). This is because usually, with MVVM-style programming, you have a View lighty dependent on the View Model (with declarative bindings), and no dependencies from your View Model to the View. Two main reasons this is nice:

  1. Your VM is not tighlty coupled to the View, so you can easily create different views dependent on the same view model.
  2. Your VM is more easily unit testable, because testing the unit doesn't require a DOM to be present.

You already mention it, what you probably should use: custom binding handlers. From the relevant docs:

This is how to control how observables interact with DOM elements, and gives you a lot of flexibility to encapsulate sophisticated behaviors in an easy-to-reuse way.

I could not find a "Noty" example custom binding, so you'd have to create your own (it's usually not hard). You could get some inspiration from this jQuery UI custom binding, as well as the docs.

To put it in code using your example, things would look like this:

ko.bindingHandlers.noty = {
    init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        // Set up any initial state, event handlers, etc. here
    },
    update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
        var value = ko.utils.unwrapObservable(valueAccessor()),
            $el = $(element);
        if(!!value) { $($el).noty({text: value}); }
    }
};

// Mock $.ajax for this example
$.ajax = function(options) { options.success({}); }

MyModel = function()
{
    var self = this;

    this.name = ko.observable();
    this.myAlertMsg = ko.observable("");

    this.name.subscribe(function()
    {
        // is it correct to do these kind of calls inside a model?
        $.ajax(
        {
            url: "... url that creates customer on server ...",
            success: function()
            {
                self.myAlertMsg("Customer created!");
            }
        });
    });
}

ko.applyBindings(new MyModel());
<script src="https://cdnjs.cloudflare.com/ajax/libs/knockout/3.2.0/knockout-min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-noty/2.3.4/packaged/jquery.noty.packaged.min.js"></script>

<div data-bind="noty: myAlertMsg"></div>
<input data-bind="value: name" /> (blur input to finish editing name)

Note that we need the var self = this idiom to be able to easily refer to the View Model inside the success callback.

Community
  • 1
  • 1
Jeroen
  • 60,696
  • 40
  • 206
  • 339