The library uses classical JavaScript inheritance which makes adding/extending the bindings relatively simple.
The BindingHandler class connects the widget's options/events and the view model's properties/functions.
Properties:
Methods:
To create a binding for a new widget you should derive a new class from BindingHandler, set the options, events and hasRefresh properties, and optionally override the init() and/or update() methods.
The example below creates a simple widget which changes the color of its element. Then a binding is created for the widget.
<div data-bind="colorizer: { color: color }, click: toggleColor" style="cursor: pointer;">Lorem ipsum dolor sit amet</div>
// Create the widget.
$.widget( "custom.colorizer", {
options: {
color: "black"
},
_setOption: function ( key, value ) {
switch ( key ) {
case "color":
this.element.css( "color", value );
break;
}
this._super( key, value );
}
});
// The binding handler's constructor function.
Colorizer = function () {
// invoke the base class's constructor with the widget's name
kojqui.BindingHandler.call(this, 'colorizer');
// specify the widget's options
this.options = [ "color" ]
};
// Set the prototype chain. kojqui.utils.createObject() is a simple wrapper around
// Object.create() which also works with pre-ES5 browsers.
Colorizer.prototype = kojqui.utils.createObject(kojqui.BindingHandler.prototype);
Colorizer.prototype.constructor = Colorizer;
// Register the new binding handler with knockout.
kojqui.utils.register(Colorizer);
// It's time to test our new widget and binding handler.
vm1 = {
color: ko.observable( "black" ),
toggleColor: function () {
vm1.color( vm1.color() === "black" ? "red" : "black" );
}
};
Creating a binding for derived widgets is similar to creating bindings for simple widgets, but you should derive from the widget's binding handler instead of BindingHandler.
The lengthy example below extends the dialog widget with a minimize/restore functionality, then creates a binding for it.
<style>
.custom-minidialog .ui-dialog-title { width: 70%; }
.custom-minidialog .custom-minidialog-titlebar-additionalicon { position: absolute; right: .6em; top: 50%; width: 20px; margin: -10px 20px 0 0; padding: 1px; height: 20px; }
</style>
<div title="Dialog Title" data-bind="minidialog: { isOpen: isOpen, minimized: minimized, resizable: false }">I'm a dialog</div>
<input id="isOpen" name="isOpen" type="checkbox" data-bind="checked: isOpen" />
<label for="isOpen">Visible</label>
<input id="minimized" name="minimized" type="checkbox" data-bind="checked: minimized" />
<label for="minimized">Minimized</label>
// creates the dialog-inherited widget
$.widget("custom.minidialog", $.ui.dialog, {
_create: function () {
var that = this,
uiDialogTitlebarMinimize;
// invoke the base widget's _create() method
this._super();
// the unique class helps to define the selectors for the custom css rules
this.uiDialog.addClass("custom-minidialog");
// the state is stored in a member variable
this.minimized = false;
// adds the minimize/restore anchor to the titlebar
uiDialogTitlebarMinimize = $("<a href='#'></a>")
.addClass("custom-minidialog-titlebar-additionalicon ui-corner-all")
.attr("role", "button")
.click(function (event) {
event.preventDefault();
that._toggleState(event);
})
.appendTo(this.uiDialogTitlebar);
// sets the icon
(this.uiDialogTitlebarMinimizeText = $("<span>"))
.addClass("ui-icon ui-icon-minus")
.text("minimize")
.appendTo(uiDialogTitlebarMinimize);
this._hoverable(uiDialogTitlebarMinimize);
this._focusable(uiDialogTitlebarMinimize);
},
_toggleState: function (event) {
this.minimized ? this.restore(event) : this.minimize(event);
},
minimize: function (event) {
// swaps the icon
this.uiDialogTitlebarMinimizeText
.removeClass("ui-icon-minus")
.addClass("ui-icon-newwin")
.text("restore");
// hides the content
this.element.hide();
// stores the state in the member variable
this.minimized = true;
// raises the minidialogminimize event
this._trigger("minimize", event);
},
restore: function (event) {
// opposite of the minimize() method
this.uiDialogTitlebarMinimizeText
.removeClass("ui-icon-newwin")
.addClass("ui-icon-minus")
.text("minimize");
this.element.show();
this.minimized = false;
this._trigger("restore", event);
}
});
// The binding's constructor function.
var Minidialog = function () {
// invoke the base class's constructor
kojqui.Dialog.call(this);
// override/extend the base class's properties
this.widgetName = "minidialog";
this.widgetEventPrefix = "minidialog";
this.events.push("minimize", "restore");
};
// Set the prototype chain. kojqui.utils.createObject() is a simple wrapper around
// Object.create() which also works with pre-ES5 browsers.
Minidialog.prototype = kojqui.utils.createObject(kojqui.Dialog.prototype);
Minidialog.prototype.constructor = Minidialog;
Minidialog.prototype.init = function (element, valueAccessor) {
var value = valueAccessor();
// invokes the base class's init() method
kojqui.Dialog.prototype.init.apply(this, arguments);
// connects the viewmodel's 'minimized' observable to the widget
if (value.minimized) {
ko.computed({
read: function () {
if (ko.utils.unwrapObservable(value.minimized)) {
$(element).minidialog('minimize');
} else {
$(element).minidialog('restore');
}
},
disposeWhenNodeIsRemoved: element
});
}
if (ko.isWriteableObservable(value.minimized)) {
this.on(element, 'minimize', function () {
value.minimized(true);
});
this.on(element, 'restore', function () {
value.minimized(false);
});
}
return { controlsDescendantBindings: true };
};
// Register the new binding handler with knockout.
kojqui.utils.register(Minidialog);
// It's time to test our new widget and binding handler.
vm2 = {
minimized: ko.observable(false),
isOpen: ko.observable(false)
};