├── LICENSE
├── NOTICE
├── README.md
├── backstack-min.js
├── backstack.js
├── build
├── almond.js
├── build.js
├── build.sh
├── r.js
├── wrap-end.frag
└── wrap-start.frag
└── src
├── StackNavigator.js
└── effects
├── Effect.js
├── FadeEffect.js
├── NoEffect.js
├── SlideEffect.js
└── vendorPrefix.js
/LICENSE:
--------------------------------------------------------------------------------
1 |
2 | Apache License
3 | Version 2.0, January 2004
4 | http://www.apache.org/licenses/
5 |
6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7 |
8 | 1. Definitions.
9 |
10 | "License" shall mean the terms and conditions for use, reproduction,
11 | and distribution as defined by Sections 1 through 9 of this document.
12 |
13 | "Licensor" shall mean the copyright owner or entity authorized by
14 | the copyright owner that is granting the License.
15 |
16 | "Legal Entity" shall mean the union of the acting entity and all
17 | other entities that control, are controlled by, or are under common
18 | control with that entity. For the purposes of this definition,
19 | "control" means (i) the power, direct or indirect, to cause the
20 | direction or management of such entity, whether by contract or
21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
22 | outstanding shares, or (iii) beneficial ownership of such entity.
23 |
24 | "You" (or "Your") shall mean an individual or Legal Entity
25 | exercising permissions granted by this License.
26 |
27 | "Source" form shall mean the preferred form for making modifications,
28 | including but not limited to software source code, documentation
29 | source, and configuration files.
30 |
31 | "Object" form shall mean any form resulting from mechanical
32 | transformation or translation of a Source form, including but
33 | not limited to compiled object code, generated documentation,
34 | and conversions to other media types.
35 |
36 | "Work" shall mean the work of authorship, whether in Source or
37 | Object form, made available under the License, as indicated by a
38 | copyright notice that is included in or attached to the work
39 | (an example is provided in the Appendix below).
40 |
41 | "Derivative Works" shall mean any work, whether in Source or Object
42 | form, that is based on (or derived from) the Work and for which the
43 | editorial revisions, annotations, elaborations, or other modifications
44 | represent, as a whole, an original work of authorship. For the purposes
45 | of this License, Derivative Works shall not include works that remain
46 | separable from, or merely link (or bind by name) to the interfaces of,
47 | the Work and Derivative Works thereof.
48 |
49 | "Contribution" shall mean any work of authorship, including
50 | the original version of the Work and any modifications or additions
51 | to that Work or Derivative Works thereof, that is intentionally
52 | submitted to Licensor for inclusion in the Work by the copyright owner
53 | or by an individual or Legal Entity authorized to submit on behalf of
54 | the copyright owner. For the purposes of this definition, "submitted"
55 | means any form of electronic, verbal, or written communication sent
56 | to the Licensor or its representatives, including but not limited to
57 | communication on electronic mailing lists, source code control systems,
58 | and issue tracking systems that are managed by, or on behalf of, the
59 | Licensor for the purpose of discussing and improving the Work, but
60 | excluding communication that is conspicuously marked or otherwise
61 | designated in writing by the copyright owner as "Not a Contribution."
62 |
63 | "Contributor" shall mean Licensor and any individual or Legal Entity
64 | on behalf of whom a Contribution has been received by Licensor and
65 | subsequently incorporated within the Work.
66 |
67 | 2. Grant of Copyright License. Subject to the terms and conditions of
68 | this License, each Contributor hereby grants to You a perpetual,
69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70 | copyright license to reproduce, prepare Derivative Works of,
71 | publicly display, publicly perform, sublicense, and distribute the
72 | Work and such Derivative Works in Source or Object form.
73 |
74 | 3. Grant of Patent License. Subject to the terms and conditions of
75 | this License, each Contributor hereby grants to You a perpetual,
76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77 | (except as stated in this section) patent license to make, have made,
78 | use, offer to sell, sell, import, and otherwise transfer the Work,
79 | where such license applies only to those patent claims licensable
80 | by such Contributor that are necessarily infringed by their
81 | Contribution(s) alone or by combination of their Contribution(s)
82 | with the Work to which such Contribution(s) was submitted. If You
83 | institute patent litigation against any entity (including a
84 | cross-claim or counterclaim in a lawsuit) alleging that the Work
85 | or a Contribution incorporated within the Work constitutes direct
86 | or contributory patent infringement, then any patent licenses
87 | granted to You under this License for that Work shall terminate
88 | as of the date such litigation is filed.
89 |
90 | 4. Redistribution. You may reproduce and distribute copies of the
91 | Work or Derivative Works thereof in any medium, with or without
92 | modifications, and in Source or Object form, provided that You
93 | meet the following conditions:
94 |
95 | (a) You must give any other recipients of the Work or
96 | Derivative Works a copy of this License; and
97 |
98 | (b) You must cause any modified files to carry prominent notices
99 | stating that You changed the files; and
100 |
101 | (c) You must retain, in the Source form of any Derivative Works
102 | that You distribute, all copyright, patent, trademark, and
103 | attribution notices from the Source form of the Work,
104 | excluding those notices that do not pertain to any part of
105 | the Derivative Works; and
106 |
107 | (d) If the Work includes a "NOTICE" text file as part of its
108 | distribution, then any Derivative Works that You distribute must
109 | include a readable copy of the attribution notices contained
110 | within such NOTICE file, excluding those notices that do not
111 | pertain to any part of the Derivative Works, in at least one
112 | of the following places: within a NOTICE text file distributed
113 | as part of the Derivative Works; within the Source form or
114 | documentation, if provided along with the Derivative Works; or,
115 | within a display generated by the Derivative Works, if and
116 | wherever such third-party notices normally appear. The contents
117 | of the NOTICE file are for informational purposes only and
118 | do not modify the License. You may add Your own attribution
119 | notices within Derivative Works that You distribute, alongside
120 | or as an addendum to the NOTICE text from the Work, provided
121 | that such additional attribution notices cannot be construed
122 | as modifying the License.
123 |
124 | You may add Your own copyright statement to Your modifications and
125 | may provide additional or different license terms and conditions
126 | for use, reproduction, or distribution of Your modifications, or
127 | for any such Derivative Works as a whole, provided Your use,
128 | reproduction, and distribution of the Work otherwise complies with
129 | the conditions stated in this License.
130 |
131 | 5. Submission of Contributions. Unless You explicitly state otherwise,
132 | any Contribution intentionally submitted for inclusion in the Work
133 | by You to the Licensor shall be under the terms and conditions of
134 | this License, without any additional terms or conditions.
135 | Notwithstanding the above, nothing herein shall supersede or modify
136 | the terms of any separate license agreement you may have executed
137 | with Licensor regarding such Contributions.
138 |
139 | 6. Trademarks. This License does not grant permission to use the trade
140 | names, trademarks, service marks, or product names of the Licensor,
141 | except as required for reasonable and customary use in describing the
142 | origin of the Work and reproducing the content of the NOTICE file.
143 |
144 | 7. Disclaimer of Warranty. Unless required by applicable law or
145 | agreed to in writing, Licensor provides the Work (and each
146 | Contributor provides its Contributions) on an "AS IS" BASIS,
147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148 | implied, including, without limitation, any warranties or conditions
149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150 | PARTICULAR PURPOSE. You are solely responsible for determining the
151 | appropriateness of using or redistributing the Work and assume any
152 | risks associated with Your exercise of permissions under this License.
153 |
154 | 8. Limitation of Liability. In no event and under no legal theory,
155 | whether in tort (including negligence), contract, or otherwise,
156 | unless required by applicable law (such as deliberate and grossly
157 | negligent acts) or agreed to in writing, shall any Contributor be
158 | liable to You for damages, including any direct, indirect, special,
159 | incidental, or consequential damages of any character arising as a
160 | result of this License or out of the use or inability to use the
161 | Work (including but not limited to damages for loss of goodwill,
162 | work stoppage, computer failure or malfunction, or any and all
163 | other commercial damages or losses), even if such Contributor
164 | has been advised of the possibility of such damages.
165 |
166 | 9. Accepting Warranty or Additional Liability. While redistributing
167 | the Work or Derivative Works thereof, You may choose to offer,
168 | and charge a fee for, acceptance of support, warranty, indemnity,
169 | or other liability obligations and/or rights consistent with this
170 | License. However, in accepting such obligations, You may act only
171 | on Your own behalf and on Your sole responsibility, not on behalf
172 | of any other Contributor, and only if You agree to indemnify,
173 | defend, and hold each Contributor harmless for any liability
174 | incurred by, or claims asserted against, such Contributor by reason
175 | of your accepting any such warranty or additional liability.
176 |
177 | END OF TERMS AND CONDITIONS
--------------------------------------------------------------------------------
/NOTICE:
--------------------------------------------------------------------------------
1 | Copyright 2012 Piotr Walczyszyn (http://outof.me | @pwalczyszyn)
2 |
3 | Licensed under the Apache License, Version 2.0 (the "License");
4 | you may not use this file except in compliance with the License.
5 | You may obtain a copy of the License at
6 |
7 | http://www.apache.org/licenses/LICENSE-2.0
8 |
9 | Unless required by applicable law or agreed to in writing, software
10 | distributed under the License is distributed on an "AS IS" BASIS,
11 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 | See the License for the specific language governing permissions and
13 | limitations under the License.
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
What is BackStack?
3 |
4 | BackStack is a JavaScript component/extension for Backbone.js that allows you to create nice view
5 | transitions in your HTML5 apps. By default it comes with an implementation of mobile-style slide transitions, fade
6 | transitions, and no-effect transitions.
7 |
8 |
9 | It is conceptually based on the ViewNavigator API from the mobile SDK of the Apache Flex framework. It enables developers to manage a stack of views that can be pushed, popped, or replaced.
10 |
11 |
12 |
Why should I use it?
13 |
Although you can use it for web development (as I did to create this site) it is especially useful when building mobile apps with PhoneGap/Cordova framework.
14 | Of course, if you are using one of the dozen or so available mobile UI frameworks like jQuery Mobile,
15 | Sencha Touch, or jQTouch you don't need it.
16 |
17 |
18 |
Are there other alternatives?
19 |
You can use the ViewNavigator implementation from the app-UI framework built by fellow Adobe Evangelist
20 | Andy Trice. Alternatively, you can build the transitions yourself; it is not really hard! Or, use one of the frameworks mentioned above.
21 |
22 |
23 |
How was it built?
24 |
This may not be particularly interesting to everyone but I used the almond library to develop/package BackStack. Almond is a replacement AMD loader for RequireJS. It is a smaller "shim" loader, providing a minimal AMD API footprint that includes loader plugin support.
25 |
26 |
27 | ### Where can I find more info about it?
28 | Here is a demo site that is actually built with BackStack. Be aware of a disclaimer that I only tested it with WebKit based browsers as I mainly used it for mobile apps development. So if you are on IE this site many not work for you, sorry for that ;)
29 |
30 | You can also checkout this simple example that is available on jsFiddle.
31 |
32 | Watch an intro on YouTube with step-by-step instructions how to get started: http://www.youtube.com/watch?v=sm5DI-iYark
33 |
34 | ### BackStack.StackNavigator class API
35 |
36 |
Constructor
37 |
38 |
39 |
40 | - options - Backbone view options hash.
41 |
42 | - options.popTransition - default transition effect object to be used during pop operations.
43 | - options.pushTransition - default transition effect object to be used during push operations.
44 |
45 |
46 |
47 |
48 |
49 |
Events
50 |
51 | BackStack.StackNavigator eventing model is based on Backbone's events implementation.
52 |
53 | - StackNavigator#viewChanging - It's triggered just before view on the stack is changed. This event is cancelable and the view change can be stopped using
event.preventDefault()
function.
54 | Event object properties:
55 |
56 | - action - this property can have following values: push, pop, popAll, replace and replaceAll.
57 | - fromViewClass - class of a from view.
58 | - fromView - instance of a from view.
59 | - toViewClass - class of a to view.
60 | - toView - instance of a to view.
61 |
62 |
63 |
64 | - StackNavigator#viewChanged - It's triggered after view on the stack is changed. This event cannot be canceled.
65 |
Event object properties:
66 |
67 | - target - Instance of BackStack.StackNavigator that triggered this event.
68 |
69 |
70 | - Backbone.View#viewActivate - This event is triggered when view is activated on the stack.
71 |
Event object properties:
72 |
73 | - target - Instance of a view that was activated.
74 |
75 |
76 | - Backbone.View#viewDeactivate - This event is triggered when view is deactivated on the stack. Either it was popped or it was covered by another view.
77 |
Event object properties:
78 |
79 | - target - Instance of a view that was deactivated.
80 |
81 |
82 |
83 |
84 |
85 |
Fields
86 |
87 | - StackNavigator.viewsStack - An array with all the view refs on the stack.
88 | - StackNavigator.activeView - View on top of the stack.
89 | - StackNavigator.defaultPushTransition - Default push transition effect.
90 | - StackNavigator.defaultPopTransition - Default pop transition effect.
91 |
92 |
93 |
Functions
94 |
95 | - StackNavigator.popAll(transition) - Pops all views from a stack and leaves empty stack.
96 |
Parameters:
97 |
98 | - {Effect} transition Transition effect to be played when popping views.
99 |
100 |
101 |
102 | - StackNavigator.popView(transition) - Pops an active view from a stack and displays one below.
103 |
Parameters:
104 |
105 | - {Effect} transition Transition effect to be played when popping new view.
106 |
107 |
108 |
109 | - StackNavigator.pushView(view, viewOptions, transition) - Pushes new view to the stack.
110 |
Parameters:
111 |
112 | - {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed on top of the stack.
113 | - {Object} viewOptions Options to be passed if view is constructed by StackNavigator.
114 | - {Effect} transition Transition effect to be played when pushing new view.
115 |
116 |
117 |
118 | - StackNavigator.replaceAll(view, viewOptions, transition) - Replaces all of the views on the stack, with the one passed as a view param.
119 |
Parameters:
120 |
121 | - {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed on top of the stack.
122 | - {Object} viewOptions Options to be passed if view is constructed by StackNavigator.
123 | - {Effect} transition Transition effect to be played when replacing views.
124 |
125 |
126 |
127 | - StackNavigator.replaceView(view, viewOptions, transition) - Replaces view on top of the stack, with the one passed as a view param.
128 |
Parameters:
129 |
130 | - {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed on top of the stack instead of current one.
131 | - {Object} viewOptions Options to be passed if view is constructed by StackNavigator.
132 | - {Effect} transition Transition effect to be played when replacing view.
133 |
134 |
135 |
136 |
--------------------------------------------------------------------------------
/backstack-min.js:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2012 Piotr Walczyszyn (http://outof.me | @pwalczyszyn)
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | //////////////////////////////////////////////////////////////////////////////////////
18 |
19 | /**
20 | * almond 0.1.1 Copyright (c) 2011, The Dojo Foundation All Rights Reserved.
21 | * Available via the MIT or new BSD license.
22 | * see: http://github.com/jrburke/almond for details
23 | */
24 |
25 | (function(a,b){typeof define=="function"&&define.amd?define(["jquery","underscore","Backbone"],b):a.BackStack=b(a.jQuery||a.Zepto||a.ender,a._,a.Backbone)})(this,function(a,b,c){var d,e,f;return function(a){function l(a,b){var c=b&&b.split("/"),d=g.map,e=d&&d["*"]||{},f,h,i,j,k,l,m;if(a&&a.charAt(0)==="."&&b){c=c.slice(0,c.length-1),a=c.concat(a.split("/"));for(k=0;m=a[k];k++)if(m===".")a.splice(k,1),k-=1;else if(m===".."){if(k===1&&(a[2]===".."||a[0]===".."))return!0;k>0&&(a.splice(k-1,2),k-=2)}a=a.join("/")}if((c||e)&&d){f=a.split("/");for(k=f.length;k>0;k-=1){h=f.slice(0,k).join("/");if(c)for(l=c.length;l>0;l-=1){i=d[c.slice(0,l).join("/")];if(i){i=i[h];if(i){j=i;break}}}j=j||e[h];if(j){f.splice(0,k,j),a=f.join("/");break}}}return a}function m(b,c){return function(){return k.apply(a,i.call(arguments,0).concat([b,c]))}}function n(a){return function(b){return l(b,a)}}function o(a){return function(c){b[a]=c}}function p(d){if(c.hasOwnProperty(d)){var e=c[d];delete c[d],h[d]=!0,j.apply(a,e)}if(!b.hasOwnProperty(d))throw new Error("No "+d);return b[d]}function q(a,b){var c,d,e=a.indexOf("!");return e!==-1?(c=l(a.slice(0,e),b),a=a.slice(e+1),d=p(c),d&&d.normalize?a=d.normalize(a,n(b)):a=l(a,b)):a=l(a,b),{f:c?c+"!"+a:a,n:a,p:d}}function r(a){return function(){return g&&g.config&&g.config[a]||{}}}var b={},c={},g={},h={},i=[].slice,j,k;j=function(d,e,f,g){var i=[],j,k,l,n,s,t;g=g||d;if(typeof f=="function"){e=!e.length&&f.length?["require","exports","module"]:e;for(t=0;t=0){h--;var g=a(b.target);g.css(j,""),g.css(k,""),c&&c[0]==b.target&&c.css("left",0),h==0&&d&&(f&&clearTimeout(f),d.call(e))}};b&&(h++,b.one(g.transitionEndEvent,l),b.css("left",0),b.css(k,[j," ",g.fromViewTransitionProps.duration,"s ",g.fromViewTransitionProps.easing," ",g.fromViewTransitionProps.delay,"s"].join(""))),c&&(h++,c.one(g.transitionEndEvent,l),c.css("left",g.direction=="left"?e.$el.width():-e.$el.width()),c.css(k,[j," ",g.toViewTransitionProps.duration,"s ",g.toViewTransitionProps.easing," ",g.toViewTransitionProps.delay,"s"].join("")),c.css("visibility","visible"));if(b||c)e.$el.css("width"),i="translate3d("+(g.direction=="left"?-e.$el.width():e.$el.width())+"px, 0, 0)";var m=Math.max(g.fromViewTransitionProps.duration,g.toViewTransitionProps.duration)+Math.max(g.fromViewTransitionProps.delay,g.toViewTransitionProps.delay);f=setTimeout(function(){h>0&&(h=-1,console.log("Warning "+g.transitionEndEvent+" didn't trigger in expected time!"),c&&(c.off(g.transitionEndEvent,l),c.css(k,""),c.css(j,""),c.css("left",0)),b&&(b.off(g.transitionEndEvent,l),b.css(k,""),b.css(j,"")),d.call(e))},m*1.5*1e3);var n;b&&c?n=b.add(c):c?n=c:b&&(n=b),n&&n.css(j,i)}});return c}),f("StackNavigator",["effects/SlideEffect"],function(a){function d(a,b){a.__backStackRendered__?a.$el.css({visibility:"hidden"}):(a.stackNavigator=b,typeof a.destructionPolicy=="undefined"&&(a.destructionPolicy="auto"),a.$el.css({position:"absolute",visibility:"hidden",overflow:"hidden",width:"100%",height:"100%"})),b.$el.append(a.el),a.__backStackRendered__||(a.render.call(a),a.__backStackRendered__=!0)}function e(a,c,d){return b.extend({type:a,cancelable:b.isUndefined(d)?!1:d,preventDefault:function(){this.cancelable&&(this.isDefaultPrevented=function(){return!0})},isDefaultPrevented:function(){return!1},trigger:function(a){return a.trigger(this.type,this),this}},c)}function f(c,f,g,h){d(f.instance,this),h=h||this.defaultPushTransition||(this.defaultPushTransition=new a({direction:"left"})),h.play(c?c.instance.$el:null,f.instance.$el,function(){var a=g>0?this.viewsStack.splice(this.viewsStack.length-g,g):c?[c]:null;a!=null&&b.each(a,function(a){e("viewDeactivate",{target:a.instance}).trigger(a.instance),a.instance.destructionPolicy=="never"?a.instance.$el.detach():(a.instance.remove(),a.instance=null)},this),this.viewsStack.push(f),this.activeView=f.instance,e("viewActivate",{target:f.instance}).trigger(f.instance),e("viewChanged",{target:this}).trigger(this),m.call(this)},this)}function g(c,f,g,h){f&&(f.instance=f.instance?f.instance:new f.viewClass(f.options),d(f.instance,this)),h=h||this.defaultPopTransition||(this.defaultPopTransition=new a({direction:"right"})),h.play(c.instance.$el,f?f.instance.$el:null,function(){var a=this.viewsStack.splice(this.viewsStack.length-g,g);b.each(a,function(a){e("viewDeactivate",{target:a.instance}).trigger(a.instance),a.instance.destructionPolicy=="never"?a.instance.$el.detach():(a.instance.remove(),a.instance=null)},this),f?(this.activeView=f.instance,e("viewActivate",{target:f.instance}).trigger(f.instance)):this.activeView=null,e("viewChanged",{target:this}).trigger(this),m.call(this)},this)}function h(a,c,d){var g=b.last(this.viewsStack),h=b.isFunction(a)?new a(c):a,i={instance:h,viewClass:h.constructor,options:c},j=e("viewChanging",{action:"push",fromViewClass:g?g.viewClass:null,fromView:g?g.instance:null,toViewClass:i.viewClass,toView:i.instance},!0).trigger(this);if(j.isDefaultPrevented())return null;f.call(this,g,i,0,d)}function i(a){if(this.viewsStack.length==0)throw new Error("Popping from an empty stack!");var c=b.last(this.viewsStack),d=this.viewsStack.length>1?this.viewsStack[this.viewsStack.length-2]:null,f=e("viewChanging",{action:"pop",fromViewClass:c.viewClass,fromView:c.instance,toViewClass:d?d.viewClass:null,toView:d?d.instance:null},!0).trigger(this);if(f.isDefaultPrevented())return;g.call(this,c,d,1,a)}function j(a){if(this.viewsStack.length==0)throw new Error("Popping from an empty stack!");var c=b.last(this.viewsStack),d=e("viewChanging",{action:"popAll",fromViewClass:c.viewClass,fromView:c.instance,toViewClass:null,toView:null},!0).trigger(this);if(d.isDefaultPrevented())return;g.call(this,c,null,this.viewsStack.length,a)}function k(a,c,d){if(this.viewsStack.length==0)throw new Error("Replacing on an empty stack!");var g=b.last(this.viewsStack),h=b.isFunction(a)?new a(c):a,i={instance:h,viewClass:h.constructor,options:c},j=e("viewChanging",{action:"replace",fromViewClass:g.viewClass,fromView:g.instance,toViewClass:i.viewClass,toView:i.instance},!0).trigger(this);if(j.isDefaultPrevented())return null;f.call(this,g,i,1,d)}function l(a,c,d){if(this.viewsStack.length==0)throw new Error("Replacing on an empty stack!");var g=b.last(this.viewsStack),h=b.isFunction(a)?new a(c):a,i={instance:h,viewClass:h.constructor,options:c},j=e("viewChanging",{action:"replaceAll",fromViewClass:g.viewClass,fromView:g.instance,toViewClass:i.viewClass,toView:i.instance},!0).trigger(this);if(j.isDefaultPrevented())return null;f.call(this,g,i,this.viewsStack.length,d)}function m(){this.actionsQueue.splice(0,1);if(this.actionsQueue.length>0){var a=this.actionsQueue[0],b=Array.prototype.slice.call(a.args);switch(a.fn){case"pushView":h.apply(this,b);break;case"popView":i.apply(this,b);break;case"popAll":j.apply(this,b);break;case"replaceView":k.apply(this,b);break;case"replaceAll":l.apply(this,b)}}}var n=c.View.extend({viewsStack:null,activeView:null,defaultPushTransition:null,defaultPopTransition:null,actionsQueue:null,initialize:function(a){this.$el.css({overflow:"hidden"}),this.viewsStack=[],this.actionsQueue=[],a.popTransition&&(this.defaultPopTransition=a.popTransition),a.pushTransition&&(this.defaultPushTransition=a.pushTransition)},pushView:function(a,b,c){this.actionsQueue.push({fn:"pushView",args:arguments}),this.actionsQueue.length==1&&h.call(this,a,b,c)},popView:function(a){this.actionsQueue.push({fn:"popView",args:arguments}),this.actionsQueue.length==1&&i.call(this,a)},popAll:function(a){this.actionsQueue.push({fn:"popAll",args:arguments}),this.actionsQueue.length==1&&j.call(this,a)},replaceView:function(a,b,c){this.actionsQueue.push({fn:"replaceView",args:arguments}),this.actionsQueue.length==1&&k.call(this,a,b,c)},replaceAll:function(a,b,c){this.actionsQueue.push({fn:"replaceAll",args:arguments}),this.actionsQueue.length==1&&l.call(this,a,b,c)}});return n}),f("effects/FadeEffect",["effects/Effect"],function(b){var c=b.extend({fromViewTransitionProps:{duration:.4,easing:"linear",delay:.1},toViewTransitionProps:{duration:.4,easing:"linear",delay:.1},play:function(b,c,d,e){var f=this,g,h=0,i=f.vendorPrefix==""?"transition":["-"+f.vendorPrefix.toLowerCase(),"-","transition"].join(""),j=function(b){h>=0&&(h--,a(b.target).css(i,""),h==0&&d&&(g&&clearTimeout(g),d.call(e)))};b&&(h++,b.one(f.transitionEndEvent,j),b.css(i,["opacity ",f.fromViewTransitionProps.duration,"s ",f.fromViewTransitionProps.easing," ",f.fromViewTransitionProps.delay,"s"].join(""))),c&&(h++,c.one(f.transitionEndEvent,j),c.css("opacity",0),c.css(i,["opacity ",f.toViewTransitionProps.duration,"s ",f.toViewTransitionProps.easing," ",f.toViewTransitionProps.delay,"s"].join("")),c.css("visibility","visible")),e.$el.css("width");var k=Math.max(f.fromViewTransitionProps.duration,f.toViewTransitionProps.duration)+Math.max(f.fromViewTransitionProps.delay,f.toViewTransitionProps.delay);g=setTimeout(function(){h>0&&(h=-1,console.log("Warning "+f.transitionEndEvent+" didn't trigger in expected time!"),c&&(c.off(f.transitionEndEvent,j),c.css(i,"")),b&&(b.off(f.transitionEndEvent,j),b.css(i,"")),d.call(e))},k*1.5*1e3),c&&c.css("opacity",1),b&&b.css("opacity",0)}});return c}),f("effects/NoEffect",["effects/Effect"],function(a){var b=a.extend();return b.prototype.play=function(a,b,c,d){b&&b.css("visibility","visible"),c.call(d)},b}),{StackNavigator:e("StackNavigator"),Effect:e("effects/Effect"),NoEffect:e("effects/NoEffect"),SlideEffect:e("effects/SlideEffect"),FadeEffect:e("effects/FadeEffect")}})
--------------------------------------------------------------------------------
/backstack.js:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2012 Piotr Walczyszyn (http://outof.me | @pwalczyszyn)
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | //////////////////////////////////////////////////////////////////////////////////////
18 |
19 | // BackStack version 1.1.2
20 |
21 | (function (root, factory) {
22 | // Set up BackStack appropriately for the environment.
23 | if (typeof define === 'function' && define.amd) {
24 | // AMD
25 | define(['jquery', 'underscore', 'Backbone'], factory);
26 | } else {
27 | // Browser globals
28 | root.BackStack = factory((root.jQuery || root.Zepto || root.ender), root._, root.Backbone);
29 | }
30 | }(this, function ($, _, Backbone) {
31 |
32 | /**
33 | * almond 0.1.1 Copyright (c) 2011, The Dojo Foundation All Rights Reserved.
34 | * Available via the MIT or new BSD license.
35 | * see: http://github.com/jrburke/almond for details
36 | */
37 | //Going sloppy to avoid 'use strict' string cost, but strict practices should
38 | //be followed.
39 | /*jslint sloppy: true */
40 | /*global setTimeout: false */
41 |
42 | var requirejs, require, define;
43 | (function (undef) {
44 | var defined = {},
45 | waiting = {},
46 | config = {},
47 | defining = {},
48 | aps = [].slice,
49 | main, req;
50 |
51 | /**
52 | * Given a relative module name, like ./something, normalize it to
53 | * a real name that can be mapped to a path.
54 | * @param {String} name the relative name
55 | * @param {String} baseName a real name that the name arg is relative
56 | * to.
57 | * @returns {String} normalized name
58 | */
59 | function normalize(name, baseName) {
60 | var baseParts = baseName && baseName.split("/"),
61 | map = config.map,
62 | starMap = (map && map['*']) || {},
63 | nameParts, nameSegment, mapValue, foundMap, i, j, part;
64 |
65 | //Adjust any relative paths.
66 | if (name && name.charAt(0) === ".") {
67 | //If have a base name, try to normalize against it,
68 | //otherwise, assume it is a top-level require that will
69 | //be relative to baseUrl in the end.
70 | if (baseName) {
71 | //Convert baseName to array, and lop off the last part,
72 | //so that . matches that "directory" and not name of the baseName's
73 | //module. For instance, baseName of "one/two/three", maps to
74 | //"one/two/three.js", but we want the directory, "one/two" for
75 | //this normalization.
76 | baseParts = baseParts.slice(0, baseParts.length - 1);
77 |
78 | name = baseParts.concat(name.split("/"));
79 |
80 | //start trimDots
81 | for (i = 0; (part = name[i]); i++) {
82 | if (part === ".") {
83 | name.splice(i, 1);
84 | i -= 1;
85 | } else if (part === "..") {
86 | if (i === 1 && (name[2] === '..' || name[0] === '..')) {
87 | //End of the line. Keep at least one non-dot
88 | //path segment at the front so it can be mapped
89 | //correctly to disk. Otherwise, there is likely
90 | //no path mapping for a path starting with '..'.
91 | //This can still fail, but catches the most reasonable
92 | //uses of ..
93 | return true;
94 | } else if (i > 0) {
95 | name.splice(i - 1, 2);
96 | i -= 2;
97 | }
98 | }
99 | }
100 | //end trimDots
101 |
102 | name = name.join("/");
103 | }
104 | }
105 |
106 | //Apply map config if available.
107 | if ((baseParts || starMap) && map) {
108 | nameParts = name.split('/');
109 |
110 | for (i = nameParts.length; i > 0; i -= 1) {
111 | nameSegment = nameParts.slice(0, i).join("/");
112 |
113 | if (baseParts) {
114 | //Find the longest baseName segment match in the config.
115 | //So, do joins on the biggest to smallest lengths of baseParts.
116 | for (j = baseParts.length; j > 0; j -= 1) {
117 | mapValue = map[baseParts.slice(0, j).join('/')];
118 |
119 | //baseName segment has config, find if it has one for
120 | //this name.
121 | if (mapValue) {
122 | mapValue = mapValue[nameSegment];
123 | if (mapValue) {
124 | //Match, update name to the new value.
125 | foundMap = mapValue;
126 | break;
127 | }
128 | }
129 | }
130 | }
131 |
132 | foundMap = foundMap || starMap[nameSegment];
133 |
134 | if (foundMap) {
135 | nameParts.splice(0, i, foundMap);
136 | name = nameParts.join('/');
137 | break;
138 | }
139 | }
140 | }
141 |
142 | return name;
143 | }
144 |
145 | function makeRequire(relName, forceSync) {
146 | return function () {
147 | //A version of a require function that passes a moduleName
148 | //value for items that may need to
149 | //look up paths relative to the moduleName
150 | return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
151 | };
152 | }
153 |
154 | function makeNormalize(relName) {
155 | return function (name) {
156 | return normalize(name, relName);
157 | };
158 | }
159 |
160 | function makeLoad(depName) {
161 | return function (value) {
162 | defined[depName] = value;
163 | };
164 | }
165 |
166 | function callDep(name) {
167 | if (waiting.hasOwnProperty(name)) {
168 | var args = waiting[name];
169 | delete waiting[name];
170 | defining[name] = true;
171 | main.apply(undef, args);
172 | }
173 |
174 | if (!defined.hasOwnProperty(name)) {
175 | throw new Error('No ' + name);
176 | }
177 | return defined[name];
178 | }
179 |
180 | /**
181 | * Makes a name map, normalizing the name, and using a plugin
182 | * for normalization if necessary. Grabs a ref to plugin
183 | * too, as an optimization.
184 | */
185 | function makeMap(name, relName) {
186 | var prefix, plugin,
187 | index = name.indexOf('!');
188 |
189 | if (index !== -1) {
190 | prefix = normalize(name.slice(0, index), relName);
191 | name = name.slice(index + 1);
192 | plugin = callDep(prefix);
193 |
194 | //Normalize according
195 | if (plugin && plugin.normalize) {
196 | name = plugin.normalize(name, makeNormalize(relName));
197 | } else {
198 | name = normalize(name, relName);
199 | }
200 | } else {
201 | name = normalize(name, relName);
202 | }
203 |
204 | //Using ridiculous property names for space reasons
205 | return {
206 | f: prefix ? prefix + '!' + name : name, //fullName
207 | n: name,
208 | p: plugin
209 | };
210 | }
211 |
212 | function makeConfig(name) {
213 | return function () {
214 | return (config && config.config && config.config[name]) || {};
215 | };
216 | }
217 |
218 | main = function (name, deps, callback, relName) {
219 | var args = [],
220 | usingExports,
221 | cjsModule, depName, ret, map, i;
222 |
223 | //Use name if no relName
224 | relName = relName || name;
225 |
226 | //Call the callback to define the module, if necessary.
227 | if (typeof callback === 'function') {
228 |
229 | //Pull out the defined dependencies and pass the ordered
230 | //values to the callback.
231 | //Default to [require, exports, module] if no deps
232 | deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
233 | for (i = 0; i < deps.length; i++) {
234 | map = makeMap(deps[i], relName);
235 | depName = map.f;
236 |
237 | //Fast path CommonJS standard dependencies.
238 | if (depName === "require") {
239 | args[i] = makeRequire(name);
240 | } else if (depName === "exports") {
241 | //CommonJS module spec 1.1
242 | args[i] = defined[name] = {};
243 | usingExports = true;
244 | } else if (depName === "module") {
245 | //CommonJS module spec 1.1
246 | cjsModule = args[i] = {
247 | id: name,
248 | uri: '',
249 | exports: defined[name],
250 | config: makeConfig(name)
251 | };
252 | } else if (defined.hasOwnProperty(depName) || waiting.hasOwnProperty(depName)) {
253 | args[i] = callDep(depName);
254 | } else if (map.p) {
255 | map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
256 | args[i] = defined[depName];
257 | } else if (!defining[depName]) {
258 | throw new Error(name + ' missing ' + depName);
259 | }
260 | }
261 |
262 | ret = callback.apply(defined[name], args);
263 |
264 | if (name) {
265 | //If setting exports via "module" is in play,
266 | //favor that over return value and exports. After that,
267 | //favor a non-undefined return value over exports use.
268 | if (cjsModule && cjsModule.exports !== undef &&
269 | cjsModule.exports !== defined[name]) {
270 | defined[name] = cjsModule.exports;
271 | } else if (ret !== undef || !usingExports) {
272 | //Use the return value from the function.
273 | defined[name] = ret;
274 | }
275 | }
276 | } else if (name) {
277 | //May just be an object definition for the module. Only
278 | //worry about defining if have a module name.
279 | defined[name] = callback;
280 | }
281 | };
282 |
283 | requirejs = require = req = function (deps, callback, relName, forceSync) {
284 | if (typeof deps === "string") {
285 | //Just return the module wanted. In this scenario, the
286 | //deps arg is the module name, and second arg (if passed)
287 | //is just the relName.
288 | //Normalize module name, if it contains . or ..
289 | return callDep(makeMap(deps, callback).f);
290 | } else if (!deps.splice) {
291 | //deps is a config object, not an array.
292 | config = deps;
293 | if (callback.splice) {
294 | //callback is an array, which means it is a dependency list.
295 | //Adjust args if there are dependencies
296 | deps = callback;
297 | callback = relName;
298 | relName = null;
299 | } else {
300 | deps = undef;
301 | }
302 | }
303 |
304 | //Support require(['a'])
305 | callback = callback || function () {};
306 |
307 | //Simulate async callback;
308 | if (forceSync) {
309 | main(undef, deps, callback, relName);
310 | } else {
311 | setTimeout(function () {
312 | main(undef, deps, callback, relName);
313 | }, 15);
314 | }
315 |
316 | return req;
317 | };
318 |
319 | /**
320 | * Just drops the config on the floor, but returns req in case
321 | * the config return value is used.
322 | */
323 | req.config = function (cfg) {
324 | config = cfg;
325 | return req;
326 | };
327 |
328 | define = function (name, deps, callback) {
329 |
330 | //This module may not have dependencies
331 | if (!deps.splice) {
332 | //deps is not an array, so probably means
333 | //an object literal or factory function for
334 | //the value. Adjust args.
335 | callback = deps;
336 | deps = [];
337 | }
338 |
339 | waiting[name] = [name, deps, callback];
340 | };
341 |
342 | define.amd = {
343 | jQuery: true
344 | };
345 | }());
346 |
347 | define("almond", function(){});
348 |
349 | define('effects/vendorPrefix',[], function () {
350 |
351 | /**
352 | * Helper function to detect browser vendor prefix.
353 | * Thanks to Lea Verou: http://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/
354 | * I just modified it slightly as I expect it to be used in mobile/WebKit scenarios mostly.
355 | */
356 | var vendorPrefix,
357 | regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/,
358 | someScript = document.getElementsByTagName('script')[0];
359 |
360 | // Exception for WebKit based browsers
361 | if ('WebkitOpacity' in someScript.style) {
362 | vendorPrefix = 'Webkit';
363 | } else if ('KhtmlOpacity' in someScript.style) {
364 | vendorPrefix = 'Khtml';
365 | } else {
366 | for (var prop in someScript.style) {
367 | if (regex.test(prop)) {
368 | // test is faster than match, so it's better to perform
369 | // that on the lot and match only when necessary
370 | vendorPrefix = prop.match(regex)[0];
371 | break;
372 | }
373 | }
374 | }
375 |
376 | return (vendorPrefix.toLowerCase() || '');
377 | });
378 | define('effects/Effect',['effects/vendorPrefix'], function (vendorPrefix) {
379 |
380 | var Effect = function Effect(params) {
381 |
382 | if (params) _.extend(this, params);
383 |
384 | this.vendorPrefix = vendorPrefix;
385 |
386 | if (this.vendorPrefix == 'moz' || this.vendorPrefix == '') this.transitionEndEvent = 'transitionend';
387 | else if (this.vendorPrefix == 'ms') this.transitionEndEvent = 'MSTransitionEnd';
388 | else this.transitionEndEvent = this.vendorPrefix + 'TransitionEnd';
389 |
390 | };
391 |
392 | // Shared empty constructor function to aid in prototype-chain creation.
393 | var ctor = function () {
394 | };
395 |
396 | Effect.extend = function (protoProps, staticProps) {
397 | var child = function () {
398 | Effect.apply(this, arguments);
399 | };
400 |
401 | // Inherit class (static) properties from parent.
402 | _.extend(child, Effect);
403 |
404 | // Set the prototype chain to inherit from `parent`, without calling
405 | // `parent`'s constructor function.
406 | ctor.prototype = Effect.prototype;
407 | child.prototype = new ctor();
408 |
409 | // Add prototype properties (instance properties) to the subclass,
410 | // if supplied.
411 | if (protoProps) _.extend(child.prototype, protoProps);
412 |
413 | // Add static properties to the constructor function, if supplied.
414 | if (staticProps) _.extend(child, staticProps);
415 |
416 | // Correctly set child's `prototype.constructor`.
417 | child.prototype.constructor = child;
418 |
419 | // Set a convenience property in case the parent's prototype is needed later.
420 | child.__super__ = Effect.prototype;
421 |
422 | return child;
423 | };
424 |
425 | return Effect;
426 | });
427 | define('effects/SlideEffect',['effects/Effect'], function (Effect) {
428 |
429 | var SlideEffect = Effect.extend({
430 |
431 | direction:'left',
432 |
433 | fromViewTransitionProps:{duration:0.4, easing:'ease-out', delay:0},
434 |
435 | toViewTransitionProps:{duration:0.4, easing:'ease-out', delay:0},
436 |
437 | play:function ($fromView, $toView, callback, context) {
438 |
439 | var timeout,
440 | that = this,
441 | activeTransitions = 0,
442 | transformParams,
443 | transformProp = that.vendorPrefix == '' ? 'transform' :
444 | ['-' + that.vendorPrefix, '-', 'transform'].join(''),
445 | transitionProp = that.vendorPrefix == '' ? 'transition' :
446 | ['-' + that.vendorPrefix, '-', 'transition'].join('');
447 |
448 | var transitionEndHandler = function (event) {
449 | if (activeTransitions >= 0) {
450 | activeTransitions--;
451 |
452 | var $target = $(event.target);
453 | $target.css(transformProp, '');
454 | $target.css(transitionProp, '');
455 |
456 | if ($toView && $toView[0] == event.target) $toView.css('left', 0);
457 |
458 | if (activeTransitions == 0 && callback) {
459 | if (timeout) clearTimeout(timeout);
460 | callback.call(context);
461 | }
462 | }
463 | };
464 |
465 | if ($fromView) {
466 | activeTransitions++;
467 |
468 | $fromView.one(that.transitionEndEvent, transitionEndHandler);
469 |
470 | $fromView.css('left', 0);
471 | $fromView.css(transitionProp, [transformProp, ' ',
472 | that.fromViewTransitionProps.duration, 's ',
473 | that.fromViewTransitionProps.easing, ' ',
474 | that.fromViewTransitionProps.delay, 's'].join(''));
475 | }
476 |
477 | if ($toView) {
478 | activeTransitions++;
479 |
480 | $toView.one(that.transitionEndEvent, transitionEndHandler);
481 |
482 | $toView.css('left', that.direction == 'left' ? context.$el.width() : -context.$el.width());
483 | $toView.css(transitionProp, [transformProp, ' ',
484 | that.toViewTransitionProps.duration, 's ',
485 | that.toViewTransitionProps.easing, ' ',
486 | that.toViewTransitionProps.delay, 's'].join(''));
487 |
488 | // Showing the view
489 | $toView.css('visibility', 'visible');
490 | }
491 |
492 | if ($fromView || $toView) {
493 | // This is a hack to force DOM reflow before transition starts
494 | context.$el.css('width');
495 |
496 | transformParams = 'translate3d(' + (that.direction == 'left' ? -context.$el.width() : context.$el.width()) + 'px, 0, 0)';
497 | }
498 |
499 | // This is a fallback for situations when TransitionEnd event doesn't get triggered
500 | var transDuration = Math.max(that.fromViewTransitionProps.duration, that.toViewTransitionProps.duration) +
501 | Math.max(that.fromViewTransitionProps.delay, that.toViewTransitionProps.delay);
502 |
503 | timeout = setTimeout(function () {
504 | if (activeTransitions > 0) {
505 | activeTransitions = -1;
506 |
507 | console.log('Warning ' + that.transitionEndEvent + ' didn\'t trigger in expected time!');
508 |
509 | if ($toView) {
510 | $toView.off(that.transitionEndEvent, transitionEndHandler);
511 | $toView.css(transitionProp, '');
512 | $toView.css(transformProp, '');
513 | $toView.css('left', 0);
514 | }
515 |
516 | if ($fromView) {
517 | $fromView.off(that.transitionEndEvent, transitionEndHandler);
518 | $fromView.css(transitionProp, '');
519 | $fromView.css(transformProp, '');
520 | }
521 |
522 | callback.call(context);
523 | }
524 | }, transDuration * 1.5 * 1000);
525 |
526 | var $views;
527 | if ($fromView && $toView) $views = $fromView.add($toView);
528 | else if ($toView) $views = $toView;
529 | else if ($fromView) $views = $fromView;
530 |
531 | if ($views) $views.css(transformProp, transformParams);
532 | }
533 | });
534 |
535 | return SlideEffect;
536 | });
537 | define('StackNavigator',['effects/SlideEffect'], function (SlideEffect) {
538 |
539 | /**
540 | * Rendering the view and setting props required by StackNavigator.
541 | * @private
542 | * @ignore
543 | *
544 | * @param {View} view View to be rendered.
545 | * @param {StackNavigator} stackNavigator View StackNavigator instance.
546 | */
547 | function appendView(view, stackNavigator) {
548 |
549 | if (!view.__backStackRendered__) {
550 |
551 | // Setting ref to parent StackNavigator
552 | view.stackNavigator = stackNavigator;
553 |
554 | // Setting default destructionPolicy if it's not set
555 | if (typeof view.destructionPolicy === 'undefined') view.destructionPolicy = 'auto';
556 |
557 | // Setting default styles
558 | view.$el.css({position:'absolute', visibility:'hidden', overflow:'hidden', width:'100%', height:'100%'});
559 |
560 | } else {
561 | // Resetting visibility to hidden
562 | view.$el.css({visibility:'hidden'});
563 | }
564 |
565 | // Adding view to the DOM
566 | stackNavigator.$el.append(view.el);
567 |
568 | if (!view.__backStackRendered__) {
569 | // Rendering the view
570 | view.render.call(view);
571 |
572 | // Setting default of __backStackRendered__ property
573 | view.__backStackRendered__ = true;
574 | }
575 | }
576 |
577 | /**
578 | * Creates event objects triggered by BackStack.
579 | * @private
580 | * @ignore
581 | *
582 | * @param {string} type Event type name.
583 | * @param {*} args Event args.
584 | * @param {boolean} cancelable Flag indicating if event is cancelable.
585 | * @return {event} The new object.
586 | */
587 | function createEvent(type, args, cancelable) {
588 | return _.extend({
589 |
590 | type:type,
591 |
592 | cancelable:_.isUndefined(cancelable) ? false : cancelable,
593 |
594 | preventDefault:function () {
595 | if (this.cancelable)
596 | this.isDefaultPrevented = function () {
597 | return true;
598 | };
599 | },
600 |
601 | isDefaultPrevented:function () {
602 | return false;
603 | },
604 |
605 | trigger:function (target) {
606 | target.trigger(this.type, this);
607 | return this;
608 | }
609 | }, args);
610 | }
611 |
612 | /**
613 | * Private common push method.
614 | * @private
615 | * @ignore
616 | *
617 | * @param {object} fromViewRef Reference to from view.
618 | * @param {object} toViewRef Reference to to view.
619 | * @param {number} replaceHowMany Number of views to replace with pushed view.
620 | * @param {Effect} transition Transition to played during push.
621 | */
622 | function push(fromViewRef, toViewRef, replaceHowMany, transition) {
623 |
624 | // Rendering view if required
625 | appendView(toViewRef.instance, this);
626 |
627 | transition = transition || this.defaultPushTransition || (this.defaultPushTransition = new SlideEffect({direction:'left'}));
628 | transition.play(fromViewRef ? fromViewRef.instance.$el : null, toViewRef.instance.$el,
629 | function () { // Callback function
630 |
631 | var remove = replaceHowMany > 0 ? this.viewsStack.splice(this.viewsStack.length - replaceHowMany, replaceHowMany)
632 | : (fromViewRef ? [fromViewRef] : null);
633 |
634 | if (remove != null) {
635 | _.each(remove, function (ref) {
636 |
637 | // Triggering viewDeactivate event
638 | createEvent('viewDeactivate', {target:ref.instance}).trigger(ref.instance);
639 |
640 | if (ref.instance.destructionPolicy == 'never') { // Detaching if destructionPolicy == 'never'
641 | ref.instance.$el.detach();
642 | } else { // Removing if destructionPolicy == 'auto'
643 | ref.instance.remove();
644 | ref.instance = null;
645 | }
646 | }, this);
647 | }
648 |
649 | // Adding view to the stack internal array
650 | this.viewsStack.push(toViewRef);
651 |
652 | // Setting activeView property
653 | this.activeView = toViewRef.instance;
654 |
655 | // Triggering viewActivate event
656 | createEvent('viewActivate', {target:toViewRef.instance}).trigger(toViewRef.instance);
657 |
658 | // Triggering viewChanged event
659 | createEvent('viewChanged', {target:this}).trigger(this);
660 |
661 | // Popping item from actions queue
662 | popActionsQueue.call(this);
663 |
664 | }, this);
665 | }
666 |
667 | /**
668 | * Private common pop method.
669 | * @private
670 | * @ignore
671 | *
672 | * @param {object} fromViewRef Reference to from view.
673 | * @param {object} toViewRef Reference to to view.
674 | * @param {number} howMany Number of views to pop from the stack.
675 | * @param {Effect} transition Transition to played during pop.
676 | */
677 | function pop(fromViewRef, toViewRef, howMany, transition) {
678 |
679 | if (toViewRef) {
680 | // Recreating view instance if necessary
681 | toViewRef.instance = toViewRef.instance ? toViewRef.instance : new toViewRef.viewClass(toViewRef.options);
682 | // Rendering view if required
683 | appendView(toViewRef.instance, this);
684 | }
685 |
686 | transition = transition || this.defaultPopTransition || (this.defaultPopTransition = new SlideEffect({direction:'right'}));
687 | transition.play(fromViewRef.instance.$el, toViewRef ? toViewRef.instance.$el : null,
688 | function () { // Callback function
689 |
690 | // Popping views from a stack
691 | var remove = this.viewsStack.splice(this.viewsStack.length - howMany, howMany);
692 | _.each(remove, function (ref) {
693 |
694 | // Triggering viewDeactivate event
695 | createEvent('viewDeactivate', {target:ref.instance}).trigger(ref.instance);
696 |
697 | if (ref.instance.destructionPolicy == 'never') { // Detaching if destructionPolicy == 'never'
698 | ref.instance.$el.detach();
699 | } else { // Removing if destructionPolicy == 'auto'
700 | ref.instance.remove();
701 | ref.instance = null;
702 | }
703 | }, this);
704 |
705 | if (toViewRef) { // If toViewRef exists activating it
706 |
707 | // Setting activeView property
708 | this.activeView = toViewRef.instance;
709 |
710 | // Triggering viewActivate event
711 | createEvent('viewActivate', {target:toViewRef.instance}).trigger(toViewRef.instance);
712 |
713 | } else { // Nulling activeView property
714 | this.activeView = null;
715 | }
716 |
717 | // Triggering viewChanged event
718 | createEvent('viewChanged', {target:this}).trigger(this);
719 |
720 | // Popping item from actions queue
721 | popActionsQueue.call(this);
722 | }, this);
723 | }
724 |
725 | function pushView(view, viewOptions, transition) {
726 | // Getting ref of the view on top of the stack
727 | var fromViewRef = _.last(this.viewsStack),
728 | // Creating new view instance if it is necessary
729 | toView = _.isFunction(view) ? new view(viewOptions) : view,
730 | // Creating new view ref
731 | toViewRef = {instance:toView, viewClass:toView.constructor, options:viewOptions},
732 | // Creating viewChanging event object and triggering it
733 | event = createEvent('viewChanging',
734 | {
735 | action:'push',
736 | fromViewClass:fromViewRef ? fromViewRef.viewClass : null,
737 | fromView:fromViewRef ? fromViewRef.instance : null,
738 | toViewClass:toViewRef.viewClass,
739 | toView:toViewRef.instance
740 | },
741 | true).trigger(this);
742 |
743 | // Checking if event wasn't cancelled
744 | if (event.isDefaultPrevented()) return null;
745 |
746 | push.call(this, fromViewRef, toViewRef, 0, transition);
747 | }
748 |
749 | function popView(transition) {
750 | if (this.viewsStack.length == 0) throw new Error('Popping from an empty stack!');
751 |
752 | // Getting ref of the view on top of the stack
753 | var fromViewRef = _.last(this.viewsStack),
754 | // Getting ref of the view below current one
755 | toViewRef = this.viewsStack.length > 1 ? this.viewsStack[this.viewsStack.length - 2] : null,
756 | // Creating viewChanging event object and triggering it
757 | event = createEvent('viewChanging',
758 | {
759 | action:'pop',
760 | fromViewClass:fromViewRef.viewClass,
761 | fromView:fromViewRef.instance,
762 | toViewClass:toViewRef ? toViewRef.viewClass : null,
763 | toView:toViewRef ? toViewRef.instance : null
764 | },
765 | true).trigger(this);
766 |
767 | // Checking if event wasn't cancelled
768 | if (event.isDefaultPrevented()) return;
769 |
770 | // Popping top view
771 | pop.call(this, fromViewRef, toViewRef, 1, transition);
772 | }
773 |
774 | function popAll(transition) {
775 | if (this.viewsStack.length == 0) throw new Error('Popping from an empty stack!');
776 |
777 | // Getting ref of the view on top of the stack
778 | var fromViewRef = _.last(this.viewsStack),
779 | // Creating viewChanging event object and triggering it
780 | event = createEvent('viewChanging',
781 | {
782 | action:'popAll',
783 | fromViewClass:fromViewRef.viewClass,
784 | fromView:fromViewRef.instance,
785 | toViewClass:null,
786 | toView:null
787 | },
788 | true).trigger(this);
789 |
790 | // Checking if event wasn't cancelled
791 | if (event.isDefaultPrevented()) return;
792 |
793 | // Popping top view
794 | pop.call(this, fromViewRef, null, this.viewsStack.length, transition);
795 | }
796 |
797 | function replaceView(view, viewOptions, transition) {
798 | if (this.viewsStack.length == 0) throw new Error('Replacing on an empty stack!');
799 |
800 | // Getting ref of the view on top of the stack
801 | var fromViewRef = _.last(this.viewsStack),
802 | // Creating new view instance if it is necessary
803 | toView = _.isFunction(view) ? new view(viewOptions) : view,
804 | // Creating new view ref
805 | toViewRef = {instance:toView, viewClass:toView.constructor, options:viewOptions},
806 | // Creating viewChanging event object and triggering it
807 | event = createEvent('viewChanging',
808 | {
809 | action:'replace',
810 | fromViewClass:fromViewRef.viewClass,
811 | fromView:fromViewRef.instance,
812 | toViewClass:toViewRef.viewClass,
813 | toView:toViewRef.instance
814 | },
815 | true).trigger(this);
816 |
817 | // Checking if event wasn't cancelled
818 | if (event.isDefaultPrevented()) return null;
819 |
820 | // Pushing new view on top
821 | push.call(this, fromViewRef, toViewRef, 1, transition);
822 | }
823 |
824 | function replaceAll(view, viewOptions, transition) {
825 | if (this.viewsStack.length == 0) throw new Error('Replacing on an empty stack!');
826 |
827 | // Getting ref of the view on top of the stack
828 | var fromViewRef = _.last(this.viewsStack),
829 | // Creating new view instance if it is necessary
830 | toView = _.isFunction(view) ? new view(viewOptions) : view,
831 | // Creating new view ref
832 | toViewRef = {instance:toView, viewClass:toView.constructor, options:viewOptions},
833 | // Creating viewChanging event object and triggering it
834 | event = createEvent('viewChanging',
835 | {
836 | action:'replaceAll',
837 | fromViewClass:fromViewRef.viewClass,
838 | fromView:fromViewRef.instance,
839 | toViewClass:toViewRef.viewClass,
840 | toView:toViewRef.instance
841 | },
842 | true).trigger(this);
843 |
844 | // Checking if event wasn't cancelled
845 | if (event.isDefaultPrevented()) return null;
846 |
847 | // Pushing new view on top
848 | push.call(this, fromViewRef, toViewRef, this.viewsStack.length, transition);
849 | }
850 |
851 | function popActionsQueue() {
852 | this.actionsQueue.splice(0, 1);
853 | if (this.actionsQueue.length > 0) {
854 | var action = this.actionsQueue[0],
855 | args = Array.prototype.slice.call(action.args);
856 | switch (action.fn) {
857 | case 'pushView':
858 | pushView.apply(this, args);
859 | break;
860 | case 'popView':
861 | popView.apply(this, args);
862 | break;
863 | case 'popAll':
864 | popAll.apply(this, args);
865 | break;
866 | case 'replaceView':
867 | replaceView.apply(this, args);
868 | break;
869 | case 'replaceAll':
870 | replaceAll.apply(this, args);
871 | break;
872 | }
873 | }
874 | }
875 |
876 | var StackNavigator = Backbone.View.extend(
877 | /** @lends BackStack.StackNavigator */
878 | {
879 | /**
880 | * @name StackNavigator#viewChanging
881 | * @event
882 | * @param {Object} e
883 | * @param {Boolean} [e.cancelable=true]
884 | */
885 |
886 | /**
887 | * An array with all the view refs on the stack.
888 | */
889 | viewsStack:null,
890 |
891 | /**
892 | * View on top of the stack.
893 | */
894 | activeView:null,
895 |
896 | /**
897 | * Default push transition effect.
898 | */
899 | defaultPushTransition:null,
900 |
901 | /**
902 | * Default pop transition effect.
903 | */
904 | defaultPopTransition:null,
905 |
906 | /**
907 | * Queue of actions to be executed on the stack.
908 | */
909 | actionsQueue:null,
910 |
911 | /**
912 | * Initializes StackNavigator.
913 | *
914 | * @param {Object} options This is a Backbone.View options hash that can have popTransition and pushTransition
915 | * properties that can be initiated for this instance of navigator.
916 | *
917 | * @constructs
918 | * */
919 | initialize:function (options) {
920 | // Setting default styles
921 | this.$el.css({overflow:'hidden'});
922 |
923 | // Setting new viewsStack array
924 | this.viewsStack = [];
925 |
926 | // Setting new queue of actions
927 | this.actionsQueue = [];
928 |
929 | // Setting default pop transition
930 | if (options.popTransition) this.defaultPopTransition = options.popTransition;
931 |
932 | // Setting default push transition
933 | if (options.pushTransition) this.defaultPushTransition = options.pushTransition;
934 | },
935 |
936 | /**
937 | * Pushes new view to the stack.
938 | *
939 | * @param {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed to the stack.
940 | * @param {Object} viewOptions Options to be passed if view is contructed by StackNavigator.
941 | * @param {Effect} transition Transition effect to be played when pushing new view.
942 | */
943 | pushView:function (view, viewOptions, transition) {
944 | // Pushing current action to the queue
945 | this.actionsQueue.push({fn:'pushView', args:arguments});
946 |
947 | if (this.actionsQueue.length == 1) pushView.call(this, view, viewOptions, transition);
948 | },
949 |
950 | /**
951 | * Pops an active view from a stack and displays one below.
952 | *
953 | * @param {Effect} transition Transition effect to be played when popping new view.
954 | */
955 | popView:function (transition) {
956 | // Pushing current action to the queue
957 | this.actionsQueue.push({fn:'popView', args:arguments});
958 |
959 | if (this.actionsQueue.length == 1) popView.call(this, transition);
960 | },
961 |
962 | /**
963 | * Pops all views from a stack and leaves empty stack.
964 | *
965 | * @param {Effect} transition Transition effect to be played when popping views.
966 | */
967 | popAll:function (transition) {
968 | // Pushing current action to the queue
969 | this.actionsQueue.push({fn:'popAll', args:arguments});
970 |
971 | if (this.actionsQueue.length == 1) popAll.call(this, transition);
972 | },
973 |
974 | /**
975 | * Replaces view on top of the stack, with the one passed as a view param.
976 | *
977 | * @param {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed on top of the stack instead of current one.
978 | * @param {Object} viewOptions Hash with options to be passed to the view, if view param is not an instance.
979 | * @param {Effect} transition Transition effect to be played when replacing views.
980 | */
981 | replaceView:function (view, viewOptions, transition) {
982 | // Pushing current action to the queue
983 | this.actionsQueue.push({fn:'replaceView', args:arguments});
984 |
985 | if (this.actionsQueue.length == 1) replaceView.call(this, view, viewOptions, transition);
986 | },
987 |
988 | /**
989 | * Replaces all of the views on the stack, with the one passed as a view param.
990 | *
991 | * @param {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed on top of the stack.
992 | * @param {Object} viewOptions Hash with options to be passed to the view, if view param is not an instance.
993 | * @param {Effect} transition Transition effect to be played when replacing views.
994 | */
995 | replaceAll:function (view, viewOptions, transition) {
996 | // Pushing current action to the queue
997 | this.actionsQueue.push({fn:'replaceAll', args:arguments});
998 |
999 | if (this.actionsQueue.length == 1) replaceAll.call(this, view, viewOptions, transition);
1000 | }
1001 | });
1002 |
1003 | return StackNavigator;
1004 | });
1005 | define('effects/FadeEffect',['effects/Effect'], function (Effect) {
1006 |
1007 | var FadeEffect = Effect.extend({
1008 |
1009 | fromViewTransitionProps:{duration:0.4, easing:'linear', delay:0.1},
1010 |
1011 | toViewTransitionProps:{duration:0.4, easing:'linear', delay:0.1},
1012 |
1013 | play:function ($fromView, $toView, callback, context) {
1014 |
1015 | var that = this,
1016 | timeout,
1017 | activeTransitions = 0,
1018 | transitionProp = that.vendorPrefix == '' ? 'transition'
1019 | : ['-' + that.vendorPrefix.toLowerCase(), '-', 'transition'].join('');
1020 |
1021 | var transitionEndHandler = function (event) {
1022 | if (activeTransitions >= 0) {
1023 | activeTransitions--;
1024 |
1025 | $(event.target).css(transitionProp, '');
1026 |
1027 | if (activeTransitions == 0 && callback) {
1028 | if (timeout) clearTimeout(timeout);
1029 | callback.call(context);
1030 | }
1031 | }
1032 | };
1033 |
1034 | if ($fromView) {
1035 | activeTransitions++;
1036 |
1037 | // Registering transition end handler
1038 | $fromView.one(that.transitionEndEvent, transitionEndHandler);
1039 |
1040 | // Setting transition css props
1041 | $fromView.css(transitionProp, ['opacity ', that.fromViewTransitionProps.duration, 's ',
1042 | that.fromViewTransitionProps.easing, ' ',
1043 | that.fromViewTransitionProps.delay, 's'].join(''));
1044 | }
1045 |
1046 | if ($toView) {
1047 | activeTransitions++;
1048 |
1049 | $toView.one(that.transitionEndEvent, transitionEndHandler);
1050 |
1051 | // Setting initial opacity
1052 | $toView.css('opacity', 0);
1053 |
1054 | // Setting transition css props
1055 | $toView.css(transitionProp, ['opacity ', that.toViewTransitionProps.duration, 's ',
1056 | that.toViewTransitionProps.easing, ' ',
1057 | that.toViewTransitionProps.delay, 's'].join(''));
1058 |
1059 | // Showing the view
1060 | $toView.css('visibility', 'visible');
1061 | }
1062 |
1063 | // This is a hack to force DOM reflow before transition starts
1064 | context.$el.css('width');
1065 |
1066 | // This is a fallback for situations when TransitionEnd event doesn't get triggered
1067 | var transDuration = Math.max(that.fromViewTransitionProps.duration, that.toViewTransitionProps.duration) +
1068 | Math.max(that.fromViewTransitionProps.delay, that.toViewTransitionProps.delay);
1069 |
1070 | timeout = setTimeout(function () {
1071 | if (activeTransitions > 0) {
1072 | activeTransitions = -1;
1073 |
1074 | console.log('Warning ' + that.transitionEndEvent + ' didn\'t trigger in expected time!');
1075 |
1076 | if ($toView) {
1077 | $toView.off(that.transitionEndEvent, transitionEndHandler);
1078 | $toView.css(transitionProp, '');
1079 | }
1080 |
1081 | if ($fromView) {
1082 | $fromView.off(that.transitionEndEvent, transitionEndHandler);
1083 | $fromView.css(transitionProp, '');
1084 | }
1085 |
1086 | callback.call(context);
1087 | }
1088 | }, transDuration * 1.5 * 1000);
1089 |
1090 | if ($toView) $toView.css('opacity', 1);
1091 | if ($fromView) $fromView.css('opacity', 0);
1092 | }
1093 | });
1094 |
1095 | return FadeEffect;
1096 | });
1097 | define('effects/NoEffect',['effects/Effect'], function (Effect) {
1098 |
1099 | var NoEffect = Effect.extend();
1100 | NoEffect.prototype.play = function ($fromView, $toView, callback, context) {
1101 | if ($toView) {
1102 | // Showing the view
1103 | $toView.css('visibility', 'visible');
1104 | }
1105 | callback.call(context);
1106 | };
1107 |
1108 | return NoEffect;
1109 | });
1110 | return {
1111 | StackNavigator : require('StackNavigator'),
1112 | Effect : require('effects/Effect'),
1113 | NoEffect : require('effects/NoEffect'),
1114 | SlideEffect : require('effects/SlideEffect'),
1115 | FadeEffect : require('effects/FadeEffect')
1116 | };
1117 | }));
--------------------------------------------------------------------------------
/build/almond.js:
--------------------------------------------------------------------------------
1 | /**
2 | * almond 0.1.1 Copyright (c) 2011, The Dojo Foundation All Rights Reserved.
3 | * Available via the MIT or new BSD license.
4 | * see: http://github.com/jrburke/almond for details
5 | */
6 | //Going sloppy to avoid 'use strict' string cost, but strict practices should
7 | //be followed.
8 | /*jslint sloppy: true */
9 | /*global setTimeout: false */
10 |
11 | var requirejs, require, define;
12 | (function (undef) {
13 | var defined = {},
14 | waiting = {},
15 | config = {},
16 | defining = {},
17 | aps = [].slice,
18 | main, req;
19 |
20 | /**
21 | * Given a relative module name, like ./something, normalize it to
22 | * a real name that can be mapped to a path.
23 | * @param {String} name the relative name
24 | * @param {String} baseName a real name that the name arg is relative
25 | * to.
26 | * @returns {String} normalized name
27 | */
28 | function normalize(name, baseName) {
29 | var baseParts = baseName && baseName.split("/"),
30 | map = config.map,
31 | starMap = (map && map['*']) || {},
32 | nameParts, nameSegment, mapValue, foundMap, i, j, part;
33 |
34 | //Adjust any relative paths.
35 | if (name && name.charAt(0) === ".") {
36 | //If have a base name, try to normalize against it,
37 | //otherwise, assume it is a top-level require that will
38 | //be relative to baseUrl in the end.
39 | if (baseName) {
40 | //Convert baseName to array, and lop off the last part,
41 | //so that . matches that "directory" and not name of the baseName's
42 | //module. For instance, baseName of "one/two/three", maps to
43 | //"one/two/three.js", but we want the directory, "one/two" for
44 | //this normalization.
45 | baseParts = baseParts.slice(0, baseParts.length - 1);
46 |
47 | name = baseParts.concat(name.split("/"));
48 |
49 | //start trimDots
50 | for (i = 0; (part = name[i]); i++) {
51 | if (part === ".") {
52 | name.splice(i, 1);
53 | i -= 1;
54 | } else if (part === "..") {
55 | if (i === 1 && (name[2] === '..' || name[0] === '..')) {
56 | //End of the line. Keep at least one non-dot
57 | //path segment at the front so it can be mapped
58 | //correctly to disk. Otherwise, there is likely
59 | //no path mapping for a path starting with '..'.
60 | //This can still fail, but catches the most reasonable
61 | //uses of ..
62 | return true;
63 | } else if (i > 0) {
64 | name.splice(i - 1, 2);
65 | i -= 2;
66 | }
67 | }
68 | }
69 | //end trimDots
70 |
71 | name = name.join("/");
72 | }
73 | }
74 |
75 | //Apply map config if available.
76 | if ((baseParts || starMap) && map) {
77 | nameParts = name.split('/');
78 |
79 | for (i = nameParts.length; i > 0; i -= 1) {
80 | nameSegment = nameParts.slice(0, i).join("/");
81 |
82 | if (baseParts) {
83 | //Find the longest baseName segment match in the config.
84 | //So, do joins on the biggest to smallest lengths of baseParts.
85 | for (j = baseParts.length; j > 0; j -= 1) {
86 | mapValue = map[baseParts.slice(0, j).join('/')];
87 |
88 | //baseName segment has config, find if it has one for
89 | //this name.
90 | if (mapValue) {
91 | mapValue = mapValue[nameSegment];
92 | if (mapValue) {
93 | //Match, update name to the new value.
94 | foundMap = mapValue;
95 | break;
96 | }
97 | }
98 | }
99 | }
100 |
101 | foundMap = foundMap || starMap[nameSegment];
102 |
103 | if (foundMap) {
104 | nameParts.splice(0, i, foundMap);
105 | name = nameParts.join('/');
106 | break;
107 | }
108 | }
109 | }
110 |
111 | return name;
112 | }
113 |
114 | function makeRequire(relName, forceSync) {
115 | return function () {
116 | //A version of a require function that passes a moduleName
117 | //value for items that may need to
118 | //look up paths relative to the moduleName
119 | return req.apply(undef, aps.call(arguments, 0).concat([relName, forceSync]));
120 | };
121 | }
122 |
123 | function makeNormalize(relName) {
124 | return function (name) {
125 | return normalize(name, relName);
126 | };
127 | }
128 |
129 | function makeLoad(depName) {
130 | return function (value) {
131 | defined[depName] = value;
132 | };
133 | }
134 |
135 | function callDep(name) {
136 | if (waiting.hasOwnProperty(name)) {
137 | var args = waiting[name];
138 | delete waiting[name];
139 | defining[name] = true;
140 | main.apply(undef, args);
141 | }
142 |
143 | if (!defined.hasOwnProperty(name)) {
144 | throw new Error('No ' + name);
145 | }
146 | return defined[name];
147 | }
148 |
149 | /**
150 | * Makes a name map, normalizing the name, and using a plugin
151 | * for normalization if necessary. Grabs a ref to plugin
152 | * too, as an optimization.
153 | */
154 | function makeMap(name, relName) {
155 | var prefix, plugin,
156 | index = name.indexOf('!');
157 |
158 | if (index !== -1) {
159 | prefix = normalize(name.slice(0, index), relName);
160 | name = name.slice(index + 1);
161 | plugin = callDep(prefix);
162 |
163 | //Normalize according
164 | if (plugin && plugin.normalize) {
165 | name = plugin.normalize(name, makeNormalize(relName));
166 | } else {
167 | name = normalize(name, relName);
168 | }
169 | } else {
170 | name = normalize(name, relName);
171 | }
172 |
173 | //Using ridiculous property names for space reasons
174 | return {
175 | f: prefix ? prefix + '!' + name : name, //fullName
176 | n: name,
177 | p: plugin
178 | };
179 | }
180 |
181 | function makeConfig(name) {
182 | return function () {
183 | return (config && config.config && config.config[name]) || {};
184 | };
185 | }
186 |
187 | main = function (name, deps, callback, relName) {
188 | var args = [],
189 | usingExports,
190 | cjsModule, depName, ret, map, i;
191 |
192 | //Use name if no relName
193 | relName = relName || name;
194 |
195 | //Call the callback to define the module, if necessary.
196 | if (typeof callback === 'function') {
197 |
198 | //Pull out the defined dependencies and pass the ordered
199 | //values to the callback.
200 | //Default to [require, exports, module] if no deps
201 | deps = !deps.length && callback.length ? ['require', 'exports', 'module'] : deps;
202 | for (i = 0; i < deps.length; i++) {
203 | map = makeMap(deps[i], relName);
204 | depName = map.f;
205 |
206 | //Fast path CommonJS standard dependencies.
207 | if (depName === "require") {
208 | args[i] = makeRequire(name);
209 | } else if (depName === "exports") {
210 | //CommonJS module spec 1.1
211 | args[i] = defined[name] = {};
212 | usingExports = true;
213 | } else if (depName === "module") {
214 | //CommonJS module spec 1.1
215 | cjsModule = args[i] = {
216 | id: name,
217 | uri: '',
218 | exports: defined[name],
219 | config: makeConfig(name)
220 | };
221 | } else if (defined.hasOwnProperty(depName) || waiting.hasOwnProperty(depName)) {
222 | args[i] = callDep(depName);
223 | } else if (map.p) {
224 | map.p.load(map.n, makeRequire(relName, true), makeLoad(depName), {});
225 | args[i] = defined[depName];
226 | } else if (!defining[depName]) {
227 | throw new Error(name + ' missing ' + depName);
228 | }
229 | }
230 |
231 | ret = callback.apply(defined[name], args);
232 |
233 | if (name) {
234 | //If setting exports via "module" is in play,
235 | //favor that over return value and exports. After that,
236 | //favor a non-undefined return value over exports use.
237 | if (cjsModule && cjsModule.exports !== undef &&
238 | cjsModule.exports !== defined[name]) {
239 | defined[name] = cjsModule.exports;
240 | } else if (ret !== undef || !usingExports) {
241 | //Use the return value from the function.
242 | defined[name] = ret;
243 | }
244 | }
245 | } else if (name) {
246 | //May just be an object definition for the module. Only
247 | //worry about defining if have a module name.
248 | defined[name] = callback;
249 | }
250 | };
251 |
252 | requirejs = require = req = function (deps, callback, relName, forceSync) {
253 | if (typeof deps === "string") {
254 | //Just return the module wanted. In this scenario, the
255 | //deps arg is the module name, and second arg (if passed)
256 | //is just the relName.
257 | //Normalize module name, if it contains . or ..
258 | return callDep(makeMap(deps, callback).f);
259 | } else if (!deps.splice) {
260 | //deps is a config object, not an array.
261 | config = deps;
262 | if (callback.splice) {
263 | //callback is an array, which means it is a dependency list.
264 | //Adjust args if there are dependencies
265 | deps = callback;
266 | callback = relName;
267 | relName = null;
268 | } else {
269 | deps = undef;
270 | }
271 | }
272 |
273 | //Support require(['a'])
274 | callback = callback || function () {};
275 |
276 | //Simulate async callback;
277 | if (forceSync) {
278 | main(undef, deps, callback, relName);
279 | } else {
280 | setTimeout(function () {
281 | main(undef, deps, callback, relName);
282 | }, 15);
283 | }
284 |
285 | return req;
286 | };
287 |
288 | /**
289 | * Just drops the config on the floor, but returns req in case
290 | * the config return value is used.
291 | */
292 | req.config = function (cfg) {
293 | config = cfg;
294 | return req;
295 | };
296 |
297 | define = function (name, deps, callback) {
298 |
299 | //This module may not have dependencies
300 | if (!deps.splice) {
301 | //deps is not an array, so probably means
302 | //an object literal or factory function for
303 | //the value. Adjust args.
304 | callback = deps;
305 | deps = [];
306 | }
307 |
308 | waiting[name] = [name, deps, callback];
309 | };
310 |
311 | define.amd = {
312 | jQuery: true
313 | };
314 | }());
315 |
--------------------------------------------------------------------------------
/build/build.js:
--------------------------------------------------------------------------------
1 | ({
2 | baseUrl:"../src",
3 | paths:{
4 | "almond":"../build/almond"
5 | },
6 | include:["almond", "StackNavigator", "effects/Effect", "effects/FadeEffect", "effects/NoEffect", "effects/SlideEffect"],
7 | preserveLicenseComments:true,
8 | out:"backstack-built.js",
9 | wrap:{
10 | startFile:"wrap-start.frag",
11 | endFile:"wrap-end.frag"
12 | },
13 | optimize:"none"
14 | })
--------------------------------------------------------------------------------
/build/build.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | /usr/local/bin/node r.js -o build.js out=../backstack.js
4 | /usr/local/bin/node r.js -o build.js out=../backstack-min.js optimize=uglify
5 |
--------------------------------------------------------------------------------
/build/wrap-end.frag:
--------------------------------------------------------------------------------
1 |
2 | return {
3 | StackNavigator : require('StackNavigator'),
4 | Effect : require('effects/Effect'),
5 | NoEffect : require('effects/NoEffect'),
6 | SlideEffect : require('effects/SlideEffect'),
7 | FadeEffect : require('effects/FadeEffect')
8 | };
9 | }));
--------------------------------------------------------------------------------
/build/wrap-start.frag:
--------------------------------------------------------------------------------
1 | //////////////////////////////////////////////////////////////////////////////////////
2 | //
3 | // Copyright 2012 Piotr Walczyszyn (http://outof.me | @pwalczyszyn)
4 | //
5 | // Licensed under the Apache License, Version 2.0 (the "License");
6 | // you may not use this file except in compliance with the License.
7 | // You may obtain a copy of the License at
8 | //
9 | // http://www.apache.org/licenses/LICENSE-2.0
10 | //
11 | // Unless required by applicable law or agreed to in writing, software
12 | // distributed under the License is distributed on an "AS IS" BASIS,
13 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 | // See the License for the specific language governing permissions and
15 | // limitations under the License.
16 | //
17 | //////////////////////////////////////////////////////////////////////////////////////
18 |
19 | // BackStack version 1.1.2
20 |
21 | (function (root, factory) {
22 | // Set up BackStack appropriately for the environment.
23 | if (typeof define === 'function' && define.amd) {
24 | // AMD
25 | define(['jquery', 'underscore', 'Backbone'], factory);
26 | } else {
27 | // Browser globals
28 | root.BackStack = factory((root.jQuery || root.Zepto || root.ender), root._, root.Backbone);
29 | }
30 | }(this, function ($, _, Backbone) {
31 |
--------------------------------------------------------------------------------
/src/StackNavigator.js:
--------------------------------------------------------------------------------
1 | define(['effects/SlideEffect'], function (SlideEffect) {
2 |
3 | /**
4 | * Rendering the view and setting props required by StackNavigator.
5 | * @private
6 | * @ignore
7 | *
8 | * @param {View} view View to be rendered.
9 | * @param {StackNavigator} stackNavigator View StackNavigator instance.
10 | */
11 | function appendView(view, stackNavigator) {
12 |
13 | if (!view.__backStackRendered__) {
14 |
15 | // Setting ref to parent StackNavigator
16 | view.stackNavigator = stackNavigator;
17 |
18 | // Setting default destructionPolicy if it's not set
19 | if (typeof view.destructionPolicy === 'undefined') view.destructionPolicy = 'auto';
20 |
21 | // Setting default styles
22 | view.$el.css({position:'absolute', visibility:'hidden', overflow:'hidden', width:'100%', height:'100%'});
23 |
24 | } else {
25 | // Resetting visibility to hidden
26 | view.$el.css({visibility:'hidden'});
27 | }
28 |
29 | // Adding view to the DOM
30 | stackNavigator.$el.append(view.el);
31 |
32 | if (!view.__backStackRendered__) {
33 | // Rendering the view
34 | view.render.call(view);
35 |
36 | // Setting default of __backStackRendered__ property
37 | view.__backStackRendered__ = true;
38 | }
39 | }
40 |
41 | /**
42 | * Creates event objects triggered by BackStack.
43 | * @private
44 | * @ignore
45 | *
46 | * @param {string} type Event type name.
47 | * @param {*} args Event args.
48 | * @param {boolean} cancelable Flag indicating if event is cancelable.
49 | * @return {event} The new object.
50 | */
51 | function createEvent(type, args, cancelable) {
52 | return _.extend({
53 |
54 | type:type,
55 |
56 | cancelable:_.isUndefined(cancelable) ? false : cancelable,
57 |
58 | preventDefault:function () {
59 | if (this.cancelable)
60 | this.isDefaultPrevented = function () {
61 | return true;
62 | };
63 | },
64 |
65 | isDefaultPrevented:function () {
66 | return false;
67 | },
68 |
69 | trigger:function (target) {
70 | target.trigger(this.type, this);
71 | return this;
72 | }
73 | }, args);
74 | }
75 |
76 | /**
77 | * Private common push method.
78 | * @private
79 | * @ignore
80 | *
81 | * @param {object} fromViewRef Reference to from view.
82 | * @param {object} toViewRef Reference to to view.
83 | * @param {number} replaceHowMany Number of views to replace with pushed view.
84 | * @param {Effect} transition Transition to played during push.
85 | */
86 | function push(fromViewRef, toViewRef, replaceHowMany, transition) {
87 |
88 | // Rendering view if required
89 | appendView(toViewRef.instance, this);
90 |
91 | transition = transition || this.defaultPushTransition || (this.defaultPushTransition = new SlideEffect({direction:'left'}));
92 | transition.play(fromViewRef ? fromViewRef.instance.$el : null, toViewRef.instance.$el,
93 | function () { // Callback function
94 |
95 | var remove = replaceHowMany > 0 ? this.viewsStack.splice(this.viewsStack.length - replaceHowMany, replaceHowMany)
96 | : (fromViewRef ? [fromViewRef] : null);
97 |
98 | if (remove != null) {
99 | _.each(remove, function (ref) {
100 |
101 | // Triggering viewDeactivate event
102 | createEvent('viewDeactivate', {target:ref.instance}).trigger(ref.instance);
103 |
104 | if (ref.instance.destructionPolicy == 'never') { // Detaching if destructionPolicy == 'never'
105 | ref.instance.$el.detach();
106 | } else { // Removing if destructionPolicy == 'auto'
107 | ref.instance.remove();
108 | ref.instance = null;
109 | }
110 | }, this);
111 | }
112 |
113 | // Adding view to the stack internal array
114 | this.viewsStack.push(toViewRef);
115 |
116 | // Setting activeView property
117 | this.activeView = toViewRef.instance;
118 |
119 | // Triggering viewActivate event
120 | createEvent('viewActivate', {target:toViewRef.instance}).trigger(toViewRef.instance);
121 |
122 | // Triggering viewChanged event
123 | createEvent('viewChanged', {target:this}).trigger(this);
124 |
125 | // Popping item from actions queue
126 | popActionsQueue.call(this);
127 |
128 | }, this);
129 | }
130 |
131 | /**
132 | * Private common pop method.
133 | * @private
134 | * @ignore
135 | *
136 | * @param {object} fromViewRef Reference to from view.
137 | * @param {object} toViewRef Reference to to view.
138 | * @param {number} howMany Number of views to pop from the stack.
139 | * @param {Effect} transition Transition to played during pop.
140 | */
141 | function pop(fromViewRef, toViewRef, howMany, transition) {
142 |
143 | if (toViewRef) {
144 | // Recreating view instance if necessary
145 | toViewRef.instance = toViewRef.instance ? toViewRef.instance : new toViewRef.viewClass(toViewRef.options);
146 | // Rendering view if required
147 | appendView(toViewRef.instance, this);
148 | }
149 |
150 | transition = transition || this.defaultPopTransition || (this.defaultPopTransition = new SlideEffect({direction:'right'}));
151 | transition.play(fromViewRef.instance.$el, toViewRef ? toViewRef.instance.$el : null,
152 | function () { // Callback function
153 |
154 | // Popping views from a stack
155 | var remove = this.viewsStack.splice(this.viewsStack.length - howMany, howMany);
156 | _.each(remove, function (ref) {
157 |
158 | // Triggering viewDeactivate event
159 | createEvent('viewDeactivate', {target:ref.instance}).trigger(ref.instance);
160 |
161 | if (ref.instance.destructionPolicy == 'never') { // Detaching if destructionPolicy == 'never'
162 | ref.instance.$el.detach();
163 | } else { // Removing if destructionPolicy == 'auto'
164 | ref.instance.remove();
165 | ref.instance = null;
166 | }
167 | }, this);
168 |
169 | if (toViewRef) { // If toViewRef exists activating it
170 |
171 | // Setting activeView property
172 | this.activeView = toViewRef.instance;
173 |
174 | // Triggering viewActivate event
175 | createEvent('viewActivate', {target:toViewRef.instance}).trigger(toViewRef.instance);
176 |
177 | } else { // Nulling activeView property
178 | this.activeView = null;
179 | }
180 |
181 | // Triggering viewChanged event
182 | createEvent('viewChanged', {target:this}).trigger(this);
183 |
184 | // Popping item from actions queue
185 | popActionsQueue.call(this);
186 | }, this);
187 | }
188 |
189 | function pushView(view, viewOptions, transition) {
190 | // Getting ref of the view on top of the stack
191 | var fromViewRef = _.last(this.viewsStack),
192 | // Creating new view instance if it is necessary
193 | toView = _.isFunction(view) ? new view(viewOptions) : view,
194 | // Creating new view ref
195 | toViewRef = {instance:toView, viewClass:toView.constructor, options:viewOptions},
196 | // Creating viewChanging event object and triggering it
197 | event = createEvent('viewChanging',
198 | {
199 | action:'push',
200 | fromViewClass:fromViewRef ? fromViewRef.viewClass : null,
201 | fromView:fromViewRef ? fromViewRef.instance : null,
202 | toViewClass:toViewRef.viewClass,
203 | toView:toViewRef.instance
204 | },
205 | true).trigger(this);
206 |
207 | // Checking if event wasn't cancelled
208 | if (event.isDefaultPrevented()) return null;
209 |
210 | push.call(this, fromViewRef, toViewRef, 0, transition);
211 | }
212 |
213 | function popView(transition) {
214 | if (this.viewsStack.length == 0) throw new Error('Popping from an empty stack!');
215 |
216 | // Getting ref of the view on top of the stack
217 | var fromViewRef = _.last(this.viewsStack),
218 | // Getting ref of the view below current one
219 | toViewRef = this.viewsStack.length > 1 ? this.viewsStack[this.viewsStack.length - 2] : null,
220 | // Creating viewChanging event object and triggering it
221 | event = createEvent('viewChanging',
222 | {
223 | action:'pop',
224 | fromViewClass:fromViewRef.viewClass,
225 | fromView:fromViewRef.instance,
226 | toViewClass:toViewRef ? toViewRef.viewClass : null,
227 | toView:toViewRef ? toViewRef.instance : null
228 | },
229 | true).trigger(this);
230 |
231 | // Checking if event wasn't cancelled
232 | if (event.isDefaultPrevented()) return;
233 |
234 | // Popping top view
235 | pop.call(this, fromViewRef, toViewRef, 1, transition);
236 | }
237 |
238 | function popAll(transition) {
239 | if (this.viewsStack.length == 0) throw new Error('Popping from an empty stack!');
240 |
241 | // Getting ref of the view on top of the stack
242 | var fromViewRef = _.last(this.viewsStack),
243 | // Creating viewChanging event object and triggering it
244 | event = createEvent('viewChanging',
245 | {
246 | action:'popAll',
247 | fromViewClass:fromViewRef.viewClass,
248 | fromView:fromViewRef.instance,
249 | toViewClass:null,
250 | toView:null
251 | },
252 | true).trigger(this);
253 |
254 | // Checking if event wasn't cancelled
255 | if (event.isDefaultPrevented()) return;
256 |
257 | // Popping top view
258 | pop.call(this, fromViewRef, null, this.viewsStack.length, transition);
259 | }
260 |
261 | function replaceView(view, viewOptions, transition) {
262 | if (this.viewsStack.length == 0) throw new Error('Replacing on an empty stack!');
263 |
264 | // Getting ref of the view on top of the stack
265 | var fromViewRef = _.last(this.viewsStack),
266 | // Creating new view instance if it is necessary
267 | toView = _.isFunction(view) ? new view(viewOptions) : view,
268 | // Creating new view ref
269 | toViewRef = {instance:toView, viewClass:toView.constructor, options:viewOptions},
270 | // Creating viewChanging event object and triggering it
271 | event = createEvent('viewChanging',
272 | {
273 | action:'replace',
274 | fromViewClass:fromViewRef.viewClass,
275 | fromView:fromViewRef.instance,
276 | toViewClass:toViewRef.viewClass,
277 | toView:toViewRef.instance
278 | },
279 | true).trigger(this);
280 |
281 | // Checking if event wasn't cancelled
282 | if (event.isDefaultPrevented()) return null;
283 |
284 | // Pushing new view on top
285 | push.call(this, fromViewRef, toViewRef, 1, transition);
286 | }
287 |
288 | function replaceAll(view, viewOptions, transition) {
289 | if (this.viewsStack.length == 0) throw new Error('Replacing on an empty stack!');
290 |
291 | // Getting ref of the view on top of the stack
292 | var fromViewRef = _.last(this.viewsStack),
293 | // Creating new view instance if it is necessary
294 | toView = _.isFunction(view) ? new view(viewOptions) : view,
295 | // Creating new view ref
296 | toViewRef = {instance:toView, viewClass:toView.constructor, options:viewOptions},
297 | // Creating viewChanging event object and triggering it
298 | event = createEvent('viewChanging',
299 | {
300 | action:'replaceAll',
301 | fromViewClass:fromViewRef.viewClass,
302 | fromView:fromViewRef.instance,
303 | toViewClass:toViewRef.viewClass,
304 | toView:toViewRef.instance
305 | },
306 | true).trigger(this);
307 |
308 | // Checking if event wasn't cancelled
309 | if (event.isDefaultPrevented()) return null;
310 |
311 | // Pushing new view on top
312 | push.call(this, fromViewRef, toViewRef, this.viewsStack.length, transition);
313 | }
314 |
315 | function popActionsQueue() {
316 | this.actionsQueue.splice(0, 1);
317 | if (this.actionsQueue.length > 0) {
318 | var action = this.actionsQueue[0],
319 | args = Array.prototype.slice.call(action.args);
320 | switch (action.fn) {
321 | case 'pushView':
322 | pushView.apply(this, args);
323 | break;
324 | case 'popView':
325 | popView.apply(this, args);
326 | break;
327 | case 'popAll':
328 | popAll.apply(this, args);
329 | break;
330 | case 'replaceView':
331 | replaceView.apply(this, args);
332 | break;
333 | case 'replaceAll':
334 | replaceAll.apply(this, args);
335 | break;
336 | }
337 | }
338 | }
339 |
340 | var StackNavigator = Backbone.View.extend(
341 | /** @lends BackStack.StackNavigator */
342 | {
343 | /**
344 | * @name StackNavigator#viewChanging
345 | * @event
346 | * @param {Object} e
347 | * @param {Boolean} [e.cancelable=true]
348 | */
349 |
350 | /**
351 | * An array with all the view refs on the stack.
352 | */
353 | viewsStack:null,
354 |
355 | /**
356 | * View on top of the stack.
357 | */
358 | activeView:null,
359 |
360 | /**
361 | * Default push transition effect.
362 | */
363 | defaultPushTransition:null,
364 |
365 | /**
366 | * Default pop transition effect.
367 | */
368 | defaultPopTransition:null,
369 |
370 | /**
371 | * Queue of actions to be executed on the stack.
372 | */
373 | actionsQueue:null,
374 |
375 | /**
376 | * Initializes StackNavigator.
377 | *
378 | * @param {Object} options This is a Backbone.View options hash that can have popTransition and pushTransition
379 | * properties that can be initiated for this instance of navigator.
380 | *
381 | * @constructs
382 | * */
383 | initialize:function (options) {
384 | // Setting default styles
385 | this.$el.css({overflow:'hidden'});
386 |
387 | // Setting new viewsStack array
388 | this.viewsStack = [];
389 |
390 | // Setting new queue of actions
391 | this.actionsQueue = [];
392 |
393 | // Setting default pop transition
394 | if (options.popTransition) this.defaultPopTransition = options.popTransition;
395 |
396 | // Setting default push transition
397 | if (options.pushTransition) this.defaultPushTransition = options.pushTransition;
398 | },
399 |
400 | /**
401 | * Pushes new view to the stack.
402 | *
403 | * @param {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed to the stack.
404 | * @param {Object} viewOptions Options to be passed if view is contructed by StackNavigator.
405 | * @param {Effect} transition Transition effect to be played when pushing new view.
406 | */
407 | pushView:function (view, viewOptions, transition) {
408 | // Pushing current action to the queue
409 | this.actionsQueue.push({fn:'pushView', args:arguments});
410 |
411 | if (this.actionsQueue.length == 1) pushView.call(this, view, viewOptions, transition);
412 | },
413 |
414 | /**
415 | * Pops an active view from a stack and displays one below.
416 | *
417 | * @param {Effect} transition Transition effect to be played when popping new view.
418 | */
419 | popView:function (transition) {
420 | // Pushing current action to the queue
421 | this.actionsQueue.push({fn:'popView', args:arguments});
422 |
423 | if (this.actionsQueue.length == 1) popView.call(this, transition);
424 | },
425 |
426 | /**
427 | * Pops all views from a stack and leaves empty stack.
428 | *
429 | * @param {Effect} transition Transition effect to be played when popping views.
430 | */
431 | popAll:function (transition) {
432 | // Pushing current action to the queue
433 | this.actionsQueue.push({fn:'popAll', args:arguments});
434 |
435 | if (this.actionsQueue.length == 1) popAll.call(this, transition);
436 | },
437 |
438 | /**
439 | * Replaces view on top of the stack, with the one passed as a view param.
440 | *
441 | * @param {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed on top of the stack instead of current one.
442 | * @param {Object} viewOptions Hash with options to be passed to the view, if view param is not an instance.
443 | * @param {Effect} transition Transition effect to be played when replacing views.
444 | */
445 | replaceView:function (view, viewOptions, transition) {
446 | // Pushing current action to the queue
447 | this.actionsQueue.push({fn:'replaceView', args:arguments});
448 |
449 | if (this.actionsQueue.length == 1) replaceView.call(this, view, viewOptions, transition);
450 | },
451 |
452 | /**
453 | * Replaces all of the views on the stack, with the one passed as a view param.
454 | *
455 | * @param {Backbone.View || Backbone.ViewClass} view View class or view instance to be pushed on top of the stack.
456 | * @param {Object} viewOptions Hash with options to be passed to the view, if view param is not an instance.
457 | * @param {Effect} transition Transition effect to be played when replacing views.
458 | */
459 | replaceAll:function (view, viewOptions, transition) {
460 | // Pushing current action to the queue
461 | this.actionsQueue.push({fn:'replaceAll', args:arguments});
462 |
463 | if (this.actionsQueue.length == 1) replaceAll.call(this, view, viewOptions, transition);
464 | }
465 | });
466 |
467 | return StackNavigator;
468 | });
--------------------------------------------------------------------------------
/src/effects/Effect.js:
--------------------------------------------------------------------------------
1 | define(['effects/vendorPrefix'], function (vendorPrefix) {
2 |
3 | var Effect = function Effect(params) {
4 |
5 | if (params) _.extend(this, params);
6 |
7 | this.vendorPrefix = vendorPrefix;
8 |
9 | if (this.vendorPrefix == 'moz' || this.vendorPrefix == '') this.transitionEndEvent = 'transitionend';
10 | else if (this.vendorPrefix == 'ms') this.transitionEndEvent = 'MSTransitionEnd';
11 | else this.transitionEndEvent = this.vendorPrefix + 'TransitionEnd';
12 |
13 | };
14 |
15 | // Shared empty constructor function to aid in prototype-chain creation.
16 | var ctor = function () {
17 | };
18 |
19 | Effect.extend = function (protoProps, staticProps) {
20 | var child = function () {
21 | Effect.apply(this, arguments);
22 | };
23 |
24 | // Inherit class (static) properties from parent.
25 | _.extend(child, Effect);
26 |
27 | // Set the prototype chain to inherit from `parent`, without calling
28 | // `parent`'s constructor function.
29 | ctor.prototype = Effect.prototype;
30 | child.prototype = new ctor();
31 |
32 | // Add prototype properties (instance properties) to the subclass,
33 | // if supplied.
34 | if (protoProps) _.extend(child.prototype, protoProps);
35 |
36 | // Add static properties to the constructor function, if supplied.
37 | if (staticProps) _.extend(child, staticProps);
38 |
39 | // Correctly set child's `prototype.constructor`.
40 | child.prototype.constructor = child;
41 |
42 | // Set a convenience property in case the parent's prototype is needed later.
43 | child.__super__ = Effect.prototype;
44 |
45 | return child;
46 | };
47 |
48 | return Effect;
49 | });
--------------------------------------------------------------------------------
/src/effects/FadeEffect.js:
--------------------------------------------------------------------------------
1 | define(['effects/Effect'], function (Effect) {
2 |
3 | var FadeEffect = Effect.extend({
4 |
5 | fromViewTransitionProps:{duration:0.4, easing:'linear', delay:0.1},
6 |
7 | toViewTransitionProps:{duration:0.4, easing:'linear', delay:0.1},
8 |
9 | play:function ($fromView, $toView, callback, context) {
10 |
11 | var that = this,
12 | timeout,
13 | activeTransitions = 0,
14 | transitionProp = that.vendorPrefix == '' ? 'transition'
15 | : ['-' + that.vendorPrefix.toLowerCase(), '-', 'transition'].join('');
16 |
17 | var transitionEndHandler = function (event) {
18 | if (activeTransitions >= 0) {
19 | activeTransitions--;
20 |
21 | $(event.target).css(transitionProp, '');
22 |
23 | if (activeTransitions == 0 && callback) {
24 | if (timeout) clearTimeout(timeout);
25 | callback.call(context);
26 | }
27 | }
28 | };
29 |
30 | if ($fromView) {
31 | activeTransitions++;
32 |
33 | // Registering transition end handler
34 | $fromView.one(that.transitionEndEvent, transitionEndHandler);
35 |
36 | // Setting transition css props
37 | $fromView.css(transitionProp, ['opacity ', that.fromViewTransitionProps.duration, 's ',
38 | that.fromViewTransitionProps.easing, ' ',
39 | that.fromViewTransitionProps.delay, 's'].join(''));
40 | }
41 |
42 | if ($toView) {
43 | activeTransitions++;
44 |
45 | $toView.one(that.transitionEndEvent, transitionEndHandler);
46 |
47 | // Setting initial opacity
48 | $toView.css('opacity', 0);
49 |
50 | // Setting transition css props
51 | $toView.css(transitionProp, ['opacity ', that.toViewTransitionProps.duration, 's ',
52 | that.toViewTransitionProps.easing, ' ',
53 | that.toViewTransitionProps.delay, 's'].join(''));
54 |
55 | // Showing the view
56 | $toView.css('visibility', 'visible');
57 | }
58 |
59 | // This is a hack to force DOM reflow before transition starts
60 | context.$el.css('width');
61 |
62 | // This is a fallback for situations when TransitionEnd event doesn't get triggered
63 | var transDuration = Math.max(that.fromViewTransitionProps.duration, that.toViewTransitionProps.duration) +
64 | Math.max(that.fromViewTransitionProps.delay, that.toViewTransitionProps.delay);
65 |
66 | timeout = setTimeout(function () {
67 | if (activeTransitions > 0) {
68 | activeTransitions = -1;
69 |
70 | console.log('Warning ' + that.transitionEndEvent + ' didn\'t trigger in expected time!');
71 |
72 | if ($toView) {
73 | $toView.off(that.transitionEndEvent, transitionEndHandler);
74 | $toView.css(transitionProp, '');
75 | }
76 |
77 | if ($fromView) {
78 | $fromView.off(that.transitionEndEvent, transitionEndHandler);
79 | $fromView.css(transitionProp, '');
80 | }
81 |
82 | callback.call(context);
83 | }
84 | }, transDuration * 1.5 * 1000);
85 |
86 | if ($toView) $toView.css('opacity', 1);
87 | if ($fromView) $fromView.css('opacity', 0);
88 | }
89 | });
90 |
91 | return FadeEffect;
92 | });
--------------------------------------------------------------------------------
/src/effects/NoEffect.js:
--------------------------------------------------------------------------------
1 | define(['effects/Effect'], function (Effect) {
2 |
3 | var NoEffect = Effect.extend();
4 | NoEffect.prototype.play = function ($fromView, $toView, callback, context) {
5 | if ($toView) {
6 | // Showing the view
7 | $toView.css('visibility', 'visible');
8 | }
9 | callback.call(context);
10 | };
11 |
12 | return NoEffect;
13 | });
--------------------------------------------------------------------------------
/src/effects/SlideEffect.js:
--------------------------------------------------------------------------------
1 | define(['effects/Effect'], function (Effect) {
2 |
3 | var SlideEffect = Effect.extend({
4 |
5 | direction:'left',
6 |
7 | fromViewTransitionProps:{duration:0.4, easing:'ease-out', delay:0},
8 |
9 | toViewTransitionProps:{duration:0.4, easing:'ease-out', delay:0},
10 |
11 | play:function ($fromView, $toView, callback, context) {
12 |
13 | var timeout,
14 | that = this,
15 | activeTransitions = 0,
16 | transformParams,
17 | transformProp = that.vendorPrefix == '' ? 'transform' :
18 | ['-' + that.vendorPrefix, '-', 'transform'].join(''),
19 | transitionProp = that.vendorPrefix == '' ? 'transition' :
20 | ['-' + that.vendorPrefix, '-', 'transition'].join('');
21 |
22 | var transitionEndHandler = function (event) {
23 | if (activeTransitions >= 0) {
24 | activeTransitions--;
25 |
26 | var $target = $(event.target);
27 | $target.css(transformProp, '');
28 | $target.css(transitionProp, '');
29 |
30 | if ($toView && $toView[0] == event.target) $toView.css('left', 0);
31 |
32 | if (activeTransitions == 0 && callback) {
33 | if (timeout) clearTimeout(timeout);
34 | callback.call(context);
35 | }
36 | }
37 | };
38 |
39 | if ($fromView) {
40 | activeTransitions++;
41 |
42 | $fromView.one(that.transitionEndEvent, transitionEndHandler);
43 |
44 | $fromView.css('left', 0);
45 | $fromView.css(transitionProp, [transformProp, ' ',
46 | that.fromViewTransitionProps.duration, 's ',
47 | that.fromViewTransitionProps.easing, ' ',
48 | that.fromViewTransitionProps.delay, 's'].join(''));
49 | }
50 |
51 | if ($toView) {
52 | activeTransitions++;
53 |
54 | $toView.one(that.transitionEndEvent, transitionEndHandler);
55 |
56 | $toView.css('left', that.direction == 'left' ? context.$el.width() : -context.$el.width());
57 | $toView.css(transitionProp, [transformProp, ' ',
58 | that.toViewTransitionProps.duration, 's ',
59 | that.toViewTransitionProps.easing, ' ',
60 | that.toViewTransitionProps.delay, 's'].join(''));
61 |
62 | // Showing the view
63 | $toView.css('visibility', 'visible');
64 | }
65 |
66 | if ($fromView || $toView) {
67 | // This is a hack to force DOM reflow before transition starts
68 | context.$el.css('width');
69 |
70 | transformParams = 'translate3d(' + (that.direction == 'left' ? -context.$el.width() : context.$el.width()) + 'px, 0, 0)';
71 | }
72 |
73 | // This is a fallback for situations when TransitionEnd event doesn't get triggered
74 | var transDuration = Math.max(that.fromViewTransitionProps.duration, that.toViewTransitionProps.duration) +
75 | Math.max(that.fromViewTransitionProps.delay, that.toViewTransitionProps.delay);
76 |
77 | timeout = setTimeout(function () {
78 | if (activeTransitions > 0) {
79 | activeTransitions = -1;
80 |
81 | console.log('Warning ' + that.transitionEndEvent + ' didn\'t trigger in expected time!');
82 |
83 | if ($toView) {
84 | $toView.off(that.transitionEndEvent, transitionEndHandler);
85 | $toView.css(transitionProp, '');
86 | $toView.css(transformProp, '');
87 | $toView.css('left', 0);
88 | }
89 |
90 | if ($fromView) {
91 | $fromView.off(that.transitionEndEvent, transitionEndHandler);
92 | $fromView.css(transitionProp, '');
93 | $fromView.css(transformProp, '');
94 | }
95 |
96 | callback.call(context);
97 | }
98 | }, transDuration * 1.5 * 1000);
99 |
100 | var $views;
101 | if ($fromView && $toView) $views = $fromView.add($toView);
102 | else if ($toView) $views = $toView;
103 | else if ($fromView) $views = $fromView;
104 |
105 | if ($views) $views.css(transformProp, transformParams);
106 | }
107 | });
108 |
109 | return SlideEffect;
110 | });
--------------------------------------------------------------------------------
/src/effects/vendorPrefix.js:
--------------------------------------------------------------------------------
1 | define([], function () {
2 |
3 | /**
4 | * Helper function to detect browser vendor prefix.
5 | * Thanks to Lea Verou: http://lea.verou.me/2009/02/find-the-vendor-prefix-of-the-current-browser/
6 | * I just modified it slightly as I expect it to be used in mobile/WebKit scenarios mostly.
7 | */
8 | var vendorPrefix,
9 | regex = /^(Moz|Webkit|Khtml|O|ms|Icab)(?=[A-Z])/,
10 | someScript = document.getElementsByTagName('script')[0];
11 |
12 | // Exception for WebKit based browsers
13 | if ('WebkitOpacity' in someScript.style) {
14 | vendorPrefix = 'Webkit';
15 | } else if ('KhtmlOpacity' in someScript.style) {
16 | vendorPrefix = 'Khtml';
17 | } else {
18 | for (var prop in someScript.style) {
19 | if (regex.test(prop)) {
20 | // test is faster than match, so it's better to perform
21 | // that on the lot and match only when necessary
22 | vendorPrefix = prop.match(regex)[0];
23 | break;
24 | }
25 | }
26 | }
27 |
28 | return (vendorPrefix.toLowerCase() || '');
29 | });
--------------------------------------------------------------------------------