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,570 @@
/**
* 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 dialog module enables the display of message boxes, custom modal dialogs and other overlays or slide-out UI abstractions. Dialogs are constructed by the composition system which interacts with a user defined dialog context. The dialog module enforced the activator lifecycle.
* @module dialog
* @requires system
* @requires app
* @requires composition
* @requires activator
* @requires viewEngine
* @requires jquery
* @requires knockout
*/
define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/activator', 'durandal/viewEngine', 'jquery', 'knockout'], function (system, app, composition, activator, viewEngine, $, ko) {
var contexts = {},
dialogCount = ko.observable(0),
dialog;
/**
* Models a message box's message, title and options.
* @class MessageBox
*/
var MessageBox = function (message, title, options, autoclose, settings) {
this.message = message;
this.title = title || MessageBox.defaultTitle;
this.options = options || MessageBox.defaultOptions;
this.autoclose = autoclose || false;
this.settings = $.extend({}, MessageBox.defaultSettings, settings);
};
/**
* Selects an option and closes the message box, returning the selected option through the dialog system's promise.
* @method selectOption
* @param {string} dialogResult The result to select.
*/
MessageBox.prototype.selectOption = function (dialogResult) {
dialog.close(this, dialogResult);
};
/**
* Provides the view to the composition system.
* @method getView
* @return {DOMElement} The view of the message box.
*/
MessageBox.prototype.getView = function () {
return viewEngine.processMarkup(MessageBox.defaultViewMarkup);
};
/**
* Configures a custom view to use when displaying message boxes.
* @method setViewUrl
* @param {string} viewUrl The view url relative to the base url which the view locator will use to find the message box's view.
* @static
*/
MessageBox.setViewUrl = function (viewUrl) {
delete MessageBox.prototype.getView;
MessageBox.prototype.viewUrl = viewUrl;
};
/**
* The title to be used for the message box if one is not provided.
* @property {string} defaultTitle
* @default Application
* @static
*/
MessageBox.defaultTitle = app.title || 'Application';
/**
* The options to display in the message box if none are specified.
* @property {string[]} defaultOptions
* @default ['Ok']
* @static
*/
MessageBox.defaultOptions = ['Ok'];
MessageBox.defaultSettings = { buttonClass: "btn btn-default", primaryButtonClass: "btn-primary autofocus", secondaryButtonClass: "", "class": "modal-content messageBox", style: null };
/**
* Sets the classes and styles used throughout the message box markup.
* @method setDefaults
* @param {object} settings A settings object containing the following optional properties: buttonClass, primaryButtonClass, secondaryButtonClass, class, style.
*/
MessageBox.setDefaults = function (settings) {
$.extend(MessageBox.defaultSettings, settings);
};
MessageBox.prototype.getButtonClass = function ($index) {
var c = "";
if (this.settings) {
if (this.settings.buttonClass) {
c = this.settings.buttonClass;
}
if ($index() === 0 && this.settings.primaryButtonClass) {
if (c.length > 0) {
c += " ";
}
c += this.settings.primaryButtonClass;
}
if ($index() > 0 && this.settings.secondaryButtonClass) {
if (c.length > 0) {
c += " ";
}
c += this.settings.secondaryButtonClass;
}
}
return c;
};
MessageBox.prototype.getClass = function () {
if (this.settings) {
return this.settings["class"];
}
return "messageBox";
};
MessageBox.prototype.getStyle = function () {
if (this.settings) {
return this.settings.style;
}
return null;
};
MessageBox.prototype.getButtonText = function (stringOrObject) {
var t = $.type(stringOrObject);
if (t === "string") {
return stringOrObject;
}
else if (t === "object") {
if ($.type(stringOrObject.text) === "string") {
return stringOrObject.text;
} else {
system.error('The object for a MessageBox button does not have a text property that is a string.');
return null;
}
}
system.error('Object for a MessageBox button is not a string or object but ' + t + '.');
return null;
};
MessageBox.prototype.getButtonValue = function (stringOrObject) {
var t = $.type(stringOrObject);
if (t === "string") {
return stringOrObject;
}
else if (t === "object") {
if ($.type(stringOrObject.text) === "undefined") {
system.error('The object for a MessageBox button does not have a value property defined.');
return null;
} else {
return stringOrObject.value;
}
}
system.error('Object for a MessageBox button is not a string or object but ' + t + '.');
return null;
};
/**
* The markup for the message box's view.
* @property {string} defaultViewMarkup
* @static
*/
MessageBox.defaultViewMarkup = [
'<div data-view="plugins/messageBox" data-bind="css: getClass(), style: getStyle()">',
'<div class="modal-header">',
'<h3 data-bind="html: title"></h3>',
'</div>',
'<div class="modal-body">',
'<p class="message" data-bind="html: message"></p>',
'</div>',
'<div class="modal-footer">',
'<!-- ko foreach: options -->',
'<button data-bind="click: function () { $parent.selectOption($parent.getButtonValue($data)); }, text: $parent.getButtonText($data), css: $parent.getButtonClass($index)"></button>',
'<!-- /ko -->',
'<div style="clear:both;"></div>',
'</div>',
'</div>'
].join('\n');
function ensureDialogInstance(objOrModuleId) {
return system.defer(function (dfd) {
if (system.isString(objOrModuleId)) {
system.acquire(objOrModuleId).then(function (module) {
dfd.resolve(system.resolveObject(module));
}).fail(function (err) {
system.error('Failed to load dialog module (' + objOrModuleId + '). Details: ' + err.message);
});
} else {
dfd.resolve(objOrModuleId);
}
}).promise();
}
/**
* @class DialogModule
* @static
*/
dialog = {
/**
* The constructor function used to create message boxes.
* @property {MessageBox} MessageBox
*/
MessageBox: MessageBox,
/**
* The css zIndex that the last dialog was displayed at.
* @property {number} currentZIndex
*/
currentZIndex: 1050,
/**
* Gets the next css zIndex at which a dialog should be displayed.
* @method getNextZIndex
* @return {number} The next usable zIndex.
*/
getNextZIndex: function () {
return ++this.currentZIndex;
},
/**
* Determines whether or not there are any dialogs open.
* @method isOpen
* @return {boolean} True if a dialog is open. false otherwise.
*/
isOpen: ko.computed(function() {
return dialogCount() > 0;
}),
/**
* Gets the dialog context by name or returns the default context if no name is specified.
* @method getContext
* @param {string} [name] The name of the context to retrieve.
* @return {DialogContext} True context.
*/
getContext: function (name) {
return contexts[name || 'default'];
},
/**
* Adds (or replaces) a dialog context.
* @method addContext
* @param {string} name The name of the context to add.
* @param {DialogContext} dialogContext The context to add.
*/
addContext: function (name, dialogContext) {
dialogContext.name = name;
contexts[name] = dialogContext;
var helperName = 'show' + name.substr(0, 1).toUpperCase() + name.substr(1);
this[helperName] = function (obj, activationData) {
return this.show(obj, activationData, name);
};
},
createCompositionSettings: function (obj, dialogContext) {
var settings = {
model: obj,
activate: false,
transition: false
};
if (dialogContext.binding) {
settings.binding = dialogContext.binding;
}
if (dialogContext.attached) {
settings.attached = dialogContext.attached;
}
if (dialogContext.compositionComplete) {
settings.compositionComplete = dialogContext.compositionComplete;
}
return settings;
},
/**
* Gets the dialog model that is associated with the specified object.
* @method getDialog
* @param {object} obj The object for whom to retrieve the dialog.
* @return {Dialog} The dialog model.
*/
getDialog: function (obj) {
if (obj) {
return obj.__dialog__;
}
return undefined;
},
/**
* Closes the dialog associated with the specified object.
* @method close
* @param {object} obj The object whose dialog should be closed.
* @param {object} results* The results to return back to the dialog caller after closing.
*/
close: function (obj) {
var theDialog = this.getDialog(obj);
if (theDialog) {
var rest = Array.prototype.slice.call(arguments, 1);
theDialog.close.apply(theDialog, rest);
}
},
/**
* Shows a dialog.
* @method show
* @param {object|string} obj The object (or moduleId) to display as a dialog.
* @param {object} [activationData] The data that should be passed to the object upon activation.
* @param {string} [context] The name of the dialog context to use. Uses the default context if none is specified.
* @return {Promise} A promise that resolves when the dialog is closed and returns any data passed at the time of closing.
*/
show: function (obj, activationData, context) {
var that = this;
var dialogContext = contexts[context || 'default'];
return system.defer(function (dfd) {
ensureDialogInstance(obj).then(function (instance) {
var dialogActivator = activator.create();
dialogActivator.activateItem(instance, activationData).then(function (success) {
if (success) {
var theDialog = instance.__dialog__ = {
owner: instance,
context: dialogContext,
activator: dialogActivator,
close: function () {
var args = arguments;
dialogActivator.deactivateItem(instance, true).then(function (closeSuccess) {
if (closeSuccess) {
dialogCount(dialogCount() - 1);
dialogContext.removeHost(theDialog);
delete instance.__dialog__;
if (args.length === 0) {
dfd.resolve();
} else if (args.length === 1) {
dfd.resolve(args[0]);
} else {
dfd.resolve.apply(dfd, args);
}
}
});
}
};
theDialog.settings = that.createCompositionSettings(instance, dialogContext);
dialogContext.addHost(theDialog);
dialogCount(dialogCount() + 1);
composition.compose(theDialog.host, theDialog.settings);
} else {
dfd.resolve(false);
}
});
});
}).promise();
},
/**
* Shows a message box.
* @method showMessage
* @param {string} message The message to display in the dialog.
* @param {string} [title] The title message.
* @param {string[]} [options] The options to provide to the user.
* @param {boolean} [autoclose] Automatically close the the message box when clicking outside?
* @param {Object} [settings] Custom settings for this instance of the messsage box, used to change classes and styles.
* @return {Promise} A promise that resolves when the message box is closed and returns the selected option.
*/
showMessage: function (message, title, options, autoclose, settings) {
if (system.isString(this.MessageBox)) {
return dialog.show(this.MessageBox, [
message,
title || MessageBox.defaultTitle,
options || MessageBox.defaultOptions,
autoclose || false,
settings || {}
]);
}
return dialog.show(new this.MessageBox(message, title, options, autoclose, settings));
},
/**
* Installs this module into Durandal; called by the framework. Adds `app.showDialog` and `app.showMessage` convenience methods.
* @method install
* @param {object} [config] Add a `messageBox` property to supply a custom message box constructor. Add a `messageBoxView` property to supply custom view markup for the built-in message box. You can also use messageBoxViewUrl to specify the view url.
*/
install: function (config) {
app.showDialog = function (obj, activationData, context) {
return dialog.show(obj, activationData, context);
};
app.closeDialog = function () {
return dialog.close.apply(dialog, arguments);
};
app.showMessage = function (message, title, options, autoclose, settings) {
return dialog.showMessage(message, title, options, autoclose, settings);
};
if (config.messageBox) {
dialog.MessageBox = config.messageBox;
}
if (config.messageBoxView) {
dialog.MessageBox.prototype.getView = function () {
return viewEngine.processMarkup(config.messageBoxView);
};
}
if (config.messageBoxViewUrl) {
dialog.MessageBox.setViewUrl(config.messageBoxViewUrl);
}
}
};
/**
* @class DialogContext
*/
dialog.addContext('default', {
blockoutOpacity: 0.2,
removeDelay: 200,
/**
* In this function, you are expected to add a DOM element to the tree which will serve as the "host" for the modal's composed view. You must add a property called host to the modalWindow object which references the dom element. It is this host which is passed to the composition module.
* @method addHost
* @param {Dialog} theDialog The dialog model.
*/
addHost: function (theDialog) {
var body = $('body');
var blockout = $('<div class="modalBlockout"></div>')
.css({ 'z-index': dialog.getNextZIndex(), 'opacity': this.blockoutOpacity })
.appendTo(body);
var host = $('<div class="modalHost"></div>')
.css({ 'z-index': dialog.getNextZIndex() })
.appendTo(body);
theDialog.host = host.get(0);
theDialog.blockout = blockout.get(0);
if (!dialog.isOpen()) {
theDialog.oldBodyMarginRight = body.css("margin-right");
theDialog.oldInlineMarginRight = body.get(0).style.marginRight;
var html = $("html");
var oldBodyOuterWidth = body.outerWidth(true);
var oldScrollTop = html.scrollTop();
$("html").css("overflow-y", "hidden");
var newBodyOuterWidth = $("body").outerWidth(true);
body.css("margin-right", (newBodyOuterWidth - oldBodyOuterWidth + parseInt(theDialog.oldBodyMarginRight, 10)) + "px");
html.scrollTop(oldScrollTop); // necessary for Firefox
}
},
/**
* This function is expected to remove any DOM machinery associated with the specified dialog and do any other necessary cleanup.
* @method removeHost
* @param {Dialog} theDialog The dialog model.
*/
removeHost: function (theDialog) {
$(theDialog.host).css('opacity', 0);
$(theDialog.blockout).css('opacity', 0);
setTimeout(function () {
ko.removeNode(theDialog.host);
ko.removeNode(theDialog.blockout);
}, this.removeDelay);
if (!dialog.isOpen()) {
var html = $("html");
var oldScrollTop = html.scrollTop(); // necessary for Firefox.
html.css("overflow-y", "").scrollTop(oldScrollTop);
if (theDialog.oldInlineMarginRight) {
$("body").css("margin-right", theDialog.oldBodyMarginRight);
} else {
$("body").css("margin-right", '');
}
}
},
attached: function (view) {
//To prevent flickering in IE8, we set visibility to hidden first, and later restore it
$(view).css("visibility", "hidden");
},
/**
* This function is called after the modal is fully composed into the DOM, allowing your implementation to do any final modifications, such as positioning or animation. You can obtain the original dialog object by using `getDialog` on context.model.
* @method compositionComplete
* @param {DOMElement} child The dialog view.
* @param {DOMElement} parent The parent view.
* @param {object} context The composition context.
*/
compositionComplete: function (child, parent, context) {
var theDialog = dialog.getDialog(context.model);
var $child = $(child);
var loadables = $child.find("img").filter(function () {
//Remove images with known width and height
var $this = $(this);
return !(this.style.width && this.style.height) && !($this.attr("width") && $this.attr("height"));
});
$child.data("predefinedWidth", $child.get(0).style.width);
var setDialogPosition = function (childView, objDialog) {
//Setting a short timeout is need in IE8, otherwise we could do this straight away
setTimeout(function () {
var $childView = $(childView);
objDialog.context.reposition(childView);
$(objDialog.host).css('opacity', 1);
$childView.css("visibility", "visible");
$childView.find('.autofocus').first().focus();
}, 1);
};
setDialogPosition(child, theDialog);
loadables.load(function () {
setDialogPosition(child, theDialog);
});
if ($child.hasClass('autoclose') || context.model.autoclose) {
$(theDialog.blockout).click(function () {
theDialog.close();
});
}
},
/**
* This function is called to reposition the model view.
* @method reposition
* @param {DOMElement} view The dialog view.
*/
reposition: function (view) {
var $view = $(view),
$window = $(window);
//We will clear and then set width for dialogs without width set
if (!$view.data("predefinedWidth")) {
$view.css({ width: '' }); //Reset width
}
var width = $view.outerWidth(false),
height = $view.outerHeight(false),
windowHeight = $window.height() - 10, //leave at least 10 pixels free
windowWidth = $window.width() - 10, //leave at least 10 pixels free
constrainedHeight = Math.min(height, windowHeight),
constrainedWidth = Math.min(width, windowWidth);
$view.css({
'margin-top': (-constrainedHeight / 2).toString() + 'px',
'margin-left': (-constrainedWidth / 2).toString() + 'px'
});
if (height > windowHeight) {
$view.css("overflow-y", "auto").outerHeight(windowHeight);
} else {
$view.css({
"overflow-y": "",
"height": ""
});
}
if (width > windowWidth) {
$view.css("overflow-x", "auto").outerWidth(windowWidth);
} else {
$view.css("overflow-x", "");
if (!$view.data("predefinedWidth")) {
//Ensure the correct width after margin-left has been set
$view.outerWidth(constrainedWidth);
} else {
$view.css("width", $view.data("predefinedWidth"));
}
}
}
});
return dialog;
});

View File

@@ -0,0 +1,332 @@
/**
* 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.
*/
/**
* This module is based on Backbone's core history support. It abstracts away the low level details of working with browser history and url changes in order to provide a solid foundation for a router.
* @module history
* @requires system
* @requires jquery
*/
define(['durandal/system', 'jquery'], function (system, $) {
// Cached regex for stripping a leading hash/slash and trailing space.
var routeStripper = /^[#\/]|\s+$/g;
// Cached regex for stripping leading and trailing slashes.
var rootStripper = /^\/+|\/+$/g;
// Cached regex for detecting MSIE.
var isExplorer = /msie [\w.]+/;
// Cached regex for removing a trailing slash.
var trailingSlash = /\/$/;
// Update the hash location, either replacing the current entry, or adding
// a new one to the browser history.
function updateHash(location, fragment, replace) {
if (replace) {
var href = location.href.replace(/(javascript:|#).*$/, '');
if (history.history.replaceState) {
history.history.replaceState({}, document.title, href + '#' + fragment); // using history.replaceState instead of location.replace to work around chrom bug
} else {
location.replace(href + '#' + fragment);
}
} else {
// Some browsers require that `hash` contains a leading #.
location.hash = '#' + fragment;
}
};
/**
* @class HistoryModule
* @static
*/
var history = {
/**
* The setTimeout interval used when the browser does not support hash change events.
* @property {string} interval
* @default 50
*/
interval: 50,
/**
* Indicates whether or not the history module is actively tracking history.
* @property {string} active
*/
active: false
};
// Ensure that `History` can be used outside of the browser.
if (typeof window !== 'undefined') {
history.location = window.location;
history.history = window.history;
}
/**
* Gets the true hash value. Cannot use location.hash directly due to a bug in Firefox where location.hash will always be decoded.
* @method getHash
* @param {string} [window] The optional window instance
* @return {string} The hash.
*/
history.getHash = function(window) {
var match = (window || history).location.href.match(/#(.*)$/);
return match ? match[1] : '';
};
/**
* Get the cross-browser normalized URL fragment, either from the URL, the hash, or the override.
* @method getFragment
* @param {string} fragment The fragment.
* @param {boolean} forcePushState Should we force push state?
* @return {string} he fragment.
*/
history.getFragment = function(fragment, forcePushState) {
if (fragment == null) {
if (history._hasPushState || !history._wantsHashChange || forcePushState) {
fragment = history.location.pathname + history.location.search;
var root = history.root.replace(trailingSlash, '');
if (!fragment.indexOf(root)) {
fragment = fragment.substr(root.length);
}
} else {
fragment = history.getHash();
}
}
return fragment.replace(routeStripper, '');
};
/**
* Activate the hash change handling, returning `true` if the current URL matches an existing route, and `false` otherwise.
* @method activate
* @param {HistoryOptions} options.
* @return {boolean|undefined} Returns true/false from loading the url unless the silent option was selected.
*/
history.activate = function(options) {
if (history.active) {
system.error("History has already been activated.");
}
history.active = true;
// Figure out the initial configuration. Do we need an iframe?
// Is pushState desired ... is it available?
history.options = system.extend({}, { root: '/' }, history.options, options);
history.root = history.options.root;
history._wantsHashChange = history.options.hashChange !== false;
history._wantsPushState = !!history.options.pushState;
history._hasPushState = !!(history.options.pushState && history.history && history.history.pushState);
var fragment = history.getFragment();
var docMode = document.documentMode;
var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && (!docMode || docMode <= 7));
// Normalize root to always include a leading and trailing slash.
history.root = ('/' + history.root + '/').replace(rootStripper, '/');
if (oldIE && history._wantsHashChange) {
history.iframe = $('<iframe src="javascript:0" tabindex="-1" />').hide().appendTo('body')[0].contentWindow;
history.navigate(fragment, false);
}
// Depending on whether we're using pushState or hashes, and whether
// 'onhashchange' is supported, determine how we check the URL state.
if (history._hasPushState) {
$(window).on('popstate', history.checkUrl);
} else if (history._wantsHashChange && ('onhashchange' in window) && !oldIE) {
$(window).on('hashchange', history.checkUrl);
} else if (history._wantsHashChange) {
history._checkUrlInterval = setInterval(history.checkUrl, history.interval);
}
// Determine if we need to change the base url, for a pushState link
// opened by a non-pushState browser.
history.fragment = fragment;
var loc = history.location;
var atRoot = loc.pathname.replace(/[^\/]$/, '$&/') === history.root;
// Transition from hashChange to pushState or vice versa if both are requested.
if (history._wantsHashChange && history._wantsPushState) {
// If we've started off with a route from a `pushState`-enabled
// browser, but we're currently in a browser that doesn't support it...
if (!history._hasPushState && !atRoot) {
history.fragment = history.getFragment(null, true);
history.location.replace(history.root + history.location.search + '#' + history.fragment);
// Return immediately as browser will do redirect to new url
return true;
// Or if we've started out with a hash-based route, but we're currently
// in a browser where it could be `pushState`-based instead...
} else if (history._hasPushState && atRoot && loc.hash) {
this.fragment = history.getHash().replace(routeStripper, '');
this.history.replaceState({}, document.title, history.root + history.fragment + loc.search);
}
}
if (!history.options.silent) {
return history.loadUrl(options.startRoute);
}
};
/**
* Disable history, perhaps temporarily. Not useful in a real app, but possibly useful for unit testing Routers.
* @method deactivate
*/
history.deactivate = function() {
$(window).off('popstate', history.checkUrl).off('hashchange', history.checkUrl);
clearInterval(history._checkUrlInterval);
history.active = false;
};
/**
* Checks the current URL to see if it has changed, and if it has, calls `loadUrl`, normalizing across the hidden iframe.
* @method checkUrl
* @return {boolean} Returns true/false from loading the url.
*/
history.checkUrl = function() {
var current = history.getFragment();
if (current === history.fragment && history.iframe) {
current = history.getFragment(history.getHash(history.iframe));
}
if (current === history.fragment) {
return false;
}
if (history.iframe) {
history.navigate(current, false);
}
history.loadUrl();
};
/**
* Attempts to load the current URL fragment. A pass-through to options.routeHandler.
* @method loadUrl
* @return {boolean} Returns true/false from the route handler.
*/
history.loadUrl = function(fragmentOverride) {
var fragment = history.fragment = history.getFragment(fragmentOverride);
return history.options.routeHandler ?
history.options.routeHandler(fragment) :
false;
};
/**
* Save a fragment into the hash history, or replace the URL state if the
* 'replace' option is passed. You are responsible for properly URL-encoding
* the fragment in advance.
* The options object can contain `trigger: false` if you wish to not have the
* route callback be fired, or `replace: true`, if
* you wish to modify the current URL without adding an entry to the history.
* @method navigate
* @param {string} fragment The url fragment to navigate to.
* @param {object|boolean} options An options object with optional trigger and replace flags. You can also pass a boolean directly to set the trigger option. Trigger is `true` by default.
* @return {boolean} Returns true/false from loading the url.
*/
history.navigate = function(fragment, options) {
if (!history.active) {
return false;
}
if(options === undefined) {
options = {
trigger: true
};
}else if(system.isBoolean(options)) {
options = {
trigger: options
};
}
fragment = history.getFragment(fragment || '');
if (history.fragment === fragment) {
return;
}
history.fragment = fragment;
var url = history.root + fragment;
// Don't include a trailing slash on the root.
if(fragment === '' && url !== '/') {
url = url.slice(0, -1);
}
// If pushState is available, we use it to set the fragment as a real URL.
if (history._hasPushState) {
history.history[options.replace ? 'replaceState' : 'pushState']({}, document.title, url);
// If hash changes haven't been explicitly disabled, update the hash
// fragment to store history.
} else if (history._wantsHashChange) {
updateHash(history.location, fragment, options.replace);
if (history.iframe && (fragment !== history.getFragment(history.getHash(history.iframe)))) {
// Opening and closing the iframe tricks IE7 and earlier to push a
// history entry on hash-tag change. When replace is true, we don't
// want history.
if (!options.replace) {
history.iframe.document.open().close();
}
updateHash(history.iframe.location, fragment, options.replace);
}
// If you've told us that you explicitly don't want fallback hashchange-
// based history, then `navigate` becomes a page refresh.
} else {
return history.location.assign(url);
}
if (options.trigger) {
return history.loadUrl(fragment);
}
};
/**
* Navigates back in the browser history.
* @method navigateBack
*/
history.navigateBack = function() {
history.history.back();
};
/**
* @class HistoryOptions
* @static
*/
/**
* The function that will be called back when the fragment changes.
* @property {function} routeHandler
*/
/**
* The url root used to extract the fragment when using push state.
* @property {string} root
*/
/**
* Use hash change when present.
* @property {boolean} hashChange
* @default true
*/
/**
* Use push state when present.
* @property {boolean} pushState
* @default false
*/
/**
* Prevents loading of the current url when activating history.
* @property {boolean} silent
* @default false
*/
return history;
});

View File

@@ -0,0 +1,126 @@
/**
* 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.
*/
/**
* Enables common http request scenarios.
* @module http
* @requires jquery
* @requires knockout
*/
define(['jquery', 'knockout'], function ($, ko) {
/**
* @class HTTPModule
* @static
*/
return {
/**
* The name of the callback parameter to inject into jsonp requests by default.
* @property {string} callbackParam
* @default callback
*/
callbackParam: 'callback',
/**
* Converts the data to JSON.
* @method toJSON
* @param {object} data The data to convert to JSON.
* @return {string} JSON.
*/
toJSON: function(data) {
return ko.toJSON(data);
},
/**
* Makes an HTTP GET request.
* @method get
* @param {string} url The url to send the get request to.
* @param {object} [query] An optional key/value object to transform into query string parameters.
* @param {object} [headers] The data to add to the request header. It will be converted to JSON. If the data contains Knockout observables, they will be converted into normal properties before serialization.
* @return {Promise} A promise of the get response data.
*/
get: function (url, query, headers) {
return $.ajax(url, { data: query, headers: ko.toJS(headers) });
},
/**
* Makes an JSONP request.
* @method jsonp
* @param {string} url The url to send the get request to.
* @param {object} [query] An optional key/value object to transform into query string parameters.
* @param {string} [callbackParam] The name of the callback parameter the api expects (overrides the default callbackParam).
* @param {object} [headers] The data to add to the request header. It will be converted to JSON. If the data contains Knockout observables, they will be converted into normal properties before serialization.
* @return {Promise} A promise of the response data.
*/
jsonp: function (url, query, callbackParam, headers) {
if (url.indexOf('=?') == -1) {
callbackParam = callbackParam || this.callbackParam;
if (url.indexOf('?') == -1) {
url += '?';
} else {
url += '&';
}
url += callbackParam + '=?';
}
return $.ajax({
url: url,
dataType: 'jsonp',
data: query,
headers: ko.toJS(headers)
});
},
/**
* Makes an HTTP PUT request.
* @method put
* @param {string} url The url to send the put request to.
* @param {object} data The data to put. It will be converted to JSON. If the data contains Knockout observables, they will be converted into normal properties before serialization.
* @param {object} [headers] The data to add to the request header. It will be converted to JSON. If the data contains Knockout observables, they will be converted into normal properties before serialization.
* @return {Promise} A promise of the response data.
*/
put:function(url, data, headers) {
return $.ajax({
url: url,
data: this.toJSON(data),
type: 'PUT',
contentType: 'application/json',
dataType: 'json',
headers: ko.toJS(headers)
});
},
/**
* Makes an HTTP POST request.
* @method post
* @param {string} url The url to send the post request to.
* @param {object} data The data to post. It will be converted to JSON. If the data contains Knockout observables, they will be converted into normal properties before serialization.
* @param {object} [headers] The data to add to the request header. It will be converted to JSON. If the data contains Knockout observables, they will be converted into normal properties before serialization.
* @return {Promise} A promise of the response data.
*/
post: function (url, data, headers) {
return $.ajax({
url: url,
data: this.toJSON(data),
type: 'POST',
contentType: 'application/json',
dataType: 'json',
headers: ko.toJS(headers)
});
},
/**
* Makes an HTTP DELETE request.
* @method remove
* @param {string} url The url to send the delete request to.
* @param {object} [query] An optional key/value object to transform into query string parameters.
* @param {object} [headers] The data to add to the request header. It will be converted to JSON. If the data contains Knockout observables, they will be converted into normal properties before serialization.
* @return {Promise} A promise of the get response data.
*/
remove:function(url, query, headers) {
return $.ajax({
url: url,
data: query,
type: 'DELETE',
headers: ko.toJS(headers)
});
}
};
});

View File

@@ -0,0 +1,410 @@
/**
* 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.
*/
/**
* Enables automatic observability of plain javascript object for ES5 compatible browsers. Also, converts promise properties into observables that are updated when the promise resolves.
* @module observable
* @requires system
* @requires binder
* @requires knockout
*/
define(['durandal/system', 'durandal/binder', 'knockout'], function(system, binder, ko) {
var observableModule,
toString = Object.prototype.toString,
nonObservableTypes = ['[object Function]', '[object String]', '[object Boolean]', '[object Number]', '[object Date]', '[object RegExp]'],
observableArrayMethods = ['remove', 'removeAll', 'destroy', 'destroyAll', 'replace'],
arrayMethods = ['pop', 'reverse', 'sort', 'shift', 'slice'],
additiveArrayFunctions = ['push', 'unshift'],
es5Functions = ['filter', 'map', 'reduce', 'reduceRight', 'forEach', 'every', 'some'],
arrayProto = Array.prototype,
observableArrayFunctions = ko.observableArray.fn,
logConversion = false,
changeDetectionMethod = undefined,
skipPromises = false,
shouldIgnorePropertyName;
/**
* You can call observable(obj, propertyName) to get the observable function for the specified property on the object.
* @class ObservableModule
*/
if (!('getPropertyDescriptor' in Object)) {
var getOwnPropertyDescriptor = Object.getOwnPropertyDescriptor;
var getPrototypeOf = Object.getPrototypeOf;
Object['getPropertyDescriptor'] = function(o, name) {
var proto = o, descriptor;
while(proto && !(descriptor = getOwnPropertyDescriptor(proto, name))) {
proto = getPrototypeOf(proto);
}
return descriptor;
};
}
function defaultShouldIgnorePropertyName(propertyName){
var first = propertyName[0];
return first === '_' || first === '$' || (changeDetectionMethod && propertyName === changeDetectionMethod);
}
function isNode(obj) {
return !!(obj && obj.nodeType !== undefined && system.isNumber(obj.nodeType));
}
function canConvertType(value) {
if (!value || isNode(value) || value.ko === ko || value.jquery) {
return false;
}
var type = toString.call(value);
return nonObservableTypes.indexOf(type) == -1 && !(value === true || value === false);
}
function createLookup(obj) {
var value = {};
Object.defineProperty(obj, "__observable__", {
enumerable: false,
configurable: false,
writable: false,
value: value
});
return value;
}
function makeObservableArray(original, observable, hasChanged) {
var lookup = original.__observable__, notify = true;
if(lookup && lookup.__full__){
return;
}
lookup = lookup || createLookup(original);
lookup.__full__ = true;
es5Functions.forEach(function (methodName) {
observable[methodName] = function () {
return arrayProto[methodName].apply(original, arguments);
};
});
observableArrayMethods.forEach(function(methodName) {
original[methodName] = function() {
notify = false;
var methodCallResult = observableArrayFunctions[methodName].apply(observable, arguments);
notify = true;
return methodCallResult;
};
});
arrayMethods.forEach(function(methodName) {
original[methodName] = function() {
if(notify){
observable.valueWillMutate();
}
var methodCallResult = arrayProto[methodName].apply(original, arguments);
if(notify){
observable.valueHasMutated();
}
return methodCallResult;
};
});
additiveArrayFunctions.forEach(function(methodName){
original[methodName] = function() {
for (var i = 0, len = arguments.length; i < len; i++) {
convertObject(arguments[i], hasChanged);
}
if(notify){
observable.valueWillMutate();
}
var methodCallResult = arrayProto[methodName].apply(original, arguments);
if(notify){
observable.valueHasMutated();
}
return methodCallResult;
};
});
original['splice'] = function() {
for (var i = 2, len = arguments.length; i < len; i++) {
convertObject(arguments[i], hasChanged);
}
if(notify){
observable.valueWillMutate();
}
var methodCallResult = arrayProto['splice'].apply(original, arguments);
if(notify){
observable.valueHasMutated();
}
return methodCallResult;
};
for (var i = 0, len = original.length; i < len; i++) {
convertObject(original[i], hasChanged);
}
}
/**
* Converts an entire object into an observable object by re-writing its attributes using ES5 getters and setters. Attributes beginning with '_' or '$' are ignored.
* @method convertObject
* @param {object} obj The target object to convert.
*/
function convertObject(obj, hasChanged) {
var lookup, value;
if (changeDetectionMethod) {
if(obj && obj[changeDetectionMethod]) {
if (hasChanged) {
hasChanged = hasChanged.slice(0);
} else {
hasChanged = [];
}
hasChanged.push(obj[changeDetectionMethod]);
}
}
if(!canConvertType(obj)){
return;
}
lookup = obj.__observable__;
if(lookup && lookup.__full__){
return;
}
lookup = lookup || createLookup(obj);
lookup.__full__ = true;
if (system.isArray(obj)) {
var observable = ko.observableArray(obj);
makeObservableArray(obj, observable, hasChanged);
} else {
for (var propertyName in obj) {
if(shouldIgnorePropertyName(propertyName)){
continue;
}
if (!lookup[propertyName]) {
var descriptor = Object.getPropertyDescriptor(obj, propertyName);
if (descriptor && (descriptor.get || descriptor.set)) {
defineProperty(obj, propertyName, {
get:descriptor.get,
set:descriptor.set
});
} else {
value = obj[propertyName];
if(!system.isFunction(value)) {
convertProperty(obj, propertyName, value, hasChanged);
}
}
}
}
}
if(logConversion) {
system.log('Converted', obj);
}
}
function innerSetter(observable, newValue, isArray) {
//if this was originally an observableArray, then always check to see if we need to add/replace the array methods (if newValue was an entirely new array)
if (isArray) {
if (!newValue) {
//don't allow null, force to an empty array
newValue = [];
makeObservableArray(newValue, observable);
}
else if (!newValue.destroyAll) {
makeObservableArray(newValue, observable);
}
} else {
convertObject(newValue);
}
//call the update to the observable after the array as been updated.
observable(newValue);
}
/**
* Converts a normal property into an observable property using ES5 getters and setters.
* @method convertProperty
* @param {object} obj The target object on which the property to convert lives.
* @param {string} propertyName The name of the property to convert.
* @param {object} [original] The original value of the property. If not specified, it will be retrieved from the object.
* @return {KnockoutObservable} The underlying observable.
*/
function convertProperty(obj, propertyName, original, hasChanged) {
var observable,
isArray,
lookup = obj.__observable__ || createLookup(obj);
if(original === undefined){
original = obj[propertyName];
}
if (system.isArray(original)) {
observable = ko.observableArray(original);
makeObservableArray(original, observable, hasChanged);
isArray = true;
} else if (typeof original == "function") {
if(ko.isObservable(original)){
observable = original;
}else{
return null;
}
} else if(!skipPromises && system.isPromise(original)) {
observable = ko.observable();
original.then(function (result) {
if(system.isArray(result)) {
var oa = ko.observableArray(result);
makeObservableArray(result, oa, hasChanged);
result = oa;
}
observable(result);
});
} else {
observable = ko.observable(original);
convertObject(original, hasChanged);
}
if (hasChanged && hasChanged.length > 0) {
hasChanged.forEach(function (func) {
if (system.isArray(original)) {
observable.subscribe(function (arrayChanges) {
func(obj, propertyName, null, arrayChanges);
}, null, "arrayChange");
} else {
observable.subscribe(function (newValue) {
func(obj, propertyName, newValue, null);
});
}
});
}
Object.defineProperty(obj, propertyName, {
configurable: true,
enumerable: true,
get: observable,
set: ko.isWriteableObservable(observable) ? (function (newValue) {
if (newValue && system.isPromise(newValue) && !skipPromises) {
newValue.then(function (result) {
innerSetter(observable, result, system.isArray(result));
});
} else {
innerSetter(observable, newValue, isArray);
}
}) : undefined
});
lookup[propertyName] = observable;
return observable;
}
/**
* Defines a computed property using ES5 getters and setters.
* @method defineProperty
* @param {object} obj The target object on which to create the property.
* @param {string} propertyName The name of the property to define.
* @param {function|object} evaluatorOrOptions The Knockout computed function or computed options object.
* @return {KnockoutObservable} The underlying computed observable.
*/
function defineProperty(obj, propertyName, evaluatorOrOptions) {
var computedOptions = { owner: obj, deferEvaluation: true },
computed;
if (typeof evaluatorOrOptions === 'function') {
computedOptions.read = evaluatorOrOptions;
} else {
if ('value' in evaluatorOrOptions) {
system.error('For defineProperty, you must not specify a "value" for the property. You must provide a "get" function.');
}
if (typeof evaluatorOrOptions.get !== 'function' && typeof evaluatorOrOptions.read !== 'function') {
system.error('For defineProperty, the third parameter must be either an evaluator function, or an options object containing a function called "get".');
}
computedOptions.read = evaluatorOrOptions.get || evaluatorOrOptions.read;
computedOptions.write = evaluatorOrOptions.set || evaluatorOrOptions.write;
}
computed = ko.computed(computedOptions);
obj[propertyName] = computed;
return convertProperty(obj, propertyName, computed);
}
observableModule = function(obj, propertyName){
var lookup, observable, value;
if (!obj) {
return null;
}
lookup = obj.__observable__;
if(lookup){
observable = lookup[propertyName];
if(observable){
return observable;
}
}
value = obj[propertyName];
if(ko.isObservable(value)){
return value;
}
return convertProperty(obj, propertyName, value);
};
observableModule.defineProperty = defineProperty;
observableModule.convertProperty = convertProperty;
observableModule.convertObject = convertObject;
/**
* Installs the plugin into the view model binder's `beforeBind` hook so that objects are automatically converted before being bound.
* @method install
*/
observableModule.install = function(options) {
var original = binder.binding;
binder.binding = function(obj, view, instruction) {
if(instruction.applyBindings && !instruction.skipConversion){
convertObject(obj);
}
original(obj, view);
};
logConversion = options.logConversion;
if (options.changeDetection) {
changeDetectionMethod = options.changeDetection;
}
skipPromises = options.skipPromises;
shouldIgnorePropertyName = options.shouldIgnorePropertyName || defaultShouldIgnorePropertyName;
};
return observableModule;
});

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,148 @@
/**
* 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.
*/
/**
* Serializes and deserializes data to/from JSON.
* @module serializer
* @requires system
*/
define(['durandal/system'], function(system) {
/**
* @class SerializerModule
* @static
*/
return {
/**
* The name of the attribute that the serializer should use to identify an object's type.
* @property {string} typeAttribute
* @default type
*/
typeAttribute: 'type',
/**
* The amount of space to use for indentation when writing out JSON.
* @property {string|number} space
* @default undefined
*/
space:undefined,
/**
* The default replacer function used during serialization. By default properties starting with '_' or '$' are removed from the serialized object.
* @method replacer
* @param {string} key The object key to check.
* @param {object} value The object value to check.
* @return {object} The value to serialize.
*/
replacer: function(key, value) {
if(key){
var first = key[0];
if(first === '_' || first === '$'){
return undefined;
}
}
return value;
},
/**
* Serializes the object.
* @method serialize
* @param {object} object The object to serialize.
* @param {object} [settings] Settings can specify a replacer or space to override the serializer defaults.
* @return {string} The JSON string.
*/
serialize: function(object, settings) {
settings = (settings === undefined) ? {} : settings;
if(system.isString(settings) || system.isNumber(settings)) {
settings = { space: settings };
}
return JSON.stringify(object, settings.replacer || this.replacer, settings.space || this.space);
},
/**
* Gets the type id for an object instance, using the configured `typeAttribute`.
* @method getTypeId
* @param {object} object The object to serialize.
* @return {string} The type.
*/
getTypeId: function(object) {
if (object) {
return object[this.typeAttribute];
}
return undefined;
},
/**
* Maps type ids to object constructor functions. Keys are type ids and values are functions.
* @property {object} typeMap.
*/
typeMap: {},
/**
* Adds a type id/constructor function mampping to the `typeMap`.
* @method registerType
* @param {string} typeId The type id.
* @param {function} constructor The constructor.
*/
registerType: function() {
var first = arguments[0];
if (arguments.length == 1) {
var id = first[this.typeAttribute] || system.getModuleId(first);
this.typeMap[id] = first;
} else {
this.typeMap[first] = arguments[1];
}
},
/**
* The default reviver function used during deserialization. By default is detects type properties on objects and uses them to re-construct the correct object using the provided constructor mapping.
* @method reviver
* @param {string} key The attribute key.
* @param {object} value The object value associated with the key.
* @param {function} getTypeId A custom function used to get the type id from a value.
* @param {object} getConstructor A custom function used to get the constructor function associated with a type id.
* @return {object} The value.
*/
reviver: function(key, value, getTypeId, getConstructor) {
var typeId = getTypeId(value);
if (typeId) {
var ctor = getConstructor(typeId);
if (ctor) {
if (ctor.fromJSON) {
return ctor.fromJSON(value);
}
return new ctor(value);
}
}
return value;
},
/**
* Deserialize the JSON.
* @method deserialize
* @param {string} text The JSON string.
* @param {object} [settings] Settings can specify a reviver, getTypeId function or getConstructor function.
* @return {object} The deserialized object.
*/
deserialize: function(text, settings) {
var that = this;
settings = settings || {};
var getTypeId = settings.getTypeId || function(object) { return that.getTypeId(object); };
var getConstructor = settings.getConstructor || function(id) { return that.typeMap[id]; };
var reviver = settings.reviver || function(key, value) { return that.reviver(key, value, getTypeId, getConstructor); };
return JSON.parse(text, reviver);
},
/**
* Clone the object.
* @method clone
* @param {object} obj The object to clone.
* @param {object} [settings] Settings can specify any of the options allowed by the serialize or deserialize methods.
* @return {object} The new clone.
*/
clone:function(obj, settings) {
return this.deserialize(this.serialize(obj, settings), settings);
}
};
});

View File

@@ -0,0 +1,195 @@
/**
* 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.
*/
/**
* Layers the widget sugar on top of the composition system.
* @module widget
* @requires system
* @requires composition
* @requires jquery
* @requires knockout
*/
define(['durandal/system', 'durandal/composition', 'jquery', 'knockout'], function(system, composition, $, ko) {
var kindModuleMaps = {},
kindViewMaps = {},
bindableSettings = ['model', 'view', 'kind'],
widgetDataKey = 'durandal-widget-data';
function extractParts(element, settings){
var data = ko.utils.domData.get(element, widgetDataKey);
if(!data){
data = {
parts:composition.cloneNodes(ko.virtualElements.childNodes(element))
};
ko.virtualElements.emptyNode(element);
ko.utils.domData.set(element, widgetDataKey, data);
}
settings.parts = data.parts;
}
/**
* @class WidgetModule
* @static
*/
var widget = {
getSettings: function(valueAccessor) {
var settings = ko.utils.unwrapObservable(valueAccessor()) || {};
if (system.isString(settings)) {
return { kind: settings };
}
for (var attrName in settings) {
if (ko.utils.arrayIndexOf(bindableSettings, attrName) != -1) {
settings[attrName] = ko.utils.unwrapObservable(settings[attrName]);
} else {
settings[attrName] = settings[attrName];
}
}
return settings;
},
/**
* Creates a ko binding handler for the specified kind.
* @method registerKind
* @param {string} kind The kind to create a custom binding handler for.
*/
registerKind: function(kind) {
ko.bindingHandlers[kind] = {
init: function() {
return { controlsDescendantBindings: true };
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var settings = widget.getSettings(valueAccessor);
settings.kind = kind;
extractParts(element, settings);
widget.create(element, settings, bindingContext, true);
}
};
ko.virtualElements.allowedBindings[kind] = true;
composition.composeBindings.push(kind + ':');
},
/**
* Maps views and module to the kind identifier if a non-standard pattern is desired.
* @method mapKind
* @param {string} kind The kind name.
* @param {string} [viewId] The unconventional view id to map the kind to.
* @param {string} [moduleId] The unconventional module id to map the kind to.
*/
mapKind: function(kind, viewId, moduleId) {
if (viewId) {
kindViewMaps[kind] = viewId;
}
if (moduleId) {
kindModuleMaps[kind] = moduleId;
}
},
/**
* Maps a kind name to it's module id. First it looks up a custom mapped kind, then falls back to `convertKindToModulePath`.
* @method mapKindToModuleId
* @param {string} kind The kind name.
* @return {string} The module id.
*/
mapKindToModuleId: function(kind) {
return kindModuleMaps[kind] || widget.convertKindToModulePath(kind);
},
/**
* Converts a kind name to it's module path. Used to conventionally map kinds who aren't explicitly mapped through `mapKind`.
* @method convertKindToModulePath
* @param {string} kind The kind name.
* @return {string} The module path.
*/
convertKindToModulePath: function(kind) {
return 'widgets/' + kind + '/viewmodel';
},
/**
* Maps a kind name to it's view id. First it looks up a custom mapped kind, then falls back to `convertKindToViewPath`.
* @method mapKindToViewId
* @param {string} kind The kind name.
* @return {string} The view id.
*/
mapKindToViewId: function(kind) {
return kindViewMaps[kind] || widget.convertKindToViewPath(kind);
},
/**
* Converts a kind name to it's view id. Used to conventionally map kinds who aren't explicitly mapped through `mapKind`.
* @method convertKindToViewPath
* @param {string} kind The kind name.
* @return {string} The view id.
*/
convertKindToViewPath: function(kind) {
return 'widgets/' + kind + '/view';
},
createCompositionSettings: function(element, settings) {
if (!settings.model) {
settings.model = this.mapKindToModuleId(settings.kind);
}
if (!settings.view) {
settings.view = this.mapKindToViewId(settings.kind);
}
settings.preserveContext = true;
settings.activate = true;
settings.activationData = settings;
settings.mode = 'templated';
return settings;
},
/**
* Creates a widget.
* @method create
* @param {DOMElement} element The DOMElement or knockout virtual element that serves as the target element for the widget.
* @param {object} settings The widget settings.
* @param {object} [bindingContext] The current binding context.
*/
create: function(element, settings, bindingContext, fromBinding) {
if(!fromBinding){
settings = widget.getSettings(function() { return settings; }, element);
}
var compositionSettings = widget.createCompositionSettings(element, settings);
composition.compose(element, compositionSettings, bindingContext);
},
/**
* Installs the widget module by adding the widget binding handler and optionally registering kinds.
* @method install
* @param {object} config The module config. Add a `kinds` array with the names of widgets to automatically register. You can also specify a `bindingName` if you wish to use another name for the widget binding, such as "control" for example.
*/
install:function(config){
config.bindingName = config.bindingName || 'widget';
if(config.kinds){
var toRegister = config.kinds;
for(var i = 0; i < toRegister.length; i++){
widget.registerKind(toRegister[i]);
}
}
ko.bindingHandlers[config.bindingName] = {
init: function() {
return { controlsDescendantBindings: true };
},
update: function(element, valueAccessor, allBindingsAccessor, viewModel, bindingContext) {
var settings = widget.getSettings(valueAccessor);
extractParts(element, settings);
widget.create(element, settings, bindingContext, true);
}
};
composition.composeBindings.push(config.bindingName + ':');
ko.virtualElements.allowedBindings[config.bindingName] = true;
}
};
return widget;
});