Extensibility
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:
widgetName : Widget name
widgetEventPrefix : Basically when the widget triggers an event it prefixes
the event name with this string. Actually the algorithm of constructing the event name
is more complex, you can find it in jQuery UI's Widget._trigger() method. This
property is only used by BindingHandler.on()
options : Names of the widget's options.
events : Names of the widget's events.
hasRefresh : Does the widget have a refresh() function?
Methods:
init : The binding's "init" callback as described in knockout's documentation .
update : The binding's "update" callback as described in knockout's documentation .
on : Helper method which subscribes to a widget-triggered event.
Simple widgets
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.
HTML
<div data-bind= "colorizer: { color: color }, click: toggleColor" style= "cursor: pointer;" > Lorem ipsum dolor sit amet</div>
JavaScript
// 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" );
}
};
Result
Lorem ipsum dolor sit amet
Derived widgets
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.
HTML
<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>
JavaScript
// 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 )
};
Result