// TimeStamper namespace var TS = {}; /* Setup after the document is ready for manipulation. */ $(document).ready(function(){ // ======== DEFINE MODELS ========// /* Entry model. * Attributes * - id * - mark * - notes * - start */ TS.EntryModel = Backbone.Model.extend({ }); /* Timeline model. * Attributes: * - id * - desc * - created */ TS.TimelineModel = Backbone.Model.extend({ }); /* User model. * Attributes: * - username * - fullname * - email * - join_date */ TS.UserModel = Backbone.Model.extend({ }); TS.EntryList = Backbone.Collection.extend({ model: TS.EntryModel, comparator: function(entry) { return entry.get('timestamp'); }, initialize: function(model, options) { if (options.timeline == undefined) { throw "Cannot create an EntryList without a TimelineModel reference." } else { this.timeline = options.timeline; } _.bindAll(this, "url"); }, url: function() { return "/entries/" + this.timeline.get('user_id') + "/" + this.timeline.get('id'); } }); TS.TimelineList = Backbone.Collection.extend({ model: TS.TimelineModel, initialize: function(models, options) { if (options.user == undefined) { throw "Cannot create a TimelineList without a UserModel reference."; } else { this.user = options.user; } _.bindAll(this, 'url'); }, url: function() { return "/ts_api/timelines/" + this.user.get('id'); } }); // ======== DEFINE VIEWS ========// /* Entry view */ TS.EntryView = Backbone.View.extend({ model: TS.EntryModel, events: { "dblclick div.mark" : "editMark", "dblclick div.timestamp" : "editTimestamp", "keypress .mark-input" : "updateOnEnter" "keypress .timestamp-input" : "updateOnEnter" }, initialize: function() { _.bindAll(this, 'render', 'close'); this.model.bind('change', this.render); this.model.view = this; }, render: function() { $(this.el).html(ich.entry(this.model.toJSON())); return this; }, editMark: function() { $(this.el).addClass('edit-mark'); this.$('.mark-input').focus(); return this; }, editTimestamp: function() { $(this.el).addClass('edit-timestamp'); this.$('timestamp-input').focus(); return this; }, close: function() { this.model.save({ mark: this.$('.mark-input').val(), timestamp: this.$('.timestamp-input').val()}); $(this.el).removeClass('edit-mark edit-timestamp'); }, updateOnEnter: function(e) { if(e.keyCode == 13) this.close(); } }); TS.EntryListView = Backbone.View.extend({ el: $("#entries"), initialize: function() { _.bindAll(this, 'addOne', 'render'); this.collection.bind('add', this.addOne); this.collection.bind('refresh', this.render); this.collection.view = this; }, addOne: function(entry) { $(this.el).append(entry.view.render().el); }, render: function() { $(this.el).empty(); this.collection.each(this.addOne); } }); TS.TimelineView = Backbone.View.extend({ el: $("#timeline"), model: TS.TimelineModel, events: { "dblclick .timeline-id" : "editId", "dblclick .timeline-desc" : "editDesc", "keypress .timeline-id-input" : "updateOnEnter", "keypress .timeline-desc-input" : "updateOnEnter" }, initialize: function() { _.bindAll(this, 'render', 'close', 'editId', 'editDesc', 'updateOnEnter'); this.model.bind('change', this.render); this.model.view = this; }, render: function() { this.$('.timeline-id').html('( ' + this.model.get('id') + ' )'); this.$('.timeline-desc').text(this.model.get('desc')); return this; }, editId: function() { $(this.el).addClass('edit-id'); this.$('.timeline-id-input').focus(); return this; }, editDesc: function() { $(this.el).addClass('edit-desc'); this.$('.timeline-desc-input').focus(); return this; }; close: function() { this.model.save({ id: this.$('timeline-id-input').val(), desc: this.$('.timeline-desc-input').val()}); $(this.el).removeClass('.edit-id .edit-desc'); }, updateOnEnter: function(e) { if (e.keyCode == 13) this.close(); } }); TS.TimelineListView = Backbone.View.extend({ el: $("#timeline .drop-menu-items"), events: { 'click #timeline .drop-menu-items a': 'selectTimeline' }, initialize: function() { _.bindAll(this, 'addOne', 'render'); this.collection.bind('add', this.addOne); this.collection.bind('refresh', this.refresh); this.collection.view = this; }, addOne: function(timeline) { $(this.el).append(ich.timelineLink(timeline.toJSON())); }, render: function() { $(this.el).empty(); this.collection.each(this.addOne); return this; }, selectTimeline: function() { // note that this refers to the element that initiated this event // TODO } }); TS.UserView = Backbone.View.extend({ el: $("user"), model: TS.UserModel, events: { 'dblclick .fullname': 'editFullname', 'keypress .fullname-input': 'updateOnEnter' }, initialize: function() { _.bindAll(this, 'render', 'close', 'editFullname', 'updateOnEnter'); this.model.bind('change', this.render); this.model.view = this; }, render: function() { this.$('.fullname').text(this.model.get('name')); this.$('.username').text(this.model.get('id')); return this; }, editFullname: function() { $(this.el).addClass('.edit-fullname'); this.$('.fullname-input').focus(); return this; }, close: function() { this.model.set({name: this.$('.fullname-input').val()}); this.model.save(); $(this.el).removeClass('.edit-fullname'); }, updateOnEnter: function(e) { if (keyCode == 13) this.close(); } }); }) function login() { // lookup the login dialog elements var name = $("#login-name"); var pwd = $("#login-password"); // call the API via AJAX $.ajax({ url: "/ts_api/login", processData: false, data: JSON.stringify({username: name.val(), password: pwd.val()}), type: "POST", error: function(jqXHR, textStatus, error) { // assuming bad credentials (possible server error or bad request, // we should check that, FIXME var tips = $(".validate-tips"); tips.text("Incorrect username/password combination."); tips.addClass("ui-state-error"); tips.slideDown(); }, success: function(data, textStatus, jqXHR) { // initialize the app data // TODO: possiblty replace by script generated server-side // create the user model TS.user = new TS.UserModel({ id: name.val();, name: '' }); // create the user view new TS.UserView({model: TS.user}); // fetch the initial user data from the server TS.user.fetch(); // create the user's timelines TS.timelineList = new TS.TimelineList([], {user: TS.user}); // create the timeline list view new TS.TimelineListView({collection: TS.timelineList}); TS.timelineList.fetch(); // load the current timeline TS.currentTimeline = TS.timelineList.at(0); // create the timeline view new TS.TimelineView({model: TS.currentTimeline}); // render the timeline view TS.currentTimeline.view.render(); // load the entries for this timeline TS.entryList = new TS.EntryList([], {timeline: TS.currentTimeline}); // create the new entry list view new TS.EntryListView }}); }