kaiwa/clientapp/modules/strictview.js

246 lines
9.5 KiB
JavaScript

var Backbone = require('backbone'),
_ = require('underscore'),
templates = require('templates');
// the base view we use to build all our other views
module.exports = Backbone.View.extend({
// ###handleBindings
// This makes it simple to bind model attributes to the view.
// To use it, add a `classBindings` and/or a `contentBindings` attribute
// to your view and call `this.handleBindings()` at the end of your view's
// `render` function. It's also used by `basicRender` which lets you do
// a complete attribute-bound views with just this:
//
// var ProfileView = BaseView.extend({
// template: 'profile',
// contentBindings: {
// 'name': '.name'
// },
// classBindings: {
// 'active': ''
// },
// render: function () {
// this.basicRender();
// return this;
// }
// });
handleBindings: function () {
var self = this;
if (this.contentBindings) {
_.each(this.contentBindings, function (selector, key) {
var func = function () {
var el = (selector.length > 0) ? self.$(selector) : $(self.el);
el.html(self.model[key]);
};
self.listenTo(self.model, 'change:' + key, func);
func();
});
}
if (this.imageBindings) {
_.each(this.imageBindings, function (selector, key) {
var func = function () {
var el = (selector.length > 0) ? self.$(selector) : $(self.el);
el.attr('src', self.model[key]);
};
self.listenTo(self.model, 'change:' + key, func);
func();
});
}
if (this.hrefBindings) {
_.each(this.hrefBindings, function (selector, key) {
var func = function () {
var el = (selector.length > 0) ? self.$(selector) : $(self.el);
el.attr('href', self.model[key]);
};
self.listenTo(self.model, 'change:' + key, func);
func();
});
}
if (this.classBindings) {
_.each(this.classBindings, function (selector, key) {
var func = function () {
var newValue = self.model[key],
prevHash = self.model.previous(),
prev = _.isFunction(prevHash) ? prevHash(key) : prevHash[key],
el = (selector.length > 0) ? self.$(selector) : $(self.el);
if (_.isBoolean(newValue)) {
if (newValue) {
el.addClass(key);
} else {
el.removeClass(key);
}
} else {
if (prev) el.removeClass(prev);
el.addClass(newValue);
}
};
self.listenTo(self.model, 'change:' + key, func);
func();
});
}
if (this.inputBindings) {
_.each(this.inputBindings, function (selector, key) {
var func = function () {
var el = (selector.length > 0) ? self.$(selector) : $(self.el);
el.val(self.model[key]);
};
self.listenTo(self.model, 'change:' + key, func);
func();
});
}
return this;
},
// ###desist
// This is method we used to remove/unbind/destroy the view.
// By default we fade it out this seemed like a reasonable default for realtime apps.
// So things to just magically disappear and to give some visual indication that
// it's going away. You can also pass an options hash `{quick: true}` to remove immediately.
desist: function (opts) {
opts || (opts = {});
_.defaults(opts, {
quick: false,
animate: true,
speed: 300,
animationProps: {
height: 0,
opacity: 0
}
});
var el = $(this.el),
kill = _.bind(this.remove, this);
if (this.interval) {
clearInterval(this.interval);
delete this.interval;
}
if (opts.quick) {
kill();
} else if (opts.animate) {
el.animate(opts.animationProps, {
speed: opts.speed,
complete: kill
});
} else {
setTimeout(kill, opts.speed);
}
},
// ###addReferences
// This is a shortcut for adding reference to specific elements within your view for
// access later. This is avoids excessive DOM queries and gives makes it easier to update
// your view if your template changes. You could argue whether this is worth doing or not,
// but I like it.
// In your `render` method. Use it like so:
//
// render: function () {
// this.basicRender();
// this.addReferences({
// pages: '#pages',
// chat: '#teamChat',
// nav: 'nav#views ul',
// me: '#me',
// cheatSheet: '#cheatSheet',
// omniBox: '#awesomeSauce'
// });
// }
//
// Then later you can access elements by reference like so: `this.$pages`, or `this.$chat`.
addReferences: function (hash) {
for (var item in hash) {
this['$' + item] = $(hash[item], this.el);
}
},
// ###basicRender
// All the usual stuff when I render a view. It assumes that the view has a `template` property
// that is the name of the ICanHaz template. You can also specify the template name by passing
// it an options hash like so: `{templateKey: 'profile'}`.
basicRender: function (opts) {
var newEl;
opts || (opts = {});
_.defaults(opts, {
templateFunc: (typeof this.template === 'string') ? templates[opts.templateKey] : this.template,
context: false
});
newEl = $(opts.templateFunc(opts.contex));
$(this.el).replaceWith(newEl);
this.setElement(newEl);
this.handleBindings();
this.delegateEvents();
},
// ###subViewRender
// This is handy for views within collections when you use `collectomatic`. Just like `basicRender` it assumes
// that the view either has a `template` property or that you pass it an options object with the name of the
// `templateKey` name of the ICanHaz template.
// Additionally, it handles appending or prepending the view to its parent container.
// It takes an options arg where you can optionally specify the `templateKey` and `placement` of the element.
// If your collections is stacked newest first, just use `{plaement: 'prepend'}`.
subViewRender: function (opts) {
opts || (opts = {});
_.defaults(opts, {
placement: 'append',
templateFunc: (typeof this.template === 'string') ? templates[opts.templateKey] : this.template
});
var data = _.isFunction(this.model.toTemplate) ? this.model.toTemplate() : this.model.toTemplate,
newEl = $(opts.templateFunc(opts.context))[0];
if (!this.el.parentNode) {
$(this.containerEl)[opts.placement](newEl);
} else {
$(this.el).replaceWith(newEl);
}
this.setElement(newEl);
this.handleBindings();
},
// ### bindomatic
// Shortcut for listening and triggering
bindomatic: function (object, events, handler, opts) {
var bound = _.bind(handler, this);
this.listenTo(object, events, bound);
if (opts && opts.trigger || opts === true) bound();
},
// ###collectomatic
// Shorthand for rendering collections and their invividual views.
// Just pass it the collection, and the view to use for the items in the
// collection. (anything in the `options` arg just gets passed through to
// view. Again, props to @natevw for this.
collectomatic: function (collection, ViewClass, options, desistOptions) {
var views = {},
self = this,
refreshResetHandler;
function addView(model, collection, opts) {
var matches = self.matchesFilters ? self.matchesFilters(model) : true;
if (matches) {
views[model.cid] = new ViewClass(_({model: model}).extend(options));
views[model.cid].parent = self;
}
}
this.listenTo(collection, 'add', addView);
this.listenTo(collection, 'remove', function (model) {
if (views[model.cid]) {
views[model.cid].desist(desistOptions);
delete views[model.cid];
}
});
this.listenTo(collection, 'move', function () {
_(views).each(function (view) {
view.desist({quick: true});
});
views = {};
collection.each(addView);
});
refreshResetHandler = function (opts) {
_(views).each(function (view) {
view.desist({quick: true});
});
views = {};
collection.each(addView);
};
this.listenTo(collection, 'refresh reset sort', refreshResetHandler);
refreshResetHandler();
}
});