multistate button

demonstrates a jquery plugin and binding to knockout

HTML

<script src="http://cdnjs.cloudflare.com/ajax/libs/knockout/2.3.0/knockout-min.js"></script>
<span data-bind="MultistateValue: truthyValue1">click here</span>
<br />
<br />
<span data-bind="MultistateValue: truthyValue2, states: [true, false]">click here too</span>

CSS

span { 
    padding-left: 4px;
    cursor: pointer;
}
.multistate-true {
    border-left: 8px solid #0c0;
}
.multistate-false {
    border-left: 8px solid Red;
}
.multistate-null {
    border-left: 8px solid Blue;
}

CoffeeScript

/*
	minimal jquery plugin to provide a multi state check box
	states: true, false 
    put it in a separate file and link to it
*/

(function ( $, undefined ) {

	// knockout compatibility bindings for the plugin go here
	if ( !!ko && !ko.bindingHandlers.MultistateValue ) {
		ko.bindingHandlers.MultistateValue = {
			init: function ( element, valueAccessor, allBindingsAccessor, viewModel, bindingContext ) {
				// is Multistate already inited on this element?
				var msObj = $( element ).data( 'multistate' );
				if ( !msObj ) {
					var allBindings = allBindingsAccessor();
					var states = allBindings.states || [false, true, null];
					var ms = $( element ).multistate( {
						states: states,
						onchange: function ( id ) {
							var value = valueAccessor();
							// "this" should be bound to the multistate instance on this element
							value( this.State() );
						}
					} );
					$( element ).data( 'multistate', ms );
				}
			},
			update: function ( element, valueAccessor, allBindingsAccessor, viewModel, bindingContext ) {
				var msObj = $( element ).data( 'multistate' );
				var value = valueAccessor();
				var valueUnwrapped = ko.unwrap( value );
				msObj.State( valueUnwrapped, false );
			}
		};
	}

	$.fn.multistate = function ( options ) {

		// handle multiple items in the jQuery object
		if ( this.length > 1 ) {
			this.each( function () { $( this ).multistate( options ) } );
			return this;
		}

		var o = $.extend( {}, $.fn.multistate.defaults, options || {} );

		var that = this;
		var self = $( this );
		var state = o.initstate;
		self.addClass( 'multistate-' + state );

		function getNextState( origstate ) {
			return o.states[( $.inArray( origstate, o.states ) + 1 ) % o.states.length];
		}

		that.State = function ( newstate, sendCall ) {
			if(newstate !== undefined) {
				self.removeClass( 'multistate-' + state );
				state = newstate;
				self.addClass( 'multistate-' + state );
				if ( !!o.onchange && !!sendCall )...