This commit is contained in:
2015-04-09 07:47:57 +00:00
commit 32da2df9be
809 changed files with 119606 additions and 0 deletions

View File

@@ -0,0 +1,645 @@
/**
* Durandal 2.1.0 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved.
* Available via the MIT license.
* see: http://durandaljs.com or https://github.com/BlueSpire/Durandal for details.
*/
/**
* The activator module encapsulates all logic related to screen/component activation.
* An activator is essentially an asynchronous state machine that understands a particular state transition protocol.
* The protocol ensures that the following series of events always occur: `canDeactivate` (previous state), `canActivate` (new state), `deactivate` (previous state), `activate` (new state).
* Each of the _can_ callbacks may return a boolean, affirmative value or promise for one of those. If either of the _can_ functions yields a false result, then activation halts.
* @module activator
* @requires system
* @requires knockout
*/
define(['durandal/system', 'knockout'], function (system, ko) {
var activator;
var defaultOptions = {
canDeactivate:true
};
function ensureSettings(settings) {
if (settings == undefined) {
settings = {};
}
if (!system.isBoolean(settings.closeOnDeactivate)) {
settings.closeOnDeactivate = activator.defaults.closeOnDeactivate;
}
if (!settings.beforeActivate) {
settings.beforeActivate = activator.defaults.beforeActivate;
}
if (!settings.afterDeactivate) {
settings.afterDeactivate = activator.defaults.afterDeactivate;
}
if(!settings.affirmations){
settings.affirmations = activator.defaults.affirmations;
}
if (!settings.interpretResponse) {
settings.interpretResponse = activator.defaults.interpretResponse;
}
if (!settings.areSameItem) {
settings.areSameItem = activator.defaults.areSameItem;
}
if (!settings.findChildActivator) {
settings.findChildActivator = activator.defaults.findChildActivator;
}
return settings;
}
function invoke(target, method, data) {
if (system.isArray(data)) {
return target[method].apply(target, data);
}
return target[method](data);
}
function deactivate(item, close, settings, dfd, setter) {
if (item && item.deactivate) {
system.log('Deactivating', item);
var result;
try {
result = item.deactivate(close);
} catch(error) {
system.log('ERROR: ' + error.message, error);
dfd.resolve(false);
return;
}
if (result && result.then) {
result.then(function() {
settings.afterDeactivate(item, close, setter);
dfd.resolve(true);
}, function(reason) {
system.log(reason);
dfd.resolve(false);
});
} else {
settings.afterDeactivate(item, close, setter);
dfd.resolve(true);
}
} else {
if (item) {
settings.afterDeactivate(item, close, setter);
}
dfd.resolve(true);
}
}
function activate(newItem, activeItem, callback, activationData) {
var result;
if(newItem && newItem.activate) {
system.log('Activating', newItem);
try {
result = invoke(newItem, 'activate', activationData);
} catch(error) {
system.log('ERROR: ' + error.message, error);
callback(false);
return;
}
}
if(result && result.then) {
result.then(function() {
activeItem(newItem);
callback(true);
}, function(reason) {
system.log('ERROR: ' + reason.message, reason);
callback(false);
});
} else {
activeItem(newItem);
callback(true);
}
}
function canDeactivateItem(item, close, settings, options) {
options = system.extend({}, defaultOptions, options);
settings.lifecycleData = null;
return system.defer(function (dfd) {
function continueCanDeactivate() {
if (item && item.canDeactivate && options.canDeactivate) {
var resultOrPromise;
try {
resultOrPromise = item.canDeactivate(close);
} catch (error) {
system.log('ERROR: ' + error.message, error);
dfd.resolve(false);
return;
}
if (resultOrPromise.then) {
resultOrPromise.then(function (result) {
settings.lifecycleData = result;
dfd.resolve(settings.interpretResponse(result));
}, function (reason) {
system.log('ERROR: ' + reason.message, reason);
dfd.resolve(false);
});
} else {
settings.lifecycleData = resultOrPromise;
dfd.resolve(settings.interpretResponse(resultOrPromise));
}
} else {
dfd.resolve(true);
}
}
var childActivator = settings.findChildActivator(item);
if (childActivator) {
childActivator.canDeactivate().then(function(result) {
if (result) {
continueCanDeactivate();
} else {
dfd.resolve(false);
}
});
} else {
continueCanDeactivate();
}
}).promise();
};
function canActivateItem(newItem, activeItem, settings, activeData, newActivationData) {
settings.lifecycleData = null;
return system.defer(function (dfd) {
if (settings.areSameItem(activeItem(), newItem, activeData, newActivationData)) {
dfd.resolve(true);
return;
}
if (newItem && newItem.canActivate) {
var resultOrPromise;
try {
resultOrPromise = invoke(newItem, 'canActivate', newActivationData);
} catch (error) {
system.log('ERROR: ' + error.message, error);
dfd.resolve(false);
return;
}
if (resultOrPromise.then) {
resultOrPromise.then(function(result) {
settings.lifecycleData = result;
dfd.resolve(settings.interpretResponse(result));
}, function(reason) {
system.log('ERROR: ' + reason.message, reason);
dfd.resolve(false);
});
} else {
settings.lifecycleData = resultOrPromise;
dfd.resolve(settings.interpretResponse(resultOrPromise));
}
} else {
dfd.resolve(true);
}
}).promise();
};
/**
* An activator is a read/write computed observable that enforces the activation lifecycle whenever changing values.
* @class Activator
*/
function createActivator(initialActiveItem, settings) {
var activeItem = ko.observable(null);
var activeData;
settings = ensureSettings(settings);
var computed = ko.computed({
read: function () {
return activeItem();
},
write: function (newValue) {
computed.viaSetter = true;
computed.activateItem(newValue);
}
});
computed.__activator__ = true;
/**
* The settings for this activator.
* @property {ActivatorSettings} settings
*/
computed.settings = settings;
settings.activator = computed;
/**
* An observable which indicates whether or not the activator is currently in the process of activating an instance.
* @method isActivating
* @return {boolean}
*/
computed.isActivating = ko.observable(false);
computed.forceActiveItem = function (item) {
activeItem(item);
};
/**
* Determines whether or not the specified item can be deactivated.
* @method canDeactivateItem
* @param {object} item The item to check.
* @param {boolean} close Whether or not to check if close is possible.
* @param {object} options Options for controlling the activation process.
* @return {promise}
*/
computed.canDeactivateItem = function (item, close, options) {
return canDeactivateItem(item, close, settings, options);
};
/**
* Deactivates the specified item.
* @method deactivateItem
* @param {object} item The item to deactivate.
* @param {boolean} close Whether or not to close the item.
* @return {promise}
*/
computed.deactivateItem = function (item, close) {
return system.defer(function(dfd) {
computed.canDeactivateItem(item, close).then(function(canDeactivate) {
if (canDeactivate) {
deactivate(item, close, settings, dfd, activeItem);
} else {
computed.notifySubscribers();
dfd.resolve(false);
}
});
}).promise();
};
/**
* Determines whether or not the specified item can be activated.
* @method canActivateItem
* @param {object} item The item to check.
* @param {object} activationData Data associated with the activation.
* @return {promise}
*/
computed.canActivateItem = function (newItem, activationData) {
return canActivateItem(newItem, activeItem, settings, activeData, activationData);
};
/**
* Activates the specified item.
* @method activateItem
* @param {object} newItem The item to activate.
* @param {object} newActivationData Data associated with the activation.
* @param {object} options Options for controlling the activation process.
* @return {promise}
*/
computed.activateItem = function (newItem, newActivationData, options) {
var viaSetter = computed.viaSetter;
computed.viaSetter = false;
return system.defer(function (dfd) {
if (computed.isActivating()) {
dfd.resolve(false);
return;
}
computed.isActivating(true);
var currentItem = activeItem();
if (settings.areSameItem(currentItem, newItem, activeData, newActivationData)) {
computed.isActivating(false);
dfd.resolve(true);
return;
}
computed.canDeactivateItem(currentItem, settings.closeOnDeactivate, options).then(function (canDeactivate) {
if (canDeactivate) {
computed.canActivateItem(newItem, newActivationData).then(function (canActivate) {
if (canActivate) {
system.defer(function (dfd2) {
deactivate(currentItem, settings.closeOnDeactivate, settings, dfd2);
}).promise().then(function () {
newItem = settings.beforeActivate(newItem, newActivationData);
activate(newItem, activeItem, function (result) {
activeData = newActivationData;
computed.isActivating(false);
dfd.resolve(result);
}, newActivationData);
});
} else {
if (viaSetter) {
computed.notifySubscribers();
}
computed.isActivating(false);
dfd.resolve(false);
}
});
} else {
if (viaSetter) {
computed.notifySubscribers();
}
computed.isActivating(false);
dfd.resolve(false);
}
});
}).promise();
};
/**
* Determines whether or not the activator, in its current state, can be activated.
* @method canActivate
* @return {promise}
*/
computed.canActivate = function () {
var toCheck;
if (initialActiveItem) {
toCheck = initialActiveItem;
initialActiveItem = false;
} else {
toCheck = computed();
}
return computed.canActivateItem(toCheck);
};
/**
* Activates the activator, in its current state.
* @method activate
* @return {promise}
*/
computed.activate = function () {
var toActivate;
if (initialActiveItem) {
toActivate = initialActiveItem;
initialActiveItem = false;
} else {
toActivate = computed();
}
return computed.activateItem(toActivate);
};
/**
* Determines whether or not the activator, in its current state, can be deactivated.
* @method canDeactivate
* @return {promise}
*/
computed.canDeactivate = function (close) {
return computed.canDeactivateItem(computed(), close);
};
/**
* Deactivates the activator, in its current state.
* @method deactivate
* @return {promise}
*/
computed.deactivate = function (close) {
return computed.deactivateItem(computed(), close);
};
computed.includeIn = function (includeIn) {
includeIn.canActivate = function () {
return computed.canActivate();
};
includeIn.activate = function () {
return computed.activate();
};
includeIn.canDeactivate = function (close) {
return computed.canDeactivate(close);
};
includeIn.deactivate = function (close) {
return computed.deactivate(close);
};
};
if (settings.includeIn) {
computed.includeIn(settings.includeIn);
} else if (initialActiveItem) {
computed.activate();
}
computed.forItems = function (items) {
settings.closeOnDeactivate = false;
settings.determineNextItemToActivate = function (list, lastIndex) {
var toRemoveAt = lastIndex - 1;
if (toRemoveAt == -1 && list.length > 1) {
return list[1];
}
if (toRemoveAt > -1 && toRemoveAt < list.length - 1) {
return list[toRemoveAt];
}
return null;
};
settings.beforeActivate = function (newItem) {
var currentItem = computed();
if (!newItem) {
newItem = settings.determineNextItemToActivate(items, currentItem ? items.indexOf(currentItem) : 0);
} else {
var index = items.indexOf(newItem);
if (index == -1) {
items.push(newItem);
} else {
newItem = items()[index];
}
}
return newItem;
};
settings.afterDeactivate = function (oldItem, close) {
if (close) {
items.remove(oldItem);
}
};
var originalCanDeactivate = computed.canDeactivate;
computed.canDeactivate = function (close) {
if (close) {
return system.defer(function (dfd) {
var list = items();
var results = [];
function finish() {
for (var j = 0; j < results.length; j++) {
if (!results[j]) {
dfd.resolve(false);
return;
}
}
dfd.resolve(true);
}
for (var i = 0; i < list.length; i++) {
computed.canDeactivateItem(list[i], close).then(function (result) {
results.push(result);
if (results.length == list.length) {
finish();
}
});
}
}).promise();
} else {
return originalCanDeactivate();
}
};
var originalDeactivate = computed.deactivate;
computed.deactivate = function (close) {
if (close) {
return system.defer(function (dfd) {
var list = items();
var results = 0;
var listLength = list.length;
function doDeactivate(item) {
setTimeout(function () {
computed.deactivateItem(item, close).then(function () {
results++;
items.remove(item);
if (results == listLength) {
dfd.resolve();
}
});
}, 1);
}
for (var i = 0; i < listLength; i++) {
doDeactivate(list[i]);
}
}).promise();
} else {
return originalDeactivate();
}
};
return computed;
};
return computed;
}
/**
* @class ActivatorSettings
* @static
*/
var activatorSettings = {
/**
* The default value passed to an object's deactivate function as its close parameter.
* @property {boolean} closeOnDeactivate
* @default true
*/
closeOnDeactivate: true,
/**
* Lower-cased words which represent a truthy value.
* @property {string[]} affirmations
* @default ['yes', 'ok', 'true']
*/
affirmations: ['yes', 'ok', 'true'],
/**
* Interprets the response of a `canActivate` or `canDeactivate` call using the known affirmative values in the `affirmations` array.
* @method interpretResponse
* @param {object} value
* @return {boolean}
*/
interpretResponse: function(value) {
if(system.isObject(value)) {
value = value.can || false;
}
if(system.isString(value)) {
return ko.utils.arrayIndexOf(this.affirmations, value.toLowerCase()) !== -1;
}
return value;
},
/**
* Determines whether or not the current item and the new item are the same.
* @method areSameItem
* @param {object} currentItem
* @param {object} newItem
* @param {object} currentActivationData
* @param {object} newActivationData
* @return {boolean}
*/
areSameItem: function(currentItem, newItem, currentActivationData, newActivationData) {
return currentItem == newItem;
},
/**
* Called immediately before the new item is activated.
* @method beforeActivate
* @param {object} newItem
*/
beforeActivate: function(newItem) {
return newItem;
},
/**
* Called immediately after the old item is deactivated.
* @method afterDeactivate
* @param {object} oldItem The previous item.
* @param {boolean} close Whether or not the previous item was closed.
* @param {function} setter The activate item setter function.
*/
afterDeactivate: function(oldItem, close, setter) {
if(close && setter) {
setter(null);
}
},
findChildActivator: function(item){
return null;
}
};
/**
* @class ActivatorModule
* @static
*/
activator = {
/**
* The default settings used by activators.
* @property {ActivatorSettings} defaults
*/
defaults: activatorSettings,
/**
* Creates a new activator.
* @method create
* @param {object} [initialActiveItem] The item which should be immediately activated upon creation of the ativator.
* @param {ActivatorSettings} [settings] Per activator overrides of the default activator settings.
* @return {Activator} The created activator.
*/
create: createActivator,
/**
* Determines whether or not the provided object is an activator or not.
* @method isActivator
* @param {object} object Any object you wish to verify as an activator or not.
* @return {boolean} True if the object is an activator; false otherwise.
*/
isActivator:function(object){
return object && object.__activator__;
}
};
return activator;
});