| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645 |
- /**
- * 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;
- });
|