Skip to content

Instantly share code, notes, and snippets.

@craigmaslowski
Last active October 13, 2015 15:38
Show Gist options
  • Save craigmaslowski/4218205 to your computer and use it in GitHub Desktop.
Save craigmaslowski/4218205 to your computer and use it in GitHub Desktop.
Backbone Mixins
(function (Mixins) {
Mixins.BootStrapModal = {
show: function () {
var self = this;
if (self.preShow)
self.preShow();
// TODO - Review CSS to make it more generic
self.$el.modal().css({
width: 'auto',
'margin-left': function () {
return -(self.$el.width() / 2);
}
});
if (self.postShow)
self.postShow();
},
hide: function () {
if (this.preHide)
this.preHide();
this.$el.modal('hide');
if (this.postHide)
this.postHide();
}
};
})(module('Mixins'));
BackboneMixins.DestroyableView = {
destroy: function () {
if (Backbone.Validation) Backbone.Validation.unbind(this);
this.undelegateEvents();
this.$el.removeData().unbind();
this.remove();
Backbone.View.prototype.remove.call(this);
}
};
(function (Mixins) {
Mixins.FocusForm = {
postInitialize: function () {
if (this.options.firstInput) this.firstInput = this.options.firstInput;
if (this.firstInput && !this.firstInput.skipFocusOnInit) {
this.focus();
}
},
focus: function () {
if (this.firstInput) {
if (typeof this.firstInput === 'string') {
this.firstInput = { selector: this.firstInput };
}
var input = this.$(this.firstInput.selector);
if (input.is(':visible')) {
var select2Data = input.data('select2');
if (select2Data) {
$(select2Data.containerSelector).find('input.select2-focusser').focus();
// select2 likes to apply the active class to too many containers.
// sanity check to remove it and make sure only the one we want has it.
//$('.select2-container').removeClass('.select2-container-active');
//$('s2id_' + input.attr('id')).addClass('.select2-container-active');
} else {
if (input.val()) {
input.select();
}
input.focus();
}
}
}
}
};
if (Backbone.View.prototype.stickit) {
var originalStickit = Backbone.View.prototype.stickit;
Backbone.View.prototype.stickit = function () {
originalStickit.apply(this, arguments);
if (this.firstInput && !this.firstInput.skipFocusOnStickit && this.focus) {
this.focus();
}
};
}
})(module('Mixins'));
(function (Mixins) {
Mixins.Formatters = {
formatDate: function (val, options) {
return moment(new Date(val)).format('MM/DD/YYYY');
}
};
})(module('Mixins'));
(function (Mixins) {
Mixins.ListItemFormView = {
events: {
'click .save': 'completeEdit',
'click .cancel': 'cancel',
},
initialize: function () {
if (this.options.template) {
this.template = _.isFunction(this.options.template)
? this.options.template
: _.template($(this.options.template).html());
} else if (this.template) {
this.template = _.isFunction(this.template)
? this.template
: _.template($(this.template).html());
}
this.originalAttributes = this.model.toJSON();
this.render();
},
render: function () {
this.$el.empty().html(this.template(this.model.attributes));
this.$('.dropdown-select2').select2();
this.$('.date-picker').datepicker();
this.stickit();
},
completeEdit: function (e) {
e.preventDefault();
this.trigger('edit.complete');
},
cancel: function (e) {
this.model.set(this.originalAttributes);
this.completeEdit(e);
}
};
})(module('Mixins'));
(function (Mixins) {
Mixins.ListItemView = {
tagName: 'tr',
highlightClass: 'warning',
inEditMode: false,
events: {
'click .edit': 'edit',
'click .remove': 'removeItem',
},
initialize: function () {
_.bindAll(this);
if (this.options.template) {
this.template = _.isFunction(this.options.template)
? this.options.template
: _.template($(this.options.template).html());
} else if (this.template) {
this.template = _.isFunction(this.template)
? this.template
: _.template($(this.template).html());
}
if (this.options.formView) {
this.formView = this.options.formView;
}
if (this.options.highlightClass) {
this.highlightClass = this.option.highlightClass;
}
this.listenTo(this.model, 'destroy', this.destroy);
this.listenTo(this.model.collection, 'reset', this.destroy);
this.bindSubscriptions();
},
render: function () {
if (!this.inEditMode) {
if (!this.template)
throw new Backbone.Error('The template must be specified.', this);
this.$el.empty().append(this.template(this.model.attributes));
this.delegateEvents();
if (this.bindings) {
this.stickit();
}
return this;
}
},
edit: function (e) {
e.preventDefault();
if (!this.formView)
throw new Backbone.Error('The formView must be specified.', this);
this.$el.addClass(this.highlightClass);
this.form = new this.formView({
el: this.$el,
model: this.model
});
this.inEditMode = true;
this.listenTo(this.form, 'edit.complete', this.completeEdit);
},
completeEdit: function () {
this.stopListening(this.form);
this.form.destroy('soft');
this.form = undefined;
this.inEditMode = false;
this.render();
this.$el.removeClass(this.highlightClass);
},
removeItem: function (e) {
e.preventDefault();
this.$el.addClass(this.highlightClass);
if (confirm('Remove this item?')) {
this.model.destroy();
} else {
this.$el.removeClass(this.highlightClass);
}
}
};
})(module('Mixins'));
(function (Mixins) {
// usage:
// var MyListView = Backbone.View.extend({
// listItemView: MyListItemView,
//
// // optional: Causes the list to be hidden if there are no records in the collection.
// autoHide: true,
//
// // optional: Causes list item views to be appended to this.$(this.containerSelector) instead of this.$el
// // This is useful for setting $el to a table and appending to the tbody
// containerSelector: 'tbody',
// });
//
// Backbone.mixin(MyView, ListView);
Mixins.ListView = {
_itemViews: [],
initialize: function () {
if (!this.collection) {
throw new Backbone.Error('ListView: A collection must be specified', this);
}
this.listenTo(this.collection, 'remove', this.setVisibility);
this.listenTo(this.collection, 'reset', this.setVisibility);
this.listenTo(this.collection, 'reset', this.render);
this.listenTo(this.collection, 'add', this.renderItem);
this.setVisibility();
},
render: function () {
var self = this;
if (this.containerSelector) {
this.$(this.containerSelector).empty();
} else {
this.$el.empty();
}
self.collection.each(function (model) {
self.renderItem(model);
});
},
renderItem: function (model) {
if (!this.listItemView) throw new Backbone.Error('listItemView property must be set.', this);
var view = new this.listItemView({
model: model
});
this._itemViews.push(view);
if (this.containerSelector) {
this.$(this.containerSelector).append(view.render().el);
} else {
this.$el.append(view.render().el);
}
this.setVisibility();
},
setVisibility: function () {
if (this.autoHide) {
if (this.collection.length === 0) {
this.$el.hide();
} else {
this.$el.show();
}
}
},
remove: function () {
_.each(this._itemViews, function (view) {
view.remove();
});
Backbone.View.prototype.remove.call(this);
}
};
})(module('Mixins'));
BackboneMixins.Subscriber = {
bindSubscriptions: function () {
var self = this;
self._subscriptions = [];
if (self.subscriptions) {
_.each(self.subscriptions, function (subscription) {
var handler = _.isFunction(subscription.handler)
? subscription.handler
: self[subscription.handler];
self._subscriptions.push(
app.broker.channel(
subscription.channel,
subscription.topic)
.subscribe(handler)
);
});
}
},
unbindSubscriptions: function () {
var self = this;
_.each(self._subscriptions, function (subscription) {
subscription.unsubscribe();
});
}
};
BackboneMixins.SlickgridCollection = {
setGrid: function (grid) {
var self = this;
this.grid = grid;
this.setGridData();
this.grid.onCellChange.subscribe(function (event, args) {
var modelToUpdate;
modelToUpdate = args.item.id != null ? self.get(args.item.id) : self.getByCid(args.item.cid);
modelToUpdate.set(args.item);
});
this.grid.onAddNewRow.subscribe(function (event, args) {
self.add(args.item);
});
this.grid.onSort.subscribe(function (event, args) {
var sortType;
sortType = args.sortCol.sortType || 'string';
self.comparator = self.comparatorDefinitions[sortType](self, args.sortCol.field, args.sortAsc);
self.sort();
self.comparator = null;
});
this.bind('add', function (model) {
self.grid.updateRowCount();
self.grid.invalidateRow(self.length - 1);
self.grid.render();
});
this.bind('change', function (model) {
self.grid.invalidateRow(self.indexOf(model));
self.grid.render();
});
this.bind('remove', function (model) {
self.grid.updateRowCount();
self.grid.render();
});
this.bind('reset', function (model) {
self.setGridData();
});
},
setGridData: function () {
this.grid.setData(this);
this.grid.invalidate();
},
getItem: function (index) {
var attrs, model;
model = this.at(index);
if (model != null) {
attrs = model.toJSON();
attrs.cid = model.cid;
}
return attrs;
},
comparatorDefinitions: {
number: function (collection, field, sortAsc) {
return function (model) {
var val;
val = Number(model.get(field));
if (sortAsc) {
return val;
} else {
return -val;
}
};
},
string: function (collection, field, sortAsc) {
return function (model) {
var fieldValue;
fieldValue = model.get(field);
if (sortAsc) {
return fieldValue;
} else {
return -(collection.sortedIndex(model, function (m) {
return m.get(field);
}));
}
};
}
}
};
(function (Mixins) {
// TODO - Document and make this a bit more generic
Mixins.TabManager = {
// list of backbone views. one for each tab. key corresponds to value in the data-tab attr on the trigger. (eg data-tab="priceList" corresponds to tabs.priceList)
tabs: {},
events: {
'click .tab': 'changePane'
},
changePane: function (e) {
e.preventDefault();
var $target = $(e.target),
tabToShow = $target.data('tab');
// set active tab
this.$('.tab').parent().removeClass('active');
$target.parent().addClass('active');
if (_.isFunction(this.preTabChange)) {
this.preTabChange(tabToShow);
}
// show the pane
_.each(this.tabs, function (tab) {
tab.$el.hide();
});
this.tabs[tabToShow].$el.show();
if (_.isFunction(this.postTabChange)) {
this.postTabChange(tabToShow);
}
}
};
})(module('Mixins'));
// For use with https://github.com/thedersen/backbone.validation
BackboneMixins.ValidatingView = {
bindValidation: function () {
Backbone.Validation.unbind(this);
Backbone.Validation.bind(this, {
forceUpdate: true,
selector: 'class',
valid: this.valid,
invalid: this.invalid
});
},
valid: function (view, attr) {
var $field = this.$("[data-field='" + attr + "']"),
$error = this.$("[data-error-for='" + attr + "']");
if ($field.length) $field.removeClass('validation-error');
if ($error.length) $error.html('').hide();
},
invalid: function (view, attr, error) {
var $field = this.$("[data-field='" + attr + "']"),
$error = this.$("[data-error-for='" + attr + "']");
if ($field.length) $field.addClass('validation-error');
if ($error.length) $error.html(error).show();
},
};
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment