Commands return immediately and signalr is used to control the UI

This commit is contained in:
Mark McDowall 2013-08-30 20:08:19 -07:00
parent 772ab3c921
commit c96ba5efd3
55 changed files with 439 additions and 238 deletions

View file

@ -0,0 +1,17 @@
'use strict';
define(
[
'backbone',
'Commands/CommandModel',
'Mixins/backbone.signalr.mixin'
], function (Backbone, CommandModel) {
var CommandCollection = Backbone.Collection.extend({
url : window.ApiRoot + '/command',
model: CommandModel
});
var collection = new CommandCollection().bindSignalR();
return collection;
});

View file

@ -0,0 +1,8 @@
'use strict';
define(
[
'backbone'
], function (Backbone) {
return Backbone.Model.extend({
});
});

View file

@ -9,9 +9,9 @@ define(
'Series/SeriesCollection',
'Shared/LoadingView',
'Shared/Messenger',
'Commands/CommandController',
'Shared/Actioneer',
'Shared/FormatHelpers'
], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection, LoadingView, Messenger, CommandController, FormatHelpers) {
], function (App, Marionette, ButtonsView, ManualSearchLayout, ReleaseCollection, SeriesCollection, LoadingView, Messenger, Actioneer, FormatHelpers) {
return Marionette.Layout.extend({
template: 'Episode/Search/LayoutTemplate',
@ -39,16 +39,19 @@ define(
e.preventDefault();
}
CommandController.Execute('episodeSearch', { episodeId: this.model.get('id') });
var series = SeriesCollection.get(this.model.get('seriesId'));
var seriesTitle = series.get('title');
var season = this.model.get('seasonNumber');
var episode = this.model.get('episodeNumber');
var message = seriesTitle + ' - ' + season + 'x' + FormatHelpers.pad(episode, 2);
Messenger.show({
message: 'Search started for: ' + message
Actioneer.ExecuteCommand({
command : 'episodeSearch',
properties : {
episodeId: this.model.get('id')
},
errorMessage: 'Search failed for: ' + message,
startMessage: 'Search started for: ' + message
});
App.vent.trigger(App.Commands.CloseModalCommand);

View file

@ -5,10 +5,11 @@ require(
'marionette',
'Controller',
'Series/SeriesCollection',
'Shared/Actioneer',
'Navbar/NavbarView',
'jQuery/RouteBinder',
'jquery'
], function (App, Marionette, Controller, SeriesCollection, NavbarView, RouterBinder, $) {
], function (App, Marionette, Controller, SeriesCollection, Actioneer, NavbarView, RouterBinder, $) {
var Router = Marionette.AppRouter.extend({

View file

@ -113,6 +113,8 @@ define(
},
_setMonitored: function (seasonNumber) {
//TODO: use Actioneer?
var self = this;
var promise = $.ajax({

View file

@ -7,9 +7,8 @@ define(
'Cells/EpisodeTitleCell',
'Cells/RelativeDateCell',
'Cells/EpisodeStatusCell',
'Commands/CommandController',
'Shared/Actioneer'
], function ( Marionette, Backgrid, ToggleCell, EpisodeTitleCell, RelativeDateCell, EpisodeStatusCell, CommandController, Actioneer) {
], function ( Marionette, Backgrid, ToggleCell, EpisodeTitleCell, RelativeDateCell, EpisodeStatusCell, Actioneer) {
return Marionette.Layout.extend({
template: 'Series/Details/SeasonLayoutTemplate',
@ -101,9 +100,10 @@ define(
seriesId : this.model.get('seriesId'),
seasonNumber: this.model.get('seasonNumber')
},
element : this.ui.seasonSearch,
failMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')),
startMessage: 'Search for season {0} started'.format(this.model.get('seasonNumber'))
element : this.ui.seasonSearch,
errorMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')),
startMessage : 'Search for season {0} started'.format(this.model.get('seasonNumber')),
successMessage: 'Search for season {0} completed'.format(this.model.get('seasonNumber'))
});
},
@ -143,13 +143,13 @@ define(
_seasonRename: function () {
Actioneer.ExecuteCommand({
command : 'renameSeason',
properties : {
command : 'renameSeason',
properties : {
seriesId : this.model.get('seriesId'),
seasonNumber: this.model.get('seasonNumber')
},
element : this.ui.seasonRename,
failMessage: 'Season rename failed'
element : this.ui.seasonRename,
errorMessage: 'Season rename failed'
});
}
});

View file

@ -51,7 +51,7 @@ define(
seasonNumber: this.model.get('seasonNumber')
},
element : this.ui.seasonSearch,
failMessage : 'Search for season {0} failed'.format(this.model.get('seasonNumber')),
errorMessage: 'Search for season {0} failed'.format(this.model.get('seasonNumber')),
startMessage: 'Search for season {0} started'.format(this.model.get('seasonNumber'))
});
},

View file

@ -154,10 +154,10 @@ define(
properties : {
seriesId: this.model.get('id')
},
element : this.ui.rename,
context : this,
onSuccess : this._refetchEpisodeFiles,
failMessage: 'Series search failed'
element : this.ui.rename,
context : this,
onSuccess : this._refetchEpisodeFiles,
errorMessage: 'Series search failed'
});
},
@ -168,7 +168,7 @@ define(
seriesId: this.model.get('id')
},
element : this.ui.search,
failMessage : 'Series search failed',
errorMessage: 'Series search failed',
startMessage: 'Search for {0} started'.format(this.model.get('title'))
});
},

View file

@ -140,7 +140,6 @@ define(
this._fetchCollection();
},
initialize: function () {
this.seriesCollection = SeriesCollection;
@ -148,7 +147,6 @@ define(
this.listenTo(SeriesCollection, 'remove', this._renderView);
},
_renderView: function () {
if (SeriesCollection.length === 0) {
@ -164,7 +162,6 @@ define(
}
},
onShow: function () {
this._showToolbar();
this._renderView();

View file

@ -66,7 +66,7 @@
<button class="btn pull-left x-back">back</button>
{{/if}}
<button class="btn x-test">test <i class="x-test-icon"/></button>
<button class="btn x-test">test <i class="x-test-icon icon-nd-test"/></button>
<button class="btn" data-dismiss="modal">cancel</button>
<div class="btn-group">

View file

@ -6,11 +6,11 @@ define([
'Settings/Notifications/Model',
'Settings/Notifications/DeleteView',
'Shared/Messenger',
'Commands/CommandController',
'Shared/Actioneer',
'Mixins/AsModelBoundView',
'Form/FormBuilder'
], function (App, Marionette, NotificationModel, DeleteView, Messenger, CommandController, AsModelBoundView) {
], function (App, Marionette, NotificationModel, DeleteView, Messenger, Actioneer, AsModelBoundView) {
var model = Marionette.ItemView.extend({
template: 'Settings/Notifications/EditTemplate',
@ -70,41 +70,28 @@ define([
var testCommand = this.model.get('testCommand');
if (testCommand) {
this.idle = false;
this.ui.testButton.addClass('disabled');
this.ui.testIcon.addClass('icon-spinner icon-spin');
var properties = {};
_.each(this.model.get('fields'), function (field) {
properties[field.name] = field.value;
});
var self = this;
var commandPromise = CommandController.Execute(testCommand, properties);
commandPromise.done(function () {
Messenger.show({
message: 'Notification settings tested successfully'
});
Actioneer.ExecuteCommand({
command : testCommand,
properties : properties,
button : this.ui.testButton,
element : this.ui.testIcon,
errorMessage : 'Failed to test notification settings',
successMessage: 'Notification settings tested successfully',
always : this._testOnAlways,
context : this
});
}
},
commandPromise.fail(function (options) {
if (options.readyState === 0 || options.status === 0) {
return;
}
Messenger.show({
message: 'Failed to test notification settings',
type : 'error'
});
});
commandPromise.always(function () {
if (!self.isClosed) {
self.ui.testButton.removeClass('disabled');
self.ui.testIcon.removeClass('icon-spinner icon-spin');
self.idle = true;
}
});
_testOnAlways: function () {
if (!this.isClosed) {
this.idle = true;
}
}
});

View file

@ -1,15 +1,30 @@
'use strict';
define(['Commands/CommandController', 'Shared/Messenger'],
function(CommandController, Messenger) {
return {
define(
[
'Commands/CommandController',
'Commands/CommandCollection',
'Shared/Messenger'],
function(CommandController, CommandCollection, Messenger) {
var actioneer = Marionette.AppRouter.extend({
initialize: function () {
this.trackedCommands = [];
CommandCollection.fetch();
this.listenTo(CommandCollection, 'sync', this._handleCommands);
},
ExecuteCommand: function (options) {
options.iconClass = this._getIconClass(options.element);
this._showStartMessage(options);
if (options.button) {
options.button.addClass('disable');
}
this._setSpinnerOnElement(options);
var promise = CommandController.Execute(options.command, options.properties);
this._handlePromise(promise, options);
this._showStartMessage(options, promise);
},
SaveModel: function (options) {
@ -24,15 +39,7 @@ define(['Commands/CommandController', 'Shared/Messenger'],
_handlePromise: function (promise, options) {
promise.done(function () {
if (options.successMessage) {
Messenger.show({
message: options.successMessage
});
}
if (options.onSuccess) {
options.onSuccess.call(options.context);
}
self._onSuccess(options);
});
promise.fail(function (ajaxOptions) {
@ -40,31 +47,41 @@ define(['Commands/CommandController', 'Shared/Messenger'],
return;
}
if (options.failMessage) {
Messenger.show({
message: options.failMessage,
type : 'error'
});
}
if (options.onError) {
options.onError.call(options.context);
}
self._onError(options);
});
promise.always(function () {
self._onComplete(options);
});
},
if (options.leaveIcon) {
options.element.removeClass('icon-spin');
_handleCommands: function () {
var self = this;
_.each(this.trackedCommands, function (trackedCommand){
if (trackedCommand.completed === true) {
return;
}
else {
options.element.addClass(options.iconClass);
options.element.removeClass('icon-nd-spinner');
var options = trackedCommand.options;
var command = CommandCollection.find({ 'id': trackedCommand.id });
if (!command) {
return;
}
if (options.always) {
options.always.call(options.context);
if (command.get('state') === 'completed') {
trackedCommand.completed = true;
self._onSuccess(options, command.get('id'));
self._onComplete(options);
}
if (command.get('state') === 'failed') {
trackedCommand.completed = true;
self._onError(options, command.get('id'));
self._onComplete(options);
}
});
},
@ -74,6 +91,10 @@ define(['Commands/CommandController', 'Shared/Messenger'],
},
_setSpinnerOnElement: function (options) {
if (!options.element) {
return;
}
if (options.leaveIcon) {
options.element.addClass('icon-spin');
}
@ -84,12 +105,79 @@ define(['Commands/CommandController', 'Shared/Messenger'],
}
},
_showStartMessage: function (options) {
if (options.startMessage) {
_onSuccess: function (options, id) {
if (options.successMessage) {
Messenger.show({
message: options.startMessage
id : id,
message: options.successMessage,
type : 'success'
});
}
if (options.onSuccess) {
options.onSuccess.call(options.context);
}
},
_onError: function (options, id) {
if (options.errorMessage) {
Messenger.show({
id : id,
message: options.errorMessage,
type : 'error'
});
}
if (options.onError) {
options.onError.call(options.context);
}
},
_onComplete: function (options) {
if (options.button) {
options.button.removeClass('disable');
}
if (options.leaveIcon) {
options.element.removeClass('icon-spin');
}
else {
options.element.addClass(options.iconClass);
options.element.removeClass('icon-nd-spinner');
options.element.removeClass('icon-spin');
}
if (options.always) {
options.always.call(options.context);
}
},
_showStartMessage: function (options, promise) {
var self = this;
if (!promise) {
if (options.startMessage) {
Messenger.show({
message: options.startMessage
});
}
return;
}
promise.done(function (data) {
self.trackedCommands.push({ id: data.id, options: options });
if (options.startMessage) {
Messenger.show({
id : data.id,
message: options.startMessage
});
}
});
}
}
});
return new actioneer();
});

View file

@ -13,6 +13,10 @@ define(function () {
options.hideAfter = 5;
break;
case 'success':
options.hideAfter = 5;
break;
default :
options.hideAfter = 0;
}
@ -22,11 +26,11 @@ define(function () {
message : options.message,
type : options.type,
showCloseButton: true,
hideAfter : options.hideAfter
hideAfter : options.hideAfter,
id : options.id
});
},
monitor: function (options) {
if (!options.promise) {

View file

@ -3,9 +3,9 @@ define(
[
'app',
'marionette',
'Commands/CommandController',
'Shared/Actioneer',
'Shared/Messenger'
], function (App, Marionette, CommandController, Messenger) {
], function (App, Marionette, Actioneer, Messenger) {
return Marionette.ItemView.extend({
template : 'Shared/Toolbar/ButtonTemplate',
@ -19,7 +19,6 @@ define(
icon: '.x-icon'
},
initialize: function () {
this.storageKey = this.model.get('menuKey') + ':' + this.model.get('key');
this.idle = true;
@ -45,68 +44,19 @@ define(
},
invokeCommand: function () {
//TODO: Use Actioneer to handle icon swapping
var command = this.model.get('command');
if (command) {
this.idle = false;
this.$el.addClass('disabled');
this.ui.icon.addClass('icon-spinner icon-spin');
var self = this;
var commandPromise = CommandController.Execute(command);
commandPromise.done(function () {
if (self.model.get('successMessage')) {
Messenger.show({
message: self.model.get('successMessage')
});
}
if (self.model.get('onSuccess')) {
if (!self.model.ownerContext) {
throw 'ownerContext must be set.';
}
self.model.get('onSuccess').call(self.model.ownerContext);
}
Actioneer.ExecuteCommand({
command : command,
button : this.$el,
element : this.ui.icon,
errorMessage : this.model.get('errorMessage'),
successMessage: this.model.get('successMessage'),
always : this._commandAlways,
context : this
});
commandPromise.fail(function (options) {
if (options.readyState === 0 || options.status === 0) {
return;
}
if (self.model.get('errorMessage')) {
Messenger.show({
message: self.model.get('errorMessage'),
type : 'error'
});
}
if (self.model.get('onError')) {
if (!self.model.ownerContext) {
throw 'ownerContext must be set.';
}
self.model.get('onError').call(self.model.ownerContext);
}
});
commandPromise.always(function () {
if (!self.isClosed) {
self.$el.removeClass('disabled');
self.ui.icon.removeClass('icon-spinner icon-spin');
self.idle = true;
}
});
if (self.model.get('always')) {
if (!self.model.ownerContext) {
throw 'ownerContext must be set.';
}
self.model.get('always').call(self.model.ownerContext);
}
}
},
@ -133,8 +83,13 @@ define(
if (callback) {
callback.call(this.model.ownerContext);
}
}
},
_commandAlways: function () {
if (!this.isClosed) {
this.idle = true;
}
}
});
});

View file

@ -30,10 +30,8 @@ define(
this.left = options.left;
this.right = options.right;
this.toolbarContext = options.context;
},
onShow: function () {
if (this.left) {
_.each(this.left, this._showToolbarLeft, this);
@ -51,7 +49,6 @@ define(
this._showToolbar(element, index, 'right');
},
_showToolbar: function (buttonGroup, index, position) {
var groupCollection = new ButtonCollection();