(function() { var root = this; var PD = root.PersonalDisplay = {}; PD.version = "1.4" PD.hasHTML5LocalStorage = function() { try { return 'localStorage' in window && window['localStorage'] !== null; } catch (e) { return false; } } /// ## Models PD.TimelineMarkModel = Backbone.Model.extend({ initialize: function() { _.bindAll(this, "equals"); }, equals: function(that) { return this.id == that.id; } }); PD.GTDEntryModel = Backbone.Model.extend({ initialize: function() { _.bindAll(this, "equals"); }, equals: function(that) { return this.id == that.id; } }); // ## Views PD.CurrentActivityView = Backbone.View.extend({ el: $("#current-task"), initialize: function() { _.bindAll(this, "render"); this.model.on('change', this.render, this); }, render: function() { this.$el.find(".task").text(this.model.get("mark")); this.$el.find(".task-notes").text(this.model.get("notes")); return this; } }); PD.GTDNextActionView = Backbone.View.extend({ className: "next-action", tagName: "div", ignoredProperties: ["action", "details", "id"], initialize: function() { _.bindAll(this, "render"); $("#priorities").append(this.el); this.model.on('change', this.render, this); }, render: function() { var elements = []; // Look for the "action" property first var actionEl = $(document.createElement("span")) .addClass("action") actionEl.text(this.model.get("action").toString()); elements.push(actionEl); // Add span elements for each of the other attributes. _.each(this.model.attributes, function(val, key) { if (!_.contains(this.ignoredProperties, key)) { var el = $(document.createElement("span")) .addClass(key.toString()); el.text(val); elements.push(el); } }, this); // Finally, look for the "details" property if (this.model.get("details")) { var detailEl = $(document.createElement("span")) .addClass("details"); detailEl.text(this.model.get("details").toString()); elements.push(detailEl); } // Clear the old data and add our new elements in order this.$el.empty(); _.each(elements, function(el) { this.$el.append(el); }, this); return this; } }); PD.GTDNextActionCollection = Backbone.Collection.extend({ model: PD.GTDEntryModel, initialize: function() { _.bindAll(this, "addNextActionView", "removeNextActionView"); this.views = {}; this.on('add', this.addNextActionView); this.on('remove', this.removeNextActionView); }, addNextActionView: function(entryModel) { var view = new PD.GTDNextActionView({model: entryModel}); view.render(); this.views[entryModel.get("id")] = view; }, removeNextActionView: function(entryModel) { var view = this.views[entryModel.get("id")]; view.$el.remove(); } }); PD.ConfigDialog = Backbone.View.extend({ el: $("#config-dialog"), events: { "blur .timestamper-config .password" : "tsLogin", "blur .gtd-config .password" : "gtdLogin", "change .gtd-config .category" : "addCategory", "click .remove-button" : "removeCategory", "click .save-button" : "saveAndClose" }, initialize: function() { _.bindAll(this, "show", "hide", "tsLogin", "gtdLogin", "loadTsData", "loadGtdData", "addCategory", "makeCategoryItem", "removeCategory", "saveAndClose"); }, show: function() { var $tsSection = this.$el.find(".timestamper-config"); var $gtdSection = this.$el.find(".gtd-config"); // Load TimeStamper configuration values. if (PD.tsCfg) { $tsSection.find(".username").val(PD.tsCfg.username); $tsSection.find(".password").val(PD.tsCfg.password); $tsSection.find(".host").val(PD.tsCfg.host); if (PD.tsAuth) { this.loadTsData(PD.tsCfg.host, PD.tsCfg.username); } this.$el.find('.timeline').val(PD.tsCfg.timelineId); } // Or suggest a default server. else { $tsSection.find(".host").val("timestamper.jdb-labs.com"); } // Load GTD configuration values. if (PD.gtdCfg) { $gtdSection.find(".username").val(PD.gtdCfg.username); $gtdSection.find(".password").val(PD.gtdCfg.password); $gtdSection.find(".host").val(PD.gtdCfg.host); if (PD.gtdAuth) { this.loadGtdData(PD.gtdCfg.host); } // Create the items for the selected categories $(".category-name").parent().remove(); _.forEach(PD.gtdCfg.categories, this.makeCategoryItem); } this.$el.find('.refresh-period').val( PD.refreshPeriod ? PD.refreshPeriod / 1000 : 15); this.$el.fadeIn(); }, hide: function() { this.$el.fadeOut(); }, tsLogin: function() { var username = this.$el.find(".timestamper-config .username").val(); var password = this.$el.find(".timestamper-config .password").val(); var host = this.$el.find(".timestamper-config .host").val(); if (!PD.tsCfg) { PD.tsCfg = {}; } // Hide the configuration dialog. this.$el.find(".wait-overlay span").text("Connecting to " + host); this.$el.find(".wait-overlay").fadeIn(); // Try to log in to the TimeStamper service. $.ajax({ url: "https://" + host + "/ts_api/login", xhrFields: { withCredentials: true }, processData: false, type: 'POST', async: false, data: JSON.stringify( {"username": username, "password": password}), error: function(jqXHR, textStatus, error) { if (jqXHR.status == 401) { $(".validate-tips") .text("Invalid username/password combination for " + "the TimeStamper service."); } else { $(".validate-tips").text("There was an error " + "trying to log into the TimeStamper service: " + error); } PD.tsAuth = false; }, success: function(data, textStatus, jqXHR) { PD.tsAuth = true; $(".validate-tips").text(""); // Load the user's timelines. PD.configDialog.loadTsData(host, username); } }); // Success or failure we hide the wait overlay. this.$el.find(".wait-overlay").fadeOut(); }, gtdLogin: function() { var username = this.$el.find(".gtd-config .username").val(); var password = this.$el.find(".gtd-config .password").val(); var host = this.$el.find(".gtd-config .host").val(); if (!PD.gtdCfg) { PD.gtdCfg = {}; } // Hide the configuration dialog. this.$el.find(".wait-overlay span").text("Connecting to " + host); this.$el.find(".wait-overlay").fadeIn(); // Try to log in to the GTD service. $.ajax({ url: "http://" + host + "/gtd/login", xhrFields: { withCredentials: true }, processData: false, type: 'POST', async: false, data: JSON.stringify( {"username": username, "password": password}), error: function(jqXHR, textStatus, error) { if (jqXHR.status == 401) { $(".validate-tips") .text("Invalid username/password combination for " + "the Getting Things Done service."); } else { $(".validate-tips").text("There was an error " + "trying to log into the Getting Things Done service: " + error); } PD.gtdAuth = false; }, success: function(data, textStatus, jqXHR) { PD.gtdAuth = true; $(".validate-tips").text(""); PD.configDialog.loadGtdData(host); } }); this.$el.find(".wait-overlay").fadeOut(); }, loadTsData: function(host, username) { // (Re)load the user's timelines. PD.tsCfg.timelines = JSON.parse($.ajax({ url: 'https://' + host + '/ts_api/timelines/' + username, xhrFields: { withCredentials: true }, async: false}).responseText); // Populate the available timelines list. var $timelineSelectEl = this.$el.find(".timestamper-config .timeline"); $timelineSelectEl.empty(); _.forEach(PD.tsCfg.timelines, function(timeline) { var $optionEl = $(document.createElement("option")); $optionEl.attr("value", timeline.id); $optionEl.text(timeline.description); $timelineSelectEl.append($optionEl); }); }, loadGtdData: function(host) { // Load the user's contexts PD.gtdCfg.contexts = JSON.parse($.ajax({ url: 'http://' + host + '/gtd/contexts', xhrFields: { withCredentials: true }, async: false }).responseText); // Load the user's projects PD.gtdCfg.projects = JSON.parse($.ajax({ url: 'http://' + host + '/gtd/projects', xhrFields: { withCredentials: true }, async: false }).responseText); // Populate the available contexts and projects drop-down. var $categorySelectEl = $(".gtd-config .category") $categorySelectEl.empty(); $categorySelectEl.append( ""); _.forEach(PD.gtdCfg.contexts.concat(PD.gtdCfg.projects), function(category) { var $optionEl = $(document.createElement("option")); $optionEl.attr("value", category.id); $optionEl.text(category.id); $categorySelectEl.append($optionEl); }); $categorySelectEl[0].selectedIndex = 0; }, makeCategoryItem: function(catName) { var $liEl = $( "