Skip to content

Instantly share code, notes, and snippets.

@dstibrany
Created September 5, 2012 04:09
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dstibrany/3630229 to your computer and use it in GitHub Desktop.
Save dstibrany/3630229 to your computer and use it in GitHub Desktop.
define([
"app",
"modules/document/documentmodels"
],
function(app, DocumentModels) {
var Views = {};
// Documents - Documents List
// --------------------------
Views.List = Backbone.View.extend({
template: "document/list",
className: "span3",
tagName: "section",
id: "document-list",
documentOffsetLength: 15,
initialize: function() {
var self = this;
/*** Load Initial Batch of Documents ***/
// fetch inital batch
app.on('search:getDocuments', function(query){
self.collection.fetch({
data: {
q: query ? query : 'null'
}
});
});
// render inital batch
this.collection.on('reset', function(collection, res) {
var rendering_list_view;
var query = res.data.q;
rendering_list_view = self.render();
// initialize infinite scroll when the list view is done rendering
rendering_list_view.done(function() {
self.initInfiniteScroll(query);
});
});
/*** Load Subsequent Batches ***/
// manually fetch next batch (ie. with a 'Load More' button)
app.on('loadMore', function() {
// invoke infiniteScroll's scroll event handler with the true option to manually trigger document fetching
self.getInfiniteScroll('target').trigger('scroll', true);
});
// render next batch when new models are added to the collection
this.collection.on('add', function(model) {
self.insertView(
"#document-items",
new Views.ListItem({
model: model
})
).render();
});
},
beforeRender: function() {
// Iterate over the passed collection and create a view for each item.
this.collection.each(function(model) {
// Pass the document model data to the new Document Item View.
this.insertView(
"#document-items",
new Views.ListItem({
model: model
})
);
}, this);
},
// initialize infinite scrolling for this collection of documents
initInfiniteScroll: function(query) {
var $document_items = $('#document-items');
var spinner_el = '<img id="infinte-scroll-spinner" src="assets/images/ajax-loader.gif" alt="" width="35" height="35">';
var onFetch = function() {
$document_items.append(spinner_el);
};
var success = function() {
$('#infinte-scroll-spinner').remove();
};
this.infiniScroll = new Backbone.InfiniScroll(this.collection, {
success: success,
onFetch: onFetch,
target: this.$('.overflow-scroll'),
includePage: true,
documentOffsetLength: this.documentOffsetLength,
scrollOffset: 100,
query: query
});
},
// return the infiniScroll object, or return a property in the infiniScroll's options
getInfiniteScroll: function(prop) {
var infiniScroll = this.infiniScroll;
if (!infiniScroll) return false;
return prop ? infiniScroll.options[prop] : infiniScroll;
}
});
// Documents List Item - Individual Document View
// ----------------------------------------------
Views.ListItem = Backbone.View.extend({
template: 'document/listItem',
className: 'drag',
events: {
'click': 'showPreview'
},
initialize: function() {
var baseUrl = app.root;
this.pdfUrl = baseUrl + 'examine/' + this.model.get('resource') + '?format=PDF';
this.pdfThumbUrl = baseUrl + 'preview/' + this.model.get('resource') + '.sec';
this.model.on('destroy', this.remove, this);
this.model.on('change', this.render, this);
},
serialize: function() {
return _.extend(this.model.toJSON(), {
pdf_url: this.pdfUrl
});
},
afterRender: function() {
this.dragInit();
},
showPreview: function(e) {
var doc_tag_collection;
e.preventDefault();
// store a reference to the model of the currently selected document
app.selectedDocument = this.model;
doc_tag_collection = new DocumentModels.Collections.DocumentTagList();
doc_tag_collection.url += '/' + this.model.get('id');
app.trigger('showPreview', this.model, doc_tag_collection);
},
// TODO may want to optimize this to use event delegation rather than binding to each element
dragInit: function() {
var self = this;
var $el = this.$el;
// set proxy dimensions here
var proxy_width = 90;
var proxy_height = 20;
// drag start
$el.drag('start', function(e, dd) {
// augment drag callback with the model belonging to this document
dd.model = self.model;
return $('<div>')
.addClass('drag-proxy')
.css({
'opacity': 0.75,
'background': 'rgba(34, 196, 248, 0.44)',
'width': proxy_width + 'px',
'height': proxy_height + 'px',
'left': '0',
'z-index': 2
})
.appendTo(document.body);
});
// during drag
$el.drag(function(e, dd) {
$(dd.proxy).css({
top: dd.offsetY + (dd.startY - dd.originalY) - (proxy_height / 2),
left: dd.offsetX + (dd.startX - dd.originalX) - (proxy_width / 2)
});
});
// drag end
$el.drag('end', function(e, dd) {
// revert proxy back to original location
var reverting_proxy = $(dd.proxy).animate({
top: dd.startY - (proxy_height / 2),
left: dd.startX - (proxy_width / 2)
}, 420).promise();
// remove proxy when revert animation is done
reverting_proxy.done(function() {
$(dd.proxy).remove();
});
});
}
});
return Views;
});
/*global serverData:false */
define([
// Global
"app",
// modules
"modules/document/document",
"modules/header/header",
"modules/biller/biller",
"modules/billercred/billercred",
"modules/account/account",
"modules/user/user",
"modules/search/search"
],
function(app, Document, Header, Biller, BillerCred, Account, User, Search) {
// For DEBUGGING
window.app = app;
// Defining the application router, you can attach sub routers here.
var Router = Backbone.Router.extend({
routes: {
"app": "app"
},
app: function() {
// Create Collection/Model Instances
// ---------------------------------
// serverData is a global embedded in from Express
app.models.user = new User.Model(
// correct for the fact that user table in db doesn't
// have a "username" field
_.extend(serverData.user, {
username: serverData.user.email,
postalcode: serverData.user.postcode
})
);
app.models.billers = new Biller.Collection(serverData.billers);
app.models.billerCreds = new BillerCred.Collection(serverData.billerCreds);
app.models.accounts = new Account.Collection(serverData.accounts);
var documents = app.models.documents = new Document.Collections.Documents();
var search = app.models.search = new Search.Collections.Collection();
// TODO make this cleaner
var folder_tree = app.models.folder_tree = new Document.Models.FolderTree(Document.Models.FolderTree.prototype.parse.call(null, serverData.folders));
var tag_list = app.models.tag_list = new Document.Collections.TagList(serverData.tags);
// Create Main Layout
// ------------------
var main = app.useLayout('main', {className: 'container'});
// Set all the views.
main.insertViews({
'#header': new Header.Views.Header({
views: {
'#search-box': new Search.Views.Search({collection: search})
}
}),
'#document-nav': [
new Document.Views.FolderTree({model: folder_tree}),
new Document.Views.TagList({collection: tag_list})
],
'#documents': [
new Document.Views.List({collection: documents}),
new Document.Views.Preview({model: new Document.Models.Preview()})
]
}).render().done(app.setHeight);
// Fetch Data from Server
// ----------------------
// getting documents in root folder (i.e. 'null')
app.trigger('search:init', null);
}
});
return Router;
});
define([
"app",
"modules/search/searchmodels"
],
function(app, SearchModels) {
var Views = {};
// Utilities
// ----------------
Views.util = {
resizeInput: function (input) {
var $input = $(input);
var length = $input.val().length;
var font_size = parseInt($input.css('fontSize'), 10);
var denominator = 1.3;
$input.css('width', ( length * (font_size / denominator) ) + 10 + 'px');
}
};
// Search View
// -----------
Views.Search = Backbone.View.extend({
template: 'search/search',
id: 'search',
className: 'input-append',
initialize: function() {
this.collection.on('add', function() {
this.render();
}, this);
this.collection.on('destroy', function() {
this.render();
this.search();
}, this);
app.on('search:init', this.init, this);
// listen for change to folder/tag names so that the search bar can be updated
app.on('search:rename', this.rename, this);
// listen for deletions of folder/tags so that the search bar can be updated
app.on('search:destroy', this.destroy, this);
app.on('search:refresh', this.search, this);
},
beforeRender: function() {
var type;
var self = this;
// helper function to allow us to set or append
function setView(type, model, append) {
var view;
if (type === 'text') {
view = new Views.SearchItemText({
model: model,
collection: self.collection
});
} else {
view = new Views.SearchItem({
model: model
});
}
self.setView('#' + type + '-search', view, append);
}
this.collection.each(function(model) {
type = model.get('type');
// insert in a different DOM location, depending on whether the model is a tag or a folder
if (type === 'tid') {
setView('tags', model, true);
} else if (type === 'fid') {
setView('folders', model, true);
} else if (type === 'text') {
setView('text', model);
}
});
},
afterRender: function() {
// resize the text box on re-render of the search view
Views.util.resizeInput(this.$('input'));
},
init: function(search_data) {
var model;
// convert search data into a model if it's not already a model
if (search_data && !(search_data instanceof Backbone.Model)) {
model = new SearchModels.Models.Model(search_data);
// a model of type textSearchModel was passed in
} else {
model = search_data;
}
// if no model exists then pass null to access all documents
if (!model) {
this.search(null);
// if the root folder was clicked then clear the search bar, trigger a search for all docs
} else if (model.get('id') === 'root') {
this.destroy(this.collection.where({type: 'fid'})[0]);
// if they are clicking on a non-selected item, continue the search
} else if (this.collection.add(model) !== false) {
this.search();
// else they are clicking on an already selected tag/folder, so just remove the item from search bar
} else {
this.destroy(model);
}
},
rename: function(id, name) {
var model = this.collection.get(id);
if (model) model.set('name', name);
},
// destroy can take a model or an id - this.collection.get can handle both cases
destroy: function(id) {
var model = this.collection.get(id);
if (model) model.trigger('destroy', model);
// pass null to folder tree event listener to deselect the currently selected folder
app.trigger('deselect:folder', id);
},
// perform the actual search by sending an event to the document list module
search: function(query) {
query = query !== null ? this.collection.toString() : null;
app.trigger('search:getDocuments', query);
}
});
// Search Item View
// ----------------
Views.SearchItem = Backbone.View.extend({
template: 'search/searchitem',
className: 'searchitem',
events: {
"click": "removeItem"
},
initialize: function() {
this.model.on('change', this.render, this);
},
serialize: function() {
return this.model.toJSON();
},
removeItem: function(e) {
var model = this.model;
var type = this.model.get('type');
var id = this.model.get('id');
model.trigger('destroy', model);
// signal folder/tag tree event listener to deselect the currently selected folder.
if (type === 'tid') {
app.trigger('deselect:tag', id);
} else if (type === 'fid') {
app.trigger('deselect:folder', id);
}
}
});
Views.SearchItemText = Backbone.View.extend({
template: 'search/searchitemtext',
className: 'searchitemtext',
events: {
'keydown': 'handleKeydown',
'keyup': 'handleKeyup'
},
serialize: function() {
return this.model.toJSON();
},
handleKeyup: function(e) {
// add the current value to the model
this.collection.textSearchModel.set('text', this.$('input').val());
},
handleKeydown: function(e) {
// resize the text box on re-render of the search view
Views.util.resizeInput(this.$('input'));
if (e.which === 13 || e.keyCode === 13) {
// user hit enter key, so perform a search
app.trigger('search:init', this.collection.textSearchModel);
}
}
});
return Views;
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment