forked from charly/backbone.grid
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
initial commit of a (pre) matured code
- Loading branch information
0 parents
commit 31f22ec
Showing
2 changed files
with
332 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Backbone.Grid | ||
|
||
Backbone.Grid is a set of [Backbone][2] View Extensions to help out build a nice Grid and a few nice things around it. It is composed of a **PageView** (the layout) an **IndexView** (the table) a **NewView** (a form) and an **EditView** (a row in the indexView) which all share a common collection to stay in sync. | ||
|
||
## PageView | ||
PageView holds the same role the __layout__ holds in a rails view. But it is also close to a controller since it is responsible for instantiating the main subviews - such as the NewView and the IndexView. It can be used to add a FilterView a NavigationView etc etc. | ||
|
||
## IndexView | ||
It is mainly responsible for displaying the __html table__ and rerendering it whenever a collection is being changed. It also is responsible for instantiating the EditView when a Row is clicked and keeping track of all those child views when they need to be clean up. (Notice : the DOM is used to figure out which model to use for EditView.) | ||
|
||
## NewView | ||
The NewView is a __form__ slidingDown on top of the Html table. It uses collection.create when the form is submitted to automatically refresh the IndexView when a Model is added. | ||
|
||
|
||
## EditView | ||
Is instantiated by a click on the __cell of the table__. The "editCell" function is usually called to do the inline editing by looking at the class attribute of the cell to determine which model attribute it is going to display and update. | ||
|
||
|
||
## Usage (e.g EditView) | ||
|
||
App.Views.AModel.EditView = Grid.EditView({ | ||
tagName : "tr", | ||
className : "new_matter", | ||
template : JST["templates/matters/show"], | ||
|
||
initialize : function() { | ||
this.model.bind("sync", this.renderRow, this); | ||
} | ||
... | ||
}) | ||
|
||
To quickly build an admin like interface [check Backbonify][1] which heavily uses backbone.grid to create all it's views. | ||
|
||
TODO : build an example site to have a better grasp of the overall concepts. | ||
|
||
[1]: https://github.com/charly/backbonify | ||
[2]: https://github.com/documentcloud/backbone |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,295 @@ | ||
// Backbone.grid.js 0.1.4 | ||
|
||
// (c) 2012 Charlie Sistovaris | ||
// Backbone.grid may be freely distributed under the MIT license. | ||
|
||
var Grid = {}; | ||
|
||
// =================================================== | ||
// # PageView : fetches collections listens to newView | ||
// =================================================== | ||
|
||
Grid.PageView = function(options){ | ||
Backbone.View.apply(this, [options]); | ||
}; | ||
|
||
_.extend(Grid.PageView.prototype, Backbone.View.prototype, { | ||
|
||
// renders newView & indexView to concatenates them in this.el | ||
// so the html is ready to be displayed | ||
render: function() { | ||
$(this.el).html(this.template({length: this.collection.length})) | ||
$(this.newView.render().el).hide().appendTo(this.el); | ||
$(this.indexView.el).appendTo(this.el); | ||
return this; | ||
}, | ||
|
||
// Display is called in the router and takes care of it all | ||
display: function() { | ||
this.render(); | ||
$(this.el).hide(); | ||
$("#content").append(this.el); | ||
this.addToPage(); | ||
this.slide(); | ||
}, | ||
|
||
addToPage: function() { | ||
// Stub method to be overriden | ||
}, | ||
|
||
slide: function() { | ||
$(this.el).fadeIn("fast") | ||
}, | ||
|
||
// Index View | ||
refreshIndex : function(event) { | ||
var year = $(event.currentTarget).prop("value"); | ||
this.indexView.collection.fetch({data: {year: year}}) | ||
}, | ||
|
||
// Reload resources with last data | ||
reloadIndex : function() { | ||
event.preventDefault(); | ||
this.indexView.collection.default_fetch(); | ||
}, | ||
|
||
// New View | ||
displayNew : function(event){ | ||
event.preventDefault(); | ||
this.$(this.newView.el).slideToggle() | ||
} | ||
|
||
}); | ||
|
||
Grid.PageView.extend = Backbone.View.extend; | ||
|
||
|
||
// ================================== | ||
// # IndexView : rendered by PageView | ||
// =================================== | ||
|
||
Grid.IndexView = function(options){ | ||
this.events = { | ||
"click tr[class!='edit'] td, tr[class!='month'] td" : "edit", | ||
"mouseover tr" : "highlightStatus", | ||
"mouseout tr" : "downlightStatus" | ||
}; | ||
|
||
Backbone.View.apply(this, [options]); | ||
|
||
this.childViews = []; | ||
this.collection.on("reset", this.render, this); | ||
this.collection.on("add", this.render, this); | ||
this.collection.on("filter", this.render, this); | ||
|
||
|
||
// consider using events instead of render() | ||
//this.collection.on("reset", this.cleanChildren, this); | ||
//this.collection.on("filter", this.cleanChildren, this); | ||
}; | ||
|
||
_.extend(Grid.IndexView.prototype, Backbone.View.prototype, { | ||
|
||
render : function() { | ||
this.cleanChildren(); | ||
var html = this.template({collection: this.collection.toJSON()}); | ||
$(this.el).html(html) | ||
return this; | ||
}, | ||
|
||
// creates an editView from row_id | ||
edit : function(event) { | ||
this._setModelFromRow(event); | ||
var row = new this.editView({model: this.model, el: this.row}); | ||
this.addChild(row); | ||
row.editCell(event); | ||
}, | ||
|
||
// convienence method to extract the models id from tr#id | ||
_setModelFromRow : function(event){ | ||
var $cell = this.$(event.currentTarget); | ||
this.row = $cell.closest("tr"); | ||
this.row.addClass("edit"); | ||
var model_id = this.row.attr("id").replace(/[a-z].*_/, ""); | ||
this.model = this.collection.get(model_id); | ||
}, | ||
|
||
highlightStatus: function(event) { | ||
$(event.currentTarget).addClass("bgyellow") | ||
}, | ||
|
||
downlightStatus: function(event) { | ||
$(event.currentTarget).removeClass("bgyellow"); | ||
}, | ||
|
||
// keeps tracks of all editViews so they can get cleaned up | ||
addChild: function(childView) { | ||
this.childViews.push(childView); | ||
}, | ||
|
||
cleanChildren: function() { | ||
_.each(this.childViews, function(child){ | ||
child.undelegateEvents(); | ||
}); | ||
this.childViews = []; | ||
}, | ||
|
||
donothing: function() {} | ||
}); | ||
|
||
Grid.IndexView.extend = Backbone.View.extend; | ||
|
||
|
||
|
||
// ===================================================== | ||
// # NewView : handles displaying formtriggers 'created' | ||
// ===================================================== | ||
|
||
Grid.NewView = function(options) { | ||
this.events = { | ||
"submit": "createModel" | ||
}; | ||
|
||
Backbone.View.apply(this, [options]); | ||
}; | ||
|
||
_.extend(Grid.NewView.prototype, Backbone.View.prototype, { | ||
// renders a nice form below the header | ||
render: function(){ | ||
var html = this.template(); | ||
$(this.el).html(html); | ||
if(this._formHelpers) this._formHelpers(); | ||
|
||
return this; | ||
}, | ||
|
||
// creates the model from the form and adds it to collection | ||
createModel: function(event) { | ||
event.preventDefault(); | ||
//this.$("input[type='submit']").prop("disabled", true); | ||
var self = this, | ||
attrs = this.gatherInputs(), | ||
model = new this.collection.model(attrs); | ||
|
||
this.collection.create(attrs, {wait: true}); | ||
}, | ||
|
||
gatherInputs: function() { | ||
var attrs = _.reduce(this.$("input[id],textarea,select"), function(memo, input){ | ||
var k = $(input).prop("name").replace(/[a-z_].*\[([a-z].*)\]/, "$1") | ||
memo[k] = $(input).val() || $(input).text(); | ||
return memo; | ||
}, {}) | ||
return attrs; | ||
} | ||
}); | ||
|
||
Grid.NewView.extend = Backbone.View.extend; | ||
|
||
|
||
// ==================================================== | ||
// # EditView : opens an input on a the dblclicked cell | ||
// ==================================================== | ||
|
||
Grid.EditView = function(options) { | ||
|
||
this.events = { | ||
"click input,textarea" : "donothing", | ||
"dblclick input,textarea" : "donothing", | ||
"click td" : "editCell", | ||
"keyup input,textarea" : "keyup" | ||
}; | ||
|
||
Backbone.View.apply(this, [options]); | ||
}; | ||
|
||
_.extend(Grid.EditView.prototype, Backbone.View.prototype, { | ||
|
||
// The meat of editView : takes care of rendering inline | ||
// in the cell clicked in indexView or editView | ||
editCell : function(event) { | ||
event.stopImmediatePropagation(); | ||
this.cell = $(event.currentTarget); | ||
this.attribute = this.cell.attr("class"); | ||
|
||
var inputTemplate = this._createInputTemplate(); | ||
if(inputTemplate == false) return; | ||
// We only have Handlebar runtime no compiling available | ||
//var template = Handlebars.compile(inputTemplate); | ||
var compiled = _.template(inputTemplate); | ||
this.inputTag = compiled( this.model.toJSON() ); | ||
|
||
// setting height & width | ||
var width = this._cellWidth(); | ||
var height = this._cellHeight(); | ||
if( $(this.inputTag).prop("type") != "checkbox" ){ | ||
this.inputTag = $(this.inputTag).width(width - 10); | ||
//this.inputTag = $(this.inputTag).height(height - 6); | ||
} | ||
|
||
// renders the input tag | ||
this.cell.html(this.inputTag); | ||
$(this.inputTag).focus(); | ||
|
||
//this._formHelpers() | ||
//this._orchestrasAutocomplete(); | ||
|
||
return this; | ||
}, | ||
|
||
// calculates cell width so it can be applied on the input tag | ||
_cellWidth : function() { | ||
var width = this.cell.width(); | ||
this.cell.width(width); | ||
return width; | ||
}, | ||
|
||
_cellHeight : function(){ | ||
var height = this.cell.height(); | ||
//this.cell.height(height); | ||
return height | ||
}, | ||
|
||
// gathers all the inputs tag of a row and sets the attributes of the model | ||
gatherInputs: function() { | ||
var attrs = _.reduce(this.$("input,textarea,select"), function(memo, input){ | ||
memo[$(input).prop("name")] = $(input).val() || $(input).text(); | ||
return memo; | ||
}, {}) | ||
return attrs; | ||
}, | ||
|
||
// saves the model after gathering changes and triggers model -> 'saved' | ||
updateModel : function() { | ||
var self = this, | ||
attrs = this.gatherInputs(); | ||
this.model.save(attrs, {wait: true}); | ||
}, | ||
|
||
// renders a clean row after an update or cancel(esc) | ||
renderRow : function(){ | ||
var html = this.template(this.model.toJSON()); | ||
$(this.el).html(html); | ||
}, | ||
|
||
// wrapper to call updateModel | ||
keyup: function (event) { | ||
if (event.keyCode == 13) { // retrun | ||
event.preventDefault(); | ||
event.stopImmediatePropagation(); | ||
this.updateModel(); | ||
} else if (event.keyCode == 27) { // escape | ||
event.stopImmediatePropagation(); | ||
this.renderRow(); | ||
} | ||
}, | ||
|
||
donothing : function(event) { | ||
event.stopImmediatePropagation(); | ||
}, | ||
|
||
noop: null | ||
}); | ||
|
||
Grid.EditView.extend = Backbone.View.extend; | ||
|