官术网_书友最值得收藏!

Custom bindings

We know what a binding is, it is everything we write inside the data-bind attribute. We have some built-in bindings. Click and value are two of them. But we can write our own custom bindings that extend the functionality of our application in a tidy way.

Writing a custom binding is very easy. It has a basic structure that we should always follow to begin with:

ko.bindingHandlers.yourBindingName = {
  init: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    // This will be called when the binding is first applied to an element
    // Set up any initial state, event handlers, etc. here
  },
  update: function(element, valueAccessor, allBindings, viewModel, bindingContext) {
    // This will be called once when the binding is first applied to an element,
    // and again whenever any observables/computeds that are accessed change
    // Update the DOM element based on the supplied values here.
  }
};

Knockout has an internal object called bindingHandlers. We can extend this object with our custom binding. Our binding should have a name to refer to it inside the bindingHandlers object. Our custom binding is an object that has two functions, init and update. Sometimes you should use just one of them, sometimes both.

Inside the init method, we should initialize the state of our binding. Inside the update method, we should set the code to update the binding when its model or value is updated. These methods give us some parameters to undertake this task:

  • element: This is the DOM element involved in the binding.
  • valueAccessor: This is the value of the binding. It is usually a function or an observable. It is safer if you use ko.unwrap to get the value, such as var value = ko.unwrap(valueAccessor());.
  • allBindings: This is an object that you can use to access other bindings. You can get a binding using allBindings.get('name'), or ask if a binding exists using allBindings.has('name');.
  • viewModel: That is deprecated in Knockout 3.x. You should use bindingContext.$data or bindigContext.$rawData instead.
  • bindingContext: With the binding context, we can access familiar context objets such as $root, $parents, $parent, $data, or $index to navigate through different contexts.

We can use custom bindings for many things. For example, we can format data automatically (currency or dates are clear examples) or increase the semantic meaning of other bindings. It's more descriptive to have a binding that is called toggle than just set click and visible bindings to show and hide an element.

Custom bindings

New folder structure with custom bindings and components

The toggle binding

To add new custom bindings to our application we are going to create a new folder called custom inside our js folder. Then we are going to create a file called koBindings.js and we are going to link it inside our index.html file, just below our template engine:

<script type="text/javascript" src="js/vendors/koExternalTemplateEngine_all.min.js"></script>
<script type="text/javascript" src="js/custom/koBindings.js"></script>

Our first custom binding will be called toggle. We will use this custom binding to change the value of a Boolean variable. With this behavior, we can show and hide elements, in our case, our cart. Just write this code at the beginning of the koBindings.js file.

ko.bindingHandlers.toggle = {
  init: function (element, valueAccessor) {
    var value = valueAccessor();
    ko.applyBindingsToNode(element, {
      click: function () {
          value(!value());
      }
    });
  }
};

In this case, we don't need to use the update method because we set all the behavior when we initialize the binding. We use the ko.applyBingidsToNode method to link the click function to the element. The applyBindingsToNode method has the same behavior as applyBindings but we set a context, a node from the DOM where the bindings are applied. We can say that applyBindings is an alias of applyBindingsToNode($('body'), viewmodel).

Now we can use this binding in our application. Update the showCartDetails button inside the views/header.html template. Remove the following code:

<button class="btn btn-primary btn-sm" data-bind="click:showCartDetails, css:{disabled:cart().length  < 1}">Show Cart Details
</button>

Update the code for the following button:

<button class="btn btn-primary btn-sm" data-bind="toggle:visibleCart, css:{disabled:cart().length  < 1}">
  <span data-bind="text: visibleCart()?'Hide':'Show'">
  </span> Cart Details
</button>

Now we don't need the showCartDetails and hideCartDetails methods any more and we can attack the visibleCart variable directly with the toggle binding.

With this simple binding, we have removed two methods of our code and we have created a reusable code that doesn't depend on our cart view-model. Because of that you can reuse the toggle binding in every project you want, as it doesn't have any external dependencies.

We should also update the cart.html template:

<button type="button" class="close pull-right" data-bind="toggle:visibleCart"><span>&times;</span></button>

Once we have made this update, we realize that there is no need to use hideCartDetails anymore. To remove it definitively, follow these steps:

  1. In the finishOrder function, remove the following line:
    hideCartDetails();
  2. Add the following line:
    visibleCart(false);

There is no need to keep a function that manages just a single line of code.

The currency binding

The other useful utility that custom bindings offer is the option of formatting the data of the node they are applied to. For example, we can format the currency fields of our cart.

Add this binding just below the toggle binding:

ko.bindingHandlers.currency = {
  symbol: ko.observable('$'),
  update: function(element, valueAccessor, allBindingsAccessor){
    return ko.bindingHandlers.text.update(element,function(){
      var value = +(ko.unwrap(valueAccessor()) || 0),
        symbol = ko.unwrap(allBindingsAccessor().symbol !== undefined? allBindingsAccessor().symbol: ko.bindingHandlers.currency.symbol);
      return symbol + value.toFixed(2).replace(/(\d)(?=(\d{3})+\.)/g, "$1,");
    });
  }
};

Here we are not going to initialize anything because the initial state and the update behavior is the same. As a must, when the init and the update method do the same thing, just use the update method.

In this case, we are going to return the number with the format we want. First we use the built-in binding called text to update the value of our element. This binding gets the element and a function that indicates how to update the text inside this element. In the local variable value, we are going to write the value that is inside valueAccessor. Remember that valueAccessor can be an observable; this is why we use the unwrap method. We should do the same with the symbol binding. The symbol is another binding that we use to set the currency symbol. We don't need to define it because this binding does not have a behavior and is just a write/read binding. We can access it using allBindingsAccesor. Finally, we return the value of joining the two variables and set a regular expression to convert the value in a formatted currency.

We can update the price binding in the catalog and the cart template:

<td data-bind="currency:price, symbol:'€'"></td>

We can set the symbol we want and the price will be formatted as: €100, or if we set the symbol $ or empty we will see $100 (if the price value is 100).

The currency binding

Currency custom binding

Notice how easy it is to add more and more useful bindings to increase the power of Knockout.

The currency binding

Debug the container with the $root context displayed

主站蜘蛛池模板: 米易县| 乐安县| 磐安县| 桦甸市| 金阳县| 都安| 四子王旗| 托克托县| 安徽省| 正阳县| 万年县| 鲁山县| 龙江县| 常熟市| 祁门县| 交城县| 定边县| 邵阳市| 龙泉市| 广灵县| 宕昌县| 庆元县| 志丹县| 宜昌市| 洮南市| 婺源县| 崇信县| 苏州市| 延津县| 永寿县| 佛学| 资溪县| 巨野县| 罗甸县| 民乐县| 秦皇岛市| 临清市| 思茅市| 青龙| 宁夏| 楚雄市|