pushpop-rails 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (31) hide show
  1. data/README.md +42 -0
  2. data/Rakefile +29 -0
  3. data/lib/generators/pushpop/install/install_generator.rb +17 -0
  4. data/lib/pushpop-rails.rb +4 -0
  5. data/lib/pushpop/rails.rb +8 -0
  6. data/lib/pushpop/rails/engine.rb +7 -0
  7. data/lib/pushpop/rails/version.rb +5 -0
  8. data/vendor/assets/Pushpop/background.png +0 -0
  9. data/vendor/assets/Pushpop/background@2x.png +0 -0
  10. data/vendor/assets/Pushpop/externals/scrollkit/background.png +0 -0
  11. data/vendor/assets/Pushpop/externals/scrollkit/scrollkit.css +202 -0
  12. data/vendor/assets/Pushpop/externals/scrollkit/scrollkit.js +924 -0
  13. data/vendor/assets/Pushpop/font/pushpop-glyphs-webfont.eot +0 -0
  14. data/vendor/assets/Pushpop/font/pushpop-glyphs-webfont.svg +57 -0
  15. data/vendor/assets/Pushpop/font/pushpop-glyphs-webfont.ttf +0 -0
  16. data/vendor/assets/Pushpop/font/pushpop-glyphs-webfont.woff +0 -0
  17. data/vendor/assets/Pushpop/pushpop-modal-view-stack/pushpop-modal-view-stack.css +148 -0
  18. data/vendor/assets/Pushpop/pushpop-modal-view-stack/pushpop-modal-view-stack.js +306 -0
  19. data/vendor/assets/Pushpop/pushpop-popover-view-stack/pushpop-popover-view-stack.css +170 -0
  20. data/vendor/assets/Pushpop/pushpop-popover-view-stack/pushpop-popover-view-stack.js +278 -0
  21. data/vendor/assets/Pushpop/pushpop-split-view/pushpop-split-view.css +38 -0
  22. data/vendor/assets/Pushpop/pushpop-split-view/pushpop-split-view.js +33 -0
  23. data/vendor/assets/Pushpop/pushpop-tab-view/pushpop-tab-view.css +130 -0
  24. data/vendor/assets/Pushpop/pushpop-tab-view/pushpop-tab-view.js +298 -0
  25. data/vendor/assets/Pushpop/pushpop-table-view/pushpop-table-view.css +1273 -0
  26. data/vendor/assets/Pushpop/pushpop-table-view/pushpop-table-view.js +2275 -0
  27. data/vendor/assets/Pushpop/pushpop.css +2243 -0
  28. data/vendor/assets/Pushpop/pushpop.js +1554 -0
  29. data/vendor/assets/javascripts/pushpop_rails.js +7 -0
  30. data/vendor/assets/stylesheets/pushpop_rails.css +9 -0
  31. metadata +92 -0
@@ -0,0 +1,1554 @@
1
+ ;'use strict';
2
+
3
+ // Load custom build of Modernizr if an appropriate build has not been loaded.
4
+ if (!window['Modernizr'] || Modernizr.touch === undefined || Modernizr.csstransitions === undefined || Modernizr.csstransforms === undefined || Modernizr.csstransforms3d === undefined) {
5
+
6
+ /* Modernizr 2.5.2 (Custom Build) | MIT & BSD
7
+ * Build: http://www.modernizr.com/download/#-csstransforms-csstransforms3d-csstransitions-touch-cssclasses-teststyles-testprop-testallprops-prefixes-domprefixes
8
+ */
9
+ ;window.Modernizr=function(a,b,c){function z(a){j.cssText=a}function A(a,b){return z(m.join(a+";")+(b||""))}function B(a,b){return typeof a===b}function C(a,b){return!!~(""+a).indexOf(b)}function D(a,b){for(var d in a)if(j[a[d]]!==c)return b=="pfx"?a[d]:!0;return!1}function E(a,b,d){for(var e in a){var f=b[a[e]];if(f!==c)return d===!1?a[e]:B(f,"function")?f.bind(d||b):f}return!1}function F(a,b,c){var d=a.charAt(0).toUpperCase()+a.substr(1),e=(a+" "+o.join(d+" ")+d).split(" ");return B(b,"string")||B(b,"undefined")?D(e,b):(e=(a+" "+p.join(d+" ")+d).split(" "),E(e,b,c))}var d="2.5.2",e={},f=!0,g=b.documentElement,h="modernizr",i=b.createElement(h),j=i.style,k,l={}.toString,m=" -webkit- -moz- -o- -ms- ".split(" "),n="Webkit Moz O ms",o=n.split(" "),p=n.toLowerCase().split(" "),q={},r={},s={},t=[],u=t.slice,v,w=function(a,c,d,e){var f,i,j,k=b.createElement("div"),l=b.body,m=l?l:b.createElement("body");if(parseInt(d,10))while(d--)j=b.createElement("div"),j.id=e?e[d]:h+(d+1),k.appendChild(j);return f=["&#173;","<style>",a,"</style>"].join(""),k.id=h,m.innerHTML+=f,m.appendChild(k),l||g.appendChild(m),i=c(k,a),l?k.parentNode.removeChild(k):m.parentNode.removeChild(m),!!i},x={}.hasOwnProperty,y;!B(x,"undefined")&&!B(x.call,"undefined")?y=function(a,b){return x.call(a,b)}:y=function(a,b){return b in a&&B(a.constructor.prototype[b],"undefined")},Function.prototype.bind||(Function.prototype.bind=function(b){var c=this;if(typeof c!="function")throw new TypeError;var d=u.call(arguments,1),e=function(){if(this instanceof e){var a=function(){};a.prototype=c.prototype;var f=new a,g=c.apply(f,d.concat(u.call(arguments)));return Object(g)===g?g:f}return c.apply(b,d.concat(u.call(arguments)))};return e});var G=function(c,d){var f=c.join(""),g=d.length;w(f,function(c,d){var f=b.styleSheets[b.styleSheets.length-1],h=f?f.cssRules&&f.cssRules[0]?f.cssRules[0].cssText:f.cssText||"":"",i=c.childNodes,j={};while(g--)j[i[g].id]=i[g];e.touch="ontouchstart"in a||a.DocumentTouch&&b instanceof DocumentTouch||(j.touch&&j.touch.offsetTop)===9,e.csstransforms3d=(j.csstransforms3d&&j.csstransforms3d.offsetLeft)===9&&j.csstransforms3d.offsetHeight===3},g,d)}([,["@media (",m.join("touch-enabled),("),h,")","{#touch{top:9px;position:absolute}}"].join(""),["@media (",m.join("transform-3d),("),h,")","{#csstransforms3d{left:9px;position:absolute;height:3px;}}"].join("")],[,"touch","csstransforms3d"]);q.touch=function(){return e.touch},q.csstransforms=function(){return!!F("transform")},q.csstransforms3d=function(){var a=!!F("perspective");return a&&"webkitPerspective"in g.style&&(a=e.csstransforms3d),a},q.csstransitions=function(){return F("transition")};for(var H in q)y(q,H)&&(v=H.toLowerCase(),e[v]=q[H](),t.push((e[v]?"":"no-")+v));return z(""),i=k=null,e._version=d,e._prefixes=m,e._domPrefixes=p,e._cssomPrefixes=o,e.testProp=function(a){return D([a])},e.testAllProps=F,e.testStyles=w,g.className=g.className.replace(/(^|\s)no-js(\s|$)/,"$1$2")+(f?" js "+t.join(" "):""),e}(this,this.document);
10
+ }
11
+
12
+ // Load spin.js v1.2.6 - fgnass.github.com/spin.js#v1.2.6
13
+ !function(e,t,n){function o(e,n){var r=t.createElement(e||"div"),i;for(i in n)r[i]=n[i];return r}function u(e){for(var t=1,n=arguments.length;t<n;t++)e.appendChild(arguments[t]);return e}function f(e,t,n,r){var o=["opacity",t,~~(e*100),n,r].join("-"),u=.01+n/r*100,f=Math.max(1-(1-e)/t*(100-u),e),l=s.substring(0,s.indexOf("Animation")).toLowerCase(),c=l&&"-"+l+"-"||"";return i[o]||(a.insertRule("@"+c+"keyframes "+o+"{"+"0%{opacity:"+f+"}"+u+"%{opacity:"+e+"}"+(u+.01)+"%{opacity:1}"+(u+t)%100+"%{opacity:"+e+"}"+"100%{opacity:"+f+"}"+"}",a.cssRules.length),i[o]=1),o}function l(e,t){var i=e.style,s,o;if(i[t]!==n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(o=0;o<r.length;o++){s=r[o]+t;if(i[s]!==n)return s}}function c(e,t){for(var n in t)e.style[l(e,n)||n]=t[n];return e}function h(e){for(var t=1;t<arguments.length;t++){var r=arguments[t];for(var i in r)e[i]===n&&(e[i]=r[i])}return e}function p(e){var t={x:e.offsetLeft,y:e.offsetTop};while(e=e.offsetParent)t.x+=e.offsetLeft,t.y+=e.offsetTop;return t}var r=["webkit","Moz","ms","O"],i={},s,a=function(){var e=o("style",{type:"text/css"});return u(t.getElementsByTagName("head")[0],e),e.sheet||e.styleSheet}(),d={lines:12,length:7,width:5,radius:10,rotate:0,corners:1,color:"#000",speed:1,trail:100,opacity:.25,fps:20,zIndex:2e9,className:"spinner",top:"auto",left:"auto"},v=function m(e){if(!this.spin)return new m(e);this.opts=h(e||{},m.defaults,d)};v.defaults={},h(v.prototype,{spin:function(e){this.stop();var t=this,n=t.opts,r=t.el=c(o(0,{className:n.className}),{position:"relative",width:0,zIndex:n.zIndex}),i=n.radius+n.length+n.width,u,a;e&&(e.insertBefore(r,e.firstChild||null),a=p(e),u=p(r),c(r,{left:(n.left=="auto"?a.x-u.x+(e.offsetWidth>>1):parseInt(n.left,10)+i)+"px",top:(n.top=="auto"?a.y-u.y+(e.offsetHeight>>1):parseInt(n.top,10)+i)+"px"})),r.setAttribute("aria-role","progressbar"),t.lines(r,t.opts);if(!s){var f=0,l=n.fps,h=l/n.speed,d=(1-n.opacity)/(h*n.trail/100),v=h/n.lines;(function m(){f++;for(var e=n.lines;e;e--){var i=Math.max(1-(f+e*v)%h*d,n.opacity);t.opacity(r,n.lines-e,i,n)}t.timeout=t.el&&setTimeout(m,~~(1e3/l))})()}return t},stop:function(){var e=this.el;return e&&(clearTimeout(this.timeout),e.parentNode&&e.parentNode.removeChild(e),this.el=n),this},lines:function(e,t){function i(e,r){return c(o(),{position:"absolute",width:t.length+t.width+"px",height:t.width+"px",background:e,boxShadow:r,transformOrigin:"left",transform:"rotate("+~~(360/t.lines*n+t.rotate)+"deg) translate("+t.radius+"px"+",0)",borderRadius:(t.corners*t.width>>1)+"px"})}var n=0,r;for(;n<t.lines;n++)r=c(o(),{position:"absolute",top:1+~(t.width/2)+"px",transform:t.hwaccel?"translate3d(0,0,0)":"",opacity:t.opacity,animation:s&&f(t.opacity,t.trail,n,t.lines)+" "+1/t.speed+"s linear infinite"}),t.shadow&&u(r,c(i("#000","0 0 4px #000"),{top:"2px"})),u(e,u(r,i(t.color,"0 0 1px rgba(0,0,0,.1)")));return e},opacity:function(e,t,n){t<e.childNodes.length&&(e.childNodes[t].style.opacity=n)}}),function(){function e(e,t){return o("<"+e+' xmlns="urn:schemas-microsoft.com:vml" class="spin-vml">',t)}var t=c(o("group"),{behavior:"url(#default#VML)"});!l(t,"transform")&&t.adj?(a.addRule(".spin-vml","behavior:url(#default#VML)"),v.prototype.lines=function(t,n){function s(){return c(e("group",{coordsize:i+" "+i,coordorigin:-r+" "+ -r}),{width:i,height:i})}function l(t,i,o){u(a,u(c(s(),{rotation:360/n.lines*t+"deg",left:~~i}),u(c(e("roundrect",{arcsize:n.corners}),{width:r,height:n.width,left:n.radius,top:-n.width>>1,filter:o}),e("fill",{color:n.color,opacity:n.opacity}),e("stroke",{opacity:0}))))}var r=n.length+n.width,i=2*r,o=-(n.width+n.length)*2+"px",a=c(s(),{position:"absolute",top:o,left:o}),f;if(n.shadow)for(f=1;f<=n.lines;f++)l(f,-2,"progid:DXImageTransform.Microsoft.Blur(pixelradius=2,makeshadow=1,shadowopacity=.3)");for(f=1;f<=n.lines;f++)l(f);return u(t,a)},v.prototype.opacity=function(e,t,n,r){var i=e.firstChild;r=r.shadow&&r.lines||0,i&&t+r<i.childNodes.length&&(i=i.childNodes[t+r],i=i&&i.firstChild,i=i&&i.firstChild,i&&(i.opacity=n))}):s=l(t,"animation")}(),typeof define=="function"&&define.amd?define(function(){return v}):e.Spinner=v}(window,document);
14
+
15
+ /**
16
+ The base Pushpop object.
17
+ */
18
+ var Pushpop = window['Pushpop'] || {};
19
+
20
+ /**
21
+ Event types for Pushpop.
22
+ */
23
+ Pushpop.EventType = {
24
+ WillPushView: 'Pushpop:WillPushView',
25
+ DidPushView: 'Pushpop:DidPushView',
26
+ WillPopView: 'Pushpop:WillPopView',
27
+ DidPopView: 'Pushpop:DidPopView',
28
+ WillDismissView: 'Pushpop:WillDismissView',
29
+ DidDismissView: 'Pushpop:DidDismissView',
30
+ WillPresentView: 'Pushpop:WillPresentView',
31
+ DidPresentView: 'Pushpop:DidPresentView'
32
+ };
33
+
34
+ /**
35
+ Default transition to use for pushing and popping views when no transition is specified.
36
+ */
37
+ Pushpop.defaultTransition = 'slide-horizontal';
38
+
39
+ /**
40
+
41
+ */
42
+ Pushpop.Util = {
43
+ _dashedToCamelCaseRegExp: /(\-[a-z])/g,
44
+ _dashedToCamelCaseReplacer: function($1) { return $1.toUpperCase().replace('-', ''); },
45
+ convertDashedStringToCamelCase: function(dashedString) {
46
+ return ('' + dashedString).replace(this._dashedToCamelCaseRegExp, this._dashedToCamelCaseReplacer);
47
+ },
48
+
49
+ _camelCaseToDashedRegExp: /([A-Z])/g,
50
+ _camelCaseToDashedReplacer: function($1) { return '-' + $1.toLowerCase(); },
51
+ convertCamelCaseStringToDashed: function(camelCaseString) {
52
+ var dashedString = ('' + camelCaseString).replace(this._camelCaseToDashedRegExp, this._camelCaseToDashedReplacer);
53
+ if (dashedString.length > 0 && dashedString.charAt(0) === '-') dashedString = dashedString.slice(1);
54
+ return dashedString;
55
+ }
56
+ };
57
+
58
+ /**
59
+ Traverses the parents of the specified element and returns the closest Pushpop.ViewStack.
60
+ @param {HTMLElement} element The element for which to find the closest parent view stack.
61
+ @type Pushpop.ViewStack
62
+ */
63
+ Pushpop.getViewStackForElement = function(element) {
64
+ var $parents = $(element).parents();
65
+ var viewStack;
66
+ for (var i = 0, length = $parents.length; i < length; i++) if (!!(viewStack = $parents[i].viewStack) && viewStack instanceof Pushpop.ViewStack) return viewStack;
67
+ return null;
68
+ };
69
+
70
+ /**
71
+ Creates a new ViewStack.
72
+ @param {HTMLDivElement} element The DIV element to initialize as a new ViewStack.
73
+ @constructor
74
+ */
75
+ Pushpop.ViewStack = function ViewStack(element) {
76
+ if (!element) return;
77
+
78
+ var $element = this.$element = $(element);
79
+ element = this.element = $element[0];
80
+
81
+ var viewStack = element.viewStack;
82
+ if (viewStack) return viewStack;
83
+
84
+ var self = element.viewStack = this;
85
+
86
+ var views = this.views = [];
87
+
88
+ var $rootViewElement = $element.children('.pp-view, .pp-split-view').first().addClass('pp-active');
89
+ var rootViewElement = $rootViewElement[0];
90
+
91
+ if (!rootViewElement) return;
92
+
93
+ var rootView = this.rootView = rootViewElement.view || new Pushpop.View($rootViewElement.addClass('root'));
94
+ views.push(rootView);
95
+
96
+ var rootScrollView = rootView.getScrollView();
97
+ if (rootScrollView) rootScrollView.recalculateDimensions();
98
+ };
99
+
100
+ Pushpop.ViewStack.prototype = {
101
+ constructor: Pushpop.ViewStack,
102
+
103
+ element: null,
104
+ $element: null,
105
+
106
+ views: null,
107
+ rootView: null,
108
+ isTransitioning: false,
109
+ handleEvent: function(evt) {
110
+ switch (evt.type) {
111
+ case 'webkitTransitionEnd':
112
+ case 'transitionend':
113
+ case 'oTransitionEnd':
114
+ case 'transitionEnd':
115
+ var target = evt.target;
116
+ if (!target.view || $(target).hasClass('pp-active')) return;
117
+
118
+ var newActiveView = target;
119
+ newActiveView = (newActiveView) ? newActiveView.view : null;
120
+ if (!newActiveView) return;
121
+
122
+ var viewStack = newActiveView.getViewStack();
123
+ if (!viewStack || !viewStack.isTransitioning) return;
124
+
125
+ var oldActiveView = viewStack.$element.children('.pp-view.pp-active')[0];
126
+ oldActiveView = (oldActiveView) ? oldActiveView.view : null;
127
+ if (!oldActiveView) return;
128
+
129
+ viewStack.isTransitioning = false;
130
+ $(target).unbind(evt);
131
+
132
+ var $newActiveViewElement = newActiveView.$element;
133
+ var $oldActiveViewElement = oldActiveView.$element;
134
+ var action = ($newActiveViewElement.hasClass('push') ? 'push' : 'pop');
135
+
136
+ $newActiveViewElement.addClass('pp-active');
137
+ $newActiveViewElement.removeClass('transition push pop ' + newActiveView.transition);
138
+ $oldActiveViewElement.removeClass('pp-active transition push pop ' + newActiveView.transition);
139
+
140
+ if (action === 'push') {
141
+
142
+ // Trigger an event indicating that the new view was pushed.
143
+ $newActiveViewElement.trigger($.Event(Pushpop.EventType.DidPushView, {
144
+ view: newActiveView
145
+ }));
146
+ } else {
147
+
148
+ // Trigger an event indicating that the previous view was popped.
149
+ $oldActiveViewElement.trigger($.Event(Pushpop.EventType.DidPopView, {
150
+ view: oldActiveView
151
+ }));
152
+ }
153
+
154
+ // Trigger an event indicating that the previous view was dismissed.
155
+ $oldActiveViewElement.trigger($.Event(Pushpop.EventType.DidDismissView, {
156
+ view: oldActiveView,
157
+ action: action
158
+ }));
159
+
160
+ // Trigger an event for each active child view of the previous view indicating
161
+ // that their parent was dismissed.
162
+ $oldActiveViewElement.find('.pp-view-stack').each(function(index, element) {
163
+ var viewStack = element.viewStack;
164
+ var activeView = viewStack.getActiveView();
165
+ if (!activeView) return;
166
+
167
+ activeView.$element.trigger($.Event(Pushpop.EventType.DidDismissView, {
168
+ view: activeView,
169
+ action: 'parent-' + action
170
+ }));
171
+ });
172
+
173
+ // Trigger an event indicating that the new view was presented.
174
+ $newActiveViewElement.trigger($.Event(Pushpop.EventType.DidPresentView, {
175
+ view: newActiveView,
176
+ action: action
177
+ }));
178
+
179
+ // Trigger an event for each active child view of the new view indicating
180
+ // that their parent was presented.
181
+ $newActiveViewElement.find('.pp-view-stack').each(function(index, element) {
182
+ var viewStack = element.viewStack;
183
+ var activeView = viewStack.getActiveView();
184
+ if (!activeView) return;
185
+
186
+ activeView.$element.trigger($.Event(Pushpop.EventType.DidPresentView, {
187
+ view: activeView,
188
+ action: 'parent-' + action
189
+ }));
190
+ });
191
+
192
+ // Hack to reset the scroll view position for views using ScrollKit on desktop browsers.
193
+ var newActiveScrollView, newActiveScrollPosition;
194
+ if ($newActiveViewElement.hasClass('sk-no-touch') && (newActiveScrollView = newActiveView.getScrollView())) {
195
+ newActiveScrollPosition = newActiveScrollView.getScrollPosition();
196
+ newActiveScrollView.setScrollPosition(newActiveScrollPosition.x - 1, newActiveScrollPosition.y - 1);
197
+ newActiveScrollView.setScrollPosition(newActiveScrollPosition.x + 1, newActiveScrollPosition.y + 1);
198
+ }
199
+
200
+ // Remove the previous active view from the DOM if it is marked for removal when popped.
201
+ if (action === 'pop' && oldActiveView.getShouldRemoveWhenPopped()) $oldActiveViewElement.remove();
202
+
203
+ // Call the callback to execute when a push/pop transition completes if one exists.
204
+ if (!viewStack.callback) return;
205
+ viewStack.callback();
206
+ viewStack.callback = null;
207
+ break;
208
+ default:
209
+ break;
210
+ }
211
+ },
212
+
213
+ /**
214
+ Pushes the specified view to the view stack using the optionally specified transition. If a
215
+ transition is not specified, the default will be used. A callback may optionally be provided
216
+ to be called after the transition completes.
217
+ @param {Pushpop.View} view The view to be pushed to the view stack.
218
+ @param {String|Function} [transitionOrCallback] Either the name of the transition to use when
219
+ pushing the view or a callback function to be executed upon completion of the default transition.
220
+ If this parameter is omitted, the default transition is used.
221
+ @param {Function} [callback] A callback function to be executed upon completion of the specified
222
+ transition.
223
+ */
224
+ push: function(view, transitionOrCallback, callback) {
225
+ var oldActiveView = this.getActiveView();
226
+ var newActiveView = view;
227
+
228
+ if (newActiveView === oldActiveView) return;
229
+
230
+ if (this.isTransitioning) {
231
+ // Manually kick off transitionEnd event. Chances are it should have fired, but didn't.
232
+ if (!oldActiveView) return;
233
+
234
+ oldActiveView.$element.trigger('transitionend');
235
+ }
236
+
237
+ this.isTransitioning = true;
238
+
239
+ // Hide the onscreen keyboard if it is currently visible.
240
+ $(document.activeElement).blur();
241
+
242
+ var $element = this.$element;
243
+
244
+ var $oldActiveViewElement = oldActiveView.$element;
245
+ var $newActiveViewElement = newActiveView.$element;
246
+
247
+ this.views.push(newActiveView);
248
+
249
+ $newActiveViewElement.bind('webkitTransitionEnd transitionend oTransitionEnd transitionEnd', this.handleEvent);
250
+
251
+ var transition;
252
+ if (transitionOrCallback) {
253
+ if (typeof transitionOrCallback === 'function') {
254
+ this.callback = transitionOrCallback;
255
+ } else {
256
+ transition = transitionOrCallback;
257
+ this.callback = callback;
258
+ }
259
+ }
260
+
261
+ transition = newActiveView.transition = transition || newActiveView.transition || Pushpop.defaultTransition;
262
+
263
+ $oldActiveViewElement.addClass('push ' + transition);
264
+ $newActiveViewElement.addClass('push ' + transition);
265
+
266
+ oldActiveView.forceReflow();
267
+ newActiveView.forceReflow();
268
+
269
+ var newActiveScrollView = newActiveView.getScrollView();
270
+ if (newActiveScrollView) newActiveScrollView.recalculateDimensions();
271
+
272
+ // Trigger an event indicating that the new view is about to be pushed.
273
+ $newActiveViewElement.trigger($.Event(Pushpop.EventType.WillPushView, {
274
+ view: newActiveView
275
+ }));
276
+
277
+ // Trigger an event indicating that the previous view is about to be dismissed.
278
+ $oldActiveViewElement.trigger($.Event(Pushpop.EventType.WillDismissView, {
279
+ view: oldActiveView,
280
+ action: 'push'
281
+ }));
282
+
283
+ // Trigger an event for each active child view of the previous view indicating that
284
+ // their parent is about to be dismissed.
285
+ $oldActiveViewElement.find('.pp-view-stack').each(function(index, element) {
286
+ var viewStack = element.viewStack;
287
+ var activeView = viewStack.getActiveView();
288
+ if (!activeView) return;
289
+
290
+ activeView.$element.trigger($.Event(Pushpop.EventType.WillDismissView, {
291
+ view: activeView,
292
+ action: 'parent-push'
293
+ }));
294
+ });
295
+
296
+ // Trigger an event indicating that the new view is about to be presented.
297
+ $newActiveViewElement.trigger($.Event(Pushpop.EventType.WillPresentView, {
298
+ view: newActiveView,
299
+ action: 'push'
300
+ }));
301
+
302
+ // Trigger an event for each active child view of the new view indicating that
303
+ // their parent is about to be presented.
304
+ $newActiveViewElement.find('.pp-view-stack').each(function(index, element) {
305
+ var viewStack = element.viewStack;
306
+ var activeView = viewStack.getActiveView();
307
+ if (!activeView) return;
308
+
309
+ var activeScrollView = activeView.getScrollView();
310
+ if (activeScrollView) activeScrollView.recalculateDimensions();
311
+
312
+ activeView.$element.trigger($.Event(Pushpop.EventType.WillPresentView, {
313
+ view: activeView,
314
+ action: 'parent-push'
315
+ }));
316
+ });
317
+
318
+ $oldActiveViewElement.addClass('transition');
319
+ $newActiveViewElement.addClass('transition');
320
+
321
+ // If the browser doesn't support css transitions, explicitely trigger the transitionend event
322
+ if (!Modernizr.csstransitions) $newActiveViewElement.trigger('transitionend');
323
+ },
324
+
325
+ /**
326
+ Pops the current or specified view off the view stack using the optionally specified transition.
327
+ If a view is not specified, the current view will be popped (unless it is the root view). If a
328
+ transition is not specified, the default will be used. A callback may optionally be provided to
329
+ be called after the transition completes.
330
+ @param {Pushpop.View|String} viewOrTransition Either the view to be popped to on the view stack
331
+ or the name of the transition to use when popping the view. If this parameter is omitted or if
332
+ it specifies a transition name, the current view will be assumed to be popped.
333
+ @param {String|Function} [transitionOrCallback] Either the name of the transition to use when
334
+ popping the view or a callback function to be executed upon completion of the default transition.
335
+ If this parameter is omitted, the default transition is used.
336
+ @param {Function} [callback] A callback function to be executed upon completion of the specified
337
+ transition.
338
+ */
339
+ pop: function(viewOrTransition, transitionOrCallback, callback) {
340
+ var oldActiveView = this.getActiveView();
341
+ var newActiveView, transition;
342
+
343
+ if (this.isTransitioning) {
344
+ // Manually kick off transitionEnd event. Chances are it should have fired, but didn't.
345
+ if (!oldActiveView) return;
346
+
347
+ oldActiveView.$element.trigger('transitionend');
348
+ }
349
+
350
+ this.isTransitioning = true;
351
+
352
+ // Hide the onscreen keyboard if it is currently visible.
353
+ $(document.activeElement).blur();
354
+
355
+ var views = this.views;
356
+ if (views.length <= 1) return;
357
+
358
+ if (viewOrTransition && typeof viewOrTransition !== 'string') {
359
+ newActiveView = viewOrTransition;
360
+
361
+ if (newActiveView === oldActiveView) return;
362
+
363
+ if (this.containsView(newActiveView)) {
364
+ while (views.length > 1 && this.getActiveView() !== newActiveView) {
365
+ views.pop();
366
+ }
367
+ }
368
+ } else {
369
+ if (viewOrTransition) transition = viewOrTransition;
370
+
371
+ views.pop();
372
+ newActiveView = this.getActiveView();
373
+ }
374
+
375
+ if (transitionOrCallback) {
376
+ if (typeof transitionOrCallback === 'function') {
377
+ this.callback = transitionOrCallback;
378
+ } else {
379
+ transition = transitionOrCallback;
380
+ this.callback = callback;
381
+ }
382
+ }
383
+
384
+ var $element = this.$element;
385
+
386
+ var $oldActiveViewElement = oldActiveView.$element;
387
+ var $newActiveViewElement = newActiveView.$element;
388
+
389
+ $newActiveViewElement.bind('webkitTransitionEnd transitionend oTransitionEnd transitionEnd', this.handleEvent);
390
+
391
+ transition = newActiveView.transition = transition || oldActiveView.transition || Pushpop.defaultTransition;
392
+
393
+ $oldActiveViewElement.addClass('pop ' + transition);
394
+ $newActiveViewElement.addClass('pop ' + transition);
395
+
396
+ oldActiveView.forceReflow();
397
+ newActiveView.forceReflow();
398
+
399
+ var newActiveScrollView = newActiveView.getScrollView();
400
+ if (newActiveScrollView) newActiveScrollView.recalculateDimensions();
401
+
402
+ // Trigger an event indicating that the previous view is about to be popped.
403
+ $oldActiveViewElement.trigger($.Event(Pushpop.EventType.WillPopView, {
404
+ view: oldActiveView
405
+ }));
406
+
407
+ // Trigger an event indicating that the previous view is about to be dismissed.
408
+ $oldActiveViewElement.trigger($.Event(Pushpop.EventType.WillDismissView, {
409
+ view: oldActiveView,
410
+ action: 'pop'
411
+ }));
412
+
413
+ // Trigger an event for each active child view of the previous view indicating that
414
+ // their parent is about to be dismissed.
415
+ $oldActiveViewElement.find('.pp-view-stack').each(function(index, element) {
416
+ var viewStack = element.viewStack;
417
+ var activeView = viewStack.getActiveView();
418
+ if (!activeView) return;
419
+
420
+ activeView.$element.trigger($.Event(Pushpop.EventType.WillDismissView, {
421
+ view: activeView,
422
+ action: 'parent-pop'
423
+ }));
424
+ });
425
+
426
+ // Trigger an event indicating that the new view is about to be presented.
427
+ $newActiveViewElement.trigger($.Event(Pushpop.EventType.WillPresentView, {
428
+ view: newActiveView,
429
+ action: 'pop'
430
+ }));
431
+
432
+ // Trigger an event for each active child view of the new view indicating that
433
+ // their parent is about to be presented.
434
+ $newActiveViewElement.find('.pp-view-stack').each(function(index, element) {
435
+ var viewStack = element.viewStack;
436
+ var activeView = viewStack.getActiveView();
437
+ if (!activeView) return;
438
+
439
+ var activeScrollView = activeView.getScrollView();
440
+ if (activeScrollView) activeScrollView.recalculateDimensions();
441
+
442
+ activeView.$element.trigger($.Event(Pushpop.EventType.WillPresentView, {
443
+ view: activeView,
444
+ action: 'parent-pop'
445
+ }));
446
+ });
447
+
448
+ $oldActiveViewElement.addClass('transition');
449
+ $newActiveViewElement.addClass('transition');
450
+
451
+ // If the browser doesn't support css transitions, explicitely trigger the transitionend event
452
+ if (!Modernizr.csstransitions) $newActiveViewElement.trigger('transitionend');
453
+ },
454
+
455
+ /**
456
+ Returns the current active (topmost) Pushpop.View on this view stack.
457
+ @type Pushpop.View
458
+ */
459
+ getActiveView: function() {
460
+ var views = this.views;
461
+ var viewCount = views.length;
462
+
463
+ return (viewCount === 0) ? null : views[viewCount - 1];
464
+ },
465
+
466
+ /**
467
+ Returns a flag indicating if the specified Pushpop.View is contained wihin this
468
+ view stack.
469
+ @param {Pushpop.View} view The view to search for in this view stack.
470
+ @type Boolean
471
+ */
472
+ containsView: function(view) {
473
+ var views = this.views;
474
+ var viewCount = views.length;
475
+
476
+ for (var i = viewCount - 1; i >= 0; i--) if (views[i] === view) return true;
477
+ return false;
478
+ },
479
+
480
+ /**
481
+ Creates a new view and pushes it to the view stack using the optionally specified transition.
482
+ Before pushing the view, a required callback is called that passes in the newly-created view to
483
+ give an opportunity to set up the view's content. If a transition is not specified, the default
484
+ will be used. A callback may optionally be provided to be called after the transition completes.
485
+ @description NOTE: By default, the newly-created view is marked for removal from the DOM when it
486
+ is popped.
487
+ @param {Function} beforePushCallback A callback function to be executed before the newly-created
488
+ view is pushed. The function is called with a single parameter passing the view to be pushed.
489
+ @param {String|Function} [transitionOrCallback] Either the name of the transition to use when
490
+ pushing the view or a callback function to be executed upon completion of the default transition.
491
+ If this parameter is omitted, the default transition is used.
492
+ @param {Function} [callback] A callback function to be executed upon completion of the specified
493
+ transition.
494
+ */
495
+ pushNewView: function(beforePushCallback, transitionOrCallback, callback) {
496
+ var $viewElement = $('<div class="pp-view"/>').appendTo(this.$element);
497
+ var view = new Pushpop.View($viewElement);
498
+
499
+ view.setShouldRemoveWhenPopped(true);
500
+
501
+ if (beforePushCallback && typeof beforePushCallback === 'function') beforePushCallback(view);
502
+
503
+ this.push(view, transitionOrCallback, callback);
504
+ },
505
+
506
+ /**
507
+ Creates a new view with a new table view and pushes it to the view stack using the optionally
508
+ specified transition. Before pushing the view, a required callback is called that passes in the
509
+ newly-created table view to give an opportunity to set up the table view's data source and other
510
+ properties. If a transition is not specified, the default will be used. A callback may optionally
511
+ be provided to be called after the transition completes.
512
+ @description NOTE: By default, the newly-created view containing the newly-created table view is
513
+ marked for removal from the DOM when it is popped.
514
+ @param {Function} beforePushCallback A callback function to be executed before the newly-created
515
+ view is pushed. The function is called with a single parameter passing the newly-created table
516
+ view contained in the view to be pushed.
517
+ @param {String|Function} [transitionOrCallback] Either the name of the transition to use when
518
+ pushing the view or a callback function to be executed upon completion of the default transition.
519
+ If this parameter is omitted, the default transition is used.
520
+ @param {Function} [callback] A callback function to be executed upon completion of the specified
521
+ transition.
522
+ */
523
+ pushNewTableView: function(beforePushCallback, transitionOrCallback, callback) {
524
+ var $viewElement = $('<div class="pp-view sk-scroll-view" data-always-bounce-vertical="true"/>').appendTo(this.$element);
525
+ var $tableViewElement = $('<ul class="pp-table-view"/>').appendTo($viewElement);
526
+ var view = new Pushpop.View($viewElement);
527
+ var scrollView = new ScrollKit.ScrollView($viewElement);
528
+ var tableView = new Pushpop.TableView($tableViewElement);
529
+
530
+ view.setShouldRemoveWhenPopped(true);
531
+
532
+ if (beforePushCallback && typeof beforePushCallback === 'function') beforePushCallback(tableView);
533
+
534
+ this.push(view, transitionOrCallback, callback);
535
+ },
536
+
537
+ /**
538
+ Convenience accessor for jQuery's .bind() method.
539
+ */
540
+ $bind: function() { this.$element.bind.apply(this.$element, arguments); },
541
+
542
+ /**
543
+ Convenience accessor for jQuery's .unbind() method.
544
+ */
545
+ $unbind: function() { this.$element.unbind.apply(this.$element, arguments); },
546
+
547
+ /**
548
+ Convenience accessor for jQuery's .delegate() method.
549
+ */
550
+ $delegate: function() { this.$element.delegate.apply(this.$element, arguments); },
551
+
552
+ /**
553
+ Convenience accessor for jQuery's .undelegate() method.
554
+ */
555
+ $undelegate: function() { this.$element.undelegate.apply(this.$element, arguments); },
556
+
557
+ /**
558
+ Convenience accessor for jQuery's .trigger() method.
559
+ */
560
+ $trigger: function() { this.$element.trigger.apply(this.$element, arguments); }
561
+ };
562
+
563
+ /**
564
+ Creates a new View.
565
+ @param {HTMLDivElement} element The DIV element to initialize as a new View.
566
+ @constructor
567
+ */
568
+ Pushpop.View = function View(element) {
569
+ if (!element) return;
570
+
571
+ var $element = this.$element = $(element);
572
+ element = this.element = $element[0];
573
+
574
+ var view = element.view;
575
+ if (view) return view;
576
+
577
+ var self = element.view = this;
578
+
579
+ this.title = $element.attr('data-view-title');
580
+
581
+ // Set up this view's navigation bar button items.
582
+ var barButtonItems = this._barButtonItems = [];
583
+ $element.find('.pp-navigation-bar-button-items > .pp-button').each(function(index, element) {
584
+ self.addBarButtonItem(element.button || new Pushpop.Button(element));
585
+ });
586
+
587
+ $element.find('.pp-navigation-bar-button-items').remove();
588
+
589
+ // Determine if the back bar button item should be hidden when this view is active.
590
+ var hideBackBarButtonItem = $element.attr('data-hide-back-bar-button-item') || 'false';
591
+ hideBackBarButtonItem = hideBackBarButtonItem !== 'false';
592
+ this.setHideBackBarButtonItem(hideBackBarButtonItem);
593
+
594
+ //////
595
+ // OLD
596
+ var $navbarButtonsContainer = $element.find('.pp-navigationbar-buttons');
597
+ var $navbarButtons = this.$navbarButtons = $navbarButtonsContainer.find('.pp-barbutton');
598
+
599
+ // Hide the back button if data-hide-back-button="true" or if there is a left navigation bar
600
+ var dataHideBackButton = $navbarButtonsContainer.attr('data-hide-back-button');
601
+ this.hideNavBackButton = ((dataHideBackButton && dataHideBackButton !== 'false') || $navbarButtons.filter('.pp-barbutton-align-left').length > 0);
602
+ // OLD
603
+ //////
604
+ };
605
+
606
+ Pushpop.View.prototype = {
607
+ constructor: Pushpop.View,
608
+
609
+ element: null,
610
+ $element: null,
611
+
612
+ $navbarButtons: null, // OLD
613
+ hideNavBackButton: false, // OLD
614
+
615
+ transition: null,
616
+
617
+ /**
618
+ Sets the transition that should be used when pushing or popping to this view.
619
+ @param {String} value The name of the transition to be used when pushing or popping
620
+ to this view.
621
+ */
622
+ setTransition: function(value) {
623
+ this.transition = value;
624
+ this.$element.addClass(value);
625
+ },
626
+
627
+ title: null,
628
+
629
+ getTitle: function() { return this.title; },
630
+
631
+ /**
632
+ Sets the title for this view.
633
+ @description The title is stored in the |data-view-title| attribute of this view's
634
+ element. NOTE: When a Pushpop.NavigationBar is used in conjunction with this view's
635
+ view stack, this view's title will appear in the navigation bar when it is the active
636
+ view.
637
+ @param {String} value The title for this view.
638
+ */
639
+ setTitle: function(value) {
640
+ this.title = value;
641
+ this.$element.attr('data-view-title', value);
642
+
643
+ var navigationBar = this.getNavigationBar();
644
+ if (navigationBar) navigationBar.setTitle(value);
645
+ },
646
+
647
+ /**
648
+
649
+ */
650
+ getActive: function() { return this.$element.hasClass('pp-active'); },
651
+
652
+ _barButtonItems: null,
653
+
654
+ /**
655
+
656
+ */
657
+ getBarButtonItems: function() { return this._barButtonItems; },
658
+
659
+ /**
660
+
661
+ */
662
+ setBarButtonItems: function(barButtonItems, animated) {
663
+ this.removeAllBarButtonItems();
664
+ for (var i = 0, length = barButtonItems.length; i < length; i++) this.addBarButtonItem(barButtonItems[i], animated);
665
+ },
666
+
667
+ /**
668
+
669
+ */
670
+ addBarButtonItem: function(barButtonItem, animated) {
671
+ if (this.getActive()) {
672
+ var navigationBar = this.getNavigationBar();
673
+ if (navigationBar) navigationBar.addBarButtonItem(barButtonItem, animated);
674
+ } else {
675
+ barButtonItem.remove();
676
+ }
677
+
678
+ this._barButtonItems.push(barButtonItem);
679
+ },
680
+
681
+ /**
682
+
683
+ */
684
+ removeBarButtonItem: function(barButtonItem, animated) {
685
+ var barButtonItems = this._barButtonItems;
686
+ for (var i = 0, length = barButtonItems.length; i < length; i++) {
687
+ if (barButtonItems[i] === barButtonItem) {
688
+ if (this.getActive()) {
689
+ var navigationBar = this.getNavigationBar();
690
+ if (navigationBar) navigationBar.removeBarButtonItem(barButtonItem, animated);
691
+ }
692
+
693
+ barButtonItems.splice(i, 1);
694
+ break;
695
+ }
696
+ }
697
+ },
698
+
699
+ /**
700
+
701
+ */
702
+ removeAllBarButtonItems: function(animated) {
703
+ var barButtonItems = this._barButtonItems;
704
+ if (this.getActive()) {
705
+ var navigationBar = this.getNavigationBar();
706
+ if (navigationBar) for (var i = 0, length = barButtonItems.length; i < length; i++) navigationBar.removeBarButtonItem(barButtonItems[i], animated);
707
+ }
708
+
709
+ barButtonItems.length = 0;
710
+ },
711
+
712
+ _hideBackBarButtonItem: false,
713
+
714
+ /**
715
+
716
+ */
717
+ getHideBackBarButtonItem: function() { return this._hideBackBarButtonItem; },
718
+
719
+ /**
720
+
721
+ */
722
+ setHideBackBarButtonItem: function(hideBackBarButtonItem) {
723
+ this._hideBackBarButtonItem = hideBackBarButtonItem;
724
+ },
725
+
726
+ _shouldRemoveWhenPopped: false,
727
+
728
+ /**
729
+
730
+ */
731
+ getShouldRemoveWhenPopped: function() { return this._shouldRemoveWhenPopped; },
732
+
733
+ /**
734
+
735
+ */
736
+ setShouldRemoveWhenPopped: function(shouldRemoveWhenPopped) { this._shouldRemoveWhenPopped = shouldRemoveWhenPopped; },
737
+
738
+ /**
739
+ Traverses the parents of this view's element and returns the closest Pushpop.ViewStack.
740
+ @type Pushpop.ViewStack
741
+ */
742
+ getViewStack: function() { return Pushpop.getViewStackForElement(this.$element); },
743
+
744
+ /**
745
+
746
+ */
747
+ getNavigationBar: function() {
748
+ var viewStack = this.getViewStack();
749
+ if (!viewStack) return null;
750
+
751
+ var navigationBar = viewStack.$element.children('.pp-navigation-bar')[0];
752
+ navigationBar = (navigationBar) ? navigationBar.navigationBar : null;
753
+
754
+ return navigationBar;
755
+ },
756
+
757
+ /**
758
+
759
+ */
760
+ getScrollView: function() {
761
+ var scrollViewElement = this.element;
762
+ var scrollView = scrollViewElement.scrollView;
763
+ if (!scrollView) {
764
+ scrollViewElement = this.$element.children('.sk-scroll-view')[0];
765
+ if (scrollViewElement) scrollView = scrollViewElement.scrollView;
766
+ }
767
+
768
+ return scrollView || null;
769
+ },
770
+
771
+ /**
772
+ Forces a reflow in the browser for this view.
773
+ */
774
+ forceReflow: function() { var doNothing = this.element.offsetWidth; },
775
+
776
+ setBackButtonVisible: function(visible) {
777
+ if (this.$navbarButtons.filter('.pp-barbutton-align-left').length === 0) this.hideNavBackButton = !visible;
778
+ },
779
+
780
+ /**
781
+ Convenience accessor for jQuery's .bind() method.
782
+ */
783
+ $bind: function() { this.$element.bind.apply(this.$element, arguments); },
784
+
785
+ /**
786
+ Convenience accessor for jQuery's .unbind() method.
787
+ */
788
+ $unbind: function() { this.$element.unbind.apply(this.$element, arguments); },
789
+
790
+ /**
791
+ Convenience accessor for jQuery's .delegate() method.
792
+ */
793
+ $delegate: function() { this.$element.delegate.apply(this.$element, arguments); },
794
+
795
+ /**
796
+ Convenience accessor for jQuery's .undelegate() method.
797
+ */
798
+ $undelegate: function() { this.$element.undelegate.apply(this.$element, arguments); },
799
+
800
+ /**
801
+ Convenience accessor for jQuery's .trigger() method.
802
+ */
803
+ $trigger: function() { this.$element.trigger.apply(this.$element, arguments); }
804
+ };
805
+
806
+ /**
807
+ Creates a new NavigationBar.
808
+ @param {HTMLDivElement} element The DIV element to initialize as a new NavigationBar.
809
+ @constructor
810
+ */
811
+ Pushpop.NavigationBar = function NavigationBar(element) {
812
+ if (!element) return;
813
+
814
+ var $element = this.$element = $(element);
815
+ element = this.element = $element[0];
816
+
817
+ var navigationBar = element.navigationBar;
818
+ if (navigationBar) return navigationBar;
819
+
820
+ var self = element.navigationBar = this;
821
+
822
+ // Set up this navigation bar's style type.
823
+ var barStyleTypes = Pushpop.NavigationBar.BarStyleType;
824
+ var barStyleType;
825
+ for (var key in barStyleTypes) {
826
+ if ($element.hasClass(barStyleTypes[key])) {
827
+ barStyleType = barStyleTypes[key];
828
+ break;
829
+ }
830
+ }
831
+
832
+ this.setBarStyleType(barStyleType || Pushpop.NavigationBar.Default);
833
+
834
+ // Set up this navigation bar's button items.
835
+ this._barButtonItems = [];
836
+
837
+ // Set up the title container.
838
+ this.$titleElement = $('<h1 class="pp-navigation-bar-title"/>').appendTo($element);
839
+
840
+ // Set up the bar button item containers. NOTE: Two containers are created and are
841
+ // alternated each time setBarButtonItems() is called to provide a smooth transition.
842
+ this.$barButtonItemContainerElement = this.$barButtonItemContainerElementA = $('<div class="pp-navigation-bar-button-item-container"/>').appendTo($element);
843
+ this.$barButtonItemContainerElementB = $('<div class="pp-navigation-bar-button-item-container"/>').appendTo($element);
844
+
845
+ // Set up the back button.
846
+ var backBarButtonItem = new Pushpop.Button('Back', function(button) {
847
+ var viewStack = button.getViewStack();
848
+ if (viewStack) viewStack.pop();
849
+ });
850
+
851
+ this.setBackBarButtonItem(backBarButtonItem);
852
+
853
+ // Prevent dragging of the navigation bar.
854
+ $element.bind('touchmove', function(evt) { evt.preventDefault(); });
855
+
856
+ // Handle the "tap-to-top" action (automatically scroll to the top of the
857
+ // scroll view when the top of the navigation bar is tapped).
858
+ var tapToTop = $element.attr('data-tap-to-top') || 'false';
859
+ tapToTop = this. _tapToTop = tapToTop !== 'false';
860
+
861
+ $('<div class="pp-navigation-bar-tap-to-top-target"/>').appendTo($element).bind('click', function(evt) {
862
+ if (!self.getTapToTop()) return;
863
+
864
+ var activeView = self.getActiveView();
865
+ if (!activeView) return;
866
+
867
+ var scrollView = activeView.getScrollView();
868
+ if (scrollView && !scrollView.isScrolling) scrollView.scrollToTop();
869
+ });
870
+
871
+ // Handle view changes for the navigation bar's view stack.
872
+ var viewStack = this.getViewStack();
873
+ if (viewStack) {
874
+ viewStack.$bind(Pushpop.EventType.WillPresentView, function(evt) {
875
+ var view = evt.view;
876
+
877
+ // This event bubbles up, so make sure that the view is a child of this viewStack.
878
+ if (view.getViewStack() !== viewStack) return;
879
+
880
+ self.setTitle(view.getTitle());
881
+
882
+ var backBarButtonItem = self.getBackBarButtonItem();
883
+ if (backBarButtonItem) backBarButtonItem.setHidden(view.getHideBackBarButtonItem() || (viewStack.containsView(view) && viewStack.views.length === 1));
884
+
885
+ self.setBarButtonItems(view.getBarButtonItems(), true);
886
+ });
887
+ }
888
+
889
+ // Set up the navigation bar for the initial active view.
890
+ var activeView = this.getActiveView();
891
+ if (activeView) {
892
+ this.setTitle(activeView.getTitle());
893
+ this.setBarButtonItems(activeView.getBarButtonItems());
894
+
895
+ backBarButtonItem.setHidden(activeView.getHideBackBarButtonItem() || (viewStack.containsView(activeView) && viewStack.views.length === 1));
896
+ }
897
+ };
898
+
899
+ /**
900
+ Style types for Pushpop.NavigationBar.
901
+ */
902
+ Pushpop.NavigationBar.BarStyleType = {
903
+ Default: 'pp-navigation-bar-style-default',
904
+ Black: 'pp-navigation-bar-style-black'
905
+ };
906
+
907
+ Pushpop.NavigationBar.prototype = {
908
+ constructor: Pushpop.NavigationBar,
909
+
910
+ element: null,
911
+ $element: null,
912
+
913
+ $titleElement: null,
914
+ $barButtonItemContainerElement: null,
915
+ $barButtonItemContainerElementA: null,
916
+ $barButtonItemContainerElementB: null,
917
+
918
+ _title: '',
919
+
920
+ /**
921
+
922
+ */
923
+ getTitle: function() { return this._title; },
924
+
925
+ /**
926
+
927
+ */
928
+ setTitle: function(title) { this.$titleElement.html(_title = title || ''); },
929
+
930
+ _tapToTop: false,
931
+
932
+ /**
933
+
934
+ */
935
+ getTapToTop: function() { return this._tapToTop; },
936
+
937
+ /**
938
+
939
+ */
940
+ setTapToTop: function(tapToTop) { this._tapToTop = tapToTop; },
941
+
942
+ _barStyle: Pushpop.NavigationBar.BarStyleType.Default,
943
+
944
+ /**
945
+
946
+ */
947
+ getBarStyleType: function() { return this._barStyle; },
948
+
949
+ /**
950
+
951
+ */
952
+ setBarStyleType: function(barStyle) {
953
+ this._barStyle = barStyle;
954
+ },
955
+
956
+ _translucent: false,
957
+
958
+ /**
959
+
960
+ */
961
+ getTranslucent: function() { return this._translucent; },
962
+
963
+ /**
964
+
965
+ */
966
+ setTranslucent: function(translucent) {
967
+ this._translucent = translucent;
968
+ },
969
+
970
+ _tintColor: null,
971
+
972
+ /**
973
+
974
+ */
975
+ getTintColor: function() { return this._tintColor; },
976
+
977
+ /**
978
+
979
+ */
980
+ setTintColor: function(tintColor) {
981
+ this._tintColor = tintColor;
982
+ },
983
+
984
+ _barButtonItems: null,
985
+
986
+ /**
987
+
988
+ */
989
+ getBarButtonItems: function() { return this._barButtonItems; },
990
+
991
+ /**
992
+
993
+ */
994
+ setBarButtonItems: function(barButtonItems, animated) {
995
+ this.removeAllBarButtonItems(animated);
996
+
997
+ // Toggle the current bar button item container before adding the new items to
998
+ // create a smooth transition.
999
+ this.$barButtonItemContainerElement = (this.$barButtonItemContainerElement === this.$barButtonItemContainerElementA) ? this.$barButtonItemContainerElementB : this.$barButtonItemContainerElementA;
1000
+
1001
+ for (var i = 0, length = barButtonItems.length; i < length; i++) this.addBarButtonItem(barButtonItems[i], animated);
1002
+ },
1003
+
1004
+ /**
1005
+
1006
+ */
1007
+ addBarButtonItem: function(barButtonItem, animated) {
1008
+ var barStyle = this.getBarStyleType();
1009
+ if (barButtonItem.getButtonStyleType() === Pushpop.Button.ButtonStyleType.Default) {
1010
+ if (barStyle === Pushpop.NavigationBar.BarStyleType.Default) {
1011
+ barButtonItem.setButtonStyleType(Pushpop.Button.ButtonStyleType.Default);
1012
+ } else if (barStyle === Pushpop.NavigationBar.BarStyleType.Black) {
1013
+ barButtonItem.setButtonStyleType(Pushpop.Button.ButtonStyleType.Black);
1014
+ }
1015
+ }
1016
+
1017
+ this._barButtonItems.push(barButtonItem);
1018
+ barButtonItem.appendTo(this.$barButtonItemContainerElement, animated);
1019
+ },
1020
+
1021
+ /**
1022
+
1023
+ */
1024
+ removeBarButtonItem: function(barButtonItem, animated) {
1025
+ var barButtonItems = this._barButtonItems;
1026
+ for (var i = 0, length = barButtonItems.length; i < length; i++) {
1027
+ if (barButtonItems[i] === barButtonItem) {
1028
+ barButtonItems.splice(i, 1);
1029
+ barButtonItem.remove(animated);
1030
+ break;
1031
+ }
1032
+ }
1033
+ },
1034
+
1035
+ /**
1036
+
1037
+ */
1038
+ removeAllBarButtonItems: function(animated) {
1039
+ var barButtonItems = this._barButtonItems;
1040
+ for (var i = 0, length = barButtonItems.length; i < length; i++) barButtonItems[i].remove(animated);
1041
+ barButtonItems.length = 0;
1042
+ },
1043
+
1044
+ _backBarButtonItem: null,
1045
+
1046
+ /**
1047
+
1048
+ */
1049
+ getBackBarButtonItem: function() { return this._backBarButtonItem; },
1050
+
1051
+ /**
1052
+
1053
+ */
1054
+ setBackBarButtonItem: function(backBarButtonItem) {
1055
+ var previousBackBarButtonItem = this._backBarButtonItem;
1056
+ if (previousBackBarButtonItem) previousBackBarButtonItem.$element.removeClass('pp-navigation-bar-back-bar-button-item');
1057
+ this.$element.prepend((this._backBarButtonItem = backBarButtonItem).$element);
1058
+
1059
+ backBarButtonItem.$element.addClass('pp-navigation-bar-back-bar-button-item');
1060
+
1061
+ var viewStack = this.getViewStack();
1062
+ if (viewStack) backBarButtonItem.setHidden(viewStack.views.length === 1);
1063
+
1064
+ var barStyle = this.getBarStyleType();
1065
+ if (barStyle === Pushpop.NavigationBar.BarStyleType.Default) {
1066
+ backBarButtonItem.setButtonStyleType(Pushpop.Button.ButtonStyleType.Default);
1067
+ } else if (barStyle === Pushpop.NavigationBar.BarStyleType.Black) {
1068
+ backBarButtonItem.setButtonStyleType(Pushpop.Button.ButtonStyleType.Black);
1069
+ }
1070
+ },
1071
+
1072
+ /**
1073
+ Returns the view stack that contains this navigation bar.
1074
+ @description NOTE: If this navigation bar is not contained within a view stack,
1075
+ this method will return null.
1076
+ @type Pushpop.ViewStack
1077
+ */
1078
+ getViewStack: function() {
1079
+ var parents = this.$element.parents();
1080
+ var viewStack;
1081
+ for (var i = 0, length = parents.length; i < length; i++) if ((viewStack = parents[i].viewStack)) return viewStack;
1082
+ return null;
1083
+ },
1084
+
1085
+ /**
1086
+ Returns the active view that this navigation bar currently represents.
1087
+ @description NOTE: If this navigation bar is not contained within a view stack
1088
+ or there is no active view, this method will return null.
1089
+ @type Pushpop.View
1090
+ */
1091
+ getActiveView: function() {
1092
+ var viewStack = this.getViewStack();
1093
+ return (viewStack) ? viewStack.getActiveView() : null;
1094
+ }
1095
+ };
1096
+
1097
+ /**
1098
+ Creates a new Button.
1099
+ @param {HTMLAnchorElement} element The A element to initialize as a new Button.
1100
+ @param {Function} [action] Optional callback function to execute for the button's action.
1101
+ @param {String} [buttonAlignmentType] Optional alignment type specified by Pushpop.Button.ButtonAlignmentType.
1102
+ @param {String} [buttonStyleType] Optional style type specified by Pushpop.Button.ButtonStyleType.
1103
+ @constructor
1104
+ */
1105
+ Pushpop.Button = function Button(elementOrTitle, action, buttonAlignmentType, buttonStyleType) {
1106
+ var $element = this.$element = (!elementOrTitle || typeof elementOrTitle === 'string') ?
1107
+ $('<a class="pp-button" href="#">' + (elementOrTitle || '') + '</a>') : $(elementOrTitle);
1108
+
1109
+ var element = this.element = $element[0];
1110
+
1111
+ var button = element.button;
1112
+ if (button) return button;
1113
+
1114
+ var self = element.button = this;
1115
+
1116
+ var buttonAlignmentTypes = Pushpop.Button.ButtonAlignmentType;
1117
+ var buttonStyleTypes = Pushpop.Button.ButtonStyleType;
1118
+ var key;
1119
+
1120
+ if (!buttonAlignmentType) for (key in buttonAlignmentTypes) {
1121
+ if ($element.hasClass(buttonAlignmentTypes[key])) {
1122
+ buttonAlignmentType = buttonAlignmentTypes[key];
1123
+ break;
1124
+ }
1125
+ }
1126
+
1127
+ if (!buttonStyleType) for (key in buttonStyleTypes) {
1128
+ if ($element.hasClass(buttonStyleTypes[key])) {
1129
+ buttonStyleType = buttonStyleTypes[key];
1130
+ break;
1131
+ }
1132
+ }
1133
+
1134
+ this.setTitle($element.html());
1135
+ this.setAction(action || null);
1136
+ this.setButtonAlignmentType(buttonAlignmentType || Pushpop.Button.ButtonAlignmentType.Default);
1137
+ this.setButtonStyleType(buttonStyleType || Pushpop.Button.ButtonStyleType.Default);
1138
+ };
1139
+
1140
+ /**
1141
+ Alignment types for Pushpop.Button.
1142
+ */
1143
+ Pushpop.Button.ButtonAlignmentType = {
1144
+ Default: 'pp-button-alignment-default',
1145
+ Left: 'pp-button-alignment-left',
1146
+ Right: 'pp-button-alignment-right'
1147
+ };
1148
+
1149
+ /**
1150
+ Style types for Pushpop.Button.
1151
+ */
1152
+ Pushpop.Button.ButtonStyleType = {
1153
+ Default: 'pp-button-style-default',
1154
+ Black: 'pp-button-style-black',
1155
+ Blue: 'pp-button-style-blue',
1156
+ Green: 'pp-button-style-green',
1157
+ Red: 'pp-button-style-red'
1158
+ };
1159
+
1160
+ /**
1161
+ Event types for Pushpop.Button.
1162
+ */
1163
+ Pushpop.Button.EventType = {
1164
+ WillTriggerAction: 'Pushpop:Button:WillTriggerAction',
1165
+ DidTriggerAction: 'Pushpop:Button:DidTriggerAction'
1166
+ };
1167
+
1168
+ Pushpop.Button.prototype = {
1169
+ constructor: Pushpop.Button,
1170
+
1171
+ element: null,
1172
+ $element: null,
1173
+
1174
+ _title: '',
1175
+
1176
+ /**
1177
+
1178
+ */
1179
+ getTitle: function() { return this._title; },
1180
+
1181
+ /**
1182
+
1183
+ */
1184
+ setTitle: function(title) { this.$element.html(_title = title || ''); },
1185
+
1186
+ _action: null,
1187
+
1188
+ /**
1189
+
1190
+ */
1191
+ getAction: function() { return this._action; },
1192
+
1193
+ /**
1194
+
1195
+ */
1196
+ setAction: function(action) { this._action = action; },
1197
+
1198
+ _active: false,
1199
+
1200
+ /**
1201
+
1202
+ */
1203
+ getActive: function() { return this._active; },
1204
+
1205
+ /**
1206
+
1207
+ */
1208
+ setActive: function(active) {
1209
+ if ((this._active = active)) {
1210
+ this.$element.addClass('pp-button-state-active');
1211
+ } else {
1212
+ this.$element.removeClass('pp-button-state-active');
1213
+ }
1214
+ },
1215
+
1216
+ _buttonAlignmentType: Pushpop.Button.ButtonAlignmentType.Default,
1217
+
1218
+ getButtonAlignmentType: function() { return this._buttonAlignmentType; },
1219
+
1220
+ setButtonAlignmentType: function(buttonAlignmentType) {
1221
+ var $element = this.$element, buttonAlignmentTypes = Pushpop.Button.ButtonAlignmentType;
1222
+ for (var i in buttonAlignmentTypes) $element.removeClass(buttonAlignmentTypes[i]);
1223
+ $element.addClass(this._buttonAlignmentType = buttonAlignmentType);
1224
+ },
1225
+
1226
+ _buttonStyleType: Pushpop.Button.ButtonStyleType.Default,
1227
+
1228
+ getButtonStyleType: function() { return this._buttonStyleType; },
1229
+
1230
+ setButtonStyleType: function(buttonStyleType) {
1231
+ var $element = this.$element, buttonStyleTypes = Pushpop.Button.ButtonStyleType;
1232
+ for (var i in buttonStyleTypes) $element.removeClass(buttonStyleTypes[i]);
1233
+ $element.addClass(this._buttonStyleType = buttonStyleType);
1234
+ },
1235
+
1236
+ _hidden: false,
1237
+
1238
+ /**
1239
+
1240
+ */
1241
+ getHidden: function() { return this._hidden; },
1242
+
1243
+ /**
1244
+
1245
+ */
1246
+ setHidden: function(hidden) {
1247
+ if ((this._hidden = hidden)) {
1248
+ this.$element.addClass('pp-hidden');
1249
+ } else {
1250
+ this.$element.removeClass('pp-hidden');
1251
+ }
1252
+ },
1253
+
1254
+ /**
1255
+
1256
+ */
1257
+ triggerAction: function() {
1258
+ var $element = this.$element;
1259
+ var action = this.getAction();
1260
+
1261
+ $element.trigger($.Event(Pushpop.Button.EventType.WillTriggerAction, {
1262
+ button: this,
1263
+ action: action
1264
+ }));
1265
+
1266
+ if (action) {
1267
+ if (typeof action === 'string') window[action](this);
1268
+ else if (typeof action === 'function') action(this);
1269
+ }
1270
+
1271
+ $element.trigger($.Event(Pushpop.Button.EventType.DidTriggerAction, {
1272
+ button: this,
1273
+ action: action
1274
+ }));
1275
+ },
1276
+
1277
+ _pendingRemovalTimeout: null,
1278
+
1279
+ remove: function(animated) {
1280
+ var $element = this.$element;
1281
+
1282
+ if (animated) {
1283
+ this.setHidden(true);
1284
+ this._pendingRemovalTimeout = window.setTimeout(function() {
1285
+ $element.detach();
1286
+ }, 300);
1287
+ } else {
1288
+ $element.detach();
1289
+ this.setHidden(true);
1290
+ }
1291
+ },
1292
+
1293
+ appendTo: function(element, animated) {
1294
+ if (this._pendingRemovalTimeout) window.clearTimeout(this._pendingRemovalTimeout);
1295
+ if (animated) {
1296
+ this.setHidden(true);
1297
+ $(element).append(this.$element);
1298
+ this.forceReflow();
1299
+ this.setHidden(false);
1300
+ } else {
1301
+ this.setHidden(false);
1302
+ $(element).append(this.$element);
1303
+ }
1304
+ },
1305
+
1306
+ /**
1307
+ Forces a reflow in the browser for this button.
1308
+ */
1309
+ forceReflow: function() { var doNothing = this.element.offsetWidth; },
1310
+
1311
+ /**
1312
+ Returns the view that contains this button.
1313
+ @description NOTE: If this button is not contained within a view, this method will return null.
1314
+ @type Pushpop.View
1315
+ */
1316
+ getView: function() {
1317
+ var parents = this.$element.parents();
1318
+ var view;
1319
+ for (var i = 0, length = parents.length; i < length; i++) if ((view = parents[i].view)) return view;
1320
+ return null;
1321
+ },
1322
+
1323
+ /**
1324
+ Returns the view stack that contains this button.
1325
+ @description NOTE: If this button is not contained within a view stack, this method will return null.
1326
+ @type Pushpop.ViewStack
1327
+ */
1328
+ getViewStack: function() {
1329
+ var parents = this.$element.parents();
1330
+ var viewStack;
1331
+ for (var i = 0, length = parents.length; i < length; i++) if ((viewStack = parents[i].viewStack)) return viewStack;
1332
+ return null;
1333
+ }
1334
+ };
1335
+
1336
+ /**
1337
+
1338
+ */
1339
+ Pushpop.ActionSheet = function ActionSheet(element) {
1340
+ if (!element) return;
1341
+
1342
+ var $element = this.$element = $(element);
1343
+ element = this.element = $element[0];
1344
+
1345
+ var actionSheet = element.actionSheet;
1346
+ if (actionSheet) return actionSheet;
1347
+
1348
+ var self = element.actionSheet = this;
1349
+
1350
+ this._visible = $element.hasClass('pp-active');
1351
+
1352
+ $element.delegate('.pp-button', Pushpop.Button.EventType.DidTriggerAction, function(evt) {
1353
+ var button = evt.button;
1354
+ if (button && button.$element.hasClass('pp-action-sheet-dismiss')) self.dismiss();
1355
+ });
1356
+
1357
+ // Prevent dragging of the action sheet.
1358
+ $element.bind('touchmove', function(evt) { evt.preventDefault(); });
1359
+ };
1360
+
1361
+ Pushpop.ActionSheet.prototype = {
1362
+ constructor: Pushpop.ActionSheet,
1363
+
1364
+ element: null,
1365
+ $element: null,
1366
+
1367
+ _visible: false,
1368
+
1369
+ /**
1370
+
1371
+ */
1372
+ getVisible: function() { return this._visible; },
1373
+
1374
+ /**
1375
+
1376
+ */
1377
+ show: function() {
1378
+ if (this.getVisible()) return;
1379
+
1380
+ this.$element.addClass('pp-active');
1381
+ this._visible = true;
1382
+ },
1383
+
1384
+ /**
1385
+
1386
+ */
1387
+ dismiss: function() {
1388
+ if (!this.getVisible()) return;
1389
+
1390
+ this.$element.removeClass('pp-active');
1391
+ this._visible = false;
1392
+ }
1393
+ };
1394
+
1395
+ $(function() {
1396
+ var buttons = Pushpop.buttons = Pushpop.buttons || {};
1397
+ var views = Pushpop.views = Pushpop.views || {};
1398
+ var viewStacks = Pushpop.viewStacks = Pushpop.viewStacks || {};
1399
+ var navigationBars = Pushpop.navigationBars = Pushpop.navigationBars || {};
1400
+ var actionSheets = Pushpop.actionSheets = Pushpop.actionSheets || {};
1401
+
1402
+ $('.pp-button').each(function(index, element) {
1403
+ var button = new Pushpop.Button(element);
1404
+ if (element.id) buttons[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = button;
1405
+ });
1406
+
1407
+ $('.pp-view').each(function(index, element) {
1408
+ var view = new Pushpop.View(element);
1409
+ if (element.id) views[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = view;
1410
+ });
1411
+
1412
+ if (Pushpop.SplitView) $('.pp-split-view').each(function(index, element) {
1413
+ var splitView = new Pushpop.SplitView(element);
1414
+ if (element.id) views[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = splitView;
1415
+ });
1416
+
1417
+ if (Pushpop.TabView) $('.pp-tab-view').each(function(index, element) {
1418
+ var tabView = new Pushpop.TabView(element);
1419
+ if (element.id) views[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = tabView;
1420
+ });
1421
+
1422
+ $('.pp-view-stack').each(function(index, element) {
1423
+ var viewStack = new Pushpop.ViewStack(element);
1424
+ if (element.id) viewStacks[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = viewStack;
1425
+ });
1426
+
1427
+ if (Pushpop.ModalViewStack) $('.pp-modal-view-stack').each(function(index, element) {
1428
+ var viewStack = new Pushpop.ModalViewStack(element);
1429
+ if (element.id) viewStacks[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = viewStack;
1430
+ });
1431
+
1432
+ if (Pushpop.PopoverViewStack) $('.pp-popover-view-stack').each(function(index, element) {
1433
+ var viewStack = new Pushpop.PopoverViewStack(element);
1434
+ if (element.id) viewStacks[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = viewStack;
1435
+ });
1436
+
1437
+ $('.pp-navigation-bar').each(function(index, element) {
1438
+ var navigationBar = new Pushpop.NavigationBar(element);
1439
+ if (element.id) navigationBars[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = navigationBar;
1440
+ });
1441
+
1442
+ $('.pp-action-sheet').each(function(index, element) {
1443
+ var actionSheet = new Pushpop.ActionSheet(element);
1444
+ if (element.id) actionSheets[Pushpop.Util.convertDashedStringToCamelCase(element.id)] = actionSheet;
1445
+ });
1446
+
1447
+ // Handle mouse/touch events globally to trigger button actions.
1448
+ var $body = $(document.body);
1449
+
1450
+ $body.delegate('.pp-button', 'click', function(evt) { evt.preventDefault(); });
1451
+
1452
+ $body.delegate('.pp-button', !!('ontouchstart' in window) ? 'touchstart' : 'mousedown', function(evt) {
1453
+ var button = this.button;
1454
+ if (!button) return;
1455
+ button.setActive(true);
1456
+ });
1457
+
1458
+ $body.delegate('.pp-button', !!('ontouchmove' in window) ? 'touchmove' : 'mousemove', function(evt) {
1459
+ var button = this.button;
1460
+ if (!button || !button.getActive()) return;
1461
+
1462
+ button.setActive(false);
1463
+ });
1464
+
1465
+ $body.delegate('.pp-button', !!('ontouchend' in window) ? 'touchend' : 'mouseup', function(evt) {
1466
+ var button = this.button;
1467
+ if (!button || !button.getActive()) return;
1468
+
1469
+ evt.preventDefault();
1470
+
1471
+ button.setActive(false);
1472
+ button.triggerAction();
1473
+ });
1474
+
1475
+ // Handle actions for buttons set up to automatically push/pop views.
1476
+ $body.delegate('.pp-button.pp-push, .pp-button.pp-pop', Pushpop.Button.EventType.DidTriggerAction, function(evt) {
1477
+ var button = evt.button;
1478
+ var $element = button.$element;
1479
+ var href = $element.attr('href');
1480
+ var transition = $element.attr('data-transition');
1481
+ var $viewElement, view, viewStack;
1482
+
1483
+ if ($element.hasClass('pp-push')) {
1484
+ $viewElement = $(href);
1485
+ if ($viewElement.length === 0) return;
1486
+
1487
+ view = $viewElement[0].view || new Pushpop.View($viewElement);
1488
+
1489
+ viewStack = view.getViewStack();
1490
+ if (viewStack) viewStack.push(view, transition);
1491
+ }
1492
+
1493
+ else if ($element.hasClass('pp-pop')) {
1494
+ if (href === '#') {
1495
+ viewStack = button.getViewStack();
1496
+ if (viewStack) viewStack.pop(transition);
1497
+ } else {
1498
+ $viewElement = $(href);
1499
+ if ($viewElement.length === 0) return;
1500
+
1501
+ view = $viewElement[0].view || new Pushpop.View($viewElement);
1502
+
1503
+ viewStack = view.getViewStack();
1504
+ if (viewStack) viewStack.pop(view, transition);
1505
+ }
1506
+ }
1507
+ });
1508
+
1509
+ // TODO: Is this still needed?
1510
+ $(document).bind('touchstart', function() {});
1511
+
1512
+ // TODO: Clean up (use new events?)
1513
+ $('a.pp-push').live('click', function(evt) {
1514
+ var $this = $(this);
1515
+ if ($this.hasClass('pp-button')) return;
1516
+
1517
+ evt.preventDefault();
1518
+
1519
+ var href = $this.attr('href');
1520
+ var $viewElement, view, viewStack;
1521
+
1522
+ $viewElement = $(href);
1523
+ if ($viewElement.length === 0) return;
1524
+
1525
+ view = $viewElement[0].view || new Pushpop.View($viewElement);
1526
+
1527
+ viewStack = view.getViewStack();
1528
+ if (viewStack) viewStack.push(view, $this.attr('data-transition'));
1529
+ });
1530
+
1531
+ // TODO: Clean up (use new events?)
1532
+ $('a.pp-pop').live('click', function(evt) {
1533
+ var $this = $(this);
1534
+ if ($this.hasClass('pp-button')) return;
1535
+
1536
+ evt.preventDefault();
1537
+
1538
+ var href = $this.attr('href');
1539
+ var $viewElement, view, viewStack;
1540
+
1541
+ if (href === '#') {
1542
+ viewStack = Pushpop.getViewStackForElement($this);
1543
+ if (viewStack) viewStack.pop($this.attr('data-transition'));
1544
+ } else {
1545
+ $viewElement = $(href);
1546
+ if ($viewElement.length === 0) return;
1547
+
1548
+ view = $viewElement[0].view || new Pushpop.View($viewElement);
1549
+
1550
+ viewStack = view.getViewStack();
1551
+ if (viewStack) viewStack.pop(view, $this.attr('data-transition'));
1552
+ }
1553
+ });
1554
+ });