Skip to content

Instantly share code, notes, and snippets.

@kt0
Last active January 2, 2016 21:59
Show Gist options
  • Save kt0/8367133 to your computer and use it in GitHub Desktop.
Save kt0/8367133 to your computer and use it in GitHub Desktop.
Model-View binding in marionette

How it tend to work :

user provide's a hash like this to view (in any way they want to and acceptable by marionette):

    bindAttributes: {
        name: [{
            to: 'html',
            el: '.change'
        }],
        color: [{
            to: 'class',
            el: '.change',
        }],
        status: [{
            to: 'checked',
            el: 'input[type="checkbox"]',
            falsy: false,
            truthy: true
        }, {
            to: 'html',
            el: '.checkit'
        }]
    }

to means which attribute of DOMElement want to be changed ('html' stands for textContent and 'bareHtml' stands for innerHtml or their equivalent jQuery text and html functions) el is an selector, not optimized (maybe with bindUiElemenets?!) and is a selector. falsy and truthy means if vlaue is falsy or non-falsy set another value (think of disabled DOM attrbiute)

Working Example : http://jsfiddle.net/KeyKaKiTO/jP35K/5/

Edit

This gist was updated and added to Backbone.Toolbelt. This gist is not optimal.

// It's may not work (just becauze we are using jquery and no handling over the class)
// We can't rely on backbone's previous value so we save state (user can use silte: true)
// DOMAttr just used for bindAttr, and oldValue for bindClass, but for smaller
// (and better!) code used all the same way!
var bindClass = function ($el, DOMAttr, value, oldValue) {
$el.removeClass(oldValue).addClass(value);
},
bindText = function ($el, DOMAttr, value, oldValue) {
$el.text(value);
},
bindHtml = function ($el, DOMAttr, value, oldValue) {
$el.html(value);
},
bindAttr = function ($el, DOMAttr, value, oldValue) {
$el.prop(DOMAttr, value)
};
var callbackGenerator = function (config, action, that) {
var oldValue, $el = that.$(config.el);
if (!$el.length) return;
return function (newValue) {
var _ret, $el = that.$(config.el);
if (config.truthy) {
_ret = !! newValue ? config.truthy : config.falsy;
} else {
_ret = newValue;
}
// This lines make sure that opposite of true or false is available (in case
// user forgot to add falsy or truthy value opposite of another one's boolean type.
// And falsy = false, truthy = "disabled" won't work!
if (typeof oldValue === 'boolean') {
newValue = !!newValue;
}
action($el, config.to, _ret, oldValue);
oldValue = _ret;
};
};
var View = Marionette.ItemView.extend({
unbindModelFromDom: function () {
if (!this._domCallbacks) {
return this;
}
var callbacks = this._domCallbacks;
for (var i = 0, l = callbacks.length; i < l; i++) {
this.stopListening(this.model, 'all', callbacks[i]);
}
return this;
},
bindModelToDom: function () {
if (!this.model) {
return;
}
var bindAttributes = Marionette.getOption(this, "bindAttributes"),
context = this;
if (!bindAttributes) {
return;
}
var domCallbacks = this._domCallbacks || (this._domCallbacks = []);
// Listen method is used for listening to models change (by attr)
listen = function (attr, callback) {
context.listenTo(context.model, 'change:' + attr, callback);
};
// Instead of using huge switch case, use a hash and (||) operator!
var type2action = {
'class': bindClass,
'html': bindText,
'bareHtml': bindHtml
};
_.each(bindAttributes, function (settings, attr) {
if (!_.isArray(settings)) {
settings = [settings];
}
if (!settings.length) return;
var callbacks = [],
callback;
for (var i = 0, l = settings.length; i < l; i++) {
// Before this must check for valid config!
//
// Below code will add a function with own workspace to callbacks.
// I think this is acceptable by browser's GC (Not sure about IE, I've linux)
callback = callbackGenerator(settings[i], type2action[settings[i].to] || bindAttr, context);
if (callback) callbacks.push(callback);
}
var changeFn = function (model, newValue) {
for (var i = 0, l = callbacks.length; i < l; i++) {
callbacks[i].call(context, newValue);
}
};
listen(attr, changeFn);
domCallbacks.push(changeFn);
changeFn(context.model, context.model.get(attr));
});
return this;
},
onBeforeRender: function () {
this.unbindModelFromDom();
},
onRender: function () {
this.bindModelToDom();
},
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment