Enhanced binding syntaxes for Knockout 3+
Using the new APIs in Knockout 3.0.0, this plugin provides a set of enhanced binding syntaxes. Each syntax can be enabled individually as described in the sections below. But generally you’ll just enable everything as follows:
ko.punches.enableAll();
Rather than using the text
binding, you can use double curly-brace syntax to insert dynamic text content. This method works by converting {{expression}}
markup to <!--ko text:expression--><!--/ko-->
before binding. For example:
<div>Hello {{name}}.</div>
If you want to insert HTML markup and have it rendered as HTML instead of as text, you can use triple curly-braces. You should be careful to only use this binding with trusted model values; otherwise you open up the possibility of a script injection attack. Example:
<div>{{{ content }}}</div>
You can embed any binding that works with virtual elements, including if
, ifnot
, foreach
, with
, and template
, by using the block-section syntax. Examples:
<!-- With a colon -->
<ul>
{{#foreach: items}}
<li>The current item is: {{name}}</li>
{{/foreach}}
</ul>
<!-- Without a colon -->
{{#ifnot items().length}}
<div>There are no items.</div>
{{/ifnot}}
<!-- Self-closing -->
{{#template 'addItem'/}}
These syntaxes are enabled by default through ko.punches.enableAll()
. To enable them specifically, call ko.punches.interpolationMarkup.enable()
.
Rather than using the attr
binding, you can use double curly-brace syntax to insert dynamic attributes. For example:
<div title="{{userName}}">Element content</div>
This method works by converting title="{{expression}}"
markup to data-bind="attr.title: expression"
before binding. It intelligently handles multiple expressions intermixed with text, and multiple attribute bindings. For example, "This is the {{which}} option."
is converted to "This is the " + ko.unwrap(which) + " option."
.
If the attribute name matches the name of a binding handler, that handler will be used instead of attr
. This means you can use this syntax for two-way bindings, such as value
. For example:
<input value="{{myTitle}}" />
This syntax is enabled by default through ko.punches.enableAll()
. To enable it individually, call ko.punches.attributeInterpolationMarkup.enable()
.
Rather than directly calling a function or using a computed observable to format output, you can use the filtering syntax. For example:
<span data-bind="text: name | fit:20 | uppercase"></span>
This method works by converting expression|filter[:arg1...]
to ko.filters.filter(expression, arg1 ...)
before binding. A matching filter function must exist in ko.filters
.
This syntax is enabled by default for the text
and attr
bindings through ko.punches.enableAll()
. To enable it for individual bindings, call ko.punches.textFilter.enableForBinding(<binding>)
.
Knockout.Punches includes the following filters:
default:<defaultValue>
- If the value is blank, null, or an empty array, replace it with the given default value.fit:<length>[:<replacement>][:<where>]
- Trim the value if it’s longer than the given length. The trimmed portion is replaced with ...
or the replacement value, if given. By default, the value is trimmed on the right but can be changed to left
or middle
through the where option. For example: name | fit:10::'middle'
will convert Shakespeare
to Shak...are
.json[:space]
- Convert the value to a JSON string using ko.toJSON
. You can give a space value to format the JSON output.lowercase
- Convert the value to lowercase.number
- Format the value using toLocaleString
.replace:<search>:<replace>
- Perform a search and replace on the value using String#replace
.uppercase
- Convert the value to uppercase.You can create your own filters by adding them to ko.filters
. Here is an example:
// Custom filter can be used like "| append: 'xyz'"
ko.filters.append = function(value, arg1) {
return '' + value + arg1;
};
When you have a set of bindings with the same functionality, the namespacing syntax allows you to dynamically create the handlers for those bindings based on which ones are used. For example, if you want to bind to arbitrary data attributes, you could create a data
namespace, which you would then bind as follows:
<div data-bind="data.color: color"></div>
Here’s how you would define the handler for the data namespace:
ko.bindingHandlers.data = {
getNamespacedHandler: function(binding) {
return {
update: function(element, valueAccessor) {
element.setAttribute('data-' + binding, ko.unwrap(valueAccessor()));
}
};
}
};
As the bindings in your documents are processed, Knockout.Punches looks for bindings with the format x.y: value
that don’t already have a binding handler. It then creates an x.y
handler using the x
namespace handler, or if none is found, it uses a default handler that calls the x
binding with the value {y: value}
. This default behavior allows you to use the namespace syntax to bind events, attributes, styles, and classes using the event
, attr
, style
, and css
namespaces respectively. For example:
<div data-bind="style.color: currentProfit() < 0 ? 'red' : 'black'"></div>
Namespaced bindings allow you to define and interact with dynamic bindings just like any other binding. For example, suppose you want to enable the filter syntax for the title attribute; you can do so by referencing it using attr.title
like this: ko.punches.textFilter.enableForBinding('attr.title')
This will work as long as you bind the title attribute using attr.title
. But what if you want to use filters with the original attr
syntax like attr: {title: name | caps}
? Knockout.Punches provides a simple solution, a preprocessor that converts the attr: {title: name}
syntax to attr.title: name
. This preprocessor is enabled by default through ko.punches.enableAll()
for the attr
, css
, event
, and style
bindings. To enable it individually, call ko.punches.namespacedBinding.enableForBinding(<binding>)
.
When binding functions in your model to events, it’s easy to simply provide the function reference to the binding such as click: $parent.removePlace
. When bound this way, though, the reference to $parent
is lost; the removePlace
method will be called with a default this
value (the current model object). (See this Github issue for more information.)
By wrapping the method call in an anonymous function, like click: function() {$parent.removePlace($data)}
, the this
value is set correctly. The wrapped callback preprocessor in Knockout.Punches does this for you, so you can use the simple reference syntax and know that this
is the correct object in your method.
This preprocessor is enabled by default through ko.punches.enableAll()
for the click
, submit
, and event
bindings,
as well as for optionsAfterRender
, template#beforeRemove
, template#afterAdd
, and template#afterRender
. To enable this preprocessor individually, call ko.punches.wrappedCallback.enableForBinding(<binding>)
for each binding that you want to use it with. If you want this functionality for a binding parameter, such as template#afterRender
, call ko.punches.preprocessBindingProperty.addPreprocessor('template', 'afterRender', ko.punches.wrappedCallback.preprocessor)
. If you want to use it for all dynamically created bindings with a certain namespace (such as event
), call ko.punches.namespacedBinding.addDefaultBindingPreprocessor('event', ko.punches.wrappedCallback.preprocessor)
.
If you’re familiar with the original on...
syntax for defining event handlers, you may wish that Knockout allowed you to bind an expression to events. Knockout.Punches provides this ability for event handling using the on
namespace. Thus, for example, if you want to run an expression when the user clicks a button, you could bind it like this:
<button type="button" data-bind="on.click: x = x + 1">Increment</button>
Any of the model and context properties are available in the expression. In addition, you can access the event object through $event
.
If you want to use the expression syntax for other bindings, you can enable it using ko.punches.expressionCallback.enableForBinding(<binding>, <args>);
The args
parameter is a string that defines the names of the parameters available in the expression. For example, you could enable this syntax for the click
binding using ko.punches.expressionCallback.enableForBinding('click', '$data,$event')
ko.punches
.utils
.addBindingPreprocessor(bindingKeyOrHandler, preprocessFn)
- Adds a preprocess function to a binding handler. Automatically handles chaining..addNodePreprocessor(preprocessFn)
- Adds a node preprocessor function. Automatically handles chaining..enableAll()
- Enables the enhanced syntaxes as follows:
text
and attr
bindings;attr
, css
, event
, and style
bindings;click
, submit
, and event
bindings,
as well as for optionsAfterRender
, template#beforeRemove
, template#afterAdd
, and template#afterRender
..interpolationMarkup
.wrapExpression(expressionText)
- The default version of this function returns a pair of comment nodes for a text or HTML binding, and a single comment node for each part of a block binding. By replacing this function, you can define your own extensions to the syntax. The function must return an array of nodes to add..preprocessor(node)
- The preprocess function for the embedded text bindings syntax..enable()
- Enables the embedded text bindings syntax..attributeInterpolationMarkup
.attributeBinding(name, value, node)
- You can replace this function to define your own extensions to the attribute binding syntax. The function can return a binding string, or return nothing to use the default behavior..preprocessor(node)
- The preprocess function for the embedded attribute bindings syntax..enable()
- Enables the embedded attribute bindings syntax..textFilter
.preprocessor(input)
- The preprocess function for the filter syntax..enableForBinding(bindingKeyOrHandler)
- Enables the filter syntax for the specified binding..namespacedBinding
.defaultGetHandler(name, namespace, namespacedName)
- Gets a binding handler for the given namespace/name that calls the namespace binding handler with a value of {name: value}
..addDefaultBindingPreprocessor(namespace, preprocessFn)
- Adds a preprocessor for each dynamically created binding for the given namespace..preprocessor(input)
- The preprocess function for the automatic namespacing syntax..enableForBinding(bindingKeyOrHandler)
- Enables the automatic namespacing syntax for the specified binding..wrappedCallback
.preprocessor(input)
- The preprocess function for the wrapped callback syntax..enableForBinding(bindingKeyOrHandler)
- Enables the wrapped callback syntax for the specified binding..expressionCallback
.makePreprocessor(args)
- Returns a preprocess function for the callback expression syntax. args
is a list of parameter names that the binding passes to the callback function..eventPreprocessor(input)
- The preprocess function for event handlers (uses args of $data,$event
)..enableForBinding(bindingKeyOrHandler, args)
- Enables the callback expression syntax for the specified binding..preprocessBindingProperty
.addPreprocessor(bindingKeyOrHandler, property, preprocessFn)
- Enables a preprocess function for a specific property of a binding.ko.filters
- A set of filter functions for use with the filter syntax.
ko.bindingHandlers.on
- The handler for the on
namespace, for expression-based event handling.
ko.getBindingHandler
- This Knockout API is extended by Knockout.Punches to dyntamically create handlers for namespaced bindings.
Knockout 3.0 includes three new APIs to extend the binding system with new syntaxes. Here’s a quick summary of the three methods:
ko.getBindingHandler
to dynamically create binding handlers.ko.bindingProvider.instance.preprocessNode
to modify or replace DOM nodes before bindings are processed.<bindingHandler>.preprocess
to modify the binding value before it is evaluated.License: MIT (http://www.opensource.org/licenses/mit-license.php)
Michael Best
https://github.com/mbest
mbest@dasya.com