From b0afa639ee825a03e2dd905c2d8977e0888798df Mon Sep 17 00:00:00 2001 From: richard-to Date: Wed, 7 May 2014 22:31:31 -0800 Subject: [PATCH 1/6] Fixes #199 - Canceling edits should revert change on edit Asset view --- static/js/screenly-ose.coffee | 8 +- static/js/screenly-ose.js | 395 +++++++++++++++------------------- 2 files changed, 181 insertions(+), 222 deletions(-) diff --git a/static/js/screenly-ose.coffee b/static/js/screenly-ose.coffee index ccee8244e..b00eedfcd 100644 --- a/static/js/screenly-ose.coffee +++ b/static/js/screenly-ose.coffee @@ -59,7 +59,13 @@ class EditAssetView extends Backbone.View (@$ 'input[name="nocache"]').prop 'checked', @model.get 'nocache' (@$ '.modal-header .close').remove() (@$el.children ":first").modal() + + @modelBackup = @model.clone() + @modelBackup.set 'start_date', new Date(@model.get('start_date')) + @modelBackup.set 'end_date', new Date(@model.get('end_date')) + @model.bind 'change', @render + @render() @validate() _.delay (=> (@$f 'uri').focus()), 300 @@ -179,7 +185,7 @@ class EditAssetView extends Backbone.View cancel: (e) => - @model.set @model.previousAttributes() + @model.set @modelBackup.previousAttributes() unless @edit then @model.destroy() (@$el.children ":first").modal 'hide' diff --git a/static/js/screenly-ose.js b/static/js/screenly-ose.js index 8320ce930..4c4cab887 100644 --- a/static/js/screenly-ose.js +++ b/static/js/screenly-ose.js @@ -1,14 +1,11 @@ -//@ sourceMappingURL=screenly-ose.map -// Generated by CoffeeScript 1.6.1 - -/* screenly-ose ui -*/ +// Generated by CoffeeScript 1.7.1 +/* screenly-ose ui */ (function() { var API, App, Asset, AssetRowView, Assets, AssetsView, EditAssetView, date_to, delay, get_filename, get_mimetype, get_template, insertWbr, mimetypes, now, url_test, - _this = this, __indexOf = [].indexOf || function(item) { for (var i = 0, l = this.length; i < l; i++) { if (i in this && this[i] === item) return i; } return -1; }, + __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = {}.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; }, __slice = [].slice; @@ -45,18 +42,20 @@ mimetypes = [['jpg jpeg png pnm gif bmp'.split(' '), 'image'], ['avi mkv mov mpg mpeg mp4 ts flv'.split(' '), 'video']]; - get_mimetype = function(filename) { - var ext, mt; - ext = (_.last(filename.split('.'))).toLowerCase(); - mt = _.find(mimetypes, function(mt) { - return __indexOf.call(mt[0], ext) >= 0; - }); - if (mt) { - return mt[1]; - } else { - return null; - } - }; + get_mimetype = (function(_this) { + return function(filename) { + var ext, mt; + ext = (_.last(filename.split('.'))).toLowerCase(); + mt = _.find(mimetypes, function(mt) { + return __indexOf.call(mt[0], ext) >= 0; + }); + if (mt) { + return mt[1]; + } else { + return null; + } + }; + })(this); url_test = function(v) { return /(http|https):\/\/[\w-]+(\.?[\w-]+)+([\w.,@?^=%&:\/~+#-]*[\w@?^=%&\/~+#-])?/.test(v); @@ -73,14 +72,10 @@ Backbone.emulateJSON = true; API.Asset = Asset = (function(_super) { - __extends(Asset, _super); function Asset() { - var _this = this; - this.defaults = function() { - return Asset.prototype.defaults.apply(_this, arguments); - }; + this.defaults = __bind(this.defaults, this); return Asset.__super__.constructor.apply(this, arguments); } @@ -106,7 +101,6 @@ })(Backbone.Model); API.Assets = Assets = (function(_super) { - __extends(Assets, _super); function Assets() { @@ -122,61 +116,25 @@ })(Backbone.Collection); EditAssetView = (function(_super) { - __extends(EditAssetView, _super); function EditAssetView() { - var _this = this; - this.displayAdvanced = function() { - return EditAssetView.prototype.displayAdvanced.apply(_this, arguments); - }; - this.toggleAdvanced = function() { - return EditAssetView.prototype.toggleAdvanced.apply(_this, arguments); - }; - this.updateMimetype = function(filename) { - return EditAssetView.prototype.updateMimetype.apply(_this, arguments); - }; - this.updateFileUploadMimetype = function() { - return EditAssetView.prototype.updateFileUploadMimetype.apply(_this, arguments); - }; - this.updateUriMimetype = function() { - return EditAssetView.prototype.updateUriMimetype.apply(_this, arguments); - }; - this.clickTabNavUpload = function(e) { - return EditAssetView.prototype.clickTabNavUpload.apply(_this, arguments); - }; - this.clickTabNavUri = function(e) { - return EditAssetView.prototype.clickTabNavUri.apply(_this, arguments); - }; - this.cancel = function(e) { - return EditAssetView.prototype.cancel.apply(_this, arguments); - }; - this.validate = function(e) { - return EditAssetView.prototype.validate.apply(_this, arguments); - }; - this.change = function(e) { - return EditAssetView.prototype.change.apply(_this, arguments); - }; - this.save = function(e) { - return EditAssetView.prototype.save.apply(_this, arguments); - }; - this.viewmodel = function() { - return EditAssetView.prototype.viewmodel.apply(_this, arguments); - }; - this.render = function() { - return EditAssetView.prototype.render.apply(_this, arguments); - }; - this.initialize = function(options) { - return EditAssetView.prototype.initialize.apply(_this, arguments); - }; - this.$fv = function() { - var field, val; - field = arguments[0], val = 2 <= arguments.length ? __slice.call(arguments, 1) : []; - return EditAssetView.prototype.$fv.apply(_this, arguments); - }; - this.$f = function(field) { - return EditAssetView.prototype.$f.apply(_this, arguments); - }; + this.displayAdvanced = __bind(this.displayAdvanced, this); + this.toggleAdvanced = __bind(this.toggleAdvanced, this); + this.updateMimetype = __bind(this.updateMimetype, this); + this.updateFileUploadMimetype = __bind(this.updateFileUploadMimetype, this); + this.updateUriMimetype = __bind(this.updateUriMimetype, this); + this.clickTabNavUpload = __bind(this.clickTabNavUpload, this); + this.clickTabNavUri = __bind(this.clickTabNavUri, this); + this.cancel = __bind(this.cancel, this); + this.validate = __bind(this.validate, this); + this.change = __bind(this.change, this); + this.save = __bind(this.save, this); + this.viewmodel = __bind(this.viewmodel, this); + this.render = __bind(this.render, this); + this.initialize = __bind(this.initialize, this); + this.$fv = __bind(this.$fv, this); + this.$f = __bind(this.$f, this); return EditAssetView.__super__.constructor.apply(this, arguments); } @@ -191,7 +149,6 @@ }; EditAssetView.prototype.initialize = function(options) { - var _this = this; this.edit = options.edit; ($('body')).append(this.$el.html(get_template('asset-modal'))); (this.$('input.time')).timepicker({ @@ -203,12 +160,17 @@ (this.$('input[name="nocache"]')).prop('checked', this.model.get('nocache')); (this.$('.modal-header .close')).remove(); (this.$el.children(":first")).modal(); + this.modelBackup = this.model.clone(); + this.modelBackup.set('start_date', new Date(this.model.get('start_date'))); + this.modelBackup.set('end_date', new Date(this.model.get('end_date'))); this.model.bind('change', this.render); this.render(); this.validate(); - _.delay((function() { - return (_this.$f('uri')).focus(); - }), 300); + _.delay(((function(_this) { + return function() { + return (_this.$f('uri')).focus(); + }; + })(this)), 300); return false; }; @@ -287,8 +249,7 @@ }; EditAssetView.prototype.save = function(e) { - var save, - _this = this; + var save; e.preventDefault(); this.viewmodel(); save = null; @@ -300,11 +261,13 @@ (this.$('.progress')).show(); this.$el.fileupload({ url: this.model.url(), - progressall: function(e, data) { - if (data.loaded && data.total) { - return (_this.$('.progress .bar')).css('width', "" + (data.loaded / data.total * 100) + "%"); - } - } + progressall: (function(_this) { + return function(e, data) { + if (data.loaded && data.total) { + return (_this.$('.progress .bar')).css('width', "" + (data.loaded / data.total * 100) + "%"); + } + }; + })(this) }); save = this.$el.fileupload('send', { fileInput: this.$f('file_upload') @@ -328,60 +291,72 @@ save = this.model.save(); } (this.$('input, select')).prop('disabled', true); - save.done(function(data) { - _this.model.id = data.asset_id; - if (!_this.model.collection) { - _this.collection.add(_this.model); - } - (_this.$el.children(":first")).modal('hide'); - _.extend(_this.model.attributes, data); - if (!_this.edit) { - return _this.model.collection.add(_this.model); - } - }); - save.fail(function() { - (_this.$('.progress')).hide(); - return (_this.$('input, select')).prop('disabled', false); - }); + save.done((function(_this) { + return function(data) { + _this.model.id = data.asset_id; + if (!_this.model.collection) { + _this.collection.add(_this.model); + } + (_this.$el.children(":first")).modal('hide'); + _.extend(_this.model.attributes, data); + if (!_this.edit) { + return _this.model.collection.add(_this.model); + } + }; + })(this)); + save.fail((function(_this) { + return function() { + (_this.$('.progress')).hide(); + return (_this.$('input, select')).prop('disabled', false); + }; + })(this)); return false; }; EditAssetView.prototype.change = function(e) { - var _this = this; - this._change || (this._change = _.throttle((function() { - _this.viewmodel(); - _this.model.trigger('change'); - _this.validate(); - return true; - }), 500)); + this._change || (this._change = _.throttle(((function(_this) { + return function() { + _this.viewmodel(); + _this.model.trigger('change'); + _this.validate(); + return true; + }; + })(this)), 500)); return this._change.apply(this, arguments); }; EditAssetView.prototype.validate = function(e) { - var errors, field, fn, that, v, validators, _i, _len, _ref, _results, - _this = this; + var errors, field, fn, that, v, validators, _i, _len, _ref, _results; that = this; validators = { - duration: function(v) { - if (('video' !== _this.model.get('mimetype')) && (!(_.isNumber(v * 1)) || v * 1 < 1)) { - return 'please enter a valid number'; - } - }, - uri: function(v) { - if (_this.model.isNew() && ((that.$('#tab-uri')).hasClass('active')) && !url_test(v)) { - return 'please enter a valid URL'; - } - }, - file_upload: function(v) { - if (_this.model.isNew() && !v && !(that.$('#tab-uri')).hasClass('active')) { - return 'please select a file'; - } - }, - end_date: function(v) { - if (!((new Date(_this.$fv('start_date'))) < (new Date(_this.$fv('end_date'))))) { - return 'end date should be after start date'; - } - } + duration: (function(_this) { + return function(v) { + if (('video' !== _this.model.get('mimetype')) && (!(_.isNumber(v * 1)) || v * 1 < 1)) { + return 'please enter a valid number'; + } + }; + })(this), + uri: (function(_this) { + return function(v) { + if (_this.model.isNew() && ((that.$('#tab-uri')).hasClass('active')) && !url_test(v)) { + return 'please enter a valid URL'; + } + }; + })(this), + file_upload: (function(_this) { + return function(v) { + if (_this.model.isNew() && !v && !(that.$('#tab-uri')).hasClass('active')) { + return 'please select a file'; + } + }; + })(this), + end_date: (function(_this) { + return function(v) { + if (!((new Date(_this.$fv('start_date'))) < (new Date(_this.$fv('end_date'))))) { + return 'end date should be after start date'; + } + }; + })(this) }; errors = (function() { var _results; @@ -408,7 +383,7 @@ }; EditAssetView.prototype.cancel = function(e) { - this.model.set(this.model.previousAttributes()); + this.model.set(this.modelBackup.previousAttributes()); if (!this.edit) { this.model.destroy(); } @@ -442,17 +417,19 @@ }; EditAssetView.prototype.updateUriMimetype = function() { - var _this = this; - return _.defer(function() { - return _this.updateMimetype(_this.$fv('uri')); - }); + return _.defer((function(_this) { + return function() { + return _this.updateMimetype(_this.$fv('uri')); + }; + })(this)); }; EditAssetView.prototype.updateFileUploadMimetype = function() { - var _this = this; - return _.defer(function() { - return _this.updateMimetype(_this.$fv('file_upload')); - }); + return _.defer((function(_this) { + return function() { + return _this.updateMimetype(_this.$fv('file_upload')); + }; + })(this)); }; EditAssetView.prototype.updateMimetype = function(filename) { @@ -484,35 +461,17 @@ })(Backbone.View); AssetRowView = (function(_super) { - __extends(AssetRowView, _super); function AssetRowView() { - var _this = this; - this.hidePopover = function() { - return AssetRowView.prototype.hidePopover.apply(_this, arguments); - }; - this.showPopover = function() { - return AssetRowView.prototype.showPopover.apply(_this, arguments); - }; - this["delete"] = function(e) { - return AssetRowView.prototype.delete.apply(_this, arguments); - }; - this.edit = function(e) { - return AssetRowView.prototype.edit.apply(_this, arguments); - }; - this.setEnabled = function(enabled) { - return AssetRowView.prototype.setEnabled.apply(_this, arguments); - }; - this.toggleIsEnabled = function(e) { - return AssetRowView.prototype.toggleIsEnabled.apply(_this, arguments); - }; - this.render = function() { - return AssetRowView.prototype.render.apply(_this, arguments); - }; - this.initialize = function(options) { - return AssetRowView.prototype.initialize.apply(_this, arguments); - }; + this.hidePopover = __bind(this.hidePopover, this); + this.showPopover = __bind(this.showPopover, this); + this["delete"] = __bind(this["delete"], this); + this.edit = __bind(this.edit, this); + this.setEnabled = __bind(this.setEnabled, this); + this.toggleIsEnabled = __bind(this.toggleIsEnabled, this); + this.render = __bind(this.render, this); + this.initialize = __bind(this.initialize, this); return AssetRowView.__super__.constructor.apply(this, arguments); } @@ -556,24 +515,27 @@ }; AssetRowView.prototype.toggleIsEnabled = function(e) { - var save, val, - _this = this; + var save, val; val = (1 + this.model.get('is_enabled')) % 2; this.model.set({ is_enabled: val }); this.setEnabled(false); save = this.model.save(); - save.done(function() { - return _this.setEnabled(true); - }); - save.fail(function() { - _this.model.set(_this.model.previousAttributes(), { - silent: true - }); - _this.setEnabled(true); - return _this.render(); - }); + save.done((function(_this) { + return function() { + return _this.setEnabled(true); + }; + })(this)); + save.fail((function(_this) { + return function() { + _this.model.set(_this.model.previousAttributes(), { + silent: true + }); + _this.setEnabled(true); + return _this.render(); + }; + })(this)); return true; }; @@ -599,13 +561,14 @@ }; AssetRowView.prototype["delete"] = function(e) { - var xhr, - _this = this; + var xhr; this.hidePopover(); if ((xhr = this.model.destroy()) === !false) { - xhr.done(function() { - return _this.remove(); - }); + xhr.done((function(_this) { + return function() { + return _this.remove(); + }; + })(this)); } else { this.remove(); } @@ -631,20 +594,12 @@ })(Backbone.View); AssetsView = (function(_super) { - __extends(AssetsView, _super); function AssetsView() { - var _this = this; - this.render = function() { - return AssetsView.prototype.render.apply(_this, arguments); - }; - this.update_order = function() { - return AssetsView.prototype.update_order.apply(_this, arguments); - }; - this.initialize = function(options) { - return AssetsView.prototype.initialize.apply(_this, arguments); - }; + this.render = __bind(this.render, this); + this.update_order = __bind(this.update_order, this); + this.initialize = __bind(this.initialize, this); return AssetsView.__super__.constructor.apply(this, arguments); } @@ -670,19 +625,20 @@ }; AssetsView.prototype.render = function() { - var which, _i, _j, _len, _len1, _ref, _ref1, - _this = this; + var which, _i, _j, _len, _len1, _ref, _ref1; _ref = ['active', 'inactive']; for (_i = 0, _len = _ref.length; _i < _len; _i++) { which = _ref[_i]; (this.$("#" + which + "-assets")).html(''); } - this.collection.each(function(model) { - which = model.get('is_active') ? 'active' : 'inactive'; - return (_this.$("#" + which + "-assets")).append((new AssetRowView({ - model: model - })).render()); - }); + this.collection.each((function(_this) { + return function(model) { + which = model.get('is_active') ? 'active' : 'inactive'; + return (_this.$("#" + which + "-assets")).append((new AssetRowView({ + model: model + })).render()); + }; + })(this)); _ref1 = ['inactive', 'active']; for (_j = 0, _len1 = _ref1.length; _j < _len1; _j++) { which = _ref1[_j]; @@ -702,32 +658,29 @@ })(Backbone.View); API.App = App = (function(_super) { - __extends(App, _super); function App() { - var _this = this; - this.add = function(e) { - return App.prototype.add.apply(_this, arguments); - }; - this.initialize = function() { - return App.prototype.initialize.apply(_this, arguments); - }; + this.add = __bind(this.add, this); + this.initialize = __bind(this.initialize, this); return App.__super__.constructor.apply(this, arguments); } App.prototype.initialize = function() { - var _this = this; - ($(window)).ajaxError(function(e, r) { - var err, j; - ($('#request-error')).html((get_template('request-error'))()); - if ((j = $.parseJSON(r.responseText)) && (err = j.error)) { - return ($('#request-error .msg')).text('Server Error: ' + err); - } - }); - ($(window)).ajaxSuccess(function(data) { - return ($('#request-error')).html(''); - }); + ($(window)).ajaxError((function(_this) { + return function(e, r) { + var err, j; + ($('#request-error')).html((get_template('request-error'))()); + if ((j = $.parseJSON(r.responseText)) && (err = j.error)) { + return ($('#request-error .msg')).text('Server Error: ' + err); + } + }; + })(this)); + ($(window)).ajaxSuccess((function(_this) { + return function(data) { + return ($('#request-error')).html(''); + }; + })(this)); (API.assets = new Assets()).fetch(); return API.assetsView = new AssetsView({ collection: API.assets, From a9a9cc9b06b0e21b0d5a1789f7e1a782af240a7d Mon Sep 17 00:00:00 2001 From: richard-to Date: Sun, 11 May 2014 17:04:36 -0800 Subject: [PATCH 2/6] Fixes #201 - Makes assets move to proper Active/Inactive section when toggled --- static/js/screenly-ose.coffee | 2 +- static/js/screenly-ose.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/static/js/screenly-ose.coffee b/static/js/screenly-ose.coffee index b00eedfcd..e0bb510bb 100644 --- a/static/js/screenly-ose.coffee +++ b/static/js/screenly-ose.coffee @@ -318,7 +318,7 @@ class AssetsView extends Backbone.View (@$ "##{which}-assets").html '' for which in ['active', 'inactive'] @collection.each (model) => - which = if model.get 'is_active' then 'active' else 'inactive' + which = if model.get 'is_enabled' then 'active' else 'inactive' (@$ "##{which}-assets").append (new AssetRowView model: model).render() for which in ['inactive', 'active'] diff --git a/static/js/screenly-ose.js b/static/js/screenly-ose.js index 4c4cab887..d8dbe8e3e 100644 --- a/static/js/screenly-ose.js +++ b/static/js/screenly-ose.js @@ -633,7 +633,7 @@ } this.collection.each((function(_this) { return function(model) { - which = model.get('is_active') ? 'active' : 'inactive'; + which = model.get('is_enabled') ? 'active' : 'inactive'; return (_this.$("#" + which + "-assets")).append((new AssetRowView({ model: model })).render()); From 20446949cca0f509aa9acab3a8bfc020693786cc Mon Sep 17 00:00:00 2001 From: richard-to Date: Sun, 11 May 2014 17:26:14 -0800 Subject: [PATCH 3/6] Update unit tests to all tests pass. --- static/coffee/specs/screenly-spec.coffee | 88 +++++++++--------------- static/js/main.js | 5 ++ static/js/screenly-ose.coffee | 12 ++-- static/js/screenly-ose.js | 16 ++--- static/spec/runner.html | 11 ++- static/spec/screenly-spec.js | 55 ++++++++++++++- views/index.haml | 1 + 7 files changed, 111 insertions(+), 77 deletions(-) create mode 100644 static/js/main.js diff --git a/static/coffee/specs/screenly-spec.coffee b/static/coffee/specs/screenly-spec.coffee index f2f0efcb1..b7ccf87c9 100644 --- a/static/coffee/specs/screenly-spec.coffee +++ b/static/coffee/specs/screenly-spec.coffee @@ -1,76 +1,50 @@ describe "Screenly Open Source", -> - it "should have a screenly object at its root", -> - expect(screenly).toBeDefined() - - it "should have an instance of Assets on the screenly object", -> - expect(screenly.Assets).toBeDefined() - expect(screenly.Assets).toEqual jasmine.any(screenly.collections.Assets) - expect(screenly.ActiveAssets).toEqual jasmine.any(screenly.collections.ActiveAssets) - expect(screenly.InactiveAssets).toEqual jasmine.any(screenly.collections.InactiveAssets) - - describe "Models", -> - - it "should exist", -> - expect(screenly.models).toBeDefined() + it "should have a Screenly object at its root", -> + expect(Screenly).toBeDefined() - describe "Asset model", -> - it "should exist", -> - expect(screenly.models.Asset).toBeDefined() - describe "Collections", -> + describe "date_to", -> - it "should exist", -> - expect(screenly.collections).toBeDefined() + testDate = new Date(2014, 5, 6, 14, 20, 0, 0); + dd = Screenly.date_to(testDate); - describe "Assets", -> - it "should exist", -> - expect(screenly.collections.Assets).toBeDefined() - expect(screenly.collections.ActiveAssets).toBeDefined() - expect(screenly.collections.InactiveAssets).toBeDefined() + it "should format date and time as 'MM/DD/YYYY hh:mm:ss A'", -> + expect(dd.string()).toBe '06/06/2014 02:20:00 PM' + + it "should format date as 'MM/DD/YYYY'", -> + expect(dd.date()).toBe '06/06/2014' + + it "should format date as 'hh:mm:ss A'", -> + expect(dd.time()).toBe '02:20 PM' - it "should use the Asset model", -> - assets = new screenly.collections.Assets() - expect(assets.model).toBe screenly.models.Asset - it "should populate ActiveAssets and InactiveAssets when fetched", -> - screenly.Assets.reset [ - {name: "zacharytamas.com", mimetype:"webpage", is_active: true}, - ] + describe "Models", -> - # ActiveAssets should have one model now - expect(screenly.ActiveAssets.models.length).toEqual 1 + describe "Asset model", -> + it "should exist", -> + expect(Screenly.Asset).toBeDefined() - # InactiveAssets should still be empty - expect(screenly.InactiveAssets.models.length).toEqual 0 - # Now make the page inactive and confirm that ActiveAssets - # is empty (the previous information is wiped away on a - # new data load) and the InactiveAssets collection contains - # the new asset. + describe "Collections", -> - screenly.Assets.reset [ - {name: "zacharytamas.com", mimetype:"webpage", is_active: false}, - ] + describe "Assets", -> + it "should exist", -> + expect(Screenly.Assets).toBeDefined() - # ActiveAssets should be empty now - expect(screenly.ActiveAssets.models.length).toEqual 0 + it "should use the Asset model", -> + assets = new Screenly.Assets() + expect(assets.model).toBe Screenly.Asset - # InactiveAssets should have a model - expect(screenly.InactiveAssets.models.length).toEqual 1 - screenly.Assets.reset [ - {name: "zacharytamas.com", mimetype:"webpage", is_active: false}, - {name: "Hacker News", mimetype: "webpage", is_active: true} - ] + describe "Views", -> - # They should both have a model now - expect(screenly.ActiveAssets.models.length).toEqual 1 - expect(screenly.InactiveAssets.models.length).toEqual 1 - expect(screenly.Assets.models.length).toEqual 2 + it "should have EditAssetView", -> + expect(Screenly.View.EditAssetView).toBeDefined() - describe "Views", -> + it "should have AssetRowView", -> + expect(Screenly.View.AssetRowView).toBeDefined() - it "should exist", -> - expect(screenly.views).toBeDefined() + it "should have AssetsView", -> + expect(Screenly.View.AssetsView).toBeDefined() \ No newline at end of file diff --git a/static/js/main.js b/static/js/main.js new file mode 100644 index 000000000..fd5dbaedf --- /dev/null +++ b/static/js/main.js @@ -0,0 +1,5 @@ +jQuery(function() { + Screenly.app = new Screenly.App({ + el: $('body') + }); +}); diff --git a/static/js/screenly-ose.coffee b/static/js/screenly-ose.coffee index b00eedfcd..44f4ca9a5 100644 --- a/static/js/screenly-ose.coffee +++ b/static/js/screenly-ose.coffee @@ -46,7 +46,8 @@ API.Assets = class Assets extends Backbone.Collection # Views -class EditAssetView extends Backbone.View +API.View = {}; +API.View.EditAssetView = class EditAssetView extends Backbone.View $f: (field) => @$ "[name='#{field}']" # get field element $fv: (field, val...) => (@$f field).val val... # get or set filed value @@ -230,7 +231,7 @@ class EditAssetView extends Backbone.View (@$ '.advanced-accordion').toggle has_nocache is on -class AssetRowView extends Backbone.View +API.View.AssetRowView = class AssetRowView extends Backbone.View tagName: "tr" initialize: (options) => @@ -302,7 +303,7 @@ class AssetRowView extends Backbone.View no -class AssetsView extends Backbone.View +API.View.AssetsView = class AssetsView extends Backbone.View initialize: (options) => @collection.bind event, @render for event in ('reset add remove sync'.split ' ') @sorted = (@$ '#active-assets').sortable @@ -318,7 +319,7 @@ class AssetsView extends Backbone.View (@$ "##{which}-assets").html '' for which in ['active', 'inactive'] @collection.each (model) => - which = if model.get 'is_active' then 'active' else 'inactive' + which = if model.get 'is_enabled' then 'active' else 'inactive' (@$ "##{which}-assets").append (new AssetRowView model: model).render() for which in ['inactive', 'active'] @@ -353,6 +354,3 @@ API.App = class App extends Backbone.View new Asset {}, {collection: API.assets} no - -jQuery -> API.app = new App el: $ 'body' - diff --git a/static/js/screenly-ose.js b/static/js/screenly-ose.js index 4c4cab887..f85ace761 100644 --- a/static/js/screenly-ose.js +++ b/static/js/screenly-ose.js @@ -115,7 +115,9 @@ })(Backbone.Collection); - EditAssetView = (function(_super) { + API.View = {}; + + API.View.EditAssetView = EditAssetView = (function(_super) { __extends(EditAssetView, _super); function EditAssetView() { @@ -460,7 +462,7 @@ })(Backbone.View); - AssetRowView = (function(_super) { + API.View.AssetRowView = AssetRowView = (function(_super) { __extends(AssetRowView, _super); function AssetRowView() { @@ -593,7 +595,7 @@ })(Backbone.View); - AssetsView = (function(_super) { + API.View.AssetsView = AssetsView = (function(_super) { __extends(AssetsView, _super); function AssetsView() { @@ -633,7 +635,7 @@ } this.collection.each((function(_this) { return function(model) { - which = model.get('is_active') ? 'active' : 'inactive'; + which = model.get('is_enabled') ? 'active' : 'inactive'; return (_this.$("#" + which + "-assets")).append((new AssetRowView({ model: model })).render()); @@ -705,10 +707,4 @@ })(Backbone.View); - jQuery(function() { - return API.app = new App({ - el: $('body') - }); - }); - }).call(this); diff --git a/static/spec/runner.html b/static/spec/runner.html index 6384f6f9d..fd24f13e0 100644 --- a/static/spec/runner.html +++ b/static/spec/runner.html @@ -8,9 +8,18 @@ + + - + + + + + + diff --git a/static/spec/screenly-spec.js b/static/spec/screenly-spec.js index 65ec9eed8..140beee12 100644 --- a/static/spec/screenly-spec.js +++ b/static/spec/screenly-spec.js @@ -1,2 +1,53 @@ -// Generated by CoffeeScript 1.4.0 -(function(){describe("Screenly Open Source",function(){it("should have a screenly object at its root",function(){return expect(screenly).toBeDefined()});it("should have an instance of Assets on the screenly object",function(){expect(screenly.Assets).toBeDefined();expect(screenly.Assets).toEqual(jasmine.any(screenly.collections.Assets));expect(screenly.ActiveAssets).toEqual(jasmine.any(screenly.collections.ActiveAssets));return expect(screenly.InactiveAssets).toEqual(jasmine.any(screenly.collections.InactiveAssets))});describe("Models",function(){it("should exist",function(){return expect(screenly.models).toBeDefined()});return describe("Asset model",function(){return it("should exist",function(){return expect(screenly.models.Asset).toBeDefined()})})});describe("Collections",function(){it("should exist",function(){return expect(screenly.collections).toBeDefined()});return describe("Assets",function(){it("should exist",function(){expect(screenly.collections.Assets).toBeDefined();expect(screenly.collections.ActiveAssets).toBeDefined();return expect(screenly.collections.InactiveAssets).toBeDefined()});it("should use the Asset model",function(){var e;e=new screenly.collections.Assets;return expect(e.model).toBe(screenly.models.Asset)});return it("should populate ActiveAssets and InactiveAssets when fetched",function(){screenly.Assets.reset([{name:"zacharytamas.com",mimetype:"webpage",is_active:!0}]);expect(screenly.ActiveAssets.models.length).toEqual(1);expect(screenly.InactiveAssets.models.length).toEqual(0);screenly.Assets.reset([{name:"zacharytamas.com",mimetype:"webpage",is_active:!1}]);expect(screenly.ActiveAssets.models.length).toEqual(0);expect(screenly.InactiveAssets.models.length).toEqual(1);screenly.Assets.reset([{name:"zacharytamas.com",mimetype:"webpage",is_active:!1},{name:"Hacker News",mimetype:"webpage",is_active:!0}]);expect(screenly.ActiveAssets.models.length).toEqual(1);expect(screenly.InactiveAssets.models.length).toEqual(1);return expect(screenly.Assets.models.length).toEqual(2)})})});return describe("Views",function(){return it("should exist",function(){return expect(screenly.views).toBeDefined()})})})}).call(this); \ No newline at end of file +// Generated by CoffeeScript 1.7.1 +(function() { + describe("Screenly Open Source", function() { + it("should have a Screenly object at its root", function() { + return expect(Screenly).toBeDefined(); + }); + describe("date_to", function() { + var dd, testDate; + testDate = new Date(2014, 5, 6, 14, 20, 0, 0); + dd = Screenly.date_to(testDate); + it("should format date and time as 'MM/DD/YYYY hh:mm:ss A'", function() { + return expect(dd.string()).toBe('06/06/2014 02:20:00 PM'); + }); + it("should format date as 'MM/DD/YYYY'", function() { + return expect(dd.date()).toBe('06/06/2014'); + }); + return it("should format date as 'hh:mm:ss A'", function() { + return expect(dd.time()).toBe('02:20 PM'); + }); + }); + describe("Models", function() { + return describe("Asset model", function() { + return it("should exist", function() { + return expect(Screenly.Asset).toBeDefined(); + }); + }); + }); + describe("Collections", function() { + return describe("Assets", function() { + it("should exist", function() { + return expect(Screenly.Assets).toBeDefined(); + }); + return it("should use the Asset model", function() { + var assets; + assets = new Screenly.Assets(); + return expect(assets.model).toBe(Screenly.Asset); + }); + }); + }); + return describe("Views", function() { + it("should have EditAssetView", function() { + return expect(Screenly.View.EditAssetView).toBeDefined(); + }); + it("should have AssetRowView", function() { + return expect(Screenly.View.AssetRowView).toBeDefined(); + }); + return it("should have AssetsView", function() { + return expect(Screenly.View.AssetsView).toBeDefined(); + }); + }); + }); + +}).call(this); diff --git a/views/index.haml b/views/index.haml index 967b9afc7..8182eb459 100644 --- a/views/index.haml +++ b/views/index.haml @@ -30,6 +30,7 @@ %script(src="/static/js/moment.js") %script(src="/static/js/screenly-ose.js") + %script(src="/static/js/main.js") %script(type="text/template", id="asset-row-template") %td.asset_row_name From 5b988d524c3c10cc16d0adff0c51bc1ae5108a7f Mon Sep 17 00:00:00 2001 From: richard-to Date: Tue, 13 May 2014 00:53:53 -0800 Subject: [PATCH 4/6] Implement correct logic for is_active Add is_active, backup, rollback methods to Asset model --- static/js/screenly-ose.coffee | 26 ++++++++++++++++++++----- static/js/screenly-ose.js | 36 ++++++++++++++++++++++++++++++----- 2 files changed, 52 insertions(+), 10 deletions(-) diff --git a/static/js/screenly-ose.coffee b/static/js/screenly-ose.coffee index e0bb510bb..1f1e73e7a 100644 --- a/static/js/screenly-ose.coffee +++ b/static/js/screenly-ose.coffee @@ -40,6 +40,24 @@ API.Asset = class Asset extends Backbone.Model is_enabled: 0 nocache: 0 + is_active: => + if @get('is_enabled') and @get('start_date') and @get('end_date') + at = now() + start_date = new Date(@get('start_date')); + end_date = new Date(@get('end_date')); + return start_date < at and end_date > at + else + return false + + backup: => + @backup_attributes = @toJSON() + + rollback: => + if @backup_attributes + @set @backup_attributes + @backup_attributes = undefined + + API.Assets = class Assets extends Backbone.Collection url: "/api/assets" model: Asset @@ -60,9 +78,7 @@ class EditAssetView extends Backbone.View (@$ '.modal-header .close').remove() (@$el.children ":first").modal() - @modelBackup = @model.clone() - @modelBackup.set 'start_date', new Date(@model.get('start_date')) - @modelBackup.set 'end_date', new Date(@model.get('end_date')) + @model.backup() @model.bind 'change', @render @@ -185,7 +201,7 @@ class EditAssetView extends Backbone.View cancel: (e) => - @model.set @modelBackup.previousAttributes() + @model.rollback() unless @edit then @model.destroy() (@$el.children ":first").modal 'hide' @@ -318,7 +334,7 @@ class AssetsView extends Backbone.View (@$ "##{which}-assets").html '' for which in ['active', 'inactive'] @collection.each (model) => - which = if model.get 'is_enabled' then 'active' else 'inactive' + which = if model.is_active() then 'active' else 'inactive' (@$ "##{which}-assets").append (new AssetRowView model: model).render() for which in ['inactive', 'active'] diff --git a/static/js/screenly-ose.js b/static/js/screenly-ose.js index d8dbe8e3e..591604b48 100644 --- a/static/js/screenly-ose.js +++ b/static/js/screenly-ose.js @@ -75,6 +75,9 @@ __extends(Asset, _super); function Asset() { + this.rollback = __bind(this.rollback, this); + this.backup = __bind(this.backup, this); + this.is_active = __bind(this.is_active, this); this.defaults = __bind(this.defaults, this); return Asset.__super__.constructor.apply(this, arguments); } @@ -96,6 +99,31 @@ }; }; + Asset.prototype.is_active = function() { + var at, end_date, start_date; + if (this.get('is_enabled') && this.get('start_date') && this.get('end_date')) { + at = now(); + start_date = new Date(this.get('start_date')); + end_date = new Date(this.get('end_date')); + return start_date < at && end_date > at; + } else { + return false; + } + }; + + Asset.prototype.backup = function() { + this.backup_attributes = this.toJSON(); + return console.log(this.backup_attributes); + }; + + Asset.prototype.rollback = function() { + console.log(this.backup_attributes); + if (this.backup_attributes) { + this.set(this.backup_attributes); + return this.backup_attributes = void 0; + } + }; + return Asset; })(Backbone.Model); @@ -160,9 +188,7 @@ (this.$('input[name="nocache"]')).prop('checked', this.model.get('nocache')); (this.$('.modal-header .close')).remove(); (this.$el.children(":first")).modal(); - this.modelBackup = this.model.clone(); - this.modelBackup.set('start_date', new Date(this.model.get('start_date'))); - this.modelBackup.set('end_date', new Date(this.model.get('end_date'))); + this.model.backup(); this.model.bind('change', this.render); this.render(); this.validate(); @@ -383,7 +409,7 @@ }; EditAssetView.prototype.cancel = function(e) { - this.model.set(this.modelBackup.previousAttributes()); + this.model.rollback(); if (!this.edit) { this.model.destroy(); } @@ -633,7 +659,7 @@ } this.collection.each((function(_this) { return function(model) { - which = model.get('is_enabled') ? 'active' : 'inactive'; + which = model.is_active() ? 'active' : 'inactive'; return (_this.$("#" + which + "-assets")).append((new AssetRowView({ model: model })).render()); From c6e11cb8e917f049d2b4f4ccceb32b45ec98e9f9 Mon Sep 17 00:00:00 2001 From: richard-to Date: Tue, 13 May 2014 01:45:32 -0800 Subject: [PATCH 5/6] Add unit tests for new methods on Asset model --- static/coffee/specs/screenly-spec.coffee | 75 ++++++++++++++++++++++-- static/spec/screenly-spec.js | 73 ++++++++++++++++++++--- 2 files changed, 134 insertions(+), 14 deletions(-) diff --git a/static/coffee/specs/screenly-spec.coffee b/static/coffee/specs/screenly-spec.coffee index b7ccf87c9..a37e7dc43 100644 --- a/static/coffee/specs/screenly-spec.coffee +++ b/static/coffee/specs/screenly-spec.coffee @@ -7,17 +7,17 @@ describe "Screenly Open Source", -> describe "date_to", -> - testDate = new Date(2014, 5, 6, 14, 20, 0, 0); - dd = Screenly.date_to(testDate); + test_date = new Date(2014, 5, 6, 14, 20, 0, 0); + a_date = Screenly.date_to(test_date); it "should format date and time as 'MM/DD/YYYY hh:mm:ss A'", -> - expect(dd.string()).toBe '06/06/2014 02:20:00 PM' + expect(a_date.string()).toBe '06/06/2014 02:20:00 PM' - it "should format date as 'MM/DD/YYYY'", -> - expect(dd.date()).toBe '06/06/2014' + it "should format date as 'MM/a_date/YYYY'", -> + expect(a_date.date()).toBe '06/06/2014' it "should format date as 'hh:mm:ss A'", -> - expect(dd.time()).toBe '02:20 PM' + expect(a_date.time()).toBe '02:20 PM' describe "Models", -> @@ -26,6 +26,69 @@ describe "Screenly Open Source", -> it "should exist", -> expect(Screenly.Asset).toBeDefined() + start_date = new Date(2014, 4, 6, 14, 20, 0, 0); + end_date = new Date(); + end_date.setMonth(end_date.getMonth() + 2) + asset = new Screenly.Asset({ + asset_id: 2 + duration: "8" + end_date: end_date + is_enabled: true + mimetype: 'webpage' + name: 'Test' + start_date: start_date + uri: 'http://www.screenlyapp.com' + }) + + it "should be active if enabled and date is in range", -> + expect(asset.active()).toBe true + + it "should be inactive if disabled and date is in range", -> + asset.set 'is_enabled', false + expect(asset.active()).toBe false + + it "should be inactive if enabled and date is out of range", -> + asset.set 'is_enabled', true + asset.set 'start_date', asset.get 'end_date' + expect(asset.active()).toBe false + + it "should rollback to backup data if it exists", -> + + asset.set 'start_date', start_date + asset.set 'end_date', end_date + asset.backup() + + asset.set({ + is_enabled: false + name: "Test 2" + start_date: new Date(2011, 4, 6, 14, 20, 0, 0) + end_date: new Date(2011, 4, 6, 14, 20, 0, 0) + uri: "http://www.wireload.net" + }) + + asset.rollback() + + expect(asset.get 'is_enabled').toBe true + expect(asset.get 'name').toBe 'Test' + expect(asset.get 'start_date').toBe start_date + expect(asset.get 'uri').toBe "http://www.screenlyapp.com" + + it "should erase backup date after rollback", -> + asset.set({ + is_enabled: false + name: "Test 2" + start_date: new Date(2011, 4, 6, 14, 20, 0, 0) + end_date: new Date(2011, 4, 6, 14, 20, 0, 0) + uri: "http://www.wireload.net" + }) + + asset.rollback() + + expect(asset.get 'is_enabled').toBe false + expect(asset.get 'name').toBe 'Test 2' + expect(asset.get('start_date').toISOString()).toBe (new Date(2011, 4, 6, 14, 20, 0, 0)).toISOString() + expect(asset.get 'uri').toBe "http://www.wireload.net" + describe "Collections", -> diff --git a/static/spec/screenly-spec.js b/static/spec/screenly-spec.js index 140beee12..ef3c9956f 100644 --- a/static/spec/screenly-spec.js +++ b/static/spec/screenly-spec.js @@ -5,24 +5,81 @@ return expect(Screenly).toBeDefined(); }); describe("date_to", function() { - var dd, testDate; - testDate = new Date(2014, 5, 6, 14, 20, 0, 0); - dd = Screenly.date_to(testDate); + var a_date, test_date; + test_date = new Date(2014, 5, 6, 14, 20, 0, 0); + a_date = Screenly.date_to(test_date); it("should format date and time as 'MM/DD/YYYY hh:mm:ss A'", function() { - return expect(dd.string()).toBe('06/06/2014 02:20:00 PM'); + return expect(a_date.string()).toBe('06/06/2014 02:20:00 PM'); }); - it("should format date as 'MM/DD/YYYY'", function() { - return expect(dd.date()).toBe('06/06/2014'); + it("should format date as 'MM/a_date/YYYY'", function() { + return expect(a_date.date()).toBe('06/06/2014'); }); return it("should format date as 'hh:mm:ss A'", function() { - return expect(dd.time()).toBe('02:20 PM'); + return expect(a_date.time()).toBe('02:20 PM'); }); }); describe("Models", function() { return describe("Asset model", function() { - return it("should exist", function() { + var asset, end_date, start_date; + it("should exist", function() { return expect(Screenly.Asset).toBeDefined(); }); + start_date = new Date(2014, 4, 6, 14, 20, 0, 0); + end_date = new Date(); + end_date.setMonth(end_date.getMonth() + 2); + asset = new Screenly.Asset({ + asset_id: 2, + duration: "8", + end_date: end_date, + is_enabled: true, + mimetype: 'webpage', + name: 'Test', + start_date: start_date, + uri: 'http://www.screenlyapp.com' + }); + it("should be active if enabled and date is in range", function() { + return expect(asset.active()).toBe(true); + }); + it("should be inactive if disabled and date is in range", function() { + asset.set('is_enabled', false); + return expect(asset.active()).toBe(false); + }); + it("should be inactive if enabled and date is out of range", function() { + asset.set('is_enabled', true); + asset.set('start_date', asset.get('end_date')); + return expect(asset.active()).toBe(false); + }); + it("should rollback to backup data if it exists", function() { + asset.set('start_date', start_date); + asset.set('end_date', end_date); + asset.backup(); + asset.set({ + is_enabled: false, + name: "Test 2", + start_date: new Date(2011, 4, 6, 14, 20, 0, 0), + end_date: new Date(2011, 4, 6, 14, 20, 0, 0), + uri: "http://www.wireload.net" + }); + asset.rollback(); + expect(asset.get('is_enabled')).toBe(true); + expect(asset.get('name')).toBe('Test'); + expect(asset.get('start_date')).toBe(start_date); + return expect(asset.get('uri')).toBe("http://www.screenlyapp.com"); + }); + return it("should erase backup date after rollback", function() { + asset.set({ + is_enabled: false, + name: "Test 2", + start_date: new Date(2011, 4, 6, 14, 20, 0, 0), + end_date: new Date(2011, 4, 6, 14, 20, 0, 0), + uri: "http://www.wireload.net" + }); + asset.rollback(); + expect(asset.get('is_enabled')).toBe(false); + expect(asset.get('name')).toBe('Test 2'); + expect(asset.get('start_date').toISOString()).toBe((new Date(2011, 4, 6, 14, 20, 0, 0)).toISOString()); + return expect(asset.get('uri')).toBe("http://www.wireload.net"); + }); }); }); describe("Collections", function() { From 0a866673bc500292e88a3e6d62eadc7b17cbc66b Mon Sep 17 00:00:00 2001 From: richard-to Date: Wed, 14 May 2014 20:16:27 -0800 Subject: [PATCH 6/6] Change start and end date to be inclusive instead of exclusive in frontend and backend logic --- assets_helper.py | 2 +- static/js/screenly-ose.coffee | 2 +- static/js/screenly-ose.js | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/assets_helper.py b/assets_helper.py index 681362cc0..dec694f1d 100644 --- a/assets_helper.py +++ b/assets_helper.py @@ -28,7 +28,7 @@ def is_active(asset, at_time=None): if asset['is_enabled'] and asset['start_date'] and asset['end_date']: at = at_time or get_time() - return asset['start_date'] < at and asset['end_date'] > at + return asset['start_date'] <= at <= asset['end_date'] return False diff --git a/static/js/screenly-ose.coffee b/static/js/screenly-ose.coffee index 492c70551..a8115da1c 100644 --- a/static/js/screenly-ose.coffee +++ b/static/js/screenly-ose.coffee @@ -45,7 +45,7 @@ API.Asset = class Asset extends Backbone.Model at = now() start_date = new Date(@get('start_date')); end_date = new Date(@get('end_date')); - return start_date < at and end_date > at + return start_date <= at <= end_date else return false diff --git a/static/js/screenly-ose.js b/static/js/screenly-ose.js index 0a3aa8948..7df11ff15 100644 --- a/static/js/screenly-ose.js +++ b/static/js/screenly-ose.js @@ -106,7 +106,7 @@ at = now(); start_date = new Date(this.get('start_date')); end_date = new Date(this.get('end_date')); - return start_date < at && end_date > at; + return (start_date <= at && at <= end_date); } else { return false; }