Skip to content

Instantly share code, notes, and snippets.

@kayhadrin
Created July 21, 2013 04:34
Show Gist options
  • Save kayhadrin/6047490 to your computer and use it in GitHub Desktop.
Save kayhadrin/6047490 to your computer and use it in GitHub Desktop.
A CodePen by David. Custom Rivets binder for jQuery plugins - Example of how to write a custom rivet binder for jQuery plugins that report changes with custom events.
<!-- JS Includes -->
<script src="//cdnjs.cloudflare.com/ajax/libs/underscore.js/1.4.4/underscore-min.js" type="text/javascript"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/backbone.js/1.0.0/backbone.js" type="text/javascript"></script>
<script src="https://rawgithub.com/mikeric/rivets/master/dist/rivets.js" type="text/javascript"></script>
<script src="https://rawgithub.com/azproduction/rivets-backbone-adapter/master/rivets-backbone.js" type="text/javascript"></script>
<p style="color: red;">
Open your dev tools and watch the console log for output. You should notice that when setting the value manually using the Backbone set(), the "routine" is fired off twice.
</p>
<div>
<h2>Normal Field</h2>
<p>Manually enter a numeric value</p>
<input type="text" data-bind-value="model.counter" />
</div>
<div>
<h2>jQuery Plugin</h2>
<p>Click the button below to increment</p>
<input type="text" data-bind-incrementer="model.counter" id="incrementer" />
</div>
<div>
<h2>Backbone</h2>
<p>Clicking the button below fires of the "rountine" binder twice. This is because the set fires off the routine, which then ultimately triggers an "update" event, which fires off the set method again.</p>
<button onclick="Tracker.increment();">Increase</button>
</div>
<div>
<h3>
Bound Value:
<span data-bind-text="model.counter"></span>
</h3>
</div>
/* backbone */
var Tracker = new Backbone.Model({counter:0});
Tracker.increment = function (){
Tracker.set("counter", parseInt(Tracker.get("counter"), 10) +1);
}
Tracker.on("change:counter", function (){
console.log("log: " + this.get("counter"));
});
/* rivets */
/* global rivets configuration */
rivets.configure({
prefix: "bind"
});
var rountineCounter = 0;
/* start custom binders */
rivets.binders.incrementer = {
publishes: true
, bind: function(el){
var rivetView = this,
binder = rivetView.binder;
// David: create a custom bind listener to check if the routine() function allowed its execution
var bindListener = function() {
var curEl = this,
isDisabled = $(curEl).data('rivets.binders.incrementer.isDisabled'),
ret;
//DEBUG
console.log('bindListener: update event received. isDisabled = ', isDisabled);
if (!isDisabled) {
ret = rivetView.publish.apply(curEl, arguments);
}
return ret;
};
// David: save it on the current DOM element's data
$(el).data('rivets.binders.incrementer.bindListener', bindListener);
return rivets._.Util.bindEvent(el, "update", bindListener);
}
, unbind: function(el){
var $el = $(el),
bindListener = $el.data('rivets.binders.incrementer.bindListener'); // David: retrieve the bindListener from the DOM Element
$el.data('rivets.binders.incrementer.bindListener', null); // David: remove reference to the bindListener from the DOM element
return rivets._.Util.unbindEvent(el, "update", bindListener);
}
, routine: function(el, value){
var $el = $(el);
console.log('rountine id:' + rountineCounter++);
$el.data('rivets.binders.incrementer.isDisabled', true) // David: disable this binder's listener handler, because we know that the jQuery plugin emits 'update' events everytime
.incrementer("val", value) // force to a string and make sure to run the callback, but skip firing off the update trigger
.data('rivets.binders.incrementer.isDisabled', false); // David: re-enable this binder's listener handler
}
};
/* jQuery plugin */
;(function($){
$.fn.incrementer = function(options) {
var method = typeof arguments[0] == "string" && arguments[0];
var args = method && Array.prototype.slice.call(arguments, 1) || arguments;
// if a method is supplied, execute it for non-empty results
if( method && this.length ){
// get a reference to the first incrementer found
var self = $.data(this[0], "incrementer");
// if request a copy of the object, return it
if( method.toLowerCase() == "object" ) return self;
// if method is defined, run it and return either it's results or the chain
else if( self[method] ){
// define a result variable to return to the jQuery chain
var result;
this.each(function (i){
// apply the method to the current element
var r = $.data(this, "incrementer")[method].apply(self, args);
// if first iteration we need to check if we're done processing or need to add it to the jquery chain
if( i == 0 && r ){
// if this is a jQuery item, we need to store them in a collection
if( !!r.jquery ){
result = $([]).add(r);
// otherwise, just store the result and stop executing
} else {
result = r;
// since we're a non-jQuery item, just cancel processing further items
return false;
}
// keep adding jQuery objects to the results
} else if( !!r && !!r.jquery ){
result = result.add(r);
}
});
// return either the results (which could be a jQuery object) or the original chain
return result || this;
// everything else, return the chain
} else return this;
// initializing request
} else {
// create a new incrementer for each object found
return this.each(function (){
new Incrementer(this, options);
});
};
};
Incrementer = function (el, options){
options = $.extend({}, Incrementer.defaults, options);
var self = this, $el = $(el), counter = 0;
// get/set the value of the field
this.val = function (value){
// update the selected value
if( arguments.length > 0 ){
setValue(value);
return $el;
}
else return $el.val();
};
// store a reference to this link select
$.data($el[0], "incrementer", this);
function setValue(value){
counter = parseInt(value, 10);
// update the hidden value
updateValue()
}
function updateValue(){
// update the hidden value
$el.val(counter);
$button.text(counter);
// announce the update (for Backbone/Rivets)
$el.triggerHandler("update", [counter, this]);
}
function increment(){
counter++;
updateValue();
}
// hide form element
$el.css({
visibility: "hidden"
, position: "absolute"
, left: "-999em"
});
var $button = $("<button>")
.on("click.incrementer", function (){
increment()
})
.insertAfter($el)
;
// update everything
updateValue();
}
Incrementer.defaults = {};
})(jQuery);
var rivetsViews;
/* DOM ready */
$(function (){
$("#incrementer").incrementer();
rivetsViews = rivets.bind($("body"), {model: Tracker});
console.log('rivetsViews = ', rivetsViews);
});
@kayhadrin
Copy link
Author

Related to this rivets conversation: mikeric/rivets#193 (comment)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment