From 613eb67114c9dacdc68604a34f91090cd33f8b0b Mon Sep 17 00:00:00 2001 From: Rob Eisenberg Date: Sun, 8 Jun 2014 15:04:29 -0400 Subject: [PATCH] Updated to 2.1.0. --- Changes.txt | 194 +++++++++++++++++++++++++ README.md | 9 +- bower.json | 8 +- css/durandal.css | 178 +++++++++++++++++++---- js/activator.js | 186 ++++++++++++++---------- js/app.js | 40 ++++- js/binder.js | 7 +- js/composition.js | 233 +++++++++++++++++------------- js/events.js | 2 +- js/plugins/dialog.js | 289 +++++++++++++++++++++++++++---------- js/plugins/history.js | 11 +- js/plugins/http.js | 70 +++++++-- js/plugins/observable.js | 153 +++++++++++++++----- js/plugins/router.js | 266 ++++++++++++++++++++++++++++------ js/plugins/serializer.js | 12 +- js/plugins/widget.js | 2 +- js/system.js | 48 ++++-- js/transitions/entrance.js | 106 ++++++++++---- js/viewEngine.js | 51 ++++++- js/viewLocator.js | 4 +- 20 files changed, 1428 insertions(+), 441 deletions(-) create mode 100644 Changes.txt diff --git a/Changes.txt b/Changes.txt new file mode 100644 index 0000000..7cefe7f --- /dev/null +++ b/Changes.txt @@ -0,0 +1,194 @@ +2.1.0 +-Fix Issue #381: Update Durandal to use KO 3 +-Fix Issue #374: Bootstrap 3.0 & Font-Awesome Update +-Fix Issue #380: Replaced html bindings with text bindings in sample application codes +-Fix Issue #375: Potentially insecure code in Durandal example app +-Fix Issue #372: Better system.guid implementation +-Fix Issue #385: Routes are case sensitive (and should not be by default) +-Fix Issue #357: Child routers which are 3 levels deep are broken with 2.0.1 +-Fix Issue #376: returning false in canDeactivate strips queryString +-Fix Issue #354: Check for obj.prototype before reading or assigning +-Fix Issue #407: Entrance Transition Bug +-Fix Issue #337: module canActivate is not fired when module is already active +-Fix Issue #382: Allow more granular control for module comparison during activation +-Fix Issue #293: Transitions now use CSS3 animations when available. +-Fix Issue #440: Remove *details part when hash generated #440 +-Fix Issues #386 and #437: Double activate when using activator with compose binding +-Fix Issue #257: Add PUT and DELETE methods to http plugin +-Fix Issue #270: Add "as" parameter to compose binding +-Fix Issue #409: Navigate method: Only set explicitNavigation to true when trigger is true +-Fix Issue #286: Adding the ability to use a .hasChildRoutes boolean route property rather than *details +-Fix Issue #312: Improved query string to handle multiple occurences of the same key +-Fix Issue #315: Append startRoute option to router activate options +-Fix Issue #343: Support for observable route titles +-Fix Issue #355: Fix bug where IE will fail to load view due to wrong typeName +-Fix Issue #360: Added a toJSON function to the http module to allow customizing how objects are converted +-Fix Issue #405: Added Layout = null to index view for vs template and nuget starter kit +-Fix Issue #368: Added headers to http.js plugin +-Fix Issue #412/#413: Route configured as RegExp object throwing error +-Fix Issue #426: Fix bug where guardRoute cannot redirect to '' +-Fix Issue #480: DurandalRouteConfiguration.nav to be optional in Typescript definition +-Fix Issue #449: dialog's config.messageBoxView working improperly +-Fix Issue #445: Improve 'Route Not Found' message +-Fix Issue #362: Dialog improvements +-Fix Issue #452: Provide original error to system.error +-Fix Issue #459: Fixed bug in router where querystring in child routes causes Route Not Found errors +-Fix Issue #464: Fix a bug that causes routes to be relative even when in pushState +-Fix Issue #473 and 476: Querystring function should only split on first '=' +-Fix Issue #482: Support full HTML templates +-Fix Issue #486: Allow `app.title` to be an observable. +-Fix Issue #483: Renaming "module" variable +-Fix Issue #300: closeDialog convenience API +-Fix Issue #361: TS File has incorrect sig for observable.defineProperty by making the actual implementation more flexible +-Fix Issue #436, #453 and #371: Composition complete callbacks not trigged +-Fix Issue #481: Dialog improvement (adding binding callback to custom contexts) +-Fix Issue #346 and #438: various composition part pass through issues +-Fix Issue #475 and #432: Abort composition when the context is changed during activation +-Fix Issue #488/#421: TypeScript - expose durandal/system and durandal/viewEngine in module +-Fix Issue #490: Add optional change detection via the observable module. See docs for details. +-Fix Issue #494: Feature: serializer.clone +-Fix Issue #495: Maybe add a router navigation started event (added router:navigation:processing) +-Fix Issue #498 and #496: Overwriting an observable array results in one-way binding of the new array +-Fix Issue #489: Observable plugin resetting Deferred properties as undefined +-Fix Issue #366: Make Observable Tracking Non-Enumerable +-Fix Issue #501: Entrance transition bug in Chrome 34 +-Fix Issue #503: Updated showMessage overload +-Fix Issue #395: setRoot() composition skips canActivate +-Fix Issues #418, #181, #297: Various fixes to activator hierarchies and child routers +-Fix Issue #461: Router: Click binding not stripping root path +-Fix Issue #508: Composition on error override +-Fix Issue #511: Always end composition, even on error +-Fix Issue #509: Unable to set activator settings "closeOnDeactivate" property to false +-Fix Issue #512: Multi-item activator issue +-Fix Issue #356: Don't throw exception in deferred fail in canActivate +-Fix Issue #417: Added support for null routes. +-Fix Issue #500: Observable.js - Custom shouldIgnorePropertyName +-Fix Issue #505: Make observable plug-in convert existing properties +-Fix Issue #262: binding different views to the same view-model (enable default behavior of router) +-Fix Issue #430: Sometimes, negative response from CanDeactivate doesn't restore previous URL +-Fix Issue #520: Ensuring that composition show never inadvertently hides elements +-Fix Issue #514: Don't enforce viewPlugin +-Fix Issue #519: Optimize Part Replacement +-Fix Issue #513: Navigating back refreshes the page in latest chrome, when using router.navigate with {replace: true} +-Fix Issue #516: Add dynamic child hash via cwooldridge +-Fix Issue #515: Cache Parsed Views +-Fix Issue #479: Can't use template with vs2013 express +-Fix Issue #523: viewEngine$createView does not deliver promise but deferred object when returning cached element +-Fix issue #530: add console.error call to logError +-Fix Issue #531: Made dialog.isOpen computed +-Fixed a bug with activation when modules were reused. Lifecycle wasn't fully enforced. +-Added a string.trim polyfill +-Updated Bootstrap to 3.1.1 +-Updated FontAwesome to 4.0.3.1 +-Updated Knockout to 3.1.0 +-Updated RequireJS to 2.1.11 +-Updated RequireJS Text Plugin to 2.0.7 +-Updated Mimosa skeleton to 2.x +-Various fixes/extensions to the docs and readme +-Various css improvements and typographic fixes + +2.0.1 +-Fix Issue #259: Fragment doesn't include query when using push state +-Fix Issue #258: Absolute links not working when using push state +-Fix Issue #254: Bad mainConfigFile entry in the default weyland-config of the Durandal nuget package +-Fix Issue #267: Added missing generic type arguments in the Durandal.d.ts file +-Fix Issue #265: Undefined reference error when trying to remove the previous view binding instruction from the dom, which doesn't exist. +-Fix Issue #261: Improving the Mimosa starter kit by adding bower support for font awesome and durandal +-Fix Issue #271: observable.defineProperty overwrites ko +-Fix Issue #272: Add a condition on the target attribute so that when pushState is activated, there is a way to bypass the router. +-Fix Issue #275: The close declaration in Durandal's TypeScript d.ts file would not allow for passing result parameters. Fixed by adding a rest argument called results. +-Fix Issue #255: router.activate Promise never resolves with 'silent' activation +-Fix Issue #278: Fixed Route Not Found error in IE11 Preview +-Fix Issue #279: Binding handlers added through composition.addBindingHandler don't work outside of a composition +-Fix Issue #266: composition.bindAndShow overwrites the elements local display style +-Fix Issue #268: TS Definition file Error - Exported variable 'router' has or is using or is using private type 'durandal/typescript.rootRouter' +-Fix Issue #264: TS Definition missing activeInstruction().config.route +-Fix Issue #281: Infinite Loop Navigation +-Fix Issue #292: Correctly position dialog in more scnenarios +-Fix Issue #266: Router 2.0 fails to render views when cacheViews is not defined +-Fix Issue #305: Navigating from one view to another hangs the application. +-Fix Issue #310: Router Swallows errors from activate() promise +-Fix Issue #319: dialog:autofocus was being called on last instead of first tagged element +-Fix Issue #316: a Widget with no "parts" results in an exception being thrown +-Fix Issue #309: Improvements for property DurandalRouteConfiguration.nav and more consistent router ordering. +-Fix Issue #324: fix binder-spec to address beforeBind/afterBind renaming to binding/bind... +-Fix Issue #334: add events tests +-Fix Issue #323: Unit test errors on OSX +-Fix Issue #333: Changed entrance animations callbacks from complete to always +-Fix Issue #331: compositioncomplete does not get triggered +-Fix Issue #330: router canReuseCurrentActivation() function is not quite right +-Fix Issue #307: Router handling of null/undefined href is possibly reversed? +-Fix Issue #303: Multiple route are active when are base on single viewmodel +-Fix Issue #304: Heap Size growing, DOM element detached +-Fix Issue #256: [data-part]'s nested in [data-bind] elements get removed from childParts in widgets +-Fix Issue #284: Publish 1.X Docs Publicly +-Fix Issue #317: Put Samples Online +-Fix Issue #308: dialog isn't positioned correctly when is set composition.defaultTransitionName +-Fix Issue #338: Old custom almond script used +-Fix Issue #341: pushState href="" (empty string) not recognized as internal navigation +-Fix Issue #339: exceptions thrown in compositionComplete callbacks prevent other callbacks to run. (other callback may need handling as well.) +-Fix Issue #342: logical `if` flow bug in observable.js -> innerSetter +-Fix Issue #351: TypeError when redirecting to currently loaded view +-Improved ordering of compositionComplete callbacks. +-Some perf improvements in the composition engine. +-Don't include a trailing slash on the root when the router navigates. +-Fixing an improper node removal in one execution path of composition relating to rebinding views. +-Tweaking the .NET Index.cshtml so that it plays nicer when using the router in push state mode. + +2.0.0 +-Massive reorganization of the github repo. The reorg was done to cleanly separate the library, samples and starter kit source from the platform targets and to facilitate a cleaner build process with less manual "fixup" as the list of supported platforms expands. +-Durandal now expects to live along side 3rd party script libraries, outside of your application code. All samples and starter kits have been updated to use requirejs.path to setup the correct mapping, per platform. Additionally, the framework no longer assumes that plugins or transitions are under the durandal folder. The starter kits all use requirejs.path to map them to their actual location. +-Durandal no longer has any dependencies on globally defined variables. It now requires everything through the module system. This means that jQuery and Knockout must be defined in order for Durandal to function properly. The samples/starter kits all set up the proper configuration to make this happen. This allows you to keep ko/jquery in global scope by using an explicit define call, giving you the same expereience as 1.x or you can now use require.paths and shim config to keep everything out of the gloabl scope if you desire. +-There is now an official plugin model which should be configured before calling app.start. This allows plugins to load in, adding binding handlers and extending durandal APIs before your application code runs. You can pass configuration to the plugins as part of this process. Once the plugin model was in place we moved several libraries out of the core and into plugins. These include: dialog (formerly modalDialog), widget and http. Additional plugins shipping with Durandal 2.0 include: router, history, serializer and observable. Plugins which should be directly installed with the new plugin system include: router, dialog, widget and observable (optionally). +-The modalDialog module was renamed to "dialog" and changed to a plugin. The messageBox module's source and view are now part of the dialog module. Formerly, showing a modal added a "modal" property on to the shown module which could be used to close the modal and return a value. This is no longer the case. Closing and returning a value should now be done with dialog.close(viewModel, returnValue). If you need to customize the MessageBox, you can access it via dialog.MessageBox. It's view now uses text bindings instead of html bindings for better security. The dialog's default context has also been updated to take advantage of the new compositionComplete event so that sizing/positioning of the dialog occurs after all internal compositions are complete. This means that new dialog contexts should have addHost, removeHost and compositionComplete callbacks. The compositionComplete callbacks receives the view, parent view and composition context as arguments. +-The viewModelBinder was renamed to binder. +-The router has been completely re-written. It no longer has any dependencies outside of Durandal's core dependencies. (No more SammyJS) The router is built on top of a new history module which encapsulates the low-level history manipulation. This was done so that developers who have unique routing requirements can dispense with Durandal's router but not have to also re-write the history portion of the system. The new router adds a new router binding handler, child routers, better deep linking support, many bug fixes, support for not only parameterized routes, but also splats, optional parameters and query strings. It also has a better mechanism for handling unknown routes, default routes and conventional routing. The router also publishes events related to its activity and sports a simple, fluent configuration interface. +-The viewModel module was renamed to "activator" and its activator function was renamed to "create". Passing an array as activationData now results in activate being called with one argument for each array element. The areItemsSame settings callback now has four parameters: currentItem, newItem, currentActivationData, newActivationData. +-The viewEngine had some refactoring to expose parseMarkup and processMarkup. We also added ensureSingleElement and createFallbackView. +-New composition lifecycle. Without an activator: activate, binding (renamed from beforeBind), bindingComplete (renamed from afterBind), attached (renamed from viewAttached), compositionComplete, detached. With an activator: canActivate, activate, binding, bindingComplete, attached, compositionComplete, canDeactivate, deactivate, detached. The compositionComplete event fires after the entire composition that the current view model is a part of is completely finished and all nodes are in the document. This event bubbles, starting by notifying child view models first, then parents. The detached event fires whenever the module's corresponding view is removed from the DOM. Composition now supports inline views by setting mode:'inline' on the binding and supplying the view as a child element. Composition now supports templated parts by setting mode:'templated' and supplying the part overrides as child elements. Composition now supports activationData which will be passed to the module's activate function. Composition now has a new function called addBindingHandler. This allows you to create a standard knockout binding handler whose execution is delayed until the composition.current.complete event is fired. The binding callback on a VM can now return false, to cancel the applyBindings call. It can also return an object with a property called applyBindings to control this. Furthermore, the object that is returned from the binding callback is passed along to the beforeBind and afterBind hooks of the view model binder to allow plugins access to this information. Returning a promise from activate is no longer necessary. You can return one to halt binding and screen composition, but no longer need to. +-Fixed bug with widget part overrides that contain widgets with part overrides. Easier mapping of widgets via new widget apis: mapKindToModuleId/convertKindToModulePath and mapKindToViewId/convertKindToViewPath. Widgets are now almost entirely built on composition so the standard composition lifecycle applies. Widgets are now composed of a view.html and a viewmodel.js (not controller.js). Settings are no longer passed to the constructor, but into the activate fuction. +-New system module helper apis: resolveObject (can be used in TypeScript to return the viewmodel out of a module), assert, error, extend, wait, isElement, isArray, isObject, isBoolean, isArguments, isFunction, isString, isNumber, isDate and isRexExp. The acquire function's promise now correctly reports load errors through the standard promise error mechanism. Previously when passing multiple module ids to system.aqcuire, the promise would resolve with one argument per module. However, this causes problems when used with non-jQuery promises. As a result the acquire function was reworked so that if an array of ids is passed or +multiple ids are passed then the promise resolves with an array of modules. system.error is plumbed throughout the framework to surface real errors as opposed to simple logs. +-Added "fadeOnly" option to the entrance transition. Removing some bogus styles after the transition completes. +-Removed app.adaptToDevice()...which was causing lots of confusion. +-Removed the optimizer. It is now replaced by Weyland. +-New samples and starter kits available for: raw HTML, Bower, .NET (Nuget and VSIX), and Mimosa. +-Added support for Bower +-Added more knockout samples +-Better support for Q integration +-Added api docs via YUIDoc +-Lots of bug fixes +-Updated docs and conversion guide +-Official TypeScript definition file + +1.2.0 + +-Fixed bugs in .NET's optimizer. Added a 'loader' options to specify 'almond' or 'require'. +-Prevent popping when transitioning with the 'entrance' transition. +-Passing activation data through the puggable beforeActivate function of the activator. +-Added throwOnErrors to the view model binder. Errors will be thrown instead of logged when true. +-Major fix in widgets which prevented proper databinding and broken/excessive rendering. +-Improved settings construction for composition and widget binding handlers. +-Upped the starting z index for modals to play better with bootstrap. +-Added beforeBind and afterBind hooks to the view model binder. +-Fixed the .NET optimizer config file generation for the 'require' loader option. +-View engine now removes empty text nodes when parsing views. +-Fixed a bug in the router which cuased non-hashed routes to malfunction. +-Added a guardRoute hook to the router to allow, deny or redirect based on the route. +-Fixed bugs in the implementation of app.adaptToDevice +-Added a missing call to the deferred's promise() function in the view model module which caused issues with Q. +-Made all the Durandal samples into a single app. +-Switched Durandal over to dependency array module syntax to enable easier use with other AMD loaders. +-Added hooks for the Dojo AMD loader. +-Router navigateTo is only used for defined routes now. +-Updated the .NET command line parser dependency to the latest version and fixed breaking changes. +-Enabled modal dialogs to return multiple values to a promise. +-Implemented route queuing for the router. +-More unit tests added. +-Fixed some invalid html generation in the view engine. +-Styled the widget demo. +-Fixed transitions so that they run even if there is no new child. +-Router now uses route.caption for document title. +-Router navigateTo now accepts a second parameter of 'skip' to skip the route handler or 'replace' to replace the url. +-Removed the app module's startup dependency on the message box module. +-Removed the setView hook that the view model binder called. Replaced it with beforeBind and afterBind hooks. \ No newline at end of file diff --git a/README.md b/README.md index 85411c2..69a26cb 100644 --- a/README.md +++ b/README.md @@ -21,13 +21,16 @@ Durandal is a cross-device, cross-platform client framework written in JavaScrip ## Documentation -All the documentation is located on [the official site](http://durandaljs.com/), so have a look there for help on how to [get started](http://durandaljs.com/pages/get-started/), [read tutorials](http://durandaljs.com/pages/docs/), [view sample descriptions](http://durandaljs.com/documentation/Understanding-the-Samples/) and peruse the module reference docs. -If you want to keep up to date with the activity that is happening on the master branch, you can [subscribe to the commit feed](https://github.com/BlueSpire/durandal/commits/master.atom). +All the documentation is located on [the official site](http://durandaljs.com/), so have a look there for help on how to [get started](http://durandaljs.com/get-started.html), [read tutorials](http://durandaljs.com/docs.html), [view sample descriptions](http://durandaljs.com/documentation/Understanding-the-Samples.html) and peruse the module reference docs. ## Community & Support Need help with something that the docs aren't providing an answer to? -Visit our [google group](https://groups.google.com/forum/?fromgroups#!forum/durandaljs) and join in the conversation. We also provide [full commercial support](http://durandaljs.com/pages/support/) no matter how large or small your team is. Additionally, we offer both [on-site and remote training](http://durandaljs.com/pages/training/). +Visit our [google group](https://groups.google.com/forum/?fromgroups#!forum/durandaljs) and join in the conversation. + +## Contributing + +We'd love for you to contribute to the Durandal project! If you are interested, please have a read through our [contributing](/CONTRIBUTING.md) guide. ## License diff --git a/bower.json b/bower.json index d9dcaff..f0d9ec9 100644 --- a/bower.json +++ b/bower.json @@ -1,10 +1,10 @@ { "name": "Durandal", - "version": "2.0.1", + "version": "2.1.0", "dependencies": { "jquery":"1.9.1", - "requirejs":"2.1.8", - "requirejs-text":"2.0.3", - "knockout.js":"2.3.0" + "requirejs":"2.1.11", + "requirejs-text":"2.0.7", + "knockout.js":"3.1.0" } } \ No newline at end of file diff --git a/css/durandal.css b/css/durandal.css index 82aeab4..50f578c 100644 --- a/css/durandal.css +++ b/css/durandal.css @@ -1,5 +1,5 @@ /*! - * Durandal 2.0.0 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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 */ @@ -12,15 +12,15 @@ height: 100%; background: black; opacity: 0; - + pointer-events: auto; - + -webkit-backface-visibility: hidden; - - -webkit-transition: opacity 0.1s linear; - -moz-transition: opacity 0.1s linear; - -o-transition: opacity 0.1s linear; - transition: opacity 0.1s linear; + + -webkit-transition: opacity 0.1s linear; + -moz-transition: opacity 0.1s linear; + -o-transition: opacity 0.1s linear; + transition: opacity 0.1s linear; } .modalHost { @@ -28,29 +28,16 @@ left: 50%; position: fixed; opacity: 0; - + -webkit-backface-visibility: hidden; - -webkit-transition: opacity 0.1s linear; - -moz-transition: opacity 0.1s linear; - -o-transition: opacity 0.1s linear; - transition: opacity 0.1s linear; + -webkit-transition: opacity 0.1s linear; + -moz-transition: opacity 0.1s linear; + -o-transition: opacity 0.1s linear; + transition: opacity 0.1s linear; } .messageBox { - background-color: white; - border: 1px solid #999; - border: 1px solid rgba(0, 0, 0, 0.3); - -webkit-border-radius: 6px; - -moz-border-radius: 6px; - border-radius: 6px; - outline: none; - -webkit-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -moz-box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3); - -webkit-background-clip: padding-box; - -moz-background-clip: padding-box; - background-clip: padding-box; min-width: 300px; } @@ -69,3 +56,142 @@ -moz-border-radius: 6px; border-radius: 6px; } + +@-webkit-keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +@-moz-keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +@-o-keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +@keyframes fadeOut { + 0% {opacity: 1;} + 100% {opacity: 0;} +} + +.entrance-out { + -webkit-animation-fill-mode:both; + -moz-animation-fill-mode:both; + -ms-animation-fill-mode:both; + -o-animation-fill-mode:both; + animation-fill-mode:both; + -webkit-animation-duration:0.1s; + -moz-animation-duration:0.1s; + -ms-animation-duration:0.1s; + -o-animation-duration:0.1s; + animation-duration:0.1s; + -webkit-animation-name: fadeOut; + -moz-animation-name: fadeOut; + -o-animation-name: fadeOut; + animation-name: fadeOut; +} + +@-webkit-keyframes slideInRight { + 0% { + opacity: 0; + -webkit-transform: translateX(20px); + } + + 100% { + opacity: 1; + -webkit-transform: translateX(0); + } +} + +@-moz-keyframes slideInRight { + 0% { + opacity: 0; + -moz-transform: translateX(20px); + } + + 100% { + opacity: 1; + -moz-transform: translateX(0); + } +} + +@-o-keyframes slideInRight { + 0% { + opacity: 0; + -o-transform: translateX(20px); + } + + 100% { + opacity: 1; + -o-transform: translateX(0); + } +} + +@keyframes slideInRight { + 0% { + opacity: 0; + transform: translateX(20px); + } + + 100% { + opacity: 1; + transform: translateX(0); + } +} + +.entrance-in { + -webkit-animation-fill-mode:both; + -moz-animation-fill-mode:both; + -ms-animation-fill-mode:both; + -o-animation-fill-mode:both; + animation-fill-mode:both; + -webkit-animation-duration:0.5s; + -moz-animation-duration:0.5s; + -ms-animation-duration:0.5s; + -o-animation-duration:0.5s; + animation-duration:0.5s; + -webkit-animation-name: slideInRight; + -moz-animation-name: slideInRight; + -o-animation-name: slideInRight; + animation-name: slideInRight; +} + +@-webkit-keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@-moz-keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@-o-keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +@keyframes fadeIn { + 0% {opacity: 0;} + 100% {opacity: 1;} +} + +.entrance-in-fade { + -webkit-animation-fill-mode:both; + -moz-animation-fill-mode:both; + -ms-animation-fill-mode:both; + -o-animation-fill-mode:both; + animation-fill-mode:both; + -webkit-animation-duration:0.5s; + -moz-animation-duration:0.5s; + -ms-animation-duration:0.5s; + -o-animation-duration:0.5s; + animation-duration:0.5s; + -webkit-animation-name: fadeIn; + -moz-animation-name: fadeIn; + -o-animation-name: fadeIn; + animation-name: fadeIn; +} diff --git a/js/activator.js b/js/activator.js index ff1160d..1bcc912 100644 --- a/js/activator.js +++ b/js/activator.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -14,13 +14,16 @@ */ define(['durandal/system', 'knockout'], function (system, ko) { var activator; + var defaultOptions = { + canDeactivate:true + }; function ensureSettings(settings) { if (settings == undefined) { settings = {}; } - if (!settings.closeOnDeactivate) { + if (!system.isBoolean(settings.closeOnDeactivate)) { settings.closeOnDeactivate = activator.defaults.closeOnDeactivate; } @@ -44,6 +47,10 @@ define(['durandal/system', 'knockout'], function (system, ko) { settings.areSameItem = activator.defaults.areSameItem; } + if (!settings.findChildActivator) { + settings.findChildActivator = activator.defaults.findChildActivator; + } + return settings; } @@ -63,7 +70,7 @@ define(['durandal/system', 'knockout'], function (system, ko) { try { result = item.deactivate(close); } catch(error) { - system.error(error); + system.log('ERROR: ' + error.message, error); dfd.resolve(false); return; } @@ -90,77 +97,87 @@ define(['durandal/system', 'knockout'], function (system, ko) { } function activate(newItem, activeItem, callback, activationData) { - if (newItem) { - if (newItem.activate) { - system.log('Activating', newItem); + var result; - var result; - try { - result = invoke(newItem, 'activate', activationData); - } catch (error) { - system.error(error); - callback(false); - return; - } + if(newItem && newItem.activate) { + system.log('Activating', newItem); - if (result && result.then) { - result.then(function() { - activeItem(newItem); - callback(true); - }, function(reason) { - system.log(reason); - callback(false); - }); - } else { - activeItem(newItem); - callback(true); - } - } else { + 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) { + function canDeactivateItem(item, close, settings, options) { + options = system.extend({}, defaultOptions, options); settings.lifecycleData = null; return system.defer(function (dfd) { - if (item && item.canDeactivate) { - var resultOrPromise; - try { - resultOrPromise = item.canDeactivate(close); - } catch(error) { - system.error(error); - dfd.resolve(false); - return; - } - - if (resultOrPromise.then) { - resultOrPromise.then(function(result) { - settings.lifecycleData = result; - dfd.resolve(settings.interpretResponse(result)); - }, function(reason) { - system.error(reason); + 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 { - settings.lifecycleData = resultOrPromise; - dfd.resolve(settings.interpretResponse(resultOrPromise)); + dfd.resolve(true); } + } + + var childActivator = settings.findChildActivator(item); + if (childActivator) { + childActivator.canDeactivate().then(function(result) { + if (result) { + continueCanDeactivate(); + } else { + dfd.resolve(false); + } + }); } else { - dfd.resolve(true); + continueCanDeactivate(); } }).promise(); }; - function canActivateItem(newItem, activeItem, settings, activationData) { + function canActivateItem(newItem, activeItem, settings, activeData, newActivationData) { settings.lifecycleData = null; return system.defer(function (dfd) { - if (newItem == activeItem()) { + if (settings.areSameItem(activeItem(), newItem, activeData, newActivationData)) { dfd.resolve(true); return; } @@ -168,9 +185,9 @@ define(['durandal/system', 'knockout'], function (system, ko) { if (newItem && newItem.canActivate) { var resultOrPromise; try { - resultOrPromise = invoke(newItem, 'canActivate', activationData); + resultOrPromise = invoke(newItem, 'canActivate', newActivationData); } catch (error) { - system.error(error); + system.log('ERROR: ' + error.message, error); dfd.resolve(false); return; } @@ -180,7 +197,7 @@ define(['durandal/system', 'knockout'], function (system, ko) { settings.lifecycleData = result; dfd.resolve(settings.interpretResponse(result)); }, function(reason) { - system.error(reason); + system.log('ERROR: ' + reason.message, reason); dfd.resolve(false); }); } else { @@ -229,15 +246,20 @@ define(['durandal/system', 'knockout'], function (system, ko) { */ 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) { - return canDeactivateItem(item, close, settings); + computed.canDeactivateItem = function (item, close, options) { + return canDeactivateItem(item, close, settings, options); }; /** @@ -268,7 +290,7 @@ define(['durandal/system', 'knockout'], function (system, ko) { * @return {promise} */ computed.canActivateItem = function (newItem, activationData) { - return canActivateItem(newItem, activeItem, settings, activationData); + return canActivateItem(newItem, activeItem, settings, activeData, activationData); }; /** @@ -276,9 +298,10 @@ define(['durandal/system', 'knockout'], function (system, ko) { * @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) { + computed.activateItem = function (newItem, newActivationData, options) { var viaSetter = computed.viaSetter; computed.viaSetter = false; @@ -297,20 +320,20 @@ define(['durandal/system', 'knockout'], function (system, ko) { return; } - computed.canDeactivateItem(currentItem, settings.closeOnDeactivate).then(function (canDeactivate) { + 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); - }); + newItem = settings.beforeActivate(newItem, newActivationData); + activate(newItem, activeItem, function (result) { + activeData = newActivationData; + computed.isActivating(false); + dfd.resolve(result); + }, newActivationData); + }); } else { if (viaSetter) { computed.notifySubscribers(); @@ -492,13 +515,15 @@ define(['durandal/system', 'knockout'], function (system, ko) { var listLength = list.length; function doDeactivate(item) { - computed.deactivateItem(item, close).then(function () { - results++; - items.remove(item); - if (results == listLength) { - dfd.resolve(); - } - }); + 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++) { @@ -581,6 +606,9 @@ define(['durandal/system', 'knockout'], function (system, ko) { if(close && setter) { setter(null); } + }, + findChildActivator: function(item){ + return null; } }; @@ -595,12 +623,12 @@ define(['durandal/system', 'knockout'], function (system, ko) { */ 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. - */ + * 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. diff --git a/js/app.js b/js/app.js index 9e37f65..ac81a7f 100644 --- a/js/app.js +++ b/js/app.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -126,7 +126,43 @@ define(['durandal/system', 'durandal/viewEngine', 'durandal/composition', 'duran settings.model = root; } - composition.compose(hostElement, settings); + function finishComposition() { + if(settings.model) { + if (settings.model.canActivate) { + try { + var result = settings.model.canActivate(); + if (result && result.then) { + result.then(function (actualResult) { + if (actualResult) { + composition.compose(hostElement, settings); + } + }).fail(function (err) { + system.error(err); + }); + } else if (result) { + composition.compose(hostElement, settings); + } + } catch (er) { + system.error(er); + } + } else { + composition.compose(hostElement, settings); + } + } else { + composition.compose(hostElement, settings); + } + } + + if(system.isString(settings.model)) { + system.acquire(settings.model).then(function(module) { + settings.model = system.resolveObject(module); + finishComposition(); + }).fail(function(err) { + system.error('Failed to load root module (' + settings.model + '). Details: ' + err.message); + }); + } else { + finishComposition(); + } } }; diff --git a/js/binder.js b/js/binder.js index 36282e8..019a93f 100644 --- a/js/binder.js +++ b/js/binder.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -130,10 +130,11 @@ define(['durandal/system', 'knockout'], function (system, ko) { * @param {KnockoutBindingContext} bindingContext The current binding context. * @param {DOMElement} view The view to bind. * @param {object} [obj] The data to bind to, causing the creation of a child binding context if present. + * @param {string} [dataAlias] An alias for $data if present. */ - bindContext: function(bindingContext, view, obj) { + bindContext: function(bindingContext, view, obj, dataAlias) { if (obj && bindingContext) { - bindingContext = bindingContext.createChildContext(obj); + bindingContext = bindingContext.createChildContext(obj, typeof(dataAlias) === 'string' ? dataAlias : null); } return doBind(obj, view, bindingContext, obj || (bindingContext ? bindingContext.$data : null)); diff --git a/js/composition.js b/js/composition.js index 4ed850a..bcc4e68 100644 --- a/js/composition.js +++ b/js/composition.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -22,9 +22,25 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ compositionCount = 0, compositionDataKey = 'durandal-composition-data', partAttributeName = 'data-part', - bindableSettings = ['model', 'view', 'transition', 'area', 'strategy', 'activationData'], + bindableSettings = ['model', 'view', 'transition', 'area', 'strategy', 'activationData', 'onError'], visibilityKey = "durandal-visibility-data", composeBindings = ['compose:']; + + function onError(context, error, element) { + try { + if (context.onError) { + try { + context.onError(error, element); + } catch (e) { + system.error(e); + } + } else { + system.error(error); + } + } finally { + endComposition(context, element, true); + } + } function getHostState(parent) { var elements = []; @@ -53,24 +69,29 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ return state; } - function endComposition() { + function endComposition(context, element, error) { compositionCount--; - if (compositionCount === 0) { - setTimeout(function(){ - var i = compositionCompleteCallbacks.length; - - while(i--) { - try{ - compositionCompleteCallbacks[i](); - }catch(e){ - system.error(e); + if(compositionCount === 0) { + var callBacks = compositionCompleteCallbacks; + compositionCompleteCallbacks = []; + + if (!error) { + setTimeout(function () { + var i = callBacks.length; + + while (i--) { + try { + callBacks[i](); + } catch (e) { + onError(context, e, element); + } } - } - - compositionCompleteCallbacks = []; - }, 1); + }, 1); + } } + + cleanUp(context); } function cleanUp(context){ @@ -78,7 +99,7 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ delete context.viewElements; } - function tryActivate(context, successCallback, skipActivation) { + function tryActivate(context, successCallback, skipActivation, element) { if(skipActivation){ successCallback(); } else if (context.activate && context.model && context.model.activate) { @@ -93,25 +114,24 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ if(result && result.then) { result.then(successCallback, function(reason) { - system.error(reason); + onError(context, reason, element); successCallback(); }); } else if(result || result === undefined) { successCallback(); } else { - endComposition(); - cleanUp(context); + endComposition(context, element); } } catch(e){ - system.error(e); + onError(context, e, element); } } else { successCallback(); } } - function triggerAttach() { + function triggerAttach(context, element) { var context = this; if (context.activeView) { @@ -137,12 +157,12 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ try{ context.model.detached(context.child, context.parent, context); }catch(e2){ - system.error(e2); + onError(context, e2, element); } }); } }catch(e){ - system.error(e); + onError(context, e, element); } } @@ -183,11 +203,20 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ function replaceParts(context){ var parts = cloneNodes(context.parts); - var replacementParts = composition.getParts(parts, null, true); + var replacementParts = composition.getParts(parts); var standardParts = composition.getParts(context.child); for (var partId in replacementParts) { - $(standardParts[partId]).replaceWith(replacementParts[partId]); + var toReplace = standardParts[partId]; + if (!toReplace) { + toReplace = $('[data-part="' + partId + '"]', context.child).get(0); + if (!toReplace) { + system.log('Could not find part to override: ' + partId); + continue; + } + } + + toReplace.parentNode.replaceChild(replacementParts[partId], toReplace); } } @@ -209,11 +238,12 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ function hide(view) { ko.utils.domData.set(view, visibilityKey, view.style.display); - view.style.display = "none"; + view.style.display = 'none'; } function show(view) { - view.style.display = ko.utils.domData.get(view, visibilityKey); + var displayStyle = ko.utils.domData.get(view, visibilityKey); + view.style.display = displayStyle === 'none' ? 'block' : displayStyle; } function hasComposition(element){ @@ -346,7 +376,7 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ * @param {DOMElement\DOMElement[]} elements The element(s) to search for parts. * @return {object} An object keyed by part. */ - getParts: function(elements, parts, isReplacementSearch) { + getParts: function(elements, parts) { parts = parts || {}; if (!elements) { @@ -358,19 +388,16 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ } for (var i = 0, length = elements.length; i < length; i++) { - var element = elements[i]; + var element = elements[i], + id; if (element.getAttribute) { - if(!isReplacementSearch && hasComposition(element)){ - continue; - } - - var id = element.getAttribute(partAttributeName); + id = element.getAttribute(partAttributeName); if (id) { parts[id] = element; } - if(!isReplacementSearch && element.hasChildNodes()){ + if (element.hasChildNodes() && !hasComposition(element)) { composition.getParts(element.childNodes, parts); } } @@ -379,7 +406,7 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ return parts; }, cloneNodes:cloneNodes, - finalize: function (context) { + finalize: function (context, element) { if(context.transition === undefined) { context.transition = this.defaultTransitionName; } @@ -389,10 +416,9 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ ko.virtualElements.emptyNode(context.parent); } - context.triggerAttach(); - endComposition(); - cleanUp(context); - }else if (shouldTransition(context)) { + context.triggerAttach(context, element); + endComposition(context, element); + } else if (shouldTransition(context)) { var transitionModuleId = this.convertTransitionToModuleId(context.transition); system.acquire(transitionModuleId).then(function (transition) { @@ -409,15 +435,20 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ var instruction = binder.getBindingInstruction(context.activeView); if(instruction && instruction.cacheViews != undefined && !instruction.cacheViews){ ko.removeNode(context.activeView); + }else{ + hide(context.activeView); } } - context.triggerAttach(); - endComposition(); - cleanUp(context); + if (context.child) { + show(context.child); + } + + context.triggerAttach(context, element); + endComposition(context, element); }); }).fail(function(err){ - system.error('Failed to load transition (' + transitionModuleId + '). Details: ' + err.message); + onError(context, 'Failed to load transition (' + transitionModuleId + '). Details: ' + err.message, element); }); } else { if (context.child != context.activeView) { @@ -443,13 +474,13 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ } } - context.triggerAttach(); - endComposition(); - cleanUp(context); + context.triggerAttach(context, element); + endComposition(context, element); } }, - bindAndShow: function (child, context, skipActivation) { + bindAndShow: function (child, element, context, skipActivation) { context.child = child; + context.parent.__composition_context = context; if (context.cacheViews) { context.composingNewView = (ko.utils.arrayIndexOf(context.viewElements, child) == -1); @@ -458,47 +489,53 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ } tryActivate(context, function () { - if (context.binding) { - context.binding(context.child, context.parent, context); - } + if (context.parent.__composition_context == context) { + delete context.parent.__composition_context; - if (context.preserveContext && context.bindingContext) { - if (context.composingNewView) { - if(context.parts){ - replaceParts(context); - } + if (context.binding) { + context.binding(context.child, context.parent, context); + } - hide(child); - ko.virtualElements.prepend(context.parent, child); + if (context.preserveContext && context.bindingContext) { + if (context.composingNewView) { + if(context.parts){ + replaceParts(context); + } - binder.bindContext(context.bindingContext, child, context.model); - } - } else if (child) { - var modelToBind = context.model || dummyModel; - var currentModel = ko.dataFor(child); - - if (currentModel != modelToBind) { - if (!context.composingNewView) { - ko.removeNode(child); - viewEngine.createView(child.getAttribute('data-view')).then(function(recreatedView) { - composition.bindAndShow(recreatedView, context, true); - }); - return; - } + hide(child); + ko.virtualElements.prepend(context.parent, child); - if(context.parts){ - replaceParts(context); + binder.bindContext(context.bindingContext, child, context.model, context.as); } + } else if (child) { + var modelToBind = context.model || dummyModel; + var currentModel = ko.dataFor(child); + + if (currentModel != modelToBind) { + if (!context.composingNewView) { + ko.removeNode(child); + viewEngine.createView(child.getAttribute('data-view')).then(function(recreatedView) { + composition.bindAndShow(recreatedView, element, context, true); + }); + return; + } - hide(child); - ko.virtualElements.prepend(context.parent, child); + if(context.parts){ + replaceParts(context); + } + + hide(child); + ko.virtualElements.prepend(context.parent, child); - binder.bind(modelToBind, child); + binder.bind(modelToBind, child); + } } - } - composition.finalize(context); - }, skipActivation); + composition.finalize(context, element); + } else { + endComposition(context, element); + } + }, skipActivation, element); }, /** * Eecutes the default view location strategy. @@ -523,7 +560,7 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ } else { settings = { model: settings, - activate: true + activate: !activatorPresent }; } @@ -534,7 +571,7 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ if (moduleId) { settings = { model: settings, - activate: true + activate: !activatorPresent }; return settings; @@ -560,20 +597,20 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ return settings; }, - executeStrategy: function (context) { + executeStrategy: function (context, element) { context.strategy(context).then(function (child) { - composition.bindAndShow(child, context); + composition.bindAndShow(child, element, context); }); }, - inject: function (context) { + inject: function (context, element) { if (!context.model) { - this.bindAndShow(null, context); + this.bindAndShow(null, element, context); return; } if (context.view) { viewLocator.locateView(context.view, context.area, context.viewElements).then(function (child) { - composition.bindAndShow(child, context); + composition.bindAndShow(child, element, context); }); return; } @@ -585,12 +622,12 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ if (system.isString(context.strategy)) { system.acquire(context.strategy).then(function (strategy) { context.strategy = strategy; - composition.executeStrategy(context); - }).fail(function(err){ - system.error('Failed to load view strategy (' + context.strategy + '). Details: ' + err.message); + composition.executeStrategy(context, element); + }).fail(function (err) { + onError(context, 'Failed to load view strategy (' + context.strategy + '). Details: ' + err.message, element); }); } else { - this.executeStrategy(context); + this.executeStrategy(context, element); } }, /** @@ -632,24 +669,24 @@ define(['durandal/system', 'durandal/viewLocator', 'durandal/binder', 'durandal/ if (!settings.model) { if (!settings.view) { - this.bindAndShow(null, settings); + this.bindAndShow(null, element, settings); } else { settings.area = settings.area || 'partial'; settings.preserveContext = true; viewLocator.locateView(settings.view, settings.area, settings.viewElements).then(function (child) { - composition.bindAndShow(child, settings); + composition.bindAndShow(child, element, settings); }); } } else if (system.isString(settings.model)) { system.acquire(settings.model).then(function (module) { settings.model = system.resolveObject(module); - composition.inject(settings); - }).fail(function(err){ - system.error('Failed to load composed module (' + settings.model + '). Details: ' + err.message); + composition.inject(settings, element); + }).fail(function (err) { + onError(settings, 'Failed to load composed module (' + settings.model + '). Details: ' + err.message, element); }); } else { - composition.inject(settings); + composition.inject(settings, element); } } }; diff --git a/js/events.js b/js/events.js index 5b16c8d..d5afdc6 100644 --- a/js/events.js +++ b/js/events.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ diff --git a/js/plugins/dialog.js b/js/plugins/dialog.js index a28fcc8..cae3915 100644 --- a/js/plugins/dialog.js +++ b/js/plugins/dialog.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -16,17 +16,19 @@ */ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/activator', 'durandal/viewEngine', 'jquery', 'knockout'], function (system, app, composition, activator, viewEngine, $, ko) { var contexts = {}, - dialogCount = 0, + dialogCount = ko.observable(0), dialog; /** * Models a message box's message, title and options. * @class MessageBox */ - var MessageBox = function(message, title, options) { + 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); }; /** @@ -43,7 +45,7 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @method getView * @return {DOMElement} The view of the message box. */ - MessageBox.prototype.getView = function(){ + MessageBox.prototype.getView = function () { return viewEngine.processMarkup(MessageBox.defaultViewMarkup); }; @@ -53,7 +55,7 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @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){ + MessageBox.setViewUrl = function (viewUrl) { delete MessageBox.prototype.getView; MessageBox.prototype.viewUrl = viewUrl; }; @@ -67,38 +69,123 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act MessageBox.defaultTitle = app.title || 'Application'; /** - * The options to display in the message box of none are specified. + * 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 = [ - '
', + '
', '', '', - '' ].join('\n'); function ensureDialogInstance(objOrModuleId) { - return system.defer(function(dfd) { + return system.defer(function (dfd) { if (system.isString(objOrModuleId)) { system.acquire(objOrModuleId).then(function (module) { dfd.resolve(system.resolveObject(module)); - }).fail(function(err){ + }).fail(function (err) { system.error('Failed to load dialog module (' + objOrModuleId + '). Details: ' + err.message); }); } else { @@ -116,7 +203,7 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * The constructor function used to create message boxes. * @property {MessageBox} MessageBox */ - MessageBox:MessageBox, + MessageBox: MessageBox, /** * The css zIndex that the last dialog was displayed at. * @property {number} currentZIndex @@ -135,16 +222,16 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @method isOpen * @return {boolean} True if a dialog is open. false otherwise. */ - isOpen: function() { - return dialogCount > 0; - }, + 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) { + getContext: function (name) { return contexts[name || 'default']; }, /** @@ -153,7 +240,7 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @param {string} name The name of the context to add. * @param {DialogContext} dialogContext The context to add. */ - addContext: function(name, dialogContext) { + addContext: function (name, dialogContext) { dialogContext.name = name; contexts[name] = dialogContext; @@ -162,13 +249,17 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act return this.show(obj, activationData, name); }; }, - createCompositionSettings: function(obj, dialogContext) { + createCompositionSettings: function (obj, dialogContext) { var settings = { - model:obj, - activate:false, + model: obj, + activate: false, transition: false }; + if (dialogContext.binding) { + settings.binding = dialogContext.binding; + } + if (dialogContext.attached) { settings.attached = dialogContext.attached; } @@ -185,8 +276,8 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @param {object} obj The object for whom to retrieve the dialog. * @return {Dialog} The dialog model. */ - getDialog:function(obj){ - if(obj){ + getDialog: function (obj) { + if (obj) { return obj.__dialog__; } @@ -198,9 +289,9 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @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){ + close: function (obj) { var theDialog = this.getDialog(obj); - if(theDialog){ + if (theDialog) { var rest = Array.prototype.slice.call(arguments, 1); theDialog.close.apply(theDialog, rest); } @@ -213,12 +304,12 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @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) { + show: function (obj, activationData, context) { var that = this; var dialogContext = contexts[context || 'default']; - return system.defer(function(dfd) { - ensureDialogInstance(obj).then(function(instance) { + return system.defer(function (dfd) { + ensureDialogInstance(obj).then(function (instance) { var dialogActivator = activator.create(); dialogActivator.activateItem(instance, activationData).then(function (success) { @@ -231,7 +322,7 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act var args = arguments; dialogActivator.deactivateItem(instance, true).then(function (closeSuccess) { if (closeSuccess) { - dialogCount--; + dialogCount(dialogCount() - 1); dialogContext.removeHost(theDialog); delete instance.__dialog__; @@ -250,7 +341,7 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act theDialog.settings = that.createCompositionSettings(instance, dialogContext); dialogContext.addHost(theDialog); - dialogCount++; + dialogCount(dialogCount() + 1); composition.compose(theDialog.host, theDialog.settings); } else { dfd.resolve(false); @@ -265,42 +356,54 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @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){ - if(system.isString(this.MessageBox)){ + showMessage: function (message, title, options, autoclose, settings) { + if (system.isString(this.MessageBox)) { return dialog.show(this.MessageBox, [ message, title || MessageBox.defaultTitle, - options || MessageBox.defaultOptions + options || MessageBox.defaultOptions, + autoclose || false, + settings || {} ]); } - return dialog.show(new this.MessageBox(message, title, options)); + 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. + * @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) { + install: function (config) { + app.showDialog = function (obj, activationData, context) { return dialog.show(obj, activationData, context); }; - app.showMessage = function(message, title, options) { - return dialog.showMessage(message, title, options); + 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){ + if (config.messageBox) { dialog.MessageBox = config.messageBox; } - if(config.messageBoxView){ - dialog.MessageBox.prototype.getView = function(){ - return config.messageBoxView; + if (config.messageBoxView) { + dialog.MessageBox.prototype.getView = function () { + return viewEngine.processMarkup(config.messageBoxView); }; } + + if (config.messageBoxViewUrl) { + dialog.MessageBox.setViewUrl(config.messageBoxViewUrl); + } } }; @@ -308,14 +411,14 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @class DialogContext */ dialog.addContext('default', { - blockoutOpacity: .2, + 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) { + addHost: function (theDialog) { var body = $('body'); var blockout = $('
') .css({ 'z-index': dialog.getNextZIndex(), 'opacity': this.blockoutOpacity }) @@ -346,11 +449,11 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act * @method removeHost * @param {Dialog} theDialog The dialog model. */ - removeHost: function(theDialog) { + removeHost: function (theDialog) { $(theDialog.host).css('opacity', 0); $(theDialog.blockout).css('opacity', 0); - setTimeout(function() { + setTimeout(function () { ko.removeNode(theDialog.host); ko.removeNode(theDialog.blockout); }, this.removeDelay); @@ -360,7 +463,7 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act var oldScrollTop = html.scrollTop(); // necessary for Firefox. html.css("overflow-y", "").scrollTop(oldScrollTop); - if(theDialog.oldInlineMarginRight) { + if (theDialog.oldInlineMarginRight) { $("body").css("margin-right", theDialog.oldBodyMarginRight); } else { $("body").css("margin-right", ''); @@ -389,49 +492,77 @@ define(['durandal/system', 'durandal/app', 'durandal/composition', 'durandal/act $child.data("predefinedWidth", $child.get(0).style.width); - var setDialogPosition = function () { + var setDialogPosition = function (childView, objDialog) { //Setting a short timeout is need in IE8, otherwise we could do this straight away setTimeout(function () { - //We will clear and then set width for dialogs without width set - if (!$child.data("predefinedWidth")) { - $child.css({ width: '' }); //Reset width - } - var width = $child.outerWidth(false); - var height = $child.outerHeight(false); - var windowHeight = $(window).height(); - var constrainedHeight = Math.min(height, windowHeight); - - $child.css({ - 'margin-top': (-constrainedHeight / 2).toString() + 'px', - 'margin-left': (-width / 2).toString() + 'px' - }); - - if (!$child.data("predefinedWidth")) { - //Ensure the correct width after margin-left has been set - $child.outerWidth(width); - } + var $childView = $(childView); - if (height > windowHeight) { - $child.css("overflow-y", "auto"); - } else { - $child.css("overflow-y", ""); - } + objDialog.context.reposition(childView); - $(theDialog.host).css('opacity', 1); - $child.css("visibility", "visible"); + $(objDialog.host).css('opacity', 1); + $childView.css("visibility", "visible"); - $child.find('.autofocus').first().focus(); + $childView.find('.autofocus').first().focus(); }, 1); }; - setDialogPosition(); - loadables.load(setDialogPosition); + setDialogPosition(child, theDialog); + loadables.load(function () { + setDialogPosition(child, theDialog); + }); - if ($child.hasClass('autoclose')) { + 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")); + } + } } }); diff --git a/js/plugins/history.js b/js/plugins/history.js index 59fdd8e..4ba2cdd 100644 --- a/js/plugins/history.js +++ b/js/plugins/history.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -27,7 +27,12 @@ define(['durandal/system', 'jquery'], function (system, $) { function updateHash(location, fragment, replace) { if (replace) { var href = location.href.replace(/(javascript:|#).*$/, ''); - location.replace(href + '#' + fragment); + + 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; @@ -160,7 +165,7 @@ define(['durandal/system', 'jquery'], function (system, $) { } if (!history.options.silent) { - return history.loadUrl(); + return history.loadUrl(options.startRoute); } }; diff --git a/js/plugins/http.js b/js/plugins/http.js index d3d1caf..af33e4b 100644 --- a/js/plugins/http.js +++ b/js/plugins/http.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -9,7 +9,7 @@ * @requires jquery * @requires knockout */ -define(['jquery', 'knockout'], function($, ko) { +define(['jquery', 'knockout'], function ($, ko) { /** * @class HTTPModule * @static @@ -20,16 +20,26 @@ define(['jquery', 'knockout'], function($, ko) { * @property {string} callbackParam * @default callback */ - callbackParam:'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) { - return $.ajax(url, { data: query }); + get: function (url, query, headers) { + return $.ajax(url, { data: query, headers: ko.toJS(headers) }); }, /** * Makes an JSONP request. @@ -37,9 +47,10 @@ define(['jquery', 'knockout'], function($, ko) { * @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) { + jsonp: function (url, query, callbackParam, headers) { if (url.indexOf('=?') == -1) { callbackParam = callbackParam || this.callbackParam; @@ -54,8 +65,27 @@ define(['jquery', 'knockout'], function($, ko) { return $.ajax({ url: url, - dataType:'jsonp', - data:query + 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) }); }, /** @@ -63,15 +93,33 @@ define(['jquery', 'knockout'], function($, ko) { * @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) { + post: function (url, data, headers) { return $.ajax({ url: url, - data: ko.toJSON(data), + data: this.toJSON(data), type: 'POST', contentType: 'application/json', - dataType: '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) }); } }; diff --git a/js/plugins/observable.js b/js/plugins/observable.js index 35b9609..311e563 100644 --- a/js/plugins/observable.js +++ b/js/plugins/observable.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -15,20 +15,39 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind 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', 'splice'], + 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; + 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 */ - function shouldIgnorePropertyName(propertyName){ + 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 === '$'; + return first === '_' || first === '$' || (changeDetectionMethod && propertyName === changeDetectionMethod); } function isNode(obj) { @@ -45,16 +64,35 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind return nonObservableTypes.indexOf(type) == -1 && !(value === true || value === false); } - function makeObservableArray(original, observable) { + 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 || (original.__observable__ = {}); + 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; @@ -83,7 +121,7 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind additiveArrayFunctions.forEach(function(methodName){ original[methodName] = function() { for (var i = 0, len = arguments.length; i < len; i++) { - convertObject(arguments[i]); + convertObject(arguments[i], hasChanged); } if(notify){ @@ -102,7 +140,7 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind original['splice'] = function() { for (var i = 2, len = arguments.length; i < len; i++) { - convertObject(arguments[i]); + convertObject(arguments[i], hasChanged); } if(notify){ @@ -119,7 +157,7 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind }; for (var i = 0, len = original.length; i < len; i++) { - convertObject(original[i]); + convertObject(original[i], hasChanged); } } @@ -128,9 +166,20 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind * @method convertObject * @param {object} obj The target object to convert. */ - function convertObject(obj){ + 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; } @@ -141,23 +190,31 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind return; } - lookup = lookup || (obj.__observable__ = {}); + lookup = lookup || createLookup(obj); lookup.__full__ = true; if (system.isArray(obj)) { var observable = ko.observableArray(obj); - makeObservableArray(obj, observable); + makeObservableArray(obj, observable, hasChanged); } else { for (var propertyName in obj) { if(shouldIgnorePropertyName(propertyName)){ continue; } - if(!lookup[propertyName]){ - value = obj[propertyName]; - - if(!system.isFunction(value)){ - convertProperty(obj, propertyName, value); + 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); + } } } } @@ -169,24 +226,22 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind } function innerSetter(observable, newValue, isArray) { - var val; - observable(newValue); - val = observable.peek(); - //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 (!val) { + if (!newValue) { //don't allow null, force to an empty array - val = []; - observable(val); - makeObservableArray(val, observable); + newValue = []; + makeObservableArray(newValue, observable); } - else if (!val.destroyAll) { - makeObservableArray(val, observable); + else if (!newValue.destroyAll) { + makeObservableArray(newValue, observable); } } else { - convertObject(val); + convertObject(newValue); } + + //call the update to the observable after the array as been updated. + observable(newValue); } /** @@ -197,10 +252,10 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind * @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){ + function convertProperty(obj, propertyName, original, hasChanged) { var observable, isArray, - lookup = obj.__observable__ || (obj.__observable__ = {}); + lookup = obj.__observable__ || createLookup(obj); if(original === undefined){ original = obj[propertyName]; @@ -208,7 +263,7 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind if (system.isArray(original)) { observable = ko.observableArray(original); - makeObservableArray(original, observable); + makeObservableArray(original, observable, hasChanged); isArray = true; } else if (typeof original == "function") { if(ko.isObservable(original)){ @@ -216,13 +271,13 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind }else{ return null; } - } else if(system.isPromise(original)) { + } 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); + makeObservableArray(result, oa, hasChanged); result = oa; } @@ -230,7 +285,21 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind }); } else { observable = ko.observable(original); - convertObject(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, { @@ -238,7 +307,7 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind enumerable: true, get: observable, set: ko.isWriteableObservable(observable) ? (function (newValue) { - if (newValue && system.isPromise(newValue)) { + if (newValue && system.isPromise(newValue) && !skipPromises) { newValue.then(function (result) { innerSetter(observable, result, system.isArray(result)); }); @@ -271,12 +340,12 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind system.error('For defineProperty, you must not specify a "value" for the property. You must provide a "get" function.'); } - if (typeof evaluatorOrOptions.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; - computedOptions.write = evaluatorOrOptions.set; + computedOptions.read = evaluatorOrOptions.get || evaluatorOrOptions.read; + computedOptions.write = evaluatorOrOptions.set || evaluatorOrOptions.write; } computed = ko.computed(computedOptions); @@ -329,6 +398,12 @@ define(['durandal/system', 'durandal/binder', 'knockout'], function(system, bind }; logConversion = options.logConversion; + if (options.changeDetection) { + changeDetectionMethod = options.changeDetection; + } + + skipPromises = options.skipPromises; + shouldIgnorePropertyName = options.shouldIgnorePropertyName || defaultShouldIgnorePropertyName; }; return observableModule; diff --git a/js/plugins/router.js b/js/plugins/router.js index 254036e..20680d1 100644 --- a/js/plugins/router.js +++ b/js/plugins/router.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -22,6 +22,8 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event var escapeRegExp = /[\-{}\[\]+?.,\\\^$|#\s]/g; var startDeferred, rootRouter; var trailingSlash = /\/$/; + var routesAreCaseSensitive = false; + var lastUrl = '/', lastTryUrl = '/'; function routeStringToRegExp(routeString) { routeString = routeString.replace(escapeRegExp, '\\$&') @@ -31,7 +33,7 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event }) .replace(splatParam, '(.*?)'); - return new RegExp('^' + routeString + '$'); + return new RegExp('^' + routeString + '$', routesAreCaseSensitive ? undefined : 'i'); } function stripParametersFromRoute(route) { @@ -62,6 +64,14 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event return true; } + function reconstructUrl(instruction){ + if(!instruction.queryString){ + return instruction.fragment; + } + + return instruction.fragment + '?' + instruction.queryString; + } + /** * @class Router * @uses Events @@ -83,6 +93,13 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event * @param {Router} router The router. */ + /** + * Triggered when navigation begins. + * @event router:navigation:processing + * @param {object} instruction The routing instruction. + * @param {Router} router The router. + */ + /** * Triggered right before a route is activated. * @event router:route:activating @@ -188,17 +205,25 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event return false; }; - function hasChildRouter(instance) { - return instance.router && instance.router.parent == router; + activeItem.settings.findChildActivator = function(item) { + if (item && item.router && item.router.parent == router) { + return item.router.activeItem; + } + + return null; + }; + + function hasChildRouter(instance, parentRouter) { + return instance.router && instance.router.parent == parentRouter; } function setCurrentInstructionRouteIsActive(flag) { if (currentInstruction && currentInstruction.config.isActive) { - currentInstruction.config.isActive(flag) + currentInstruction.config.isActive(flag); } } - function completeNavigation(instance, instruction) { + function completeNavigation(instance, instruction, mode) { system.log('Navigation Complete', instance, instruction); var fromModuleId = system.getModuleId(currentActivation); @@ -217,12 +242,25 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event router.trigger('router:navigation:to:' + toModuleId); } - if (!hasChildRouter(instance)) { + if (!hasChildRouter(instance, router)) { router.updateDocumentTitle(instance, instruction); } + switch (mode) { + case 'rootRouter': + lastUrl = reconstructUrl(currentInstruction); + break; + case 'rootRouterWithChild': + lastTryUrl = reconstructUrl(currentInstruction); + break; + case 'lastChildRouter': + lastUrl = lastTryUrl; + break; + } + rootRouter.explicitNavigation = false; rootRouter.navigatingBack = false; + router.trigger('router:navigation:complete', instance, instruction, router); } @@ -231,9 +269,7 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event router.activeInstruction(currentInstruction); - if (currentInstruction) { - router.navigate(currentInstruction.fragment, false); - } + router.navigate(lastUrl, false); isProcessing(false); rootRouter.explicitNavigation = false; @@ -254,12 +290,33 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event rootRouter.navigatingBack = !rootRouter.explicitNavigation && currentActivation != instruction.fragment; router.trigger('router:route:activating', instance, instruction, router); - activator.activateItem(instance, instruction.params).then(function(succeeded) { + var options = { + canDeactivate: !router.parent + }; + + activator.activateItem(instance, instruction.params, options).then(function(succeeded) { if (succeeded) { var previousActivation = currentActivation; - completeNavigation(instance, instruction); + var withChild = hasChildRouter(instance, router); + var mode = ''; + + if (router.parent) { + if(!withChild) { + mode = 'lastChildRouter'; + } + } else { + if (withChild) { + mode = 'rootRouterWithChild'; + } else { + mode = 'rootRouter'; + } + } + + completeNavigation(instance, instruction, mode); + + if (withChild) { + instance.router.trigger('router:route:before-child-routes', instance, instruction, router); - if (hasChildRouter(instance)) { var fullFragment = instruction.fragment; if (instruction.queryString) { fullFragment += "?" + instruction.queryString; @@ -284,7 +341,7 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event } }).fail(function(err){ system.error(err); - });; + }); } /** @@ -296,7 +353,7 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event */ function handleGuardedRoute(activator, instance, instruction) { var resultOrPromise = router.guardRoute(instance, instruction); - if (resultOrPromise) { + if (resultOrPromise || resultOrPromise === '') { if (resultOrPromise.then) { resultOrPromise.then(function(result) { if (result) { @@ -351,16 +408,33 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event isProcessing(true); router.activeInstruction(instruction); + router.trigger('router:navigation:processing', instruction, router); if (canReuseCurrentActivation(instruction)) { - ensureActivation(activator.create(), currentActivation, instruction); + var tempActivator = activator.create(); + tempActivator.forceActiveItem(currentActivation); //enforce lifecycle without re-compose + tempActivator.settings.areSameItem = activeItem.settings.areSameItem; + tempActivator.settings.findChildActivator = activeItem.settings.findChildActivator; + ensureActivation(tempActivator, currentActivation, instruction); + } else if(!instruction.config.moduleId) { + ensureActivation(activeItem, { + viewUrl:instruction.config.viewUrl, + canReuseForRoute:function() { + return true; + } + }, instruction); } else { - system.acquire(instruction.config.moduleId).then(function(module) { - var instance = system.resolveObject(module); + system.acquire(instruction.config.moduleId).then(function(m) { + var instance = system.resolveObject(m); + + if(instruction.config.viewUrl) { + instance.viewUrl = instruction.config.viewUrl; + } + ensureActivation(activeItem, instance, instruction); - }).fail(function(err){ - system.error('Failed to load routed module (' + instruction.config.moduleId + '). Details: ' + err.message); - }); + }).fail(function(err) { + system.error('Failed to load routed module (' + instruction.config.moduleId + '). Details: ' + err.message, err); + }); } } @@ -394,10 +468,19 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event function configureRoute(config){ router.trigger('router:route:before-config', config, router); - if (!system.isRegExp(config)) { + if (!system.isRegExp(config.route)) { config.title = config.title || router.convertRouteToTitle(config.route); - config.moduleId = config.moduleId || router.convertRouteToModuleId(config.route); + + if (!config.viewUrl) { + config.moduleId = config.moduleId || router.convertRouteToModuleId(config.route); + } + config.hash = config.hash || router.convertRouteToHash(config.route); + + if (config.hasChildRoutes) { + config.route = config.route + '*childRoutes'; + } + config.routePattern = routeStringToRegExp(config.route); }else{ config.routePattern = config.route; @@ -469,8 +552,22 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event continue; } - var parts = pair.split('='); - queryObject[parts[0]] = parts[1] && decodeURIComponent(parts[1].replace(/\+/g, ' ')); + var parts = pair.split(/=(.+)?/), + key = parts[0], + value = parts[1] && decodeURIComponent(parts[1].replace(/\+/g, ' ')); + + var existing = queryObject[key]; + + if (existing) { + if (system.isArray(existing)) { + existing.push(value); + } else { + queryObject[key] = [existing, value]; + } + } + else { + queryObject[key] = value; + } } return queryObject; @@ -505,7 +602,7 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event if(router.relativeToParentRouter){ var instruction = this.parent.activeInstruction(); - coreFragment = instruction.params.join('/'); + coreFragment = queryIndex == -1 ? instruction.params.join('/') : instruction.params.slice(0, -1).join('/'); if(coreFragment && coreFragment.charAt(0) == '/'){ coreFragment = coreFragment.substr(1); @@ -528,34 +625,64 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event } } - system.log('Route Not Found'); + system.log('Route Not Found', fragment, currentInstruction); router.trigger('router:route:not-found', fragment, router); - if (currentInstruction) { - history.navigate(currentInstruction.fragment, { trigger:false, replace:true }); + if (router.parent) { + lastUrl = lastTryUrl; } + history.navigate(lastUrl, { trigger:false, replace:true }); + rootRouter.explicitNavigation = false; rootRouter.navigatingBack = false; return false; }; + var titleSubscription; + function setTitle(value) { + var appTitle = ko.unwrap(app.title); + + if (appTitle) { + document.title = value + " | " + appTitle; + } else { + document.title = value; + } + } + + // Allow observable to be used for app.title + if(ko.isObservable(app.title)) { + app.title.subscribe(function () { + var instruction = router.activeInstruction(); + var title = instruction != null ? ko.unwrap(instruction.config.title) : ''; + setTitle(title); + }); + } + /** * Updates the document title based on the activated module instance, the routing instruction and the app.title. * @method updateDocumentTitle * @param {object} instance The activated module. * @param {object} instruction The routing instruction associated with the action. It has a `config` property that references the original route mapping config. */ - router.updateDocumentTitle = function(instance, instruction) { - if (instruction.config.title) { - if (app.title) { - document.title = instruction.config.title + " | " + app.title; + router.updateDocumentTitle = function (instance, instruction) { + var appTitle = ko.unwrap(app.title), + title = instruction.config.title; + + if (titleSubscription) { + titleSubscription.dispose(); + } + + if (title) { + if (ko.isObservable(title)) { + titleSubscription = title.subscribe(setTitle); + setTitle(title()); } else { - document.title = instruction.config.title; + setTitle(title); } - } else if (app.title) { - document.title = app.title; + } else if (appTitle) { + document.title = appTitle; } }; @@ -572,12 +699,19 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event * @return {boolean} Returns true/false from loading the url. */ router.navigate = function(fragment, options) { - if(fragment && fragment.indexOf('://') != -1){ + if(fragment && fragment.indexOf('://') != -1) { window.location.href = fragment; return true; } - rootRouter.explicitNavigation = true; + if(options === undefined || (system.isBoolean(options) && options) || (system.isObject(options) && options.trigger)) { + rootRouter.explicitNavigation = true; + } + + if ((system.isBoolean(options) && !options) || (options && options.trigger != undefined && !options.trigger)) { + lastUrl = fragment; + } + return history.navigate(fragment, options); }; @@ -606,9 +740,11 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event * @return {string} The hash. */ router.convertRouteToHash = function(route) { + route = route.replace(/\*.*$/, ''); + if(router.relativeToParentRouter){ var instruction = router.parent.activeInstruction(), - hash = instruction.config.hash + '/' + route; + hash = route ? instruction.config.hash + '/' + route : instruction.config.hash; if(history._hasPushState){ hash = '/' + hash; @@ -653,10 +789,10 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event * @param {object} [config] The config for the specified route. * @chainable * @example - router.map([ - { route: '', title:'Home', moduleId: 'homeScreen', nav: true }, - { route: 'customer/:id', moduleId: 'customerDetails'} - ]); + router.map([ + { route: '', title:'Home', moduleId: 'homeScreen', nav: true }, + { route: 'customer/:id', moduleId: 'customerDetails'} + ]); */ router.map = function(route, config) { if (system.isArray(route)) { @@ -821,6 +957,29 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event } }); + if (settings.dynamicHash) { + router.on('router:route:after-config').then(function (config) { + config.routePattern = routeStringToRegExp(config.route ? settings.dynamicHash + '/' + config.route : settings.dynamicHash); + config.dynamicHash = config.dynamicHash || ko.observable(config.hash); + }); + + router.on('router:route:before-child-routes').then(function(instance, instruction, parentRouter) { + var childRouter = instance.router; + + for(var i = 0; i < childRouter.routes.length; i++) { + var route = childRouter.routes[i]; + var params = instruction.params.slice(0); + + route.hash = childRouter.convertRouteToHash(route.route) + .replace(namedParam, function(match) { + return params.length > 0 ? params.shift() : match; + }); + + route.dynamicHash(route.hash); + } + }); + } + return router; }; @@ -847,6 +1006,14 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event rootRouter.explicitNavigation = false; rootRouter.navigatingBack = false; + /** + * Makes the RegExp generated for routes case sensitive, rather than the default of case insensitive. + * @method makeRoutesCaseSensitive + */ + rootRouter.makeRoutesCaseSensitive = function(){ + routesAreCaseSensitive = true; + }; + /** * Verify that the target is the current window * @method targetIsThisWindow @@ -854,12 +1021,12 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event */ rootRouter.targetIsThisWindow = function(event) { var targetWindow = $(event.target).attr('target'); - + if (!targetWindow || targetWindow === window.name || targetWindow === '_self' || (targetWindow === 'top' && window === window.top)) { return true; } - + return false; }; @@ -881,10 +1048,12 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event while(i--){ var current = routes[i]; - current.hash = current.hash.replace('#', ''); + current.hash = current.hash.replace('#', '/'); } } + var rootStripper = rootRouter.options.root && new RegExp("^" + rootRouter.options.root + "/"); + $(document).delegate("a", 'click', function(evt){ if(history._hasPushState){ if(!evt.altKey && !evt.ctrlKey && !evt.metaKey && !evt.shiftKey && rootRouter.targetIsThisWindow(evt)){ @@ -895,6 +1064,11 @@ define(['durandal/system', 'durandal/app', 'durandal/activator', 'durandal/event if (href != null && !(href.charAt(0) === "#" || /^[a-z]+:/i.test(href))) { rootRouter.explicitNavigation = true; evt.preventDefault(); + + if (rootStripper) { + href = href.replace(rootStripper, ""); + } + history.navigate(href); } } diff --git a/js/plugins/serializer.js b/js/plugins/serializer.js index bed6332..a689927 100644 --- a/js/plugins/serializer.js +++ b/js/plugins/serializer.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -133,6 +133,16 @@ define(['durandal/system'], function(system) { 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); } }; }); diff --git a/js/plugins/widget.js b/js/plugins/widget.js index 6fc41a8..8e6621e 100644 --- a/js/plugins/widget.js +++ b/js/plugins/widget.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ diff --git a/js/system.js b/js/system.js index c8d16cd..67934d5 100644 --- a/js/system.js +++ b/js/system.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -19,6 +19,13 @@ define(['require', 'jquery'], function(require, $) { nativeIsArray = Array.isArray, slice = Array.prototype.slice; + //polyfill from https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/Trim + if (!String.prototype.trim) { + String.prototype.trim = function () { + return this.replace(/^\s+|\s+$/g, ''); + }; + } + //see http://patik.com/blog/complete-cross-browser-console-log/ // Tell IE9 to use its built-in console if (Function.prototype.bind && (typeof console === 'object' || typeof console === 'function') && typeof console.log == 'object') { @@ -84,12 +91,31 @@ define(['require', 'jquery'], function(require, $) { } catch (ignore) { } }; - var logError = function(error) { + var logError = function(error, err) { + var exception; + if(error instanceof Error){ - throw error; + exception = error; + } else { + exception = new Error(error); } + + exception.innerError = err; + + //Report the error as an error, not as a log + try { + // Modern browsers (it's only a single item, no need for argument splitting as in log() above) + if (typeof console != 'undefined' && typeof console.error == 'function') { + console.error(exception); + } + // IE8 + else if ((!Function.prototype.bind || treatAsIE8) && typeof console != 'undefined' && typeof console.error == 'object') { + Function.prototype.call.call(console.error, console, exception); + } + // IE7 and lower, and other old browsers + } catch (ignore) { } - throw new Error(error); + throw exception; }; /** @@ -101,7 +127,7 @@ define(['require', 'jquery'], function(require, $) { * Durandal's version. * @property {string} version */ - version: "2.0.1", + version: "2.1.0", /** * A noop function. * @method noop @@ -118,7 +144,7 @@ define(['require', 'jquery'], function(require, $) { return null; } - if (typeof obj == 'function') { + if (typeof obj == 'function' && obj.prototype) { return obj.prototype.__moduleId__; } @@ -139,7 +165,7 @@ define(['require', 'jquery'], function(require, $) { return; } - if (typeof obj == 'function') { + if (typeof obj == 'function' && obj.prototype) { obj.prototype.__moduleId__ = id; return; } @@ -223,9 +249,11 @@ define(['require', 'jquery'], function(require, $) { * @return {string} The guid. */ guid: function() { - return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) { - var r = Math.random() * 16 | 0, v = c == 'x' ? r : (r & 0x3 | 0x8); - return v.toString(16); + var d = new Date().getTime(); + return 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) { + var r = (d + Math.random() * 16) % 16 | 0; + d = Math.floor(d/16); + return (c == 'x' ? r : (r & 0x7 | 0x8)).toString(16); }); }, /** diff --git a/js/transitions/entrance.js b/js/transitions/entrance.js index dff6e85..345d7a3 100644 --- a/js/transitions/entrance.js +++ b/js/transitions/entrance.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -13,17 +13,52 @@ define(['durandal/system', 'durandal/composition', 'jquery'], function(system, composition, $) { var fadeOutDuration = 100; var endValues = { - marginRight: 0, - marginLeft: 0, + left: '0px', opacity: 1 }; var clearValues = { - marginLeft: '', - marginRight: '', - opacity: '', - display: '' + left: '', + top: '', + right: '', + bottom:'', + position:'', + opacity: '' }; + var isIE = navigator.userAgent.match(/Trident/) || navigator.userAgent.match(/MSIE/); + + var animation = false, + domPrefixes = 'Webkit Moz O ms Khtml'.split(' '), + elm = document.createElement('div'); + + if(elm.style.animationName !== undefined) { + animation = true; + } + + if(!animation) { + for(var i = 0; i < domPrefixes.length; i++) { + if(elm.style[domPrefixes[i] + 'AnimationName'] !== undefined) { + animation = true; + break; + } + } + } + + if(animation) { + if(isIE){ + system.log('Using CSS3/jQuery mixed animations.'); + }else{ + system.log('Using CSS3 animations.'); + } + } else { + system.log('Using jQuery animations.'); + } + + function removeAnimationClasses(ele, fadeOnly){ + ele.classList.remove(fadeOnly ? 'entrance-in-fade' : 'entrance-in'); + ele.classList.remove('entrance-out'); + } + /** * @class EntranceModule * @constructor @@ -44,34 +79,55 @@ define(['durandal/system', 'durandal/composition', 'jquery'], function(system, c $(context.activeView).fadeOut(fadeOutDuration, endTransition); } else { var duration = context.duration || 500; + var $child = $(context.child); var fadeOnly = !!context.fadeOnly; + var startValues = { + display: 'block', + opacity: 0, + position: 'absolute', + left: fadeOnly || animation ? '0px' : '20px', + right: 0, + top: 0, + bottom: 0 + }; function startTransition() { scrollIfNeeded(); context.triggerAttach(); - var startValues = { - marginLeft: fadeOnly ? '0' : '20px', - marginRight: fadeOnly ? '0' : '-20px', - opacity: 0, - display: 'block' - }; - - var $child = $(context.child); - - $child.css(startValues); - $child.animate(endValues, { - duration: duration, - easing: 'swing', - always: function () { + if (animation) { + removeAnimationClasses(context.child, fadeOnly); + context.child.classList.add(fadeOnly ? 'entrance-in-fade' : 'entrance-in'); + setTimeout(function () { + removeAnimationClasses(context.child, fadeOnly); + if(context.activeView){ + removeAnimationClasses(context.activeView, fadeOnly); + } $child.css(clearValues); endTransition(); - } - }); + }, duration); + } else { + $child.animate(endValues, { + duration: duration, + easing: 'swing', + always: function() { + $child.css(clearValues); + endTransition(); + } + }); + } } - if (context.activeView) { - $(context.activeView).fadeOut({ duration: fadeOutDuration, always: startTransition }); + $child.css(startValues); + + if(context.activeView) { + if (animation && !isIE) { + removeAnimationClasses(context.activeView, fadeOnly); + context.activeView.classList.add('entrance-out'); + setTimeout(startTransition, fadeOutDuration); + } else { + $(context.activeView).fadeOut({ duration: fadeOutDuration, always: startTransition }); + } } else { startTransition(); } diff --git a/js/viewEngine.js b/js/viewEngine.js index fa487f9..8c8428a 100644 --- a/js/viewEngine.js +++ b/js/viewEngine.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -27,6 +27,7 @@ define(['durandal/system', 'jquery'], function (system, $) { * @static */ return { + cache:{}, /** * The file extension that view source files are expected to have. * @property {string} viewExtension @@ -39,6 +40,12 @@ define(['durandal/system', 'jquery'], function (system, $) { * @default text */ viewPlugin: 'text', + /** + * Parameters passed to the RequireJS loader plugin used by the viewLocator to obtain the view source. + * @property {string} viewPluginParameters + * @default The empty string by default. + */ + viewPluginParameters: '', /** * Determines if the url is a url for a view, according to the view engine. * @method isViewUrl @@ -64,7 +71,8 @@ define(['durandal/system', 'jquery'], function (system, $) { * @return {string} The require path. */ convertViewIdToRequirePath: function (viewId) { - return this.viewPlugin + '!' + viewId + this.viewExtension; + var plugin = this.viewPlugin ? this.viewPlugin + '!' : ''; + return plugin + viewId + this.viewExtension + this.viewPluginParameters; }, /** * Parses the view engine recognized markup and returns DOM elements. @@ -116,6 +124,24 @@ define(['durandal/system', 'jquery'], function (system, $) { return withoutCommentsOrEmptyText[0]; }, + /** + * Gets the view associated with the id from the cache of parsed views. + * @method tryGetViewFromCache + * @param {string} id The view id to lookup in the cache. + * @return {DOMElement|null} The cached view or null if it's not in the cache. + */ + tryGetViewFromCache:function(id) { + return this.cache[id]; + }, + /** + * Puts the view associated with the id into the cache of parsed views. + * @method putViewInCache + * @param {string} id The view id whose view should be cached. + * @param {DOMElement} view The view to cache. + */ + putViewInCache: function (id, view) { + this.cache[id] = view; + }, /** * Creates the view associated with the view id. * @method createView @@ -125,18 +151,27 @@ define(['durandal/system', 'jquery'], function (system, $) { createView: function(viewId) { var that = this; var requirePath = this.convertViewIdToRequirePath(viewId); + var existing = this.tryGetViewFromCache(requirePath); + + if (existing) { + return system.defer(function(dfd) { + dfd.resolve(existing.cloneNode(true)); + }).promise(); + } return system.defer(function(dfd) { system.acquire(requirePath).then(function(markup) { var element = that.processMarkup(markup); element.setAttribute('data-view', viewId); - dfd.resolve(element); - }).fail(function(err){ - that.createFallbackView(viewId, requirePath, err).then(function(element){ - element.setAttribute('data-view', viewId); - dfd.resolve(element); - }); + that.putViewInCache(requirePath, element); + dfd.resolve(element.cloneNode(true)); + }).fail(function(err) { + that.createFallbackView(viewId, requirePath, err).then(function(element) { + element.setAttribute('data-view', viewId); + that.cache[requirePath] = element; + dfd.resolve(element.cloneNode(true)); }); + }); }).promise(); }, /** diff --git a/js/viewLocator.js b/js/viewLocator.js index ae8b257..ca56fa3 100644 --- a/js/viewLocator.js +++ b/js/viewLocator.js @@ -1,5 +1,5 @@ /** - * Durandal 2.0.1 Copyright (c) 2012 Blue Spire Consulting, Inc. All Rights Reserved. + * 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. */ @@ -103,7 +103,7 @@ define(['durandal/system', 'durandal/viewEngine'], function (system, viewEngine) var funcNameRegex = /function (.{1,})\(/; var results = (funcNameRegex).exec((obj).constructor.toString()); var typeName = (results && results.length > 1) ? results[1] : ""; - + typeName = typeName.trim(); return 'views/' + typeName; }, /**