ionic-rails-engine 0.9.17 → 0.9.26

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ /**
2
+ * State-based routing for AngularJS
3
+ * @version v0.2.7
4
+ * @link http://angular-ui.github.com/
5
+ * @license MIT License, http://www.opensource.org/licenses/MIT
6
+ */
7
+ "undefined"!=typeof module&&"undefined"!=typeof exports&&module.exports===exports&&(module.exports="ui.router"),function(a,b,c){"use strict";function d(a,b){return E(new(E(function(){},{prototype:a})),b)}function e(a){return D(arguments,function(b){b!==a&&D(b,function(b,c){a.hasOwnProperty(c)||(a[c]=b)})}),a}function f(a,b){var c=[];for(var d in a.path)if(""!==a.path[d]){if(!b.path[d])break;c.push(a.path[d])}return c}function g(a,b){if(Array.prototype.indexOf)return a.indexOf(b,Number(arguments[2])||0);var c=a.length>>>0,d=Number(arguments[2])||0;for(d=0>d?Math.ceil(d):Math.floor(d),0>d&&(d+=c);c>d;d++)if(d in a&&a[d]===b)return d;return-1}function h(a,b,c,d){var e,h=f(c,d),i={},j=[];for(var k in h)if(h[k].params&&h[k].params.length){e=h[k].params;for(var l in e)g(j,e[l])>=0||(j.push(e[l]),i[e[l]]=a[e[l]])}return E({},i,b)}function i(a,b){var c={};return D(a,function(a){var d=b[a];c[a]=null!=d?String(d):null}),c}function j(a,b,c){if(!c){c=[];for(var d in a)c.push(d)}for(var e=0;e<c.length;e++){var f=c[e];if(a[f]!=b[f])return!1}return!0}function k(a,b){var c={};return D(a,function(a){c[a]=b[a]}),c}function l(a,b){var d=1,f=2,g={},h=[],i=g,j=E(a.when(g),{$$promises:g,$$values:g});this.study=function(g){function k(a,c){if(o[c]!==f){if(n.push(c),o[c]===d)throw n.splice(0,n.indexOf(c)),new Error("Cyclic dependency: "+n.join(" -> "));if(o[c]=d,A(a))m.push(c,[function(){return b.get(a)}],h);else{var e=b.annotate(a);D(e,function(a){a!==c&&g.hasOwnProperty(a)&&k(g[a],a)}),m.push(c,a,e)}n.pop(),o[c]=f}}function l(a){return B(a)&&a.then&&a.$$promises}if(!B(g))throw new Error("'invocables' must be an object");var m=[],n=[],o={};return D(g,k),g=n=o=null,function(d,f,g){function h(){--s||(t||e(r,f.$$values),p.$$values=r,p.$$promises=!0,o.resolve(r))}function k(a){p.$$failure=a,o.reject(a)}function n(c,e,f){function i(a){l.reject(a),k(a)}function j(){if(!y(p.$$failure))try{l.resolve(b.invoke(e,g,r)),l.promise.then(function(a){r[c]=a,h()},i)}catch(a){i(a)}}var l=a.defer(),m=0;D(f,function(a){q.hasOwnProperty(a)&&!d.hasOwnProperty(a)&&(m++,q[a].then(function(b){r[a]=b,--m||j()},i))}),m||j(),q[c]=l.promise}if(l(d)&&g===c&&(g=f,f=d,d=null),d){if(!B(d))throw new Error("'locals' must be an object")}else d=i;if(f){if(!l(f))throw new Error("'parent' must be a promise returned by $resolve.resolve()")}else f=j;var o=a.defer(),p=o.promise,q=p.$$promises={},r=E({},d),s=1+m.length/3,t=!1;if(y(f.$$failure))return k(f.$$failure),p;f.$$values?(t=e(r,f.$$values),h()):(E(q,f.$$promises),f.then(h,k));for(var u=0,v=m.length;v>u;u+=3)d.hasOwnProperty(m[u])?h():n(m[u],m[u+1],m[u+2]);return p}},this.resolve=function(a,b,c,d){return this.study(a)(b,c,d)}}function m(a,b,c){this.fromConfig=function(a,b,c){return y(a.template)?this.fromString(a.template,b):y(a.templateUrl)?this.fromUrl(a.templateUrl,b):y(a.templateProvider)?this.fromProvider(a.templateProvider,b,c):null},this.fromString=function(a,b){return z(a)?a(b):a},this.fromUrl=function(c,d){return z(c)&&(c=c(d)),null==c?null:a.get(c,{cache:b}).then(function(a){return a.data})},this.fromProvider=function(a,b,d){return c.invoke(a,null,d||{params:b})}}function n(a){function b(b){if(!/^\w+(-+\w+)*$/.test(b))throw new Error("Invalid parameter name '"+b+"' in pattern '"+a+"'");if(f[b])throw new Error("Duplicate parameter name '"+b+"' in pattern '"+a+"'");f[b]=!0,j.push(b)}function c(a){return a.replace(/[\\\[\]\^$*+?.()|{}]/g,"\\$&")}var d,e=/([:*])(\w+)|\{(\w+)(?:\:((?:[^{}\\]+|\\.|\{(?:[^{}\\]+|\\.)*\})+))?\}/g,f={},g="^",h=0,i=this.segments=[],j=this.params=[];this.source=a;for(var k,l,m;(d=e.exec(a))&&(k=d[2]||d[3],l=d[4]||("*"==d[1]?".*":"[^/]*"),m=a.substring(h,d.index),!(m.indexOf("?")>=0));)g+=c(m)+"("+l+")",b(k),i.push(m),h=e.lastIndex;m=a.substring(h);var n=m.indexOf("?");if(n>=0){var o=this.sourceSearch=m.substring(n);m=m.substring(0,n),this.sourcePath=a.substring(0,h+n),D(o.substring(1).split(/[&?]/),b)}else this.sourcePath=a,this.sourceSearch="";g+=c(m)+"$",i.push(m),this.regexp=new RegExp(g),this.prefix=i[0]}function o(){this.compile=function(a){return new n(a)},this.isMatcher=function(a){return B(a)&&z(a.exec)&&z(a.format)&&z(a.concat)},this.$get=function(){return this}}function p(a){function b(a){var b=/^\^((?:\\[^a-zA-Z0-9]|[^\\\[\]\^$*+?.()|{}]+)*)/.exec(a.source);return null!=b?b[1].replace(/\\(.)/g,"$1"):""}function c(a,b){return a.replace(/\$(\$|\d{1,2})/,function(a,c){return b["$"===c?0:Number(c)]})}function d(a,b,c){if(!c)return!1;var d=a.invoke(b,b,{$match:c});return y(d)?d:!0}var e=[],f=null;this.rule=function(a){if(!z(a))throw new Error("'rule' must be a function");return e.push(a),this},this.otherwise=function(a){if(A(a)){var b=a;a=function(){return b}}else if(!z(a))throw new Error("'rule' must be a function");return f=a,this},this.when=function(e,f){var g,h=A(f);if(A(e)&&(e=a.compile(e)),!h&&!z(f)&&!C(f))throw new Error("invalid 'handler' in when()");var i={matcher:function(b,c){return h&&(g=a.compile(c),c=["$match",function(a){return g.format(a)}]),E(function(a,e){return d(a,c,b.exec(e.path(),e.search()))},{prefix:A(b.prefix)?b.prefix:""})},regex:function(a,e){if(a.global||a.sticky)throw new Error("when() RegExp must not be global or sticky");return h&&(g=e,e=["$match",function(a){return c(g,a)}]),E(function(b,c){return d(b,e,a.exec(c.path()))},{prefix:b(a)})}},j={matcher:a.isMatcher(e),regex:e instanceof RegExp};for(var k in j)if(j[k])return this.rule(i[k](e,f));throw new Error("invalid 'what' in when()")},this.$get=["$location","$rootScope","$injector",function(a,b,c){function d(b){function d(b){var d=b(c,a);return d?(A(d)&&a.replace().url(d),!0):!1}if(!b||!b.defaultPrevented){var g,h=e.length;for(g=0;h>g;g++)if(d(e[g]))return;f&&d(f)}}return b.$on("$locationChangeSuccess",d),{sync:function(){d()}}}]}function q(a,e,f){function g(a){return 0===a.indexOf(".")||0===a.indexOf("^")}function l(a,b){var d=A(a),e=d?a:a.name,f=g(e);if(f){if(!b)throw new Error("No reference point given for path '"+e+"'");for(var h=e.split("."),i=0,j=h.length,k=b;j>i;i++)if(""!==h[i]||0!==i){if("^"!==h[i])break;if(!k.parent)throw new Error("Path '"+e+"' not valid for state '"+b.name+"'");k=k.parent}else k=b;h=h.slice(i).join("."),e=k.name+(k.name&&h?".":"")+h}var l=u[e];return!l||!d&&(d||l!==a&&l.self!==a)?c:l}function m(a,b){v[a]||(v[a]=[]),v[a].push(b)}function n(b){b=d(b,{self:b,resolve:b.resolve||{},toString:function(){return this.name}});var c=b.name;if(!A(c)||c.indexOf("@")>=0)throw new Error("State must have a valid name");if(u.hasOwnProperty(c))throw new Error("State '"+c+"'' is already defined");var e=-1!==c.indexOf(".")?c.substring(0,c.lastIndexOf(".")):A(b.parent)?b.parent:"";if(e&&!u[e])return m(e,b.self);for(var f in x)z(x[f])&&(b[f]=x[f](b,x.$delegates[f]));if(u[c]=b,!b[w]&&b.url&&a.when(b.url,["$match","$stateParams",function(a,c){t.$current.navigable==b&&j(a,c)||t.transitionTo(b,a,{location:!1})}]),v[c])for(var g=0;g<v[c].length;g++)n(v[c][g]);return b}function o(a,b){return A(a)&&!y(b)?x[a]:z(b)&&A(a)?(x[a]&&!x.$delegates[a]&&(x.$delegates[a]=x[a]),x[a]=b,this):this}function p(a,b){return B(a)?b=a:b.name=a,n(b),this}function q(a,e,g,m,n,o,p){function q(){p.url()!==H&&(p.url(H),p.replace())}function v(a,c,d,f,h){var i=d?c:k(a.params,c),j={$stateParams:i};h.resolve=n.resolve(a.resolve,j,h.resolve,a);var l=[h.resolve.then(function(a){h.globals=a})];return f&&l.push(f),D(a.views,function(c,d){var e=c.resolve&&c.resolve!==a.resolve?c.resolve:{};e.$template=[function(){return g.load(d,{view:c,locals:j,params:i,notify:!1})||""}],l.push(n.resolve(e,j,h.resolve,a).then(function(f){if(z(c.controllerProvider)||C(c.controllerProvider)){var g=b.extend({},e,j);f.$$controller=m.invoke(c.controllerProvider,null,g)}else f.$$controller=c.controller;f.$$state=a,h[d]=f}))}),e.all(l).then(function(){return h})}var x=e.reject(new Error("transition superseded")),A=e.reject(new Error("transition prevented")),B=e.reject(new Error("transition aborted")),G=e.reject(new Error("transition failed")),H=p.url();return s.locals={resolve:null,globals:{$stateParams:{}}},t={params:{},current:s.self,$current:s,transition:null},t.reload=function(){t.transitionTo(t.current,o,{reload:!0,inherit:!1,notify:!1})},t.go=function(a,b,c){return this.transitionTo(a,b,E({inherit:!0,relative:t.$current},c))},t.transitionTo=function(b,c,f){c=c||{},f=E({location:!0,inherit:!1,relative:null,notify:!0,reload:!1,$retry:!1},f||{});var g,k=t.$current,n=t.params,u=k.path,z=l(b,f.relative);if(!y(z)){var C={to:b,toParams:c,options:f};if(g=a.$broadcast("$stateNotFound",C,k.self,n),g.defaultPrevented)return q(),B;if(g.retry){if(f.$retry)return q(),G;var D=t.transition=e.when(g.retry);return D.then(function(){return D!==t.transition?x:(C.options.$retry=!0,t.transitionTo(C.to,C.toParams,C.options))},function(){return B}),q(),D}if(b=C.to,c=C.toParams,f=C.options,z=l(b,f.relative),!y(z)){if(f.relative)throw new Error("Could not resolve '"+b+"' from state '"+f.relative+"'");throw new Error("No such state '"+b+"'")}}if(z[w])throw new Error("Cannot transition to abstract state '"+b+"'");f.inherit&&(c=h(o,c||{},t.$current,z)),b=z;var I,J,K=b.path,L=s.locals,M=[];for(I=0,J=K[I];J&&J===u[I]&&j(c,n,J.ownParams)&&!f.reload;I++,J=K[I])L=M[I]=J.locals;if(r(b,k,L,f))return b.self.reloadOnSearch!==!1&&q(),t.transition=null,e.when(t.current);if(c=i(b.params,c||{}),f.notify&&(g=a.$broadcast("$stateChangeStart",b.self,c,k.self,n),g.defaultPrevented))return q(),A;for(var N=e.when(L),O=I;O<K.length;O++,J=K[O])L=M[O]=d(L),N=v(J,c,J===b,N,L);var P=t.transition=N.then(function(){var d,e,g;if(t.transition!==P)return x;for(d=u.length-1;d>=I;d--)g=u[d],g.self.onExit&&m.invoke(g.self.onExit,g.self,g.locals.globals),g.locals=null;for(d=I;d<K.length;d++)e=K[d],e.locals=M[d],e.self.onEnter&&m.invoke(e.self.onEnter,e.self,e.locals.globals);if(t.transition!==P)return x;t.$current=b,t.current=b.self,t.params=c,F(t.params,o),t.transition=null;var h=b.navigable;return f.location&&h&&(p.url(h.url.format(h.locals.globals.$stateParams)),"replace"===f.location&&p.replace()),f.notify&&a.$broadcast("$stateChangeSuccess",b.self,c,k.self,n),H=p.url(),t.current},function(d){return t.transition!==P?x:(t.transition=null,a.$broadcast("$stateChangeError",b.self,c,k.self,n,d),q(),e.reject(d))});return P},t.is=function(a,d){var e=l(a);return y(e)?t.$current!==e?!1:y(d)?b.equals(o,d):!0:c},t.includes=function(a,d){var e=l(a);if(!y(e))return c;if(!y(t.$current.includes[e.name]))return!1;var f=!0;return b.forEach(d,function(a,b){y(o[b])&&o[b]===a||(f=!1)}),f},t.href=function(a,b,c){c=E({lossy:!0,inherit:!1,absolute:!1,relative:t.$current},c||{});var d=l(a,c.relative);if(!y(d))return null;b=h(o,b||{},t.$current,d);var e=d&&c.lossy?d.navigable:d,g=e&&e.url?e.url.format(i(d.params,b||{})):null;return!f.html5Mode()&&g&&(g="#"+f.hashPrefix()+g),c.absolute&&g&&(g=p.protocol()+"://"+p.host()+(80==p.port()||443==p.port()?"":":"+p.port())+(!f.html5Mode()&&g?"/":"")+g),g},t.get=function(a,b){if(!y(a)){var c=[];return D(u,function(a){c.push(a.self)}),c}var d=l(a,b);return d&&d.self?d.self:null},t}function r(a,b,c,d){return a!==b||(c!==b.locals||d.reload)&&a.self.reloadOnSearch!==!1?void 0:!0}var s,t,u={},v={},w="abstract",x={parent:function(a){if(y(a.parent)&&a.parent)return l(a.parent);var b=/^(.+)\.[^.]+$/.exec(a.name);return b?l(b[1]):s},data:function(a){return a.parent&&a.parent.data&&(a.data=a.self.data=E({},a.parent.data,a.data)),a.data},url:function(a){var b=a.url;if(A(b))return"^"==b.charAt(0)?e.compile(b.substring(1)):(a.parent.navigable||s).url.concat(b);if(e.isMatcher(b)||null==b)return b;throw new Error("Invalid url '"+b+"' in state '"+a+"'")},navigable:function(a){return a.url?a:a.parent?a.parent.navigable:null},params:function(a){if(!a.params)return a.url?a.url.parameters():a.parent.params;if(!C(a.params))throw new Error("Invalid params in state '"+a+"'");if(a.url)throw new Error("Both params and url specicified in state '"+a+"'");return a.params},views:function(a){var b={};return D(y(a.views)?a.views:{"":a},function(c,d){d.indexOf("@")<0&&(d+="@"+a.parent.name),b[d]=c}),b},ownParams:function(a){if(!a.parent)return a.params;var b={};D(a.params,function(a){b[a]=!0}),D(a.parent.params,function(c){if(!b[c])throw new Error("Missing required parameter '"+c+"' in state '"+a.name+"'");b[c]=!1});var c=[];return D(b,function(a,b){a&&c.push(b)}),c},path:function(a){return a.parent?a.parent.path.concat(a):[]},includes:function(a){var b=a.parent?E({},a.parent.includes):{};return b[a.name]=!0,b},$delegates:{}};s=n({name:"",url:"^",views:null,"abstract":!0}),s.navigable=null,this.decorator=o,this.state=p,this.$get=q,q.$inject=["$rootScope","$q","$view","$injector","$resolve","$stateParams","$location","$urlRouter"]}function r(){function a(a,b){return{load:function(c,d){var e,f={template:null,controller:null,view:null,locals:null,notify:!0,async:!0,params:{}};return d=E(f,d),d.view&&(e=b.fromConfig(d.view,d.params,d.locals)),e&&d.notify&&a.$broadcast("$viewContentLoading",d),e}}}this.$get=a,a.$inject=["$rootScope","$templateFactory"]}function s(a,c,d,e,f){var g=e.has("$animator")?e.get("$animator"):!1,h=!1,i={restrict:"ECA",terminal:!0,priority:1e3,transclude:!0,compile:function(e,j,k){return function(e,j,l){function m(b){var g=a.$current&&a.$current.locals[p];if(g!==o){var h=t(r&&b);if(h.remove(j),n&&(n.$destroy(),n=null),!g)return o=null,v.state=null,h.restore(s,j);o=g,v.state=g.$$state;var i=c(h.populate(g.$template,j));if(n=e.$new(),g.$$controller){g.$scope=n;var k=d(g.$$controller,g);j.children().data("$ngControllerController",k)}i(n),n.$emit("$viewContentLoaded"),q&&n.$eval(q),f()}}var n,o,p=l[i.name]||l.name||"",q=l.onload||"",r=g&&g(e,l),s=k(e),t=function(a){return{"true":{remove:function(a){r.leave(a.contents(),a)},restore:function(a,b){r.enter(a,b)},populate:function(a,c){var d=b.element("<div></div>").html(a).contents();return r.enter(d,c),d}},"false":{remove:function(a){a.html("")},restore:function(a,b){b.append(a)},populate:function(a,b){return b.html(a),b.contents()}}}[a.toString()]};j.append(s);var u=j.parent().inheritedData("$uiView");p.indexOf("@")<0&&(p=p+"@"+(u?u.state.name:""));var v={name:p,state:null};j.data("$uiView",v);var w=function(){if(!h){h=!0;try{m(!0)}catch(a){throw h=!1,a}h=!1}};e.$on("$stateChangeSuccess",w),e.$on("$viewContentLoading",w),m(!1)}}};return i}function t(a){var b=a.replace(/\n/g," ").match(/^([^(]+?)\s*(\((.*)\))?$/);if(!b||4!==b.length)throw new Error("Invalid state ref '"+a+"'");return{state:b[1],paramExpr:b[3]||null}}function u(a){var b=a.parent().inheritedData("$uiView");return b&&b.state&&b.state.name?b.state:void 0}function v(a,b){return{restrict:"A",require:"?^uiSrefActive",link:function(c,d,e,f){var g=t(e.uiSref),h=null,i=u(d)||a.$current,j="FORM"===d[0].nodeName,k=j?"action":"href",l=!0,m=function(b){if(b&&(h=b),l){var c=a.href(g.state,h,{relative:i});if(!c)return l=!1,!1;d[0][k]=c,f&&f.$$setStateInfo(g.state,h)}};g.paramExpr&&(c.$watch(g.paramExpr,function(a){a!==h&&m(a)},!0),h=c.$eval(g.paramExpr)),m(),j||d.bind("click",function(d){var e=d.which||d.button;0!==e&&1!=e||d.ctrlKey||d.metaKey||d.shiftKey||(b(function(){c.$apply(function(){a.go(g.state,h,{relative:i})})}),d.preventDefault())})}}}function w(a,b,c){return{restrict:"A",controller:function(d,e,f){function g(){a.$current.self===i&&h()?e.addClass(l):e.removeClass(l)}function h(){return!k||j(k,b)}var i,k,l;l=c(f.uiSrefActive||"",!1)(d),this.$$setStateInfo=function(b,c){i=a.get(b,u(e)),k=c,g()},d.$on("$stateChangeSuccess",g)}}}function x(a,b){function e(a){this.locals=a.locals.globals,this.params=this.locals.$stateParams}function f(){this.locals=null,this.params=null}function g(c,g){if(null!=g.redirectTo){var h,j=g.redirectTo;if(A(j))h=j;else{if(!z(j))throw new Error("Invalid 'redirectTo' in when()");h=function(a,b){return j(a,b.path(),b.search())}}b.when(c,h)}else a.state(d(g,{parent:null,name:"route:"+encodeURIComponent(c),url:c,onEnter:e,onExit:f}));return i.push(g),this}function h(a,b,d){function e(a){return""!==a.name?a:c}var f={routes:i,params:d,current:c};return b.$on("$stateChangeStart",function(a,c,d,f){b.$broadcast("$routeChangeStart",e(c),e(f))}),b.$on("$stateChangeSuccess",function(a,c,d,g){f.current=e(c),b.$broadcast("$routeChangeSuccess",e(c),e(g)),F(d,f.params)}),b.$on("$stateChangeError",function(a,c,d,f,g,h){b.$broadcast("$routeChangeError",e(c),e(f),h)}),f}var i=[];e.$inject=["$$state"],this.when=g,this.$get=h,h.$inject=["$state","$rootScope","$routeParams"]}var y=b.isDefined,z=b.isFunction,A=b.isString,B=b.isObject,C=b.isArray,D=b.forEach,E=b.extend,F=b.copy;b.module("ui.router.util",["ng"]),b.module("ui.router.router",["ui.router.util"]),b.module("ui.router.state",["ui.router.router","ui.router.util"]),b.module("ui.router",["ui.router.state"]),b.module("ui.router.compat",["ui.router"]),l.$inject=["$q","$injector"],b.module("ui.router.util").service("$resolve",l),m.$inject=["$http","$templateCache","$injector"],b.module("ui.router.util").service("$templateFactory",m),n.prototype.concat=function(a){return new n(this.sourcePath+a+this.sourceSearch)},n.prototype.toString=function(){return this.source},n.prototype.exec=function(a,b){var c=this.regexp.exec(a);if(!c)return null;var d,e=this.params,f=e.length,g=this.segments.length-1,h={};if(g!==c.length-1)throw new Error("Unbalanced capture group in route '"+this.source+"'");for(d=0;g>d;d++)h[e[d]]=c[d+1];for(;f>d;d++)h[e[d]]=b[e[d]];return h},n.prototype.parameters=function(){return this.params},n.prototype.format=function(a){var b=this.segments,c=this.params;if(!a)return b.join("");var d,e,f,g=b.length-1,h=c.length,i=b[0];for(d=0;g>d;d++)f=a[c[d]],null!=f&&(i+=encodeURIComponent(f)),i+=b[d+1];for(;h>d;d++)f=a[c[d]],null!=f&&(i+=(e?"&":"?")+c[d]+"="+encodeURIComponent(f),e=!0);return i},b.module("ui.router.util").provider("$urlMatcherFactory",o),p.$inject=["$urlMatcherFactoryProvider"],b.module("ui.router.router").provider("$urlRouter",p),q.$inject=["$urlRouterProvider","$urlMatcherFactoryProvider","$locationProvider"],b.module("ui.router.state").value("$stateParams",{}).provider("$state",q),r.$inject=[],b.module("ui.router.state").provider("$view",r),s.$inject=["$state","$compile","$controller","$injector","$anchorScroll"],b.module("ui.router.state").directive("uiView",s),v.$inject=["$state","$timeout"],w.$inject=["$state","$stateParams","$interpolate"],b.module("ui.router.state").directive("uiSref",v).directive("uiSrefActive",w),x.$inject=["$stateProvider","$urlRouterProvider"],b.module("ui.router.compat").provider("$route",x).directive("ngView",s)}(window,window.angular);
@@ -1,59 +1,354 @@
1
1
  /*!
2
- * Copyright 2013 Drifty Co.
2
+ * Copyright 2014 Drifty Co.
3
3
  * http://drifty.com/
4
4
  *
5
- * Ionic, v0.9.17
5
+ * Ionic, v0.9.26
6
6
  * A powerful HTML5 mobile app framework.
7
7
  * http://ionicframework.com/
8
8
  *
9
- * By @maxlynch, @helloimben, @adamdbradley <3
9
+ * By @maxlynch, @benjsperry, @adamdbradley <3
10
10
  *
11
11
  * Licensed under the MIT license. Please see LICENSE for more information.
12
12
  *
13
- */;
13
+ */
14
+ ;
14
15
  /**
15
16
  * Create a wrapping module to ease having to include too many
16
17
  * modules.
17
18
  */
18
19
  angular.module('ionic.service', [
20
+ 'ionic.service.bind',
19
21
  'ionic.service.platform',
20
22
  'ionic.service.actionSheet',
21
23
  'ionic.service.gesture',
22
24
  'ionic.service.loading',
23
25
  'ionic.service.modal',
24
26
  'ionic.service.popup',
25
- 'ionic.service.templateLoad'
27
+ 'ionic.service.templateLoad',
28
+ 'ionic.service.view',
29
+ 'ionic.decorator.location'
30
+ ]);
31
+
32
+ // UI specific services and delegates
33
+ angular.module('ionic.ui.service', [
34
+ 'ionic.ui.service.scrollDelegate',
35
+ 'ionic.ui.service.slideBoxDelegate',
36
+ 'ionic.ui.service.sideMenuDelegate',
26
37
  ]);
27
38
 
28
39
  angular.module('ionic.ui', [
29
40
  'ionic.ui.content',
30
41
  'ionic.ui.scroll',
31
42
  'ionic.ui.tabs',
32
- 'ionic.ui.navRouter',
43
+ 'ionic.ui.viewState',
33
44
  'ionic.ui.header',
34
45
  'ionic.ui.sideMenu',
35
46
  'ionic.ui.slideBox',
36
47
  'ionic.ui.list',
37
48
  'ionic.ui.checkbox',
38
49
  'ionic.ui.toggle',
39
- 'ionic.ui.radio'
50
+ 'ionic.ui.radio',
51
+ 'ionic.ui.touch'
40
52
  ]);
41
53
 
54
+
42
55
  angular.module('ionic', [
43
56
  'ionic.service',
57
+ 'ionic.ui.service',
44
58
  'ionic.ui',
45
-
59
+
46
60
  // Angular deps
47
61
  'ngAnimate',
48
- 'ngRoute',
49
- 'ngTouch',
50
- 'ngSanitize'
62
+ 'ngSanitize',
63
+ 'ui.router'
51
64
  ]);
52
65
  ;
53
- angular.module('ionic.service.actionSheet', ['ionic.service.templateLoad', 'ionic.ui.actionSheet', 'ngAnimate'])
54
66
 
55
- .factory('ActionSheet', ['$rootScope', '$document', '$compile', '$animate', '$timeout', 'TemplateLoader',
56
- function($rootScope, $document, $compile, $animate, $timeout, TemplateLoader) {
67
+ angular.element.prototype.addClass = function(cssClasses) {
68
+ var x, y, cssClass, el, splitClasses, existingClasses;
69
+ if (cssClasses && cssClasses != 'ng-scope' && cssClasses != 'ng-isolate-scope') {
70
+ for(x=0; x<this.length; x++) {
71
+ el = this[x];
72
+ if(el.setAttribute) {
73
+
74
+ if(cssClasses.indexOf(' ') < 0) {
75
+ el.classList.add(cssClasses);
76
+ } else {
77
+ existingClasses = (' ' + (el.getAttribute('class') || '') + ' ')
78
+ .replace(/[\n\t]/g, " ");
79
+ splitClasses = cssClasses.split(' ');
80
+
81
+ for (y=0; y<splitClasses.length; y++) {
82
+ cssClass = splitClasses[y].trim();
83
+ if (existingClasses.indexOf(' ' + cssClass + ' ') === -1) {
84
+ existingClasses += cssClass + ' ';
85
+ }
86
+ }
87
+ el.setAttribute('class', existingClasses.trim());
88
+ }
89
+ }
90
+ }
91
+ }
92
+ return this;
93
+ };
94
+
95
+ angular.element.prototype.removeClass = function(cssClasses) {
96
+ var x, y, splitClasses, cssClass, el;
97
+ if (cssClasses) {
98
+ for(x=0; x<this.length; x++) {
99
+ el = this[x];
100
+ if(el.getAttribute) {
101
+ if(cssClasses.indexOf(' ') < 0) {
102
+ el.classList.remove(cssClasses);
103
+ } else {
104
+ splitClasses = cssClasses.split(' ');
105
+
106
+ for (y=0; y<splitClasses.length; y++) {
107
+ cssClass = splitClasses[y];
108
+ el.setAttribute('class', (
109
+ (" " + (el.getAttribute('class') || '') + " ")
110
+ .replace(/[\n\t]/g, " ")
111
+ .replace(" " + cssClass.trim() + " ", " ")).trim()
112
+ );
113
+ }
114
+ }
115
+ }
116
+ }
117
+ }
118
+ return this;
119
+ };;
120
+ angular.module('ionic.decorator.location', [])
121
+
122
+ .config(['$provide', function($provide) {
123
+ $provide.decorator('$location', ['$delegate', '$timeout', $LocationDecorator]);
124
+ }]);
125
+
126
+ function $LocationDecorator($location, $timeout) {
127
+
128
+ $location.__hash = $location.hash;
129
+ //Fix: first time window.location.hash is set, the scrollable area
130
+ //found nearest to body's scrollTop is set to scroll to an element
131
+ //with that ID.
132
+ $location.hash = function(value) {
133
+ if (angular.isDefined(value)) {
134
+ $timeout(function() {
135
+ var scroll = document.querySelector('.scroll-content');
136
+ if (scroll)
137
+ scroll.scrollTop = 0;
138
+ }, 0, false);
139
+ }
140
+ return $location.__hash(value);
141
+ };
142
+
143
+ return $location;
144
+ }
145
+ ;
146
+ (function() {
147
+ 'use strict';
148
+
149
+ angular.module('ionic.ui.service.scrollDelegate', [])
150
+
151
+ .factory('$ionicScrollDelegate', ['$rootScope', '$timeout', '$q', '$anchorScroll', '$location', '$document', function($rootScope, $timeout, $q, $anchorScroll, $location, $document) {
152
+ return {
153
+ /**
154
+ * Trigger a scroll-to-top event on child scrollers.
155
+ */
156
+ scrollTop: function(animate) {
157
+ $rootScope.$broadcast('scroll.scrollTop', animate);
158
+ },
159
+ scrollBottom: function(animate) {
160
+ $rootScope.$broadcast('scroll.scrollBottom', animate);
161
+ },
162
+ scrollTo: function(left, top, animate) {
163
+ $rootScope.$broadcast('scroll.scrollTo', left, top, animate);
164
+ },
165
+ resize: function() {
166
+ $rootScope.$broadcast('scroll.resize');
167
+ },
168
+ anchorScroll: function(animate) {
169
+ $rootScope.$broadcast('scroll.anchorScroll', animate);
170
+ },
171
+ tapScrollToTop: function(element, animate) {
172
+ var _this = this;
173
+ if (!angular.isDefined(animate)) {
174
+ animate = true;
175
+ }
176
+
177
+ ionic.on('tap', function(e) {
178
+ var target = e.target;
179
+ //Don't scroll to top for a button click
180
+ if (ionic.DomUtil.getParentOrSelfWithClass(target, 'button')) {
181
+ return;
182
+ }
183
+
184
+ var el = element[0];
185
+ var bounds = el.getBoundingClientRect();
186
+
187
+ if(ionic.DomUtil.rectContains(e.gesture.touches[0].pageX, e.gesture.touches[0].pageY, bounds.left, bounds.top, bounds.left + bounds.width, bounds.top + 20)) {
188
+ _this.scrollTop(animate);
189
+ }
190
+ }, element[0]);
191
+ },
192
+
193
+ finishRefreshing: function($scope) {
194
+ $scope.$broadcast('scroll.refreshComplete');
195
+ },
196
+
197
+ /**
198
+ * Attempt to get the current scroll view in scope (if any)
199
+ *
200
+ * Note: will not work in an isolated scope context.
201
+ */
202
+ getScrollView: function($scope) {
203
+ return $scope.scrollView;
204
+ },
205
+
206
+ /**
207
+ * Register a scope and scroll view for scroll event handling.
208
+ * $scope {Scope} the scope to register and listen for events
209
+ */
210
+ register: function($scope, $element, scrollView) {
211
+
212
+ var scrollEl = $element[0];
213
+
214
+ function scrollViewResize() {
215
+ // Run the resize after this digest
216
+ return $timeout(function() {
217
+ scrollView.resize();
218
+ });
219
+ }
220
+
221
+ $element.on('scroll', function(e) {
222
+ var detail = (e.originalEvent || e).detail || {};
223
+
224
+ $scope.$onScroll && $scope.$onScroll({
225
+ event: e,
226
+ scrollTop: detail.scrollTop || 0,
227
+ scrollLeft: detail.scrollLeft || 0
228
+ });
229
+
230
+ });
231
+
232
+ $scope.$parent.$on('scroll.resize', scrollViewResize);
233
+
234
+ // Called to stop refreshing on the scroll view
235
+ $scope.$parent.$on('scroll.refreshComplete', function(e) {
236
+ scrollView.finishPullToRefresh();
237
+ });
238
+
239
+ $scope.$parent.$on('scroll.anchorScroll', function(e, animate) {
240
+ scrollViewResize().then(function() {
241
+ var hash = $location.hash();
242
+ var elm;
243
+ if (hash && (elm = document.getElementById(hash)) ) {
244
+ var scroll = ionic.DomUtil.getPositionInParent(elm, scrollEl);
245
+ scrollView.scrollTo(scroll.left, scroll.top, !!animate);
246
+ } else {
247
+ scrollView.scrollTo(0,0, !!animate);
248
+ }
249
+ });
250
+ });
251
+
252
+ $scope.$parent.$on('scroll.scrollTo', function(e, left, top, animate) {
253
+ scrollViewResize().then(function() {
254
+ scrollView.scrollTo(left, top, !!animate);
255
+ });
256
+ });
257
+ $scope.$parent.$on('scroll.scrollTop', function(e, animate) {
258
+ scrollViewResize().then(function() {
259
+ scrollView.scrollTo(0, 0, !!animate);
260
+ });
261
+ });
262
+ $scope.$parent.$on('scroll.scrollBottom', function(e, animate) {
263
+ scrollViewResize().then(function() {
264
+ var sv = scrollView;
265
+ if (sv) {
266
+ var max = sv.getScrollMax();
267
+ sv.scrollTo(max.left, max.top, !!animate);
268
+ }
269
+ });
270
+ });
271
+ }
272
+ };
273
+ }]);
274
+
275
+ })(ionic);
276
+ ;
277
+ (function() {
278
+ 'use strict';
279
+
280
+ angular.module('ionic.ui.service.sideMenuDelegate', [])
281
+
282
+ .factory('$ionicSideMenuDelegate', ['$rootScope', '$timeout', '$q', function($rootScope, $timeout, $q) {
283
+ return {
284
+ getSideMenuController: function($scope) {
285
+ return $scope.sideMenuController;
286
+ },
287
+ close: function($scope) {
288
+ if($scope.sideMenuController) {
289
+ $scope.sideMenuController.close();
290
+ }
291
+ },
292
+ toggleLeft: function($scope) {
293
+ if($scope.sideMenuController) {
294
+ $scope.sideMenuController.toggleLeft();
295
+ }
296
+ },
297
+ toggleRight: function($scope) {
298
+ if($scope.sideMenuController) {
299
+ $scope.sideMenuController.toggleRight();
300
+ }
301
+ },
302
+ openLeft: function($scope) {
303
+ if($scope.sideMenuController) {
304
+ $scope.sideMenuController.openPercentage(100);
305
+ }
306
+ },
307
+ openRight: function($scope) {
308
+ if($scope.sideMenuController) {
309
+ $scope.sideMenuController.openPercentage(-100);
310
+ }
311
+ }
312
+ };
313
+ }]);
314
+
315
+ })();
316
+ ;
317
+ (function() {
318
+ 'use strict';
319
+
320
+ angular.module('ionic.ui.service.slideBoxDelegate', [])
321
+
322
+ .factory('$ionicSlideBoxDelegate', ['$rootScope', '$timeout', function($rootScope, $timeout) {
323
+ return {
324
+ /**
325
+ * Trigger a slidebox to update and resize itself
326
+ */
327
+ update: function(animate) {
328
+ $rootScope.$broadcast('slideBox.update');
329
+ },
330
+
331
+ register: function($scope, $element) {
332
+ $scope.$parent.$on('slideBox.update', function(e) {
333
+ if(e.defaultPrevented) {
334
+ return;
335
+ }
336
+ $timeout(function() {
337
+ $scope.$parent.slideBox.setup();
338
+ });
339
+ e.preventDefault();
340
+ });
341
+ }
342
+ };
343
+ }]);
344
+
345
+ })(ionic);
346
+ ;
347
+ angular.module('ionic.service.actionSheet', ['ionic.service.templateLoad', 'ionic.service.platform', 'ionic.ui.actionSheet', 'ngAnimate'])
348
+
349
+ .factory('$ionicActionSheet', ['$rootScope', '$document', '$compile', '$animate', '$timeout',
350
+ '$ionicTemplateLoader', '$ionicPlatform',
351
+ function($rootScope, $document, $compile, $animate, $timeout, $ionicTemplateLoader, $ionicPlatform) {
57
352
 
58
353
  return {
59
354
  /**
@@ -69,12 +364,11 @@ angular.module('ionic.service.actionSheet', ['ionic.service.templateLoad', 'ioni
69
364
 
70
365
  angular.extend(scope, opts);
71
366
 
72
-
73
367
  // Compile the template
74
- var element = $compile('<action-sheet buttons="buttons"></action-sheet>')(scope);
368
+ var element = $compile('<ion-action-sheet buttons="buttons"></ion-action-sheet>')(scope);
75
369
 
76
370
  // Grab the sheet element for animation
77
- var sheetEl = angular.element(element[0].querySelector('.action-sheet'));
371
+ var sheetEl = angular.element(element[0].querySelector('.action-sheet-wrapper'));
78
372
 
79
373
  var hideSheet = function(didCancel) {
80
374
  $animate.leave(sheetEl, function() {
@@ -86,8 +380,21 @@ angular.module('ionic.service.actionSheet', ['ionic.service.templateLoad', 'ioni
86
380
  $animate.removeClass(element, 'active', function() {
87
381
  scope.$destroy();
88
382
  });
383
+
384
+ $document[0].body.classList.remove('action-sheet-open');
385
+ };
386
+
387
+ var onHardwareBackButton = function() {
388
+ hideSheet();
89
389
  };
90
390
 
391
+ scope.$on('$destroy', function() {
392
+ $ionicPlatform.offHardwareBackButton(onHardwareBackButton);
393
+ });
394
+
395
+ // Support Android back button to close
396
+ $ionicPlatform.onHardwareBackButton(onHardwareBackButton);
397
+
91
398
  scope.cancel = function() {
92
399
  hideSheet(true);
93
400
  };
@@ -110,6 +417,8 @@ angular.module('ionic.service.actionSheet', ['ionic.service.templateLoad', 'ioni
110
417
 
111
418
  $document[0].body.appendChild(element[0]);
112
419
 
420
+ $document[0].body.classList.add('action-sheet-open');
421
+
113
422
  var sheet = new ionic.views.ActionSheet({el: element[0] });
114
423
  scope.sheet = sheet;
115
424
 
@@ -123,9 +432,63 @@ angular.module('ionic.service.actionSheet', ['ionic.service.templateLoad', 'ioni
123
432
 
124
433
  }]);
125
434
  ;
435
+ angular.module('ionic.service.bind', [])
436
+ .factory('$ionicBind', ['$parse', '$interpolate', function($parse, $interpolate) {
437
+ var LOCAL_REGEXP = /^\s*([@=&])(\??)\s*(\w*)\s*$/;
438
+ return function(scope, attrs, bindDefinition) {
439
+ angular.forEach(bindDefinition || {}, function (definition, scopeName) {
440
+ //Adapted from angular.js $compile
441
+ var match = definition.match(LOCAL_REGEXP) || [],
442
+ attrName = match[3] || scopeName,
443
+ mode = match[1], // @, =, or &
444
+ parentGet,
445
+ unwatch;
446
+
447
+ switch(mode) {
448
+ case '@':
449
+ if (!attrs[attrName]) {
450
+ return;
451
+ }
452
+ attrs.$observe(attrName, function(value) {
453
+ scope[scopeName] = value;
454
+ });
455
+ // we trigger an interpolation to ensure
456
+ // the value is there for use immediately
457
+ if (attrs[attrName]) {
458
+ scope[scopeName] = $interpolate(attrs[attrName])(scope);
459
+ }
460
+ break;
461
+
462
+ case '=':
463
+ if (!attrs[attrName]) {
464
+ return;
465
+ }
466
+ unwatch = scope.$watch(attrs[attrName], function(value) {
467
+ scope[scopeName] = value;
468
+ });
469
+ //Destroy parent scope watcher when this scope is destroyed
470
+ scope.$on('$destroy', unwatch);
471
+ break;
472
+
473
+ case '&':
474
+ /* jshint -W044 */
475
+ if (attrs[attrName] && attrs[attrName].match(RegExp(scopeName + '\(.*?\)'))) {
476
+ throw new Error('& expression binding "' + scopeName + '" looks like it will recursively call "' +
477
+ attrs[attrName] + '" and cause a stack overflow! Please choose a different scopeName.');
478
+ }
479
+ parentGet = $parse(attrs[attrName]);
480
+ scope[scopeName] = function(locals) {
481
+ return parentGet(scope, locals);
482
+ };
483
+ break;
484
+ }
485
+ });
486
+ };
487
+ }]);
488
+ ;
126
489
  angular.module('ionic.service.gesture', [])
127
490
 
128
- .factory('Gesture', [function() {
491
+ .factory('$ionicGesture', [function() {
129
492
  return {
130
493
  on: function(eventType, cb, $element) {
131
494
  return window.ionic.onGesture(eventType, cb, $element[0]);
@@ -138,12 +501,12 @@ angular.module('ionic.service.gesture', [])
138
501
  ;
139
502
  angular.module('ionic.service.loading', ['ionic.ui.loading'])
140
503
 
141
- .factory('Loading', ['$rootScope', '$document', '$compile', function($rootScope, $document, $compile) {
504
+ .factory('$ionicLoading', ['$rootScope', '$document', '$compile', function($rootScope, $document, $compile) {
142
505
  return {
143
506
  /**
144
507
  * Load an action sheet with the given template string.
145
508
  *
146
- * A new isolated scope will be created for the
509
+ * A new isolated scope will be created for the
147
510
  * action sheet and the new element will be appended into the body.
148
511
  *
149
512
  * @param {object} opts the options for this ActionSheet (see docs)
@@ -154,7 +517,7 @@ angular.module('ionic.service.loading', ['ionic.ui.loading'])
154
517
  animation: 'fade-in',
155
518
  showBackdrop: true,
156
519
  maxWidth: 200,
157
- showDelay: 2000
520
+ showDelay: 0
158
521
  };
159
522
 
160
523
  opts = angular.extend(defaults, opts);
@@ -165,15 +528,11 @@ angular.module('ionic.service.loading', ['ionic.ui.loading'])
165
528
  // Make sure there is only one loading element on the page at one point in time
166
529
  var existing = angular.element($document[0].querySelector('.loading-backdrop'));
167
530
  if(existing.length) {
168
- scope = existing.scope();
169
- if(scope.loading) {
170
- scope.loading.show();
171
- return scope.loading;
172
- }
531
+ existing.remove();
173
532
  }
174
533
 
175
534
  // Compile the template
176
- var element = $compile('<loading>' + opts.content + '</loading>')(scope);
535
+ var element = $compile('<ion-loading>' + opts.content + '</ion-loading>')(scope);
177
536
 
178
537
  $document[0].body.appendChild(element[0]);
179
538
 
@@ -192,10 +551,10 @@ angular.module('ionic.service.loading', ['ionic.ui.loading'])
192
551
  };
193
552
  }]);
194
553
  ;
195
- angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ngAnimate'])
554
+ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ionic.service.platform', 'ngAnimate'])
196
555
 
197
556
 
198
- .factory('Modal', ['$rootScope', '$document', '$compile', '$animate', '$q', 'TemplateLoader', function($rootScope, $document, $compile, $animate, $q, TemplateLoader) {
557
+ .factory('$ionicModal', ['$rootScope', '$document', '$compile', '$animate', '$q', '$timeout', '$ionicPlatform', '$ionicTemplateLoader', function($rootScope, $document, $compile, $animate, $q, $timeout, $ionicPlatform, $ionicTemplateLoader) {
199
558
  var ModalView = ionic.views.Modal.inherit({
200
559
  initialize: function(opts) {
201
560
  ionic.views.Modal.prototype.initialize.call(this, opts);
@@ -203,32 +562,75 @@ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ngAnimate'
203
562
  },
204
563
  // Show the modal
205
564
  show: function() {
206
- var _this = this;
565
+ var self = this;
207
566
  var element = angular.element(this.el);
567
+
568
+ document.body.classList.add('modal-open');
569
+
570
+ self._isShown = true;
571
+
208
572
  if(!element.parent().length) {
209
- angular.element($document[0].body).append(element);
210
- ionic.views.Modal.prototype.show.call(_this);
573
+ element.addClass(this.animation);
574
+ $animate.enter(element, angular.element($document[0].body), null, function() {
575
+ });
576
+ ionic.views.Modal.prototype.show.call(self);
577
+ } else {
578
+ $animate.addClass(element, this.animation, function() {
579
+ });
211
580
  }
212
- $animate.addClass(element, this.animation, function() {
213
- });
581
+
582
+ if(!this.didInitEvents) {
583
+ var onHardwareBackButton = function() {
584
+ self.hide();
585
+ };
586
+
587
+ self.scope.$on('$destroy', function() {
588
+ $ionicPlatform.offHardwareBackButton(onHardwareBackButton);
589
+ });
590
+
591
+ // Support Android back button to close
592
+ $ionicPlatform.onHardwareBackButton(onHardwareBackButton);
593
+
594
+ this.didInitEvents = true;
595
+ }
596
+
597
+ this.scope.$parent.$broadcast('modal.shown', this);
598
+
214
599
  },
215
600
  // Hide the modal
216
601
  hide: function() {
602
+ this._isShown = false;
217
603
  var element = angular.element(this.el);
218
- $animate.removeClass(element, this.animation);
604
+ $animate.removeClass(element, this.animation, function() {
605
+ onHideModal(element[0]);
606
+ });
219
607
 
220
608
  ionic.views.Modal.prototype.hide.call(this);
609
+
610
+ this.scope.$parent.$broadcast('modal.hidden', this);
221
611
  },
222
612
 
223
613
  // Remove and destroy the modal scope
224
614
  remove: function() {
225
- var element = angular.element(this.el);
615
+ var self = this,
616
+ element = angular.element(this.el);
617
+ this._isShown = false;
226
618
  $animate.leave(angular.element(this.el), function() {
227
- scope.$destroy();
619
+ onHideModal(element[0]);
620
+ self.scope.$parent.$broadcast('modal.removed', self);
621
+ self.scope.$destroy();
228
622
  });
623
+ },
624
+
625
+ isShown: function() {
626
+ return !!this._isShown;
229
627
  }
230
628
  });
231
629
 
630
+ function onHideModal(element) {
631
+ document.body.classList.remove('modal-open');
632
+ }
633
+
232
634
  var createModal = function(templateString, options) {
233
635
  // Create a new scope for the modal
234
636
  var scope = options.scope && options.scope.$new() || $rootScope.$new(true);
@@ -254,7 +656,7 @@ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ngAnimate'
254
656
  /**
255
657
  * Load a modal with the given template string.
256
658
  *
257
- * A new isolated scope will be created for the
659
+ * A new isolated scope will be created for the
258
660
  * modal and the new element will be appended into the body.
259
661
  */
260
662
  fromTemplate: function(templateString, options) {
@@ -262,16 +664,16 @@ angular.module('ionic.service.modal', ['ionic.service.templateLoad', 'ngAnimate'
262
664
  return modal;
263
665
  },
264
666
  fromTemplateUrl: function(url, cb, options) {
265
- TemplateLoader.load(url).then(function(templateString) {
667
+ return $ionicTemplateLoader.load(url).then(function(templateString) {
266
668
  var modal = createModal(templateString, options || {});
267
- cb(modal);
669
+ cb ? cb(modal) : null;
670
+ return modal;
268
671
  });
269
672
  },
270
673
  };
271
674
  }]);
272
675
  ;
273
- (function() {
274
- 'use strict';
676
+ (function(ionic) {'use strict';
275
677
 
276
678
  angular.module('ionic.service.platform', [])
277
679
 
@@ -282,39 +684,10 @@ angular.module('ionic.service.platform', [])
282
684
  * initializing some defaults that depend on the platform, such as the
283
685
  * height of header bars on iOS 7.
284
686
  */
285
- .provider('Platform', function() {
286
- var platform = 'web';
287
- var isPlatformReady = false;
288
-
289
- if(window.cordova || window.PhoneGap || window.phonegap) {
290
- platform = 'cordova';
291
- }
292
-
293
- var isReady = function() {
294
- if(platform == 'cordova') {
295
- return window.device || window.Cordova;
296
- }
297
- return true;
298
- };
299
-
300
- // We need to do some stuff as soon as we know the platform,
301
- // like adjust header margins for iOS 7, etc.
302
- setTimeout(function afterReadyWait() {
303
- if(isReady()) {
304
- ionic.Platform.detect();
305
- } else {
306
- setTimeout(afterReadyWait, 50);
307
- }
308
- }, 10);
309
-
310
-
311
-
687
+ .provider('$ionicPlatform', function() {
312
688
 
313
689
  return {
314
- setPlatform: function(p) {
315
- platform = p;
316
- },
317
- $get: ['$q', '$timeout', function($q, $timeout) {
690
+ $get: ['$q', function($q) {
318
691
  return {
319
692
  /**
320
693
  * Some platforms have hardware back buttons, so this is one way to bind to it.
@@ -322,7 +695,7 @@ angular.module('ionic.service.platform', [])
322
695
  * @param {function} cb the callback to trigger when this event occurs
323
696
  */
324
697
  onHardwareBackButton: function(cb) {
325
- this.ready(function() {
698
+ ionic.Platform.ready(function() {
326
699
  document.addEventListener('backbutton', cb, false);
327
700
  });
328
701
  },
@@ -333,34 +706,33 @@ angular.module('ionic.service.platform', [])
333
706
  * @param {function} fn the listener function that was originally bound.
334
707
  */
335
708
  offHardwareBackButton: function(fn) {
336
- this.ready(function() {
709
+ ionic.Platform.ready(function() {
337
710
  document.removeEventListener('backbutton', fn);
338
711
  });
339
712
  },
340
713
 
714
+ is: function(type) {
715
+ return ionic.Platform.is(type);
716
+ },
717
+
341
718
  /**
342
719
  * Trigger a callback once the device is ready, or immediately if the device is already
343
720
  * ready.
344
721
  */
345
722
  ready: function(cb) {
346
- var self = this;
347
723
  var q = $q.defer();
348
724
 
349
- $timeout(function readyWait() {
350
- if(isReady()) {
351
- isPlatformReady = true;
352
- q.resolve();
353
- cb();
354
- } else {
355
- $timeout(readyWait, 50);
356
- }
357
- }, 50);
725
+ ionic.Platform.ready(function(){
726
+ q.resolve();
727
+ cb();
728
+ });
358
729
 
359
730
  return q.promise;
360
731
  }
361
732
  };
362
733
  }]
363
734
  };
735
+
364
736
  });
365
737
 
366
738
  })(ionic);
@@ -368,7 +740,7 @@ angular.module('ionic.service.platform', [])
368
740
  angular.module('ionic.service.popup', ['ionic.service.templateLoad'])
369
741
 
370
742
 
371
- .factory('Popup', ['$rootScope', '$document', '$compile', 'TemplateLoader', function($rootScope, $document, $compile, TemplateLoader) {
743
+ .factory('$ionicPopup', ['$rootScope', '$document', '$compile', 'TemplateLoader', function($rootScope, $document, $compile, TemplateLoader) {
372
744
 
373
745
  var getPopup = function() {
374
746
  // Make sure there is only one loading element on the page at one point in time
@@ -425,94 +797,635 @@ angular.module('ionic.service.popup', ['ionic.service.templateLoad'])
425
797
  ;
426
798
  angular.module('ionic.service.templateLoad', [])
427
799
 
428
- .factory('TemplateLoader', ['$q', '$http', '$templateCache', function($q, $http, $templateCache) {
800
+ .factory('$ionicTemplateLoader', ['$q', '$http', '$templateCache', function($q, $http, $templateCache) {
429
801
  return {
430
802
  load: function(url) {
431
- var deferred = $q.defer();
432
-
433
- $http({
434
- method: 'GET',
435
- url: url,
436
- cache: $templateCache
437
- }).success(function(html) {
438
- deferred.resolve(html && html.trim());
439
- }).error(function(err) {
440
- deferred.reject(err);
803
+ return $http.get(url, {cache: $templateCache})
804
+ .then(function(response) {
805
+ return response.data && response.data.trim();
441
806
  });
442
-
443
- return deferred.promise;
444
807
  }
445
808
  };
446
809
  }]);
447
810
  ;
448
- (function() {
449
- 'use strict';
450
-
451
- angular.module('ionic.ui.actionSheet', [])
811
+ angular.module('ionic.service.view', ['ui.router', 'ionic.service.platform'])
452
812
 
453
- .directive('actionSheet', ['$document', function($document) {
454
- return {
455
- restrict: 'E',
456
- scope: true,
457
- replace: true,
458
- link: function($scope, $element){
459
- var keyUp = function(e) {
460
- if(e.which == 27) {
461
- $scope.cancel();
462
- $scope.$apply();
463
- }
464
- };
465
813
 
466
- var backdropClick = function(e) {
467
- if(e.target == $element[0]) {
468
- $scope.cancel();
469
- $scope.$apply();
470
- }
471
- };
472
- $scope.$on('$destroy', function() {
473
- $element.remove();
474
- $document.unbind('keyup', keyUp);
475
- });
814
+ .run( ['$rootScope', '$state', '$location', '$document', '$animate', '$ionicPlatform',
815
+ function( $rootScope, $state, $location, $document, $animate, $ionicPlatform) {
476
816
 
477
- $document.bind('keyup', keyUp);
478
- $element.bind('click', backdropClick);
479
- },
480
- template: '<div class="action-sheet-backdrop">' +
481
- '<div class="action-sheet action-sheet-up">' +
482
- '<div class="action-sheet-group">' +
483
- '<div class="action-sheet-title" ng-if="titleText">{{titleText}}</div>' +
484
- '<button class="button" ng-click="buttonClicked($index)" ng-repeat="button in buttons">{{button.text}}</button>' +
485
- '</div>' +
486
- '<div class="action-sheet-group" ng-if="destructiveText">' +
487
- '<button class="button destructive" ng-click="destructiveButtonClicked()">{{destructiveText}}</button>' +
488
- '</div>' +
489
- '<div class="action-sheet-group" ng-if="cancelText">' +
490
- '<button class="button" ng-click="cancel()">{{cancelText}}</button>' +
491
- '</div>' +
492
- '</div>' +
493
- '</div>'
817
+ // init the variables that keep track of the view history
818
+ $rootScope.$viewHistory = {
819
+ histories: { root: { historyId: 'root', parentHistoryId: null, stack: [], cursor: -1 } },
820
+ backView: null,
821
+ forwardView: null,
822
+ currentView: null,
823
+ disabledRegistrableTagNames: []
494
824
  };
495
- }]);
496
825
 
497
- })();
498
- ;
499
- (function(ionic) {
500
- 'use strict';
826
+ $rootScope.$on('viewState.changeHistory', function(e, data) {
827
+ if(!data) return;
501
828
 
502
- angular.module('ionic.ui.header', ['ngAnimate'])
829
+ var hist = (data.historyId ? $rootScope.$viewHistory.histories[ data.historyId ] : null );
830
+ if(hist && hist.cursor > -1 && hist.cursor < hist.stack.length) {
831
+ // the history they're going to already exists
832
+ // go to it's last view in its stack
833
+ var view = hist.stack[ hist.cursor ];
834
+ return view.go(data);
835
+ }
503
836
 
837
+ // this history does not have a URL, but it does have a uiSref
838
+ // figure out its URL from the uiSref
839
+ if(!data.url && data.uiSref) {
840
+ data.url = $state.href(data.uiSref);
841
+ }
504
842
 
505
- .directive('headerBar', function() {
506
- return {
507
- restrict: 'E',
508
- replace: true,
509
- transclude: true,
510
- template: '<header class="bar bar-header">\
511
- <div class="buttons">\
512
- <button ng-repeat="button in leftButtons" class="button no-animation" ng-class="button.type" ng-click="button.tap($event, $index)" ng-bind-html="button.content">\
513
- </button>\
514
- </div>\
515
- <h1 class="title" ng-bind-html="title"></h1>\
843
+ if(data.url) {
844
+ // don't let it start with a #, messes with $location.url()
845
+ if(data.url.indexOf('#') === 0) {
846
+ data.url = data.url.replace('#', '');
847
+ }
848
+ if(data.url !== $location.url()) {
849
+ // we've got a good URL, ready GO!
850
+ $location.url(data.url);
851
+ }
852
+ }
853
+ });
854
+
855
+ // Set the document title when a new view is shown
856
+ $rootScope.$on('viewState.viewEnter', function(e, data) {
857
+ if(data && data.title) {
858
+ $document[0].title = data.title;
859
+ }
860
+ });
861
+
862
+ // Triggered when devices with a hardware back button (Android) is clicked by the user
863
+ // This is a Cordova/Phonegap platform specifc method
864
+ function onHardwareBackButton(e) {
865
+ if($rootScope.$viewHistory.backView) {
866
+ // there is a back view, go to it
867
+ $rootScope.$viewHistory.backView.go();
868
+ } else {
869
+ // there is no back view, so close the app instead
870
+ ionic.Platform.exitApp();
871
+ }
872
+ e.preventDefault();
873
+ return false;
874
+ }
875
+ $ionicPlatform.onHardwareBackButton(onHardwareBackButton);
876
+
877
+ }])
878
+
879
+ .factory('$ionicViewService', ['$rootScope', '$state', '$location', '$window', '$injector',
880
+ function( $rootScope, $state, $location, $window, $injector) {
881
+ var $animate = $injector.has('$animate') ? $injector.get('$animate') : false;
882
+
883
+ var View = function(){};
884
+ View.prototype.initialize = function(data) {
885
+ if(data) {
886
+ for(var name in data) this[name] = data[name];
887
+ return this;
888
+ }
889
+ return null;
890
+ };
891
+ View.prototype.go = function() {
892
+
893
+ if(this.stateName) {
894
+ return $state.go(this.stateName, this.stateParams);
895
+ }
896
+
897
+ if(this.url && this.url !== $location.url()) {
898
+
899
+ if($rootScope.$viewHistory.backView === this) {
900
+ return $window.history.go(-1);
901
+ } else if($rootScope.$viewHistory.forwardView === this) {
902
+ return $window.history.go(1);
903
+ }
904
+
905
+ $location.url(this.url);
906
+ return;
907
+ }
908
+
909
+ return null;
910
+ };
911
+ View.prototype.destory = function() {
912
+ if(this.scope) {
913
+ this.scope.destory && this.scope.destory();
914
+ this.scope = null;
915
+ }
916
+ };
917
+
918
+ function createViewId(stateId) {
919
+ return ionic.Utils.nextUid();
920
+ }
921
+
922
+ return {
923
+
924
+ register: function(containerScope, element) {
925
+
926
+ var viewHistory = $rootScope.$viewHistory,
927
+ currentStateId = this.getCurrentStateId(),
928
+ hist = this._getHistory(containerScope),
929
+ currentView = viewHistory.currentView,
930
+ backView = viewHistory.backView,
931
+ forwardView = viewHistory.forwardView,
932
+ rsp = {
933
+ viewId: null,
934
+ navAction: null,
935
+ navDirection: null,
936
+ historyId: hist.historyId
937
+ };
938
+
939
+ if(element && !this.isTagNameRegistrable(element)) {
940
+ // first check to see if this element can even be registered as a view.
941
+ // Certain tags are only containers for views, but are not views themselves.
942
+ // For example, the <ion-tabs> directive contains a <ion-tab> and the <ion-tab> is the
943
+ // view, but the <ion-tabs> directive itself should not be registered as a view.
944
+ rsp.navAction = 'disabledByTagName';
945
+ return rsp;
946
+ }
947
+
948
+ if(currentView &&
949
+ currentView.stateId === currentStateId &&
950
+ currentView.historyId === hist.historyId) {
951
+ // do nothing if its the same stateId in the same history
952
+ rsp.navAction = 'noChange';
953
+ return rsp;
954
+ }
955
+
956
+ if(viewHistory.forcedNav) {
957
+ // we've previously set exactly what to do
958
+ ionic.Utils.extend(rsp, viewHistory.forcedNav);
959
+ $rootScope.$viewHistory.forcedNav = null;
960
+
961
+ } else if(backView && backView.stateId === currentStateId) {
962
+ // they went back one, set the old current view as a forward view
963
+ rsp.viewId = backView.viewId;
964
+ rsp.navAction = 'moveBack';
965
+ currentView.scrollValues = {}; //when going back, erase scrollValues
966
+ if(backView.historyId === currentView.historyId) {
967
+ // went back in the same history
968
+ rsp.navDirection = 'back';
969
+ }
970
+
971
+ } else if(forwardView && forwardView.stateId === currentStateId) {
972
+ // they went to the forward one, set the forward view to no longer a forward view
973
+ rsp.viewId = forwardView.viewId;
974
+ rsp.navAction = 'moveForward';
975
+ if(forwardView.historyId === currentView.historyId) {
976
+ rsp.navDirection = 'forward';
977
+ }
978
+
979
+ var parentHistory = this._getParentHistoryObj(containerScope);
980
+ if(forwardView.historyId && parentHistory.scope) {
981
+ // if a history has already been created by the forward view then make sure it stays the same
982
+ parentHistory.scope.$historyId = forwardView.historyId;
983
+ rsp.historyId = forwardView.historyId;
984
+ }
985
+
986
+ } else if(currentView && currentView.historyId !== hist.historyId &&
987
+ hist.cursor > -1 && hist.stack.length > 0 && hist.cursor < hist.stack.length &&
988
+ hist.stack[hist.cursor].stateId === currentStateId) {
989
+ // they just changed to a different history and the history already has views in it
990
+ rsp.viewId = hist.stack[hist.cursor].viewId;
991
+ rsp.navAction = 'moveBack';
992
+
993
+ } else {
994
+
995
+ // set a new unique viewId
996
+ rsp.viewId = createViewId(currentStateId);
997
+
998
+ if(currentView) {
999
+ // set the forward view if there is a current view (ie: if its not the first view)
1000
+ currentView.forwardViewId = rsp.viewId;
1001
+
1002
+ // its only moving forward if its in the same history
1003
+ if(hist.historyId === currentView.historyId) {
1004
+ rsp.navDirection = 'forward';
1005
+ }
1006
+ rsp.navAction = 'newView';
1007
+
1008
+ // check if there is a new forward view
1009
+ if(forwardView && currentView.stateId !== forwardView.stateId) {
1010
+ // they navigated to a new view but the stack already has a forward view
1011
+ // since its a new view remove any forwards that existed
1012
+ var forwardsHistory = this._getView(forwardView.historyId);
1013
+ if(forwardsHistory) {
1014
+ // the forward has a history
1015
+ for(var x=forwardsHistory.stack.length - 1; x >= forwardView.index; x--) {
1016
+ // starting from the end destory all forwards in this history from this point
1017
+ forwardsHistory.stack[x].destory();
1018
+ forwardsHistory.stack.splice(x);
1019
+ }
1020
+ }
1021
+ }
1022
+
1023
+ } else {
1024
+ // there's no current view, so this must be the initial view
1025
+ rsp.navAction = 'initialView';
1026
+ }
1027
+
1028
+ // add the new view to the stack
1029
+ viewHistory.histories[rsp.viewId] = this.createView({
1030
+ viewId: rsp.viewId,
1031
+ index: hist.stack.length,
1032
+ historyId: hist.historyId,
1033
+ backViewId: (currentView && currentView.viewId ? currentView.viewId : null),
1034
+ forwardViewId: null,
1035
+ stateId: currentStateId,
1036
+ stateName: this.getCurrentStateName(),
1037
+ stateParams: this.getCurrentStateParams(),
1038
+ url: $location.url(),
1039
+ scrollValues: null
1040
+ });
1041
+
1042
+ // add the new view to this history's stack
1043
+ hist.stack.push(viewHistory.histories[rsp.viewId]);
1044
+ }
1045
+
1046
+ this.setNavViews(rsp.viewId);
1047
+
1048
+ hist.cursor = viewHistory.currentView.index;
1049
+
1050
+ return rsp;
1051
+ },
1052
+
1053
+ setNavViews: function(viewId) {
1054
+ var viewHistory = $rootScope.$viewHistory;
1055
+
1056
+ viewHistory.currentView = this._getView(viewId);
1057
+ viewHistory.backView = this._getBackView(viewHistory.currentView);
1058
+ viewHistory.forwardView = this._getForwardView(viewHistory.currentView);
1059
+
1060
+ $rootScope.$broadcast('$viewHistory.historyChange', {
1061
+ showBack: (viewHistory.backView && viewHistory.backView.historyId === viewHistory.currentView.historyId)
1062
+ });
1063
+ },
1064
+
1065
+ registerHistory: function(scope) {
1066
+ scope.$historyId = ionic.Utils.nextUid();
1067
+ },
1068
+
1069
+ createView: function(data) {
1070
+ var newView = new View();
1071
+ return newView.initialize(data);
1072
+ },
1073
+
1074
+ getCurrentView: function() {
1075
+ return $rootScope.$viewHistory.currentView;
1076
+ },
1077
+
1078
+ getBackView: function() {
1079
+ return $rootScope.$viewHistory.backView;
1080
+ },
1081
+
1082
+ getForwardView: function() {
1083
+ return $rootScope.$viewHistory.forwardView;
1084
+ },
1085
+
1086
+ getNavDirection: function() {
1087
+ return $rootScope.$viewHistory.navDirection;
1088
+ },
1089
+
1090
+ getCurrentStateName: function() {
1091
+ return ($state && $state.current ? $state.current.name : null);
1092
+ },
1093
+
1094
+ isCurrentStateNavView: function(navView) {
1095
+ return ($state &&
1096
+ $state.current &&
1097
+ $state.current.views &&
1098
+ $state.current.views[navView] ? true : false);
1099
+ },
1100
+
1101
+ getCurrentStateParams: function() {
1102
+ var rtn;
1103
+ if ($state && $state.params) {
1104
+ for(var key in $state.params) {
1105
+ if($state.params.hasOwnProperty(key)) {
1106
+ rtn = rtn || {};
1107
+ rtn[key] = $state.params[key];
1108
+ }
1109
+ }
1110
+ }
1111
+ return rtn;
1112
+ },
1113
+
1114
+ getCurrentStateId: function() {
1115
+ var id;
1116
+ if($state && $state.current && $state.current.name) {
1117
+ id = $state.current.name;
1118
+ if($state.params) {
1119
+ for(var key in $state.params) {
1120
+ if($state.params.hasOwnProperty(key) && $state.params[key]) {
1121
+ id += "_" + key + "=" + $state.params[key];
1122
+ }
1123
+ }
1124
+ }
1125
+ return id;
1126
+ }
1127
+ // if something goes wrong make sure its got a unique stateId
1128
+ return ionic.Utils.nextUid();
1129
+ },
1130
+
1131
+ goToHistoryRoot: function(historyId) {
1132
+ if(historyId) {
1133
+ var hist = $rootScope.$viewHistory.histories[ historyId ];
1134
+ if(hist && hist.stack.length) {
1135
+ if($rootScope.$viewHistory.currentView && $rootScope.$viewHistory.currentView.viewId === hist.stack[0].viewId) {
1136
+ return;
1137
+ }
1138
+ $rootScope.$viewHistory.forcedNav = {
1139
+ viewId: hist.stack[0].viewId,
1140
+ navAction: 'moveBack',
1141
+ navDirection: 'back'
1142
+ };
1143
+ hist.stack[0].go();
1144
+ }
1145
+ }
1146
+ },
1147
+
1148
+ _getView: function(viewId) {
1149
+ return (viewId ? $rootScope.$viewHistory.histories[ viewId ] : null );
1150
+ },
1151
+
1152
+ _getBackView: function(view) {
1153
+ return (view ? this._getView(view.backViewId) : null );
1154
+ },
1155
+
1156
+ _getForwardView: function(view) {
1157
+ return (view ? this._getView(view.forwardViewId) : null );
1158
+ },
1159
+
1160
+ _getHistory: function(scope) {
1161
+ var histObj = this._getParentHistoryObj(scope);
1162
+
1163
+ if( !$rootScope.$viewHistory.histories[ histObj.historyId ] ) {
1164
+ // this history object exists in parent scope, but doesn't
1165
+ // exist in the history data yet
1166
+ $rootScope.$viewHistory.histories[ histObj.historyId ] = {
1167
+ historyId: histObj.historyId,
1168
+ parentHistoryId: this._getParentHistoryObj(histObj.scope.$parent).historyId,
1169
+ stack: [],
1170
+ cursor: -1
1171
+ };
1172
+ }
1173
+
1174
+ return $rootScope.$viewHistory.histories[ histObj.historyId ];
1175
+ },
1176
+
1177
+ _getParentHistoryObj: function(scope) {
1178
+ var parentScope = scope;
1179
+ while(parentScope) {
1180
+ if(parentScope.hasOwnProperty('$historyId')) {
1181
+ // this parent scope has a historyId
1182
+ return { historyId: parentScope.$historyId, scope: parentScope };
1183
+ }
1184
+ // nothing found keep climbing up
1185
+ parentScope = parentScope.$parent;
1186
+ }
1187
+ // no history for for the parent, use the root
1188
+ return { historyId: 'root', scope: $rootScope };
1189
+ },
1190
+
1191
+ getRenderer: function(navViewElement, navViewAttrs, navViewScope) {
1192
+ var service = this;
1193
+ var registerData;
1194
+ var doAnimation;
1195
+
1196
+ // climb up the DOM and see which animation classname to use, if any
1197
+ var animationClass = angular.isDefined(navViewScope.$nextAnimation) ?
1198
+ navViewScope.$nextAnimation :
1199
+ getParentAnimationClass(navViewElement[0]);
1200
+
1201
+ navViewScope.$nextAnimation = undefined;
1202
+
1203
+ function getParentAnimationClass(el) {
1204
+ var className = '';
1205
+ while(!className && el) {
1206
+ className = el.getAttribute('animation');
1207
+ el = el.parentElement;
1208
+ }
1209
+ return className;
1210
+ }
1211
+
1212
+ function setAnimationClass() {
1213
+ // add the animation CSS class we're gonna use to transition between views
1214
+ if (animationClass) {
1215
+ navViewElement[0].classList.add(animationClass);
1216
+ }
1217
+
1218
+ if(registerData.navDirection === 'back') {
1219
+ // animate like we're moving backward
1220
+ navViewElement[0].classList.add('reverse');
1221
+ } else {
1222
+ // defaults to animate forward
1223
+ // make sure the reverse class isn't already added
1224
+ navViewElement[0].classList.remove('reverse');
1225
+ }
1226
+ }
1227
+
1228
+ return function(shouldAnimate) {
1229
+
1230
+ return {
1231
+
1232
+ enter: function(element) {
1233
+
1234
+ if(doAnimation && shouldAnimate) {
1235
+ // enter with an animation
1236
+ setAnimationClass();
1237
+
1238
+ element.addClass('ng-enter');
1239
+ document.body.classList.add('disable-pointer-events');
1240
+
1241
+ $animate.enter(element, navViewElement, null, function() {
1242
+ document.body.classList.remove('disable-pointer-events');
1243
+ if (animationClass) {
1244
+ navViewElement[0].classList.remove(animationClass);
1245
+ }
1246
+ });
1247
+ return;
1248
+ }
1249
+
1250
+ // no animation
1251
+ navViewElement.append(element);
1252
+ },
1253
+
1254
+ leave: function() {
1255
+ var element = navViewElement.contents();
1256
+
1257
+ if(doAnimation && shouldAnimate) {
1258
+ // leave with an animation
1259
+ setAnimationClass();
1260
+
1261
+ $animate.leave(element, function() {
1262
+ element.remove();
1263
+ });
1264
+ return;
1265
+ }
1266
+
1267
+ // no animation
1268
+ element.remove();
1269
+ },
1270
+
1271
+ register: function(element) {
1272
+ // register a new view
1273
+ registerData = service.register(navViewScope, element);
1274
+ doAnimation = (animationClass !== null && registerData.navDirection !== null);
1275
+ return registerData;
1276
+ }
1277
+
1278
+ };
1279
+ };
1280
+ },
1281
+
1282
+ disableRegisterByTagName: function(tagName) {
1283
+ // not every element should animate betwee transitions
1284
+ // For example, the <ion-tabs> directive should not animate when it enters,
1285
+ // but instead the <ion-tabs> directve would just show, and its children
1286
+ // <ion-tab> directives would do the animating, but <ion-tabs> itself is not a view
1287
+ $rootScope.$viewHistory.disabledRegistrableTagNames.push(tagName.toUpperCase());
1288
+ },
1289
+
1290
+ isTagNameRegistrable: function(element) {
1291
+ // check if this element has a tagName (at its root, not recursively)
1292
+ // that shouldn't be animated, like <ion-tabs> or <ion-side-menu>
1293
+ var x, y, disabledTags = $rootScope.$viewHistory.disabledRegistrableTagNames;
1294
+ for(x=0; x<element.length; x++) {
1295
+ if(element[x].nodeType !== 1) continue;
1296
+ for(y=0; y<disabledTags.length; y++) {
1297
+ if(element[x].tagName === disabledTags[y]) {
1298
+ return false;
1299
+ }
1300
+ }
1301
+ }
1302
+ return true;
1303
+ },
1304
+
1305
+ clearHistory: function() {
1306
+ var historyId, x, view,
1307
+ histories = $rootScope.$viewHistory.histories,
1308
+ currentView = $rootScope.$viewHistory.currentView;
1309
+
1310
+ for(historyId in histories) {
1311
+
1312
+ if(histories[historyId].stack) {
1313
+ histories[historyId].stack = [];
1314
+ histories[historyId].cursor = -1;
1315
+ }
1316
+
1317
+ if(currentView.historyId === historyId) {
1318
+ currentView.backViewId = null;
1319
+ currentView.forwardViewId = null;
1320
+ histories[historyId].stack.push(currentView);
1321
+ } else if(histories[historyId].destroy) {
1322
+ histories[historyId].destroy();
1323
+ }
1324
+
1325
+ }
1326
+
1327
+ this.setNavViews(currentView.viewId);
1328
+ }
1329
+
1330
+ };
1331
+
1332
+ }]);
1333
+ ;
1334
+ angular.module('ionic.ui.navAnimation', [])
1335
+ .directive('ionNavAnimation', function() {
1336
+ return {
1337
+ restrict: 'A',
1338
+ require: '^?ionNavView',
1339
+ link: function($scope, $element, $attrs, navViewCtrl) {
1340
+ if (!navViewCtrl) {
1341
+ return;
1342
+ }
1343
+ ionic.on('tap', function() {
1344
+ navViewCtrl.setNextAnimation($attrs.ionNavAnimation);
1345
+ }, $element[0]);
1346
+ }
1347
+ };
1348
+ });
1349
+ ;
1350
+ (function() {
1351
+ 'use strict';
1352
+
1353
+ angular.module('ionic.ui.actionSheet', [])
1354
+
1355
+ .directive('ionActionSheet', ['$document', function($document) {
1356
+ return {
1357
+ restrict: 'E',
1358
+ scope: true,
1359
+ replace: true,
1360
+ link: function($scope, $element){
1361
+ var keyUp = function(e) {
1362
+ if(e.which == 27) {
1363
+ $scope.cancel();
1364
+ $scope.$apply();
1365
+ }
1366
+ };
1367
+
1368
+ var backdropClick = function(e) {
1369
+ if(e.target == $element[0]) {
1370
+ $scope.cancel();
1371
+ $scope.$apply();
1372
+ }
1373
+ };
1374
+ $scope.$on('$destroy', function() {
1375
+ $element.remove();
1376
+ $document.unbind('keyup', keyUp);
1377
+ });
1378
+
1379
+ $document.bind('keyup', keyUp);
1380
+ $element.bind('click', backdropClick);
1381
+ },
1382
+ template: '<div class="action-sheet-backdrop">' +
1383
+ '<div class="action-sheet-wrapper action-sheet-up">' +
1384
+ '<div class="action-sheet">' +
1385
+ '<div class="action-sheet-group">' +
1386
+ '<div class="action-sheet-title" ng-if="titleText">{{titleText}}</div>' +
1387
+ '<button class="button" ng-click="buttonClicked($index)" ng-repeat="button in buttons">{{button.text}}</button>' +
1388
+ '</div>' +
1389
+ '<div class="action-sheet-group" ng-if="destructiveText">' +
1390
+ '<button class="button destructive" ng-click="destructiveButtonClicked()">{{destructiveText}}</button>' +
1391
+ '</div>' +
1392
+ '<div class="action-sheet-group" ng-if="cancelText">' +
1393
+ '<button class="button" ng-click="cancel()">{{cancelText}}</button>' +
1394
+ '</div>' +
1395
+ '</div>' +
1396
+ '</div>' +
1397
+ '</div>'
1398
+ };
1399
+ }]);
1400
+
1401
+ })();
1402
+ ;
1403
+ (function(ionic) {
1404
+ 'use strict';
1405
+
1406
+ angular.module('ionic.ui.header', ['ngAnimate', 'ngSanitize'])
1407
+
1408
+ .directive('barHeader', ['$ionicScrollDelegate', function($ionicScrollDelegate) {
1409
+ return {
1410
+ restrict: 'C',
1411
+ link: function($scope, $element, $attr) {
1412
+ // We want to scroll to top when the top of this element is clicked
1413
+ $ionicScrollDelegate.tapScrollToTop($element);
1414
+ }
1415
+ };
1416
+ }])
1417
+
1418
+ .directive('ionHeaderBar', ['$ionicScrollDelegate', function($ionicScrollDelegate) {
1419
+ return {
1420
+ restrict: 'E',
1421
+ replace: true,
1422
+ transclude: true,
1423
+ template: '<header class="bar bar-header">\
1424
+ <div class="buttons">\
1425
+ <button ng-repeat="button in leftButtons" class="button no-animation" ng-class="button.type" ng-click="button.tap($event, $index)" ng-bind-html="button.content">\
1426
+ </button>\
1427
+ </div>\
1428
+ <h1 class="title" ng-bind-html="title"></h1>\
516
1429
  <div class="buttons">\
517
1430
  <button ng-repeat="button in rightButtons" class="button no-animation" ng-class="button.type" ng-click="button.tap($event, $index)" ng-bind-html="button.content">\
518
1431
  </button>\
@@ -543,7 +1456,6 @@ angular.module('ionic.ui.header', ['ngAnimate'])
543
1456
  });
544
1457
 
545
1458
  $scope.$watch('rightButtons', function(val) {
546
- console.log('Right buttons changed');
547
1459
  // Resize the title since the buttons have changed
548
1460
  hb.align();
549
1461
  });
@@ -554,9 +1466,9 @@ angular.module('ionic.ui.header', ['ngAnimate'])
554
1466
  });
555
1467
  }
556
1468
  };
557
- })
1469
+ }])
558
1470
 
559
- .directive('footerBar', function() {
1471
+ .directive('ionFooterBar', function() {
560
1472
  return {
561
1473
  restrict: 'E',
562
1474
  replace: true,
@@ -582,43 +1494,34 @@ angular.module('ionic.ui.header', ['ngAnimate'])
582
1494
  angular.module('ionic.ui.checkbox', [])
583
1495
 
584
1496
 
585
- .directive('checkbox', function() {
1497
+ .directive('ionCheckbox', function() {
586
1498
  return {
587
1499
  restrict: 'E',
588
1500
  replace: true,
589
1501
  require: '?ngModel',
590
- scope: {},
1502
+ scope: {
1503
+ ngModel: '=?',
1504
+ ngValue: '=?',
1505
+ ngChecked: '=?',
1506
+ ngChange: '&'
1507
+ },
591
1508
  transclude: true,
592
- template: '<li class="item item-checkbox">\
593
- <label class="checkbox">\
594
- <input type="checkbox">\
595
- </label>\
596
- <div class="item-content" ng-transclude>\
597
- </div>\
598
- </li>',
599
1509
 
600
- link: function($scope, $element, $attr, ngModel) {
601
- var checkbox;
602
-
603
- if(!ngModel) { return; }
604
-
605
- checkbox = angular.element($element[0].querySelector('input[type="checkbox"]'));
606
-
607
- if(!checkbox.length) { return; }
608
-
609
- checkbox.bind('change', function(e) {
610
- ngModel.$setViewValue(checkbox[0].checked);
611
- $scope.$apply(function() {
612
- e.alreadyHandled = true;
613
- });
614
- });
615
-
616
- if(ngModel) {
617
- ngModel.$render = function() {
618
- checkbox[0].checked = ngModel.$viewValue;
619
- };
620
- }
1510
+ template: '<div class="item item-checkbox disable-pointer-events">' +
1511
+ '<label class="checkbox enable-pointer-events">' +
1512
+ '<input type="checkbox" ng-model="ngModel" ng-value="ngValue" ng-change="ngChange()">' +
1513
+ '</label>' +
1514
+ '<div class="item-content" ng-transclude></div>' +
1515
+ '</div>',
1516
+
1517
+ compile: function(element, attr) {
1518
+ var input = element.find('input');
1519
+ if(attr.name) input.attr('name', attr.name);
1520
+ if(attr.ngChecked) input.attr('ng-checked', 'ngChecked');
1521
+ if(attr.ngTrueValue) input.attr('ng-true-value', attr.ngTrueValue);
1522
+ if(attr.ngFalseValue) input.attr('ng-false-value', attr.ngFalseValue);
621
1523
  }
1524
+
622
1525
  };
623
1526
  });
624
1527
 
@@ -627,13 +1530,13 @@ angular.module('ionic.ui.checkbox', [])
627
1530
  (function() {
628
1531
  'use strict';
629
1532
 
630
- angular.module('ionic.ui.content', [])
1533
+ angular.module('ionic.ui.content', ['ionic.ui.service', 'ionic.ui.scroll'])
631
1534
 
632
1535
  /**
633
1536
  * Panel is a simple 100% width and height, fixed panel. It's meant for content to be
634
1537
  * added to it, or animated around.
635
1538
  */
636
- .directive('pane', function() {
1539
+ .directive('ionPane', function() {
637
1540
  return {
638
1541
  restrict: 'E',
639
1542
  link: function(scope, element, attr) {
@@ -642,160 +1545,219 @@ angular.module('ionic.ui.content', [])
642
1545
  };
643
1546
  })
644
1547
 
645
- // The content directive is a core scrollable content area
646
- // that is part of many View hierarchies
647
- .directive('content', ['$parse', '$timeout', function($parse, $timeout) {
1548
+ /*
1549
+ * @ngdoc directive
1550
+ * @name ionContent
1551
+ *
1552
+ * @description
1553
+ * The ionContent directive provides an easy to use content area that can be configured to use
1554
+ * Ionic's custom Scroll View, or the built in overflow scorlling of the browser.
1555
+ *
1556
+ * While we recommend using the custom Scroll features in Ionic in most cases, sometimes (for performance reasons) only the browser's native overflow scrolling will suffice, and so we've made it easy to toggle between the Ionic scroll implementation and overflow scrolling.
1557
+ *
1558
+ * When using the Ionic scroll features, you'll get pull-to-refresh, customizable scroll mechanics (like bounce easing, momentum acceleration, etc.) which aligns Ionic with native SDKs that give you access to scroll behavior. You'll also get events while in a momentum scroll, which -webkit-overflow-scrolling: touch will not, making it of limited use in real applications.
1559
+ *
1560
+ * Also, we are working on virtual list rendering which will only work when using Ionic's scroll view. That is on the upcoming roadmap.
1561
+ *
1562
+ * @restrict E
1563
+ * @param {boolean=} scroll Whether to allow scrolling of content. Defaults to true.
1564
+ * @param {boolean=} overflow-scroll Whether to use overflow-scrolling instead of Ionic scroll.
1565
+ * @param {boolean=} padding Whether to add padding to the content.
1566
+ * @param {boolean=} has-header Whether to offset the content for a header bar.
1567
+ * @param {boolean=} has-subheader Whether to offset the content for a subheader bar.
1568
+ * @param {boolean=} has-footer Whether to offset the content for a footer bar.
1569
+ * @param {boolean=} has-bouncing Whether to allow scrolling to bounce past the edges of the content. Defaults to true on iOS, false on Android.
1570
+ * @param {expression=} on-refresh Expression to evaluate on refresh completion.
1571
+ * @param {expression=} on-refresh-opening Expression to evaluate on refresh opening.
1572
+ * @param {expression=} on-scroll Expression to evaluate when the content is scrolled.
1573
+ * @param {expression=} on-scroll-complete Expression to evaluate when a scroll action completes.
1574
+ */
1575
+ .directive('ionContent', [
1576
+ '$parse',
1577
+ '$timeout',
1578
+ '$ionicScrollDelegate',
1579
+ '$controller',
1580
+ '$ionicBind',
1581
+ function($parse, $timeout, $ionicScrollDelegate, $controller, $ionicBind) {
648
1582
  return {
649
1583
  restrict: 'E',
650
1584
  replace: true,
651
- template: '<div class="scroll-content"></div>',
652
1585
  transclude: true,
653
- scope: {
654
- onRefresh: '&',
655
- onRefreshOpening: '&',
656
- onScroll: '&',
657
- onScrollComplete: '&',
658
- refreshComplete: '=',
659
- scroll: '@',
660
- hasScrollX: '@',
661
- hasScrollY: '@',
662
- scrollbarX: '@',
663
- scrollbarY: '@',
664
- scrollEventInterval: '@'
665
- },
1586
+ require: '^?ionNavView',
1587
+ scope: true,
1588
+ template:
1589
+ '<div class="scroll-content">' +
1590
+ '<div class="scroll"></div>' +
1591
+ '</div>',
666
1592
  compile: function(element, attr, transclude) {
667
- return function($scope, $element, $attr) {
668
- var clone, sc, sv,
669
- addedPadding = false,
670
- c = $element.eq(0);
671
-
672
- if(attr.hasHeader == "true") { c.addClass('has-header'); }
673
- if(attr.hasSubheader == "true") { c.addClass('has-subheader'); }
674
- if(attr.hasFooter == "true") { c.addClass('has-footer'); }
675
- if(attr.hasTabs == "true") { c.addClass('has-tabs'); }
1593
+ if(attr.hasHeader == "true") { element.addClass('has-header'); }
1594
+ if(attr.hasSubheader == "true") { element.addClass('has-subheader'); }
1595
+ if(attr.hasFooter == "true") { element.addClass('has-footer'); }
1596
+ if(attr.hasTabs == "true") { element.addClass('has-tabs'); }
1597
+ if(attr.padding == "true") { element.find('div').addClass('padding'); }
676
1598
 
677
- // If they want plain overflow scrolling, add that as a class
678
- if($scope.scroll === "false") {
679
- clone = transclude($scope.$parent);
680
- $element.append(clone);
681
- } else if(attr.overflowScroll === "true") {
682
- c.addClass('overflow-scroll');
683
- clone = transclude($scope.$parent);
684
- $element.append(clone);
685
- } else {
686
- sc = document.createElement('div');
687
- sc.className = 'scroll';
688
- if(attr.padding == "true") {
689
- sc.className += ' padding';
690
- addedPadding = true;
691
- }
692
- $element.append(sc);
1599
+ return {
1600
+ //Prelink <ion-content> so it can compile before other directives compile.
1601
+ //Then other directives can require ionicScrollCtrl
1602
+ pre: prelink
1603
+ };
693
1604
 
694
- // Pass the parent scope down to the child
695
- clone = transclude($scope.$parent);
696
- angular.element($element[0].firstElementChild).append(clone);
1605
+ function prelink($scope, $element, $attr, navViewCtrl) {
1606
+ var clone, sc, scrollView, scrollCtrl,
1607
+ scrollContent = angular.element($element[0].querySelector('.scroll'));
697
1608
 
698
- var refresher = $element[0].querySelector('.scroll-refresher');
699
- var refresherHeight = refresher && refresher.clientHeight || 0;
1609
+ transclude($scope, function(clone) {
1610
+ scrollContent.append(clone);
1611
+ });
700
1612
 
701
- if(attr.refreshComplete) {
702
- $scope.refreshComplete = function() {
703
- if($scope.scrollView) {
704
- refresher && refresher.classList.remove('active');
705
- $scope.scrollView.finishPullToRefresh();
706
- $scope.$parent.$broadcast('scroll.onRefreshComplete');
707
- }
708
- };
709
- }
1613
+ $ionicBind($scope, $attr, {
1614
+ //Use $ to stop onRefresh from recursively calling itself
1615
+ $onRefresh: '&onRefresh',
1616
+ $onRefreshOpening: '&onRefreshOpening',
1617
+ $onScroll: '&onScroll',
1618
+ $onScrollComplete: '&onScrollComplete',
1619
+ $onInfiniteScroll: '&onInfiniteScroll',
1620
+ refreshComplete: '=',
1621
+ infiniteScrollDistance: '@',
1622
+ hasBouncing: '@',
1623
+ scroll: '@',
1624
+ padding: '@',
1625
+ hasScrollX: '@',
1626
+ hasScrollY: '@',
1627
+ scrollbarX: '@',
1628
+ scrollbarY: '@',
1629
+ startX: '@',
1630
+ startY: '@',
1631
+ scrollEventInterval: '@'
1632
+ });
710
1633
 
1634
+ if($scope.scroll === "false") {
1635
+ // No scrolling
1636
+ return;
1637
+ }
711
1638
 
712
- // Otherwise, supercharge this baby!
713
- $timeout(function() {
714
- sv = new ionic.views.Scroll({
715
- el: $element[0],
716
- scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
717
- scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
718
- scrollingX: $scope.$eval($scope.hasScrollX) === true,
719
- scrollingY: $scope.$eval($scope.hasScrollY) !== false,
720
- scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 20,
721
- scrollingComplete: function() {
722
- $scope.onScrollComplete({
723
- scrollTop: this.__scrollTop,
724
- scrollLeft: this.__scrollLeft
725
- });
726
- }
727
- });
1639
+ if(attr.overflowScroll === "true") {
1640
+ $element.addClass('overflow-scroll');
1641
+ return;
1642
+ }
728
1643
 
729
- // Activate pull-to-refresh
730
- if(refresher) {
731
- sv.activatePullToRefresh(50, function() {
732
- refresher.classList.add('active');
733
- }, function() {
734
- refresher.classList.remove('refreshing');
735
- refresher.classList.remove('active');
736
- }, function() {
737
- refresher.classList.add('refreshing');
738
- $scope.onRefresh();
739
- $scope.$parent.$broadcast('scroll.onRefresh');
1644
+ scrollCtrl = $controller('$ionicScroll', {
1645
+ $scope: $scope,
1646
+ scrollViewOptions: {
1647
+ el: $element[0],
1648
+ bouncing: $scope.$eval($scope.hasBouncing),
1649
+ startX: $scope.$eval($scope.startX) || 0,
1650
+ startY: $scope.$eval($scope.startY) || 0,
1651
+ scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
1652
+ scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
1653
+ scrollingX: $scope.$eval($scope.hasScrollX) === true,
1654
+ scrollingY: $scope.$eval($scope.hasScrollY) !== false,
1655
+ scrollEventInterval: parseInt($scope.scrollEventInterval, 10) || 20,
1656
+ scrollingComplete: function() {
1657
+ $scope.$onScrollComplete({
1658
+ scrollTop: this.__scrollTop,
1659
+ scrollLeft: this.__scrollLeft
740
1660
  });
741
1661
  }
1662
+ }
1663
+ });
742
1664
 
743
- $element.bind('scroll', function(e) {
744
- $scope.onScroll({
745
- event: e,
746
- scrollTop: e.detail ? e.detail.scrollTop : e.originalEvent ? e.originalEvent.detail.scrollTop : 0,
747
- scrollLeft: e.detail ? e.detail.scrollLeft: e.originalEvent ? e.originalEvent.detail.scrollLeft : 0
748
- });
749
- });
1665
+ //Publish scrollView to parent so children can access it
1666
+ scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView;
750
1667
 
751
- $scope.$parent.$on('scroll.resize', function(e) {
752
- // Run the resize after this digest
753
- $timeout(function() {
754
- sv && sv.resize();
755
- });
756
- });
1668
+ $scope.$on('$viewContentLoaded', function(e, viewHistoryData) {
1669
+ viewHistoryData || (viewHistoryData = {});
1670
+ var scroll = viewHistoryData.scrollValues;
1671
+ if (scroll) {
1672
+ $timeout(function() {
1673
+ scrollView.scrollTo(+scroll.left || null, +scroll.top || null);
1674
+ }, 0);
1675
+ }
757
1676
 
758
- $scope.$parent.$on('scroll.refreshComplete', function(e) {
759
- sv && sv.finishPullToRefresh();
760
- });
761
-
762
- // Let child scopes access this
763
- $scope.$parent.scrollView = sv;
1677
+ //Save scroll onto viewHistoryData when scope is destroyed
1678
+ $scope.$on('$destroy', function() {
1679
+ viewHistoryData.scrollValues = scrollView.getValues();
764
1680
  });
1681
+ });
765
1682
 
766
-
767
-
768
- }
769
-
770
- // if padding attribute is true, then add padding if it wasn't added to the .scroll
771
- if(attr.padding == "true" && !addedPadding) {
772
- c.addClass('padding');
1683
+ if(attr.refreshComplete) {
1684
+ $scope.refreshComplete = function() {
1685
+ if($scope.scrollView) {
1686
+ scrollCtrl.refresher && scrollCtrl.refresher.classList.remove('active');
1687
+ scrollView.finishPullToRefresh();
1688
+ $scope.$parent.$broadcast('scroll.onRefreshComplete');
1689
+ }
1690
+ };
773
1691
  }
774
-
775
- };
1692
+ }
776
1693
  }
777
1694
  };
778
1695
  }])
779
1696
 
780
- .directive('refresher', function() {
1697
+ .directive('ionRefresher', function() {
781
1698
  return {
782
1699
  restrict: 'E',
783
1700
  replace: true,
784
- require: ['^?content', '^?list'],
1701
+ require: ['^?ionContent', '^?ionList'],
785
1702
  template: '<div class="scroll-refresher"><div class="ionic-refresher-content"><i class="icon ion-arrow-down-c icon-pulling"></i><i class="icon ion-loading-d icon-refreshing"></i></div></div>',
786
1703
  scope: true
787
1704
  };
788
1705
  })
789
1706
 
790
- .directive('scrollRefresher', function() {
1707
+ .directive('ionScrollRefresher', function() {
791
1708
  return {
792
1709
  restrict: 'E',
793
1710
  replace: true,
794
1711
  transclude: true,
795
1712
  template: '<div class="scroll-refresher"><div class="scroll-refresher-content" ng-transclude></div></div>'
796
1713
  };
797
- });
1714
+ })
1715
+
1716
+ .directive('ionInfiniteScroll', ['$ionicBind', function($ionicBind) {
1717
+ return {
1718
+ restrict: 'E',
1719
+ require: '^?$ionicScroll',
1720
+ template:
1721
+ '<div class="scroll-infinite">' +
1722
+ '<div class="scroll-infinite-content">' +
1723
+ '<i class="icon ion-loading-d icon-refreshing"></i>' +
1724
+ '</div>' +
1725
+ '</div>',
1726
+ link: function($scope, $element, $attrs, scrollCtrl) {
1727
+ setTimeout(function() {
1728
+ var scrollCtrl = $element.controller('$ionicScroll');
1729
+ var scrollView = scrollCtrl.scrollView;
798
1730
 
1731
+ $ionicBind($scope, $attrs, {
1732
+ distance: '@infiniteScrollDistance'
1733
+ });
1734
+ function maxScroll() {
1735
+ var dist = $scope.distance || '1%';
1736
+ return dist.indexOf('%') > -1 ?
1737
+ scrollView.getScrollMax().top * (1 - parseInt(dist,10) / 100) :
1738
+ scrollView.getScrollMax().top - parseInt(dist, 10);
1739
+ }
1740
+
1741
+ var infiniteScrolling = false;
1742
+ $scope.$on('scroll.infiniteScrollComplete', function() {
1743
+ $element[0].classList.remove('active');
1744
+ setTimeout(function() {
1745
+ scrollView.resize();
1746
+ });
1747
+ infiniteScrolling = false;
1748
+ });
1749
+
1750
+ scrollCtrl.$element.on('scroll', ionic.animationFrameThrottle(function() {
1751
+ if (!infiniteScrolling && scrollView.getValues().top >= maxScroll()) {
1752
+ $element[0].classList.add('active');
1753
+ infiniteScrolling = true;
1754
+ $scope.$apply(angular.bind($scope, $scope.$onInfiniteScroll));
1755
+ }
1756
+ }));
1757
+ });
1758
+ }
1759
+ };
1760
+ }]);
799
1761
 
800
1762
  })();
801
1763
  ;
@@ -804,10 +1766,10 @@ angular.module('ionic.ui.content', [])
804
1766
 
805
1767
  angular.module('ionic.ui.list', ['ngAnimate'])
806
1768
 
807
- .directive('item', ['$timeout', function($timeout) {
1769
+ .directive('ionItem', ['$timeout', '$parse', function($timeout, $parse) {
808
1770
  return {
809
1771
  restrict: 'E',
810
- require: '?^list',
1772
+ require: '?^ionList',
811
1773
  replace: true,
812
1774
  transclude: true,
813
1775
 
@@ -823,22 +1785,22 @@ angular.module('ionic.ui.list', ['ngAnimate'])
823
1785
  reorderIcon: '@'
824
1786
  },
825
1787
 
826
- template: '<div class="item item-complex" ng-class="itemClass">\
1788
+ template: '<div class="item item-complex">\
827
1789
  <div class="item-edit" ng-if="deleteClick !== undefined">\
828
- <button class="button button-icon icon" ng-class="deleteIconClass" ng-click="deleteClick()"></button>\
1790
+ <button class="button button-icon icon" ng-class="deleteIconClass" ng-click="deleteClick()" ion-stop-event="click"></button>\
829
1791
  </div>\
830
1792
  <a class="item-content" ng-href="{{ href }}" ng-transclude></a>\
831
1793
  <div class="item-drag" ng-if="reorderIconClass !== undefined">\
832
1794
  <button data-ionic-action="reorder" class="button button-icon icon" ng-class="reorderIconClass"></button>\
833
1795
  </div>\
834
1796
  <div class="item-options" ng-if="itemOptionButtons">\
835
- <button ng-click="b.onTap(item, b)" class="button" ng-class="b.type" ng-repeat="b in itemOptionButtons" ng-bind="b.text"></button>\
1797
+ <button ng-click="b.onTap(item, b)" ion-stop-event="click" class="button" ng-class="b.type" ng-repeat="b in itemOptionButtons" ng-bind="b.text"></button>\
836
1798
  </div>\
837
1799
  </div>',
838
1800
 
839
1801
  link: function($scope, $element, $attr, list) {
840
1802
  if(!list) return;
841
-
1803
+
842
1804
  var $parentScope = list.scope;
843
1805
  var $parentAttrs = list.attrs;
844
1806
 
@@ -846,10 +1808,16 @@ angular.module('ionic.ui.list', ['ngAnimate'])
846
1808
  if(value) $scope.href = value.trim();
847
1809
  });
848
1810
 
1811
+ if(!$scope.itemType) {
1812
+ $scope.itemType = $parentScope.itemType;
1813
+ }
1814
+
849
1815
  // Set this item's class, first from the item directive attr, and then the list attr if item not set
850
- $scope.itemClass = $scope.itemType || $parentScope.itemType;
1816
+ $element.addClass($scope.itemType || $parentScope.itemType);
851
1817
 
852
- // Decide if this item can do stuff, and follow a certain priority
1818
+ $scope.itemClass = $scope.itemType;
1819
+
1820
+ // Decide if this item can do stuff, and follow a certain priority
853
1821
  // depending on where the value comes from
854
1822
  if(($attr.canDelete ? $scope.canDelete : $parentScope.canDelete) !== "false") {
855
1823
  if($attr.onDelete || $parentAttrs.onDelete) {
@@ -890,12 +1858,12 @@ angular.module('ionic.ui.list', ['ngAnimate'])
890
1858
  };
891
1859
  }])
892
1860
 
893
- .directive('list', ['$timeout', function($timeout) {
1861
+ .directive('ionList', ['$timeout', function($timeout) {
894
1862
  return {
895
1863
  restrict: 'E',
896
1864
  replace: true,
897
1865
  transclude: true,
898
-
1866
+ require: '^?$ionicScroll',
899
1867
  scope: {
900
1868
  itemType: '@',
901
1869
  canDelete: '@',
@@ -912,15 +1880,22 @@ angular.module('ionic.ui.list', ['ngAnimate'])
912
1880
 
913
1881
  template: '<div class="list" ng-class="{\'list-editing\': showDelete, \'list-reordering\': showReorder}" ng-transclude></div>',
914
1882
 
915
- controller: function($scope, $attrs) {
1883
+ controller: ['$scope', '$attrs', function($scope, $attrs) {
916
1884
  this.scope = $scope;
917
1885
  this.attrs = $attrs;
918
- },
1886
+ }],
919
1887
 
920
- link: function($scope, $element, $attr) {
1888
+ link: function($scope, $element, $attr, ionicScrollCtrl) {
921
1889
  $scope.listView = new ionic.views.ListView({
922
1890
  el: $element[0],
923
- listEl: $element[0].children[0]
1891
+ listEl: $element[0].children[0],
1892
+ scrollEl: ionicScrollCtrl && ionicScrollCtrl.element,
1893
+ scrollView: ionicScrollCtrl && ionicScrollCtrl.scrollView,
1894
+ onReorder: function(el, oldIndex, newIndex) {
1895
+ $scope.$apply(function() {
1896
+ $scope.onReorder({el: el, start: oldIndex, end: newIndex});
1897
+ });
1898
+ }
924
1899
  });
925
1900
 
926
1901
  if($attr.animation) {
@@ -954,7 +1929,7 @@ angular.module('ionic.ui.list', ['ngAnimate'])
954
1929
 
955
1930
  angular.module('ionic.ui.loading', [])
956
1931
 
957
- .directive('loading', function() {
1932
+ .directive('ionLoading', function() {
958
1933
  return {
959
1934
  restrict: 'E',
960
1935
  replace: true,
@@ -967,420 +1942,7 @@ angular.module('ionic.ui.loading', [])
967
1942
  '</div>' +
968
1943
  '</div>'
969
1944
  };
970
- });
971
-
972
- })();
973
- ;
974
- (function() {
975
- 'use strict';
976
-
977
- /**
978
- * @description
979
- * The NavController is a navigation stack View Controller modelled off of
980
- * UINavigationController from Cocoa Touch. With the Nav Controller, you can
981
- * "push" new "pages" on to the navigation stack, and then pop them off to go
982
- * back. The NavController controls a navigation bar with a back button and title
983
- * which updates as the pages switch.
984
- *
985
- * The NavController makes sure to not recycle scopes of old pages
986
- * so that a pop will still show the same state that the user left.
987
- *
988
- * However, once a page is popped, its scope is destroyed and will have to be
989
- * recreated then next time it is pushed.
990
- *
991
- */
992
-
993
- var actualLocation = null;
994
-
995
- angular.module('ionic.ui.navRouter', ['ionic.service.gesture'])
996
-
997
- .run(['$rootScope', function($rootScope) {
998
- $rootScope.stackCursorPosition = 0;
999
- }])
1000
-
1001
- .directive('navRouter', ['$rootScope', '$timeout', '$location', '$window', '$route', function($rootScope, $timeout, $location, $window, $route) {
1002
- return {
1003
- restrict: 'AC',
1004
- // So you can require being under this
1005
- controller: ['$scope', '$element', function($scope, $element) {
1006
- this.navBar = {
1007
- isVisible: true
1008
- };
1009
- $scope.navController = this;
1010
-
1011
- this.goBack = function() {
1012
- $scope.direction = 'back';
1013
- };
1014
- }],
1015
-
1016
- link: function($scope, $element, $attr, ctrl) {
1017
- if(!$element.length) return;
1018
-
1019
- $scope.animation = $attr.animation;
1020
-
1021
- $element[0].classList.add('noop-animation');
1022
-
1023
- var isFirst = true;
1024
- // Store whether we did an animation yet, to know if
1025
- // we should let the first state animate
1026
- var didAnimate = false;
1027
-
1028
- var initTransition = function() {
1029
- //$element.addClass($scope.animation);
1030
- };
1031
-
1032
- var reverseTransition = function() {
1033
- $element[0].classList.remove('noop-animation');
1034
- $element[0].classList.add($scope.animation);
1035
- $element[0].classList.add('reverse');
1036
- };
1037
-
1038
- var forwardTransition = function() {
1039
- $element[0].classList.remove('noop-animation');
1040
- $element[0].classList.remove('reverse');
1041
- $element[0].classList.add($scope.animation);
1042
- };
1043
-
1044
- $scope.$on('$routeChangeSuccess', function(e, a) {
1045
- });
1046
- $scope.$on('$routeChangeStart', function(e, next, current) {
1047
- var back, historyState = $window.history.state;
1048
-
1049
- back = $scope.direction == 'back' || (!!(historyState && historyState.position <= $rootScope.stackCursorPosition));
1050
-
1051
- if(isFirst || (next && next.$$route && next.$$route.originalPath === "")) {
1052
- // Don't animate
1053
- isFirst = false;
1054
- return;
1055
- }
1056
-
1057
- if(didAnimate || $rootScope.stackCursorPosition > 0) {
1058
- didAnimate = true;
1059
- if(back) {
1060
- reverseTransition();
1061
- } else {
1062
- forwardTransition();
1063
- }
1064
- }
1065
- });
1066
-
1067
- $scope.$on('$locationChangeSuccess', function(a, b, c) {
1068
- // Store the new location
1069
- $rootScope.actualLocation = $location.path();
1070
- if(isFirst && $location.path() !== '/') {
1071
- isFirst = false;
1072
- }
1073
- });
1074
-
1075
- $scope.$on('navRouter.goBack', function(e) {
1076
- ctrl.goBack();
1077
- });
1078
-
1079
-
1080
- // Keep track of location changes and update a stack pointer that tracks whether we are
1081
- // going forwards or back
1082
- $scope.$watch(function () { return $location.path(); }, function (newLocation, oldLocation) {
1083
- if($rootScope.actualLocation === newLocation) {
1084
- if(oldLocation === '') {// || newLocation == '/') {
1085
- // initial route, skip this
1086
- return;
1087
- }
1088
-
1089
- var back, historyState = $window.history.state;
1090
-
1091
- back = $scope.direction == 'back' || (!!(historyState && historyState.position <= $rootScope.stackCursorPosition));
1092
-
1093
- if (back) {
1094
- //back button
1095
- $rootScope.stackCursorPosition--;
1096
- } else {
1097
- //forward button
1098
- $rootScope.stackCursorPosition++;
1099
- }
1100
-
1101
- $scope.direction = 'forwards';
1102
-
1103
- } else {
1104
- var currentRouteBeforeChange = $route.current;
1105
-
1106
- if (currentRouteBeforeChange) {
1107
-
1108
- $window.history.replaceState({
1109
- position: $rootScope.stackCursorPosition
1110
- });
1111
-
1112
- $rootScope.stackCursorPosition++;
1113
- }
1114
- }
1115
- });
1116
- }
1117
- };
1118
- }])
1119
-
1120
- /**
1121
- * Our Nav Bar directive which updates as the controller state changes.
1122
- */
1123
- .directive('navBar', ['$rootScope', '$animate', '$compile', function($rootScope, $animate, $compile) {
1124
-
1125
- /**
1126
- * Perform an animation between one tab bar state and the next.
1127
- * Right now this just animates the titles.
1128
- */
1129
- var animate = function($scope, $element, oldTitle, data, cb) {
1130
- var title, nTitle, oTitle, titles = $element[0].querySelectorAll('.title');
1131
-
1132
- var newTitle = data.title;
1133
- if(!oldTitle || oldTitle === newTitle) {
1134
- cb();
1135
- return;
1136
- }
1137
-
1138
- // Clone the old title and add a new one so we can show two animating in and out
1139
- // add ng-leave and ng-enter during creation to prevent flickering when they are swapped during animation
1140
- title = angular.element(titles[0]);
1141
- oTitle = $compile('<h1 class="title ng-leave" ng-bind="oldTitle"></h1>')($scope);
1142
- title.replaceWith(oTitle);
1143
- nTitle = $compile('<h1 class="title ng-enter" ng-bind="currentTitle"></h1>')($scope);
1144
-
1145
- var insert = $element[0].firstElementChild || null;
1146
-
1147
- // Insert the new title
1148
- $animate.enter(nTitle, $element, insert && angular.element(insert), function() {
1149
- cb();
1150
- });
1151
-
1152
- // Remove the old title
1153
- $animate.leave(angular.element(oTitle), function() {
1154
- });
1155
- };
1156
-
1157
- return {
1158
- restrict: 'E',
1159
- require: '^navRouter',
1160
- replace: true,
1161
- scope: {
1162
- type: '@',
1163
- backButtonType: '@',
1164
- backButtonLabel: '@',
1165
- backButtonIcon: '@',
1166
- alignTitle: '@'
1167
- },
1168
- template: '<header class="bar bar-header nav-bar" ng-class="{invisible: !navController.navBar.isVisible}">' +
1169
- '<div class="buttons"> ' +
1170
- '<button nav-back class="button" ng-if="enableBackButton && showBackButton" ng-class="backButtonClass" ng-bind-html="backButtonLabel"></button>' +
1171
- '<button ng-click="button.tap($event)" ng-repeat="button in leftButtons" class="button no-animation {{button.type}}" ng-bind-html="button.content"></button>' +
1172
- '</div>' +
1173
- '<h1 class="title" ng-bind="currentTitle"></h1>' +
1174
- '<div class="buttons" ng-if="rightButtons.length"> ' +
1175
- '<button ng-click="button.tap($event)" ng-repeat="button in rightButtons" class="button no-animation {{button.type}}" ng-bind-html="button.content"></button>' +
1176
- '</div>' +
1177
- '</header>',
1178
- link: function($scope, $element, $attr, navCtrl) {
1179
- var backButton;
1180
-
1181
- $element.addClass($attr.animation);
1182
-
1183
- // Create the back button content and show/hide it based on scope settings
1184
- $scope.enableBackButton = true;
1185
- $scope.backButtonClass = $attr.backButtonType;
1186
- if($attr.backButtonIcon) {
1187
- $scope.backButtonClass += ' icon ' + $attr.backButtonIcon;
1188
- }
1189
-
1190
- // Listen for changes in the stack cursor position to indicate whether a back
1191
- // button should be shown (this can still be disabled by the $scope.enableBackButton
1192
- $rootScope.$watch('stackCursorPosition', function(value) {
1193
- if(value > 0) {
1194
- $scope.showBackButton = true;
1195
- } else {
1196
- $scope.showBackButton = false;
1197
- }
1198
- });
1199
-
1200
- // Store a reference to our nav controller
1201
- $scope.navController = navCtrl;
1202
-
1203
- // Initialize our header bar view which will handle resizing and aligning our title labels
1204
- var hb = new ionic.views.HeaderBar({
1205
- el: $element[0],
1206
- alignTitle: $scope.alignTitle || 'center'
1207
- });
1208
- $scope.headerBarView = hb;
1209
-
1210
- // Add the type of header bar class to this element
1211
- $element.addClass($scope.type);
1212
-
1213
- var updateHeaderData = function(data) {
1214
- var oldTitle = $scope.currentTitle;
1215
- $scope.oldTitle = oldTitle;
1216
-
1217
- if(typeof data.title !== 'undefined') {
1218
- $scope.currentTitle = data.title;
1219
- }
1220
-
1221
- $scope.leftButtons = data.leftButtons;
1222
- $scope.rightButtons = data.rightButtons;
1223
-
1224
- if(typeof data.hideBackButton !== 'undefined') {
1225
- $scope.enableBackButton = data.hideBackButton !== true;
1226
- }
1227
-
1228
- if(data.animate !== false && typeof data.title !== 'undefined') {
1229
- animate($scope, $element, oldTitle, data, function() {
1230
- hb.align();
1231
- });
1232
- } else {
1233
- hb.align();
1234
- }
1235
- };
1236
-
1237
- $scope.$parent.$on('navRouter.showBackButton', function(e, data) {
1238
- $scope.enableBackButton = true;
1239
- });
1240
-
1241
- $scope.$parent.$on('navRouter.hideBackButton', function(e, data) {
1242
- $scope.enableBackButton = false;
1243
- });
1244
-
1245
- // Listen for changes on title change, and update the title
1246
- $scope.$parent.$on('navRouter.pageChanged', function(e, data) {
1247
- updateHeaderData(data);
1248
- });
1249
-
1250
- $scope.$parent.$on('navRouter.pageShown', function(e, data) {
1251
- updateHeaderData(data);
1252
- });
1253
-
1254
- $scope.$parent.$on('navRouter.titleChanged', function(e, data) {
1255
- var oldTitle = $scope.currentTitle;
1256
- $scope.oldTitle = oldTitle;
1257
-
1258
- if(typeof data.title !== 'undefined') {
1259
- $scope.currentTitle = data.title;
1260
- }
1261
-
1262
- if(data.animate !== false && typeof data.title !== 'undefined') {
1263
- animate($scope, $element, oldTitle, data, function() {
1264
- hb.align();
1265
- });
1266
- } else {
1267
- hb.align();
1268
- }
1269
- });
1270
-
1271
- // If a nav page changes the left or right buttons, update our scope vars
1272
- $scope.$parent.$on('navRouter.leftButtonsChanged', function(e, data) {
1273
- $scope.leftButtons = data;
1274
- });
1275
- $scope.$parent.$on('navRouter.rightButtonsChanged', function(e, data) {
1276
- $scope.rightButtons = data;
1277
- });
1278
-
1279
- /*
1280
- $scope.$parent.$on('navigation.push', function() {
1281
- backButton = angular.element($element[0].querySelector('.button'));
1282
- backButton.addClass($scope.backButtonType);
1283
- hb.align();
1284
- });
1285
- $scope.$parent.$on('navigation.pop', function() {
1286
- hb.align();
1287
- });
1288
- */
1289
-
1290
- $scope.$on('$destroy', function() {
1291
- //
1292
- });
1293
- }
1294
- };
1295
- }])
1296
-
1297
- .directive('navPage', ['$parse', function($parse) {
1298
- return {
1299
- restrict: 'E',
1300
- require: '^navRouter',
1301
- scope: {
1302
- leftButtons: '=',
1303
- rightButtons: '=',
1304
- title: '=',
1305
- icon: '@',
1306
- iconOn: '@',
1307
- iconOff: '@',
1308
- type: '@',
1309
- alignTitle: '@',
1310
- hideBackButton: '@',
1311
- hideNavBar: '@',
1312
- animate: '@',
1313
- },
1314
- link: function($scope, $element, $attr, navCtrl) {
1315
- $element.addClass('pane');
1316
-
1317
- // Should we hide a back button when this tab is shown
1318
- $scope.hideBackButton = $scope.$eval($scope.hideBackButton);
1319
-
1320
- $scope.hideNavBar = $scope.$eval($scope.hideNavBar);
1321
-
1322
- navCtrl.navBar.isVisible = !$scope.hideNavBar;
1323
-
1324
- if($scope.hideBackButton === true) {
1325
- $scope.$emit('navRouter.hideBackButton');
1326
- } else {
1327
- $scope.$emit('navRouter.showBackButton');
1328
- }
1329
-
1330
- // Whether we should animate on tab change, also impacts whether we
1331
- // tell any parent nav controller to animate
1332
- $scope.animate = $scope.$eval($scope.animate);
1333
-
1334
-
1335
- // watch for changes in the left buttons
1336
- $scope.$watch('leftButtons', function(value) {
1337
- $scope.$emit('navRouter.leftButtonsChanged', $scope.leftButtons);
1338
- });
1339
-
1340
- $scope.$watch('rightButtons', function(val) {
1341
- $scope.$emit('navRouter.rightButtonsChanged', $scope.rightButtons);
1342
- });
1343
-
1344
- /*
1345
- $scope.$watch('hideBackButton', function(value) {
1346
- if(value === true) {
1347
- navCtrl.hideBackButton();
1348
- } else {
1349
- navCtrl.showBackButton();
1350
- }
1351
- });
1352
- */
1353
-
1354
- // watch for changes in the title
1355
- $scope.$watch('title', function(value) {
1356
- $scope.$emit('navRouter.titleChanged', {
1357
- title: value,
1358
- animate: $scope.animate
1359
- });
1360
- });
1361
- }
1362
- };
1363
- }])
1364
-
1365
- .directive('navBack', ['$window', '$rootScope', 'Gesture', function($window, $rootScope, Gesture) {
1366
- return {
1367
- restrict: 'AC',
1368
- link: function($scope, $element, $attr, navCtrl) {
1369
- var goBack = function(e) {
1370
- // Only trigger back if the stack is greater than zero
1371
- if($rootScope.stackCursorPosition > 0) {
1372
- $window.history.back();
1373
-
1374
- // Fallback for bad history supporting devices
1375
- $scope.$emit('navRouter.goBack');
1376
- }
1377
- e.alreadyHandled = true;
1378
- return false;
1379
- };
1380
- $element.bind('click', goBack);
1381
- }
1382
- };
1383
- }]);
1945
+ });
1384
1946
 
1385
1947
  })();
1386
1948
  ;
@@ -1391,56 +1953,35 @@ angular.module('ionic.ui.radio', [])
1391
1953
 
1392
1954
  // The radio button is a radio powered element with only
1393
1955
  // one possible selection in a set of options.
1394
- .directive('radio', function() {
1956
+ .directive('ionRadio', function() {
1395
1957
  return {
1396
1958
  restrict: 'E',
1397
1959
  replace: true,
1398
1960
  require: '?ngModel',
1399
1961
  scope: {
1400
- value: '@'
1962
+ ngModel: '=?',
1963
+ ngValue: '=?',
1964
+ ngChange: '&',
1965
+ icon: '@'
1401
1966
  },
1402
1967
  transclude: true,
1403
- template: '<label class="item item-radio">\
1404
- <input type="radio" name="radio-group">\
1405
- <div class="item-content" ng-transclude>\
1406
- </div>\
1407
- <i class="radio-icon icon ion-checkmark"></i>\
1408
- </label>',
1409
-
1410
- link: function($scope, $element, $attr, ngModel) {
1411
- var radio;
1412
-
1413
- if(!ngModel) { return; }
1414
-
1415
- radio = $element.children().eq(0);
1416
-
1417
- if(!radio.length) { return; }
1418
-
1419
- if(ngModel) {
1420
- radio.bind('click', function(e) {
1421
- console.log('RADIO CLICK');
1422
- $scope.$apply(function() {
1423
- ngModel.$setViewValue($scope.$eval($attr.ngValue));
1424
- });
1425
- e.alreadyHandled = true;
1426
- });
1427
-
1428
- ngModel.$render = function() {
1429
- var val = $scope.$eval($attr.ngValue);
1430
- if(val === ngModel.$viewValue) {
1431
- radio.attr('checked', 'checked');
1432
- } else {
1433
- radio.removeAttr('checked');
1434
- }
1435
- };
1436
- }
1968
+ template: '<label class="item item-radio">' +
1969
+ '<input type="radio" name="radio-group"' +
1970
+ ' ng-model="ngModel" ng-value="ngValue" ng-change="ngChange()">' +
1971
+ '<div class="item-content disable-pointer-events" ng-transclude></div>' +
1972
+ '<i class="radio-icon disable-pointer-events icon ion-checkmark"></i>' +
1973
+ '</label>',
1974
+
1975
+ compile: function(element, attr) {
1976
+ if(attr.name) element.children().eq(0).attr('name', attr.name);
1977
+ if(attr.icon) element.children().eq(2).removeClass('ion-checkmark').addClass(attr.icon);
1437
1978
  }
1438
1979
  };
1439
1980
  })
1440
1981
 
1441
1982
  // The radio button is a radio powered element with only
1442
1983
  // one possible selection in a set of options.
1443
- .directive('radioButtons', function() {
1984
+ .directive('ionRadioButtons', function() {
1444
1985
  return {
1445
1986
  restrict: 'E',
1446
1987
  replace: true,
@@ -1483,17 +2024,17 @@ angular.module('ionic.ui.radio', [])
1483
2024
  };
1484
2025
  })
1485
2026
 
1486
- .directive('buttonRadio', function() {
2027
+ .directive('ionButtonRadio', function() {
1487
2028
  return {
1488
2029
  restrict: 'CA',
1489
- require: ['?^ngModel', '?^radioButtons'],
2030
+ require: ['?^ngModel', '?^ionRadioButtons'],
1490
2031
  link: function($scope, $element, $attr, ctrls) {
1491
2032
  var ngModel = ctrls[0];
1492
2033
  var radioButtons = ctrls[1];
1493
2034
  if(!ngModel || !radioButtons) { return; }
1494
2035
 
1495
2036
  var setIt = function() {
1496
- console.log('SET');
2037
+
1497
2038
  $element.addClass('active');
1498
2039
  ngModel.$setViewValue($scope.$eval($attr.ngValue));
1499
2040
 
@@ -1501,7 +2042,7 @@ angular.module('ionic.ui.radio', [])
1501
2042
  };
1502
2043
 
1503
2044
  var clickHandler = function(e) {
1504
- console.log('CLICK');
2045
+
1505
2046
  setIt();
1506
2047
  };
1507
2048
 
@@ -1527,11 +2068,11 @@ angular.module('ionic.ui.radio', [])
1527
2068
 
1528
2069
  angular.module('ionic.ui.scroll', [])
1529
2070
 
1530
- .directive('scroll', ['$parse', '$timeout', function($parse, $timeout) {
2071
+ .directive('ionScroll', ['$parse', '$timeout', '$controller', function($parse, $timeout, $controller) {
1531
2072
  return {
1532
2073
  restrict: 'E',
1533
2074
  replace: true,
1534
- template: '<div class="scroll-view"></div>',
2075
+ template: '<div class="scroll-view"><div class="scroll" ng-transclude></div></div>',
1535
2076
  transclude: true,
1536
2077
  scope: {
1537
2078
  direction: '@',
@@ -1547,86 +2088,46 @@ angular.module('ionic.ui.scroll', [])
1547
2088
  controller: function() {},
1548
2089
 
1549
2090
  compile: function(element, attr, transclude) {
1550
- return function($scope, $element, $attr) {
1551
- var clone, sv, sc = document.createElement('div');
1552
2091
 
1553
- // Create the internal scroll div
1554
- sc.className = 'scroll';
2092
+ return {
2093
+ //Prelink <ion-scroll> so it can compile before other directives compile.
2094
+ //Then other directives can require ionicScrollCtrl
2095
+ pre: prelink
2096
+ };
2097
+
2098
+ function prelink($scope, $element, $attr) {
2099
+ var scrollView, scrollCtrl,
2100
+ sc = $element[0].children[0];
2101
+
1555
2102
  if(attr.padding == "true") {
1556
2103
  sc.classList.add('padding');
1557
- addedPadding = true;
1558
2104
  }
1559
2105
  if($scope.$eval($scope.paging) === true) {
1560
2106
  sc.classList.add('scroll-paging');
1561
2107
  }
1562
- $element.append(sc);
1563
-
1564
- // Pass the parent scope down to the child
1565
- clone = transclude($scope.$parent);
1566
- angular.element($element[0].firstElementChild).append(clone);
1567
-
1568
- // Get refresher size
1569
- var refresher = $element[0].querySelector('.scroll-refresher');
1570
- var refresherHeight = refresher && refresher.clientHeight || 0;
1571
2108
 
1572
2109
  if(!$scope.direction) { $scope.direction = 'y'; }
1573
- var hasScrollingX = $scope.direction.indexOf('x') >= 0;
1574
- var hasScrollingY = $scope.direction.indexOf('y') >= 0;
1575
-
1576
- $timeout(function() {
1577
- var options = {
1578
- el: $element[0],
1579
- paging: $scope.$eval($scope.paging) === true,
1580
- scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
1581
- scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
1582
- scrollingX: hasScrollingX,
1583
- scrollingY: hasScrollingY
1584
- };
1585
-
1586
- if(options.paging) {
1587
- options.speedMultiplier = 0.8;
1588
- options.bouncing = false;
1589
- }
1590
-
1591
- sv = new ionic.views.Scroll(options);
1592
-
1593
- // Activate pull-to-refresh
1594
- if(refresher) {
1595
- sv.activatePullToRefresh(refresherHeight, function() {
1596
- refresher.classList.add('active');
1597
- }, function() {
1598
- refresher.classList.remove('refreshing');
1599
- refresher.classList.remove('active');
1600
- }, function() {
1601
- refresher.classList.add('refreshing');
1602
- $scope.onRefresh();
1603
- $scope.$parent.$broadcast('scroll.onRefresh');
1604
- });
1605
- }
1606
-
1607
- $element.bind('scroll', function(e) {
1608
- $scope.onScroll({
1609
- event: e,
1610
- scrollTop: e.detail ? e.detail.scrollTop : e.originalEvent ? e.originalEvent.detail.scrollTop : 0,
1611
- scrollLeft: e.detail ? e.detail.scrollLeft: e.originalEvent ? e.originalEvent.detail.scrollLeft : 0
1612
- });
1613
- });
1614
-
1615
- $scope.$parent.$on('scroll.resize', function(e) {
1616
- // Run the resize after this digest
1617
- $timeout(function() {
1618
- sv && sv.resize();
1619
- });
1620
- });
2110
+ var isPaging = $scope.$eval($scope.paging) === true;
2111
+
2112
+ var scrollViewOptions= {
2113
+ el: $element[0],
2114
+ paging: isPaging,
2115
+ scrollbarX: $scope.$eval($scope.scrollbarX) !== false,
2116
+ scrollbarY: $scope.$eval($scope.scrollbarY) !== false,
2117
+ scrollingX: $scope.direction.indexOf('x') >= 0,
2118
+ scrollingY: $scope.direction.indexOf('y') >= 0
2119
+ };
2120
+ if (isPaging) {
2121
+ scrollViewOptions.speedMultiplier = 0.8;
2122
+ scrollViewOptions.bouncing = false;
2123
+ }
1621
2124
 
1622
- $scope.$parent.$on('scroll.refreshComplete', function(e) {
1623
- sv && sv.finishPullToRefresh();
1624
- });
1625
-
1626
- // Let child scopes access this
1627
- $scope.$parent.scrollView = sv;
2125
+ scrollCtrl = $controller('$ionicScroll', {
2126
+ $scope: $scope,
2127
+ scrollViewOptions: scrollViewOptions
1628
2128
  });
1629
- };
2129
+ scrollView = $scope.$parent.scrollView = scrollCtrl.scrollView;
2130
+ }
1630
2131
  }
1631
2132
  };
1632
2133
  }]);
@@ -1642,7 +2143,7 @@ angular.module('ionic.ui.scroll', [])
1642
2143
  * left and/or right menu, which a center content area.
1643
2144
  */
1644
2145
 
1645
- angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
2146
+ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture', 'ionic.service.view'])
1646
2147
 
1647
2148
  /**
1648
2149
  * The internal controller for the side menu controller. This
@@ -1650,24 +2151,22 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1650
2151
  * some side menu stuff on the current scope.
1651
2152
  */
1652
2153
 
1653
- .directive('sideMenus', function() {
2154
+ .run(['$ionicViewService', function($ionicViewService) {
2155
+ // set that the side-menus directive should not animate when transitioning to it
2156
+ $ionicViewService.disableRegisterByTagName('ion-side-menus');
2157
+ }])
2158
+
2159
+ .directive('ionSideMenus', function() {
1654
2160
  return {
1655
2161
  restrict: 'ECA',
1656
- controller: ['$scope', function($scope) {
2162
+ controller: ['$scope', '$attrs', function($scope, $attrs) {
1657
2163
  var _this = this;
1658
2164
 
1659
2165
  angular.extend(this, ionic.controllers.SideMenuController.prototype);
1660
2166
 
1661
2167
  ionic.controllers.SideMenuController.call(this, {
1662
- // Our quick implementation of the left side menu
1663
- left: {
1664
- width: 275,
1665
- },
1666
-
1667
- // Our quick implementation of the right side menu
1668
- right: {
1669
- width: 275,
1670
- }
2168
+ left: { width: 275 },
2169
+ right: { width: 275 }
1671
2170
  });
1672
2171
 
1673
2172
  $scope.sideMenuContentTranslateX = 0;
@@ -1676,30 +2175,31 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1676
2175
  }],
1677
2176
  replace: true,
1678
2177
  transclude: true,
1679
- template: '<div class="pane" ng-transclude></div>'
2178
+ template: '<div class="view" ng-transclude></div>'
1680
2179
  };
1681
2180
  })
1682
2181
 
1683
- .directive('sideMenuContent', ['$timeout', 'Gesture', function($timeout, Gesture) {
2182
+ .directive('ionSideMenuContent', ['$timeout', '$ionicGesture', function($timeout, $ionicGesture) {
1684
2183
  return {
1685
2184
  restrict: 'AC',
1686
- require: '^sideMenus',
2185
+ require: '^ionSideMenus',
1687
2186
  scope: true,
1688
2187
  compile: function(element, attr, transclude) {
1689
2188
  return function($scope, $element, $attr, sideMenuCtrl) {
1690
2189
 
1691
2190
  $element.addClass('menu-content');
1692
2191
 
1693
- $scope.dragContent = $scope.$eval($attr.dragContent) === false ? false : true;
2192
+ if (angular.isDefined(attr.dragContent)) {
2193
+ $scope.$watch(attr.dragContent, function(value) {
2194
+ $scope.dragContent = value;
2195
+ });
2196
+ } else {
2197
+ $scope.dragContent = true;
2198
+ }
1694
2199
 
1695
2200
  var defaultPrevented = false;
1696
2201
  var isDragging = false;
1697
2202
 
1698
- ionic.on('mousedown', function(e) {
1699
- // If the child element prevented the drag, don't drag
1700
- defaultPrevented = e.defaultPrevented;
1701
- });
1702
-
1703
2203
  // Listen for taps on the content to close the menu
1704
2204
  /*
1705
2205
  ionic.on('tap', function(e) {
@@ -1709,7 +2209,7 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1709
2209
 
1710
2210
  var dragFn = function(e) {
1711
2211
  if($scope.dragContent) {
1712
- if(defaultPrevented) {
2212
+ if(defaultPrevented || e.gesture.srcEvent.defaultPrevented) {
1713
2213
  return;
1714
2214
  }
1715
2215
  isDragging = true;
@@ -1725,10 +2225,10 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1725
2225
  };
1726
2226
 
1727
2227
  //var dragGesture = Gesture.on('drag', dragFn, $element);
1728
- var dragRightGesture = Gesture.on('dragright', dragFn, $element);
1729
- var dragLeftGesture = Gesture.on('dragleft', dragFn, $element);
1730
- var dragUpGesture = Gesture.on('dragup', dragVertFn, $element);
1731
- var dragDownGesture = Gesture.on('dragdown', dragVertFn, $element);
2228
+ var dragRightGesture = $ionicGesture.on('dragright', dragFn, $element);
2229
+ var dragLeftGesture = $ionicGesture.on('dragleft', dragFn, $element);
2230
+ var dragUpGesture = $ionicGesture.on('dragup', dragVertFn, $element);
2231
+ var dragDownGesture = $ionicGesture.on('dragdown', dragVertFn, $element);
1732
2232
 
1733
2233
  var dragReleaseFn = function(e) {
1734
2234
  isDragging = false;
@@ -1738,7 +2238,7 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1738
2238
  defaultPrevented = false;
1739
2239
  };
1740
2240
 
1741
- var releaseGesture = Gesture.on('release', dragReleaseFn, $element);
2241
+ var releaseGesture = $ionicGesture.on('release', dragReleaseFn, $element);
1742
2242
 
1743
2243
  sideMenuCtrl.setContent({
1744
2244
  onDrag: function(e) {},
@@ -1746,12 +2246,16 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1746
2246
  getTranslateX: function() {
1747
2247
  return $scope.sideMenuContentTranslateX || 0;
1748
2248
  },
1749
- setTranslateX: function(amount) {
1750
- $element[0].style.webkitTransform = 'translate3d(' + amount + 'px, 0, 0)';
2249
+ setTranslateX: ionic.animationFrameThrottle(function(amount) {
2250
+ if(amount === 0) {
2251
+ $element[0].style[ionic.CSS.TRANSFORM] = 'none';
2252
+ } else {
2253
+ $element[0].style[ionic.CSS.TRANSFORM] = 'translate3d(' + amount + 'px, 0, 0)';
2254
+ }
1751
2255
  $timeout(function() {
1752
2256
  $scope.sideMenuContentTranslateX = amount;
1753
2257
  });
1754
- },
2258
+ }),
1755
2259
  enableAnimation: function() {
1756
2260
  //this.el.classList.add(this.animateClass);
1757
2261
  $scope.animationEnabled = true;
@@ -1766,11 +2270,11 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1766
2270
 
1767
2271
  // Cleanup
1768
2272
  $scope.$on('$destroy', function() {
1769
- Gesture.off(dragLeftGesture, 'dragleft', dragFn);
1770
- Gesture.off(dragRightGesture, 'dragright', dragFn);
1771
- Gesture.off(dragUpGesture, 'dragup', dragFn);
1772
- Gesture.off(dragDownGesture, 'dragdown', dragFn);
1773
- Gesture.off(releaseGesture, 'release', dragReleaseFn);
2273
+ $ionicGesture.off(dragLeftGesture, 'dragleft', dragFn);
2274
+ $ionicGesture.off(dragRightGesture, 'dragright', dragFn);
2275
+ $ionicGesture.off(dragUpGesture, 'dragup', dragFn);
2276
+ $ionicGesture.off(dragDownGesture, 'dragdown', dragFn);
2277
+ $ionicGesture.off(releaseGesture, 'release', dragReleaseFn);
1774
2278
  });
1775
2279
  };
1776
2280
  }
@@ -1778,37 +2282,40 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1778
2282
  }])
1779
2283
 
1780
2284
 
1781
- .directive('sideMenu', function() {
2285
+ .directive('ionSideMenu', function() {
1782
2286
  return {
1783
2287
  restrict: 'E',
1784
- require: '^sideMenus',
2288
+ require: '^ionSideMenus',
1785
2289
  replace: true,
1786
2290
  transclude: true,
1787
2291
  scope: true,
1788
2292
  template: '<div class="menu menu-{{side}}"></div>',
1789
2293
  compile: function(element, attr, transclude) {
2294
+ angular.isUndefined(attr.isEnabled) && attr.$set('isEnabled', 'true');
2295
+ angular.isUndefined(attr.width) && attr.$set('width', '275');
2296
+
1790
2297
  return function($scope, $element, $attr, sideMenuCtrl) {
1791
2298
  $scope.side = $attr.side;
1792
2299
 
1793
- if($scope.side == 'left') {
1794
- sideMenuCtrl.left.isEnabled = true;
1795
- sideMenuCtrl.left.pushDown = function() {
1796
- $element[0].style.zIndex = -1;
1797
- };
1798
- sideMenuCtrl.left.bringUp = function() {
1799
- $element[0].style.zIndex = 0;
1800
- };
1801
- } else if($scope.side == 'right') {
1802
- sideMenuCtrl.right.isEnabled = true;
1803
- sideMenuCtrl.right.pushDown = function() {
1804
- $element[0].style.zIndex = -1;
1805
- };
1806
- sideMenuCtrl.right.bringUp = function() {
1807
- $element[0].style.zIndex = 0;
1808
- };
1809
- }
2300
+ var sideMenu = sideMenuCtrl[$scope.side] = new ionic.views.SideMenu({
2301
+ width: 275,
2302
+ el: $element[0],
2303
+ isEnabled: true
2304
+ });
2305
+
2306
+ $scope.$watch($attr.width, function(val) {
2307
+ var numberVal = +val;
2308
+ if (numberVal && numberVal == val) {
2309
+ sideMenu.setWidth(+val);
2310
+ }
2311
+ });
2312
+ $scope.$watch($attr.isEnabled, function(val) {
2313
+ sideMenu.setIsEnabled(!!val);
2314
+ });
1810
2315
 
1811
- $element.append(transclude($scope));
2316
+ transclude($scope, function(clone) {
2317
+ $element.append(clone);
2318
+ });
1812
2319
  };
1813
2320
  }
1814
2321
  };
@@ -1820,34 +2327,41 @@ angular.module('ionic.ui.sideMenu', ['ionic.service.gesture'])
1820
2327
 
1821
2328
  /**
1822
2329
  * @description
1823
- * The sideMenuCtrl lets you quickly have a draggable side
1824
- * left and/or right menu, which a center content area.
2330
+ * The slideBoxCtrol lets you quickly create a multi-page
2331
+ * container where each page can be swiped or dragged between
1825
2332
  */
1826
2333
 
1827
2334
  angular.module('ionic.ui.slideBox', [])
1828
2335
 
1829
2336
  /**
1830
- * The internal controller for the side menu controller. This
1831
- * extends our core Ionic side menu controller and exposes
1832
- * some side menu stuff on the current scope.
2337
+ * The internal controller for the slide box controller.
1833
2338
  */
1834
2339
 
1835
- .directive('slideBox', ['$timeout', '$compile', function($timeout, $compile) {
2340
+ .directive('ionSlideBox', ['$timeout', '$compile', '$ionicSlideBoxDelegate', function($timeout, $compile, $ionicSlideBoxDelegate) {
1836
2341
  return {
1837
2342
  restrict: 'E',
1838
2343
  replace: true,
1839
2344
  transclude: true,
1840
2345
  scope: {
1841
2346
  doesContinue: '@',
2347
+ slideInterval: '@',
1842
2348
  showPager: '@',
1843
- onSlideChanged: '&'
2349
+ disableScroll: '@',
2350
+ onSlideChanged: '&',
2351
+ activeSlide: '=?'
1844
2352
  },
1845
2353
  controller: ['$scope', '$element', function($scope, $element) {
1846
2354
  var _this = this;
1847
2355
 
2356
+ var continuous = $scope.$eval($scope.doesContinue) === true;
2357
+ var slideInterval = continuous ? $scope.$eval($scope.slideInterval) || 4000 : 0;
2358
+
1848
2359
  var slider = new ionic.views.Slider({
1849
2360
  el: $element[0],
1850
- continuous: $scope.$eval($scope.doesContinue) === true,
2361
+ auto: slideInterval,
2362
+ disableScroll: ($scope.$eval($scope.disableScroll) === true) || false,
2363
+ continuous: continuous,
2364
+ startSlide: $scope.activeSlide,
1851
2365
  slidesChanged: function() {
1852
2366
  $scope.currentSlide = slider.getPos();
1853
2367
 
@@ -1858,12 +2372,18 @@ angular.module('ionic.ui.slideBox', [])
1858
2372
  $scope.currentSlide = slideIndex;
1859
2373
  $scope.onSlideChanged({index:$scope.currentSlide});
1860
2374
  $scope.$parent.$broadcast('slideBox.slideChanged', slideIndex);
1861
-
2375
+ $scope.activeSlide = slideIndex;
1862
2376
  // Try to trigger a digest
1863
2377
  $timeout(function() {});
1864
2378
  }
1865
2379
  });
1866
2380
 
2381
+ $scope.$watch('activeSlide', function(nv) {
2382
+ if(angular.isDefined(nv)){
2383
+ slider.slide(nv);
2384
+ }
2385
+ });
2386
+
1867
2387
  $scope.$on('slideBox.nextSlide', function() {
1868
2388
  slider.next();
1869
2389
  });
@@ -1876,7 +2396,13 @@ angular.module('ionic.ui.slideBox', [])
1876
2396
  slider.slide(index);
1877
2397
  });
1878
2398
 
1879
- $scope.slideBox = slider;
2399
+ $scope.$parent.slideBox = slider;
2400
+
2401
+ $ionicSlideBoxDelegate.register($scope, $element);
2402
+
2403
+ this.getNumSlides = function() {
2404
+ return slider.getNumSlides();
2405
+ };
1880
2406
 
1881
2407
  $timeout(function() {
1882
2408
  slider.load();
@@ -1891,7 +2417,7 @@ angular.module('ionic.ui.slideBox', [])
1891
2417
  // If the pager should show, append it to the slide box
1892
2418
  if($scope.$eval($scope.showPager) !== false) {
1893
2419
  var childScope = $scope.$new();
1894
- var pager = angular.element('<pager></pager>');
2420
+ var pager = angular.element('<ion-pager></ion-pager>');
1895
2421
  $element.append(pager);
1896
2422
  $compile(pager)(childScope);
1897
2423
  }
@@ -1899,343 +2425,877 @@ angular.module('ionic.ui.slideBox', [])
1899
2425
  };
1900
2426
  }])
1901
2427
 
1902
- .directive('slide', function() {
2428
+ .directive('ionSlide', function() {
2429
+ return {
2430
+ restrict: 'E',
2431
+ require: '^ionSlideBox',
2432
+ compile: function(element, attr) {
2433
+ element.addClass('slider-slide');
2434
+ return function($scope, $element, $attr) {};
2435
+ },
2436
+ };
2437
+ })
2438
+
2439
+ .directive('ionPager', function() {
2440
+ return {
2441
+ restrict: 'E',
2442
+ replace: true,
2443
+ require: '^ionSlideBox',
2444
+ template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}"><i class="icon ion-record"></i></span></div>',
2445
+ link: function($scope, $element, $attr, slideBox) {
2446
+ var selectPage = function(index) {
2447
+ var children = $element[0].children;
2448
+ var length = children.length;
2449
+ for(var i = 0; i < length; i++) {
2450
+ if(i == index) {
2451
+ children[i].classList.add('active');
2452
+ } else {
2453
+ children[i].classList.remove('active');
2454
+ }
2455
+ }
2456
+ };
2457
+
2458
+ $scope.numSlides = function() {
2459
+ return new Array(slideBox.getNumSlides());
2460
+ };
2461
+
2462
+ $scope.$watch('currentSlide', function(v) {
2463
+ selectPage(v);
2464
+ });
2465
+ }
2466
+ };
2467
+
2468
+ });
2469
+
2470
+ })();
2471
+ ;
2472
+ angular.module('ionic.ui.tabs', ['ionic.service.view'])
2473
+
2474
+ /**
2475
+ * @description
2476
+ *
2477
+ * The Tab Controller renders a set of pages that switch based on taps
2478
+ * on a tab bar. Modelled off of UITabBarController.
2479
+ */
2480
+
2481
+ .run(['$ionicViewService', function($ionicViewService) {
2482
+ // set that the tabs directive should not animate when transitioning
2483
+ // to it. Instead, the children <tab> directives would animate
2484
+ $ionicViewService.disableRegisterByTagName('ion-tabs');
2485
+ }])
2486
+
2487
+ .controller('$ionicTabs', ['$scope', '$ionicViewService', '$element', function($scope, $ionicViewService, $element) {
2488
+ var self = $scope.tabsController = this;
2489
+ self.tabs = [];
2490
+
2491
+ self.selectedTab = null;
2492
+
2493
+ self.add = function(tab) {
2494
+ $ionicViewService.registerHistory(tab);
2495
+ self.tabs.push(tab);
2496
+ if(self.tabs.length === 1) {
2497
+ self.select(tab);
2498
+ }
2499
+ };
2500
+
2501
+ self.remove = function(tab) {
2502
+ var tabIndex = self.tabs.indexOf(tab);
2503
+ if (tabIndex === -1) {
2504
+ return;
2505
+ }
2506
+ //Use a field like '$tabSelected' so developers won't accidentally set it in controllers etc
2507
+ if (tab.$tabSelected) {
2508
+ self.deselect(tab);
2509
+ //Try to select a new tab if we're removing a tab
2510
+ if (self.tabs.length === 1) {
2511
+ //do nothing if there are no other tabs to select
2512
+ } else {
2513
+ //Select previous tab if it's the last tab, else select next tab
2514
+ var newTabIndex = tabIndex === self.tabs.length - 1 ? tabIndex - 1 : tabIndex + 1;
2515
+ self.select(self.tabs[newTabIndex]);
2516
+ }
2517
+ }
2518
+ self.tabs.splice(tabIndex, 1);
2519
+ };
2520
+
2521
+ self.getTabIndex = function(tab) {
2522
+ return self.tabs.indexOf(tab);
2523
+ };
2524
+
2525
+ self.deselect = function(tab) {
2526
+ if (tab.$tabSelected) {
2527
+ self.selectedTab = null;
2528
+ tab.$tabSelected = false;
2529
+ (tab.onDeselect || angular.noop)();
2530
+ }
2531
+ };
2532
+
2533
+ self.select = function(tab, shouldEmitEvent) {
2534
+ var tabIndex;
2535
+ if (angular.isNumber(tab)) {
2536
+ tabIndex = tab;
2537
+ tab = self.tabs[tabIndex];
2538
+ } else {
2539
+ tabIndex = self.tabs.indexOf(tab);
2540
+ }
2541
+ if (!tab || tabIndex == -1) {
2542
+ throw new Error('Cannot select tab "' + tabIndex + '"!');
2543
+ }
2544
+
2545
+ if (self.selectedTab && self.selectedTab.$historyId == tab.$historyId) {
2546
+ if (shouldEmitEvent) {
2547
+ $ionicViewService.goToHistoryRoot(tab.$historyId);
2548
+ }
2549
+ } else {
2550
+ angular.forEach(self.tabs, function(tab) {
2551
+ self.deselect(tab);
2552
+ });
2553
+
2554
+ self.selectedTab = tab;
2555
+ //Use a funny name like $tabSelected so the developer doesn't overwrite the var in a child scope
2556
+ tab.$tabSelected = true;
2557
+ (tab.onSelect || angular.noop)();
2558
+
2559
+ if (shouldEmitEvent) {
2560
+ var viewData = {
2561
+ type: 'tab',
2562
+ tabIndex: tabIndex,
2563
+ historyId: tab.$historyId,
2564
+ navViewName: tab.navViewName,
2565
+ hasNavView: !!tab.navViewName,
2566
+ title: tab.title,
2567
+ //Skip the first character of href if it's #
2568
+ url: tab.href,
2569
+ uiSref: tab.uiSref
2570
+ };
2571
+ $scope.$emit('viewState.changeHistory', viewData);
2572
+ }
2573
+ }
2574
+ };
2575
+ }])
2576
+
2577
+ .directive('ionTabs', ['$ionicViewService', '$ionicBind', function($ionicViewService, $ionicBind) {
2578
+ return {
2579
+ restrict: 'E',
2580
+ replace: true,
2581
+ scope: true,
2582
+ transclude: true,
2583
+ controller: '$ionicTabs',
2584
+ template:
2585
+ '<div class="view {{$animation}}">' +
2586
+ '<div class="tabs {{$tabsStyle}} {{$tabsType}}">' +
2587
+ '</div>' +
2588
+ '</div>',
2589
+ compile: function(element, attr, transclude) {
2590
+ if(angular.isUndefined(attr.tabsType)) attr.$set('tabsType', 'tabs-positive');
2591
+
2592
+ return function link($scope, $element, $attr, tabsCtrl) {
2593
+
2594
+ $ionicBind($scope, $attr, {
2595
+ $animation: '@animation',
2596
+ $tabsStyle: '@tabsStyle',
2597
+ $tabsType: '@tabsType'
2598
+ });
2599
+
2600
+ tabsCtrl.$scope = $scope;
2601
+ tabsCtrl.$element = $element;
2602
+ tabsCtrl.$tabsElement = angular.element($element[0].querySelector('.tabs'));
2603
+
2604
+ transclude($scope, function(clone) {
2605
+ $element.append(clone);
2606
+ });
2607
+ };
2608
+ }
2609
+ };
2610
+ }])
2611
+
2612
+ .controller('$ionicTab', ['$scope', '$ionicViewService', '$rootScope', '$element',
2613
+ function($scope, $ionicViewService, $rootScope, $element) {
2614
+ this.$scope = $scope;
2615
+ }])
2616
+
2617
+ // Generic controller directive
2618
+ .directive('ionTab', ['$rootScope', '$animate', '$ionicBind', '$compile', '$ionicViewService', function($rootScope, $animate, $ionicBind, $compile, $ionicViewService) {
2619
+
2620
+ //Returns ' key="value"' if value exists
2621
+ function attrStr(k,v) {
2622
+ return angular.isDefined(v) ? ' ' + k + '="' + v + '"' : '';
2623
+ }
2624
+ return {
2625
+ restrict: 'E',
2626
+ require: ['^ionTabs', 'ionTab'],
2627
+ replace: true,
2628
+ transclude: 'element',
2629
+ controller: '$ionicTab',
2630
+ scope: true,
2631
+ compile: function(element, attr, transclude) {
2632
+ return function link($scope, $element, $attr, ctrls) {
2633
+ var childScope, childElement, tabNavElement;
2634
+ tabsCtrl = ctrls[0],
2635
+ tabCtrl = ctrls[1];
2636
+
2637
+ $ionicBind($scope, $attr, {
2638
+ animate: '=',
2639
+ leftButtons: '=',
2640
+ rightButtons: '=',
2641
+ onSelect: '&',
2642
+ onDeselect: '&',
2643
+ title: '@',
2644
+ uiSref: '@',
2645
+ href: '@',
2646
+ });
2647
+
2648
+ tabNavElement = angular.element(
2649
+ '<ion-tab-nav' +
2650
+ attrStr('title', attr.title) +
2651
+ attrStr('icon', attr.icon) +
2652
+ attrStr('icon-on', attr.iconOn) +
2653
+ attrStr('icon-off', attr.iconOff) +
2654
+ attrStr('badge', attr.badge) +
2655
+ attrStr('badge-style', attr.badgeStyle) +
2656
+ '></ion-tab-nav>'
2657
+ );
2658
+ tabNavElement.data('$ionTabsController', tabsCtrl);
2659
+ tabNavElement.data('$ionTabController', tabCtrl);
2660
+ tabsCtrl.$tabsElement.append($compile(tabNavElement)($scope));
2661
+
2662
+ tabsCtrl.add($scope);
2663
+ $scope.$on('$destroy', function() {
2664
+ tabsCtrl.remove($scope);
2665
+ tabNavElement.isolateScope().$destroy();
2666
+ tabNavElement.remove();
2667
+ });
2668
+
2669
+ $scope.$watch('$tabSelected', function(value) {
2670
+ if (!value) {
2671
+ $scope.$broadcast('tab.hidden', $scope);
2672
+ }
2673
+ childScope && childScope.$destroy();
2674
+ childScope = null;
2675
+ childElement && $animate.leave(childElement);
2676
+ childElement = null;
2677
+ if (value) {
2678
+ childScope = $scope.$new();
2679
+ transclude(childScope, function(clone) {
2680
+ //remove title attr to stop hover annoyance!
2681
+ clone[0].removeAttribute('title');
2682
+ $animate.enter(clone, tabsCtrl.$element);
2683
+ clone.addClass('pane');
2684
+ childElement = clone;
2685
+ });
2686
+ $scope.$broadcast('tab.shown', $scope);
2687
+ }
2688
+ });
2689
+
2690
+ transclude($scope, function(clone) {
2691
+ var navView = clone[0].querySelector('ion-nav-view');
2692
+ if (navView) {
2693
+ $scope.navViewName = navView.getAttribute('name');
2694
+ selectTabIfMatchesState();
2695
+ $scope.$on('$stateChangeSuccess', selectTabIfMatchesState);
2696
+ }
2697
+ });
2698
+
2699
+ function selectTabIfMatchesState() {
2700
+ // this tab's ui-view is the current one, go to it!
2701
+ if ($ionicViewService.isCurrentStateNavView($scope.navViewName)) {
2702
+ tabsCtrl.select($scope);
2703
+ }
2704
+ }
2705
+ };
2706
+ }
2707
+ };
2708
+ }])
2709
+
2710
+ .directive('ionTabNav', function() {
2711
+ return {
2712
+ restrict: 'E',
2713
+ replace: true,
2714
+ require: ['^ionTabs', '^ionTab'],
2715
+ template:
2716
+ '<a ng-class="{active: isTabActive(), \'has-badge\':badge}" ' +
2717
+ 'ng-click="selectTab($event)" class="tab-item">' +
2718
+ '<span class="badge {{badgeStyle}}" ng-if="badge">{{badge}}</span>' +
2719
+ '<i class="icon {{getIconOn()}}" ng-if="getIconOn() && isTabActive()"></i>' +
2720
+ '<i class="icon {{getIconOff()}}" ng-if="getIconOff() && !isTabActive()"></i>' +
2721
+ '<span class="tab-title" ng-bind-html="title"></span>' +
2722
+ '</a>',
2723
+ scope: {
2724
+ title: '@',
2725
+ icon: '@',
2726
+ iconOn: '@',
2727
+ iconOff: '@',
2728
+ badge: '=',
2729
+ badgeStyle: '@'
2730
+ },
2731
+ compile: function(element, attr, transclude) {
2732
+ return function link($scope, $element, $attrs, ctrls) {
2733
+ var tabsCtrl = ctrls[0],
2734
+ tabCtrl = ctrls[1];
2735
+
2736
+ $scope.getIconOn = function() {
2737
+ return $scope.iconOn || $scope.icon;
2738
+ };
2739
+ $scope.getIconOff = function() {
2740
+ return $scope.iconOff || $scope.icon;
2741
+ };
2742
+
2743
+ $scope.isTabActive = function() {
2744
+ return tabsCtrl.selectedTab === tabCtrl.$scope;
2745
+ };
2746
+ $scope.selectTab = function(e) {
2747
+ e.preventDefault();
2748
+ tabsCtrl.select(tabCtrl.$scope, true);
2749
+ };
2750
+ };
2751
+ }
2752
+ };
2753
+ });
2754
+ ;
2755
+ (function(ionic) {
2756
+ 'use strict';
2757
+
2758
+ angular.module('ionic.ui.toggle', [])
2759
+
2760
+ // The Toggle directive is a toggle switch that can be tapped to change
2761
+ // its value
2762
+ .directive('ionToggle', function() {
2763
+
1903
2764
  return {
1904
2765
  restrict: 'E',
1905
2766
  replace: true,
1906
- require: '^slideBox',
2767
+ require: '?ngModel',
2768
+ scope: {
2769
+ ngModel: '=?',
2770
+ ngValue: '=?',
2771
+ ngChecked: '=?',
2772
+ ngChange: '&',
2773
+ ngDisabled: '=?'
2774
+ },
1907
2775
  transclude: true,
1908
- template: '<div class="slider-slide" ng-transclude></div>',
1909
- compile: function(element, attr, transclude) {
1910
- return function($scope, $element, $attr, slideBoxCtrl) {
1911
- };
2776
+ template: '<div class="item item-toggle disable-pointer-events">' +
2777
+ '<div ng-transclude></div>' +
2778
+ '<label class="toggle enable-pointer-events">' +
2779
+ '<input type="checkbox" ng-model="ngModel" ng-value="ngValue" ng-change="ngChange()" ng-disabled="ngDisabled">' +
2780
+ '<div class="track disable-pointer-events">' +
2781
+ '<div class="handle"></div>' +
2782
+ '</div>' +
2783
+ '</label>' +
2784
+ '</div>',
2785
+
2786
+ compile: function(element, attr) {
2787
+ var input = element.find('input');
2788
+ if(attr.name) input.attr('name', attr.name);
2789
+ if(attr.ngChecked) input.attr('ng-checked', 'ngChecked');
2790
+ if(attr.ngTrueValue) input.attr('ng-true-value', attr.ngTrueValue);
2791
+ if(attr.ngFalseValue) input.attr('ng-false-value', attr.ngFalseValue);
2792
+
2793
+ // return function link($scope, $element, $attr, ngModel) {
2794
+ // var el, checkbox, track, handle;
2795
+
2796
+ // el = $element[0].getElementsByTagName('label')[0];
2797
+ // checkbox = el.children[0];
2798
+ // track = el.children[1];
2799
+ // handle = track.children[0];
2800
+
2801
+ // $scope.toggle = new ionic.views.Toggle({
2802
+ // el: el,
2803
+ // track: track,
2804
+ // checkbox: checkbox,
2805
+ // handle: handle
2806
+ // });
2807
+
2808
+ // ionic.on('drag', function(e) {
2809
+ //
2810
+ // $scope.toggle.drag(e);
2811
+ // }, handle);
2812
+
2813
+ // }
1912
2814
  }
2815
+
1913
2816
  };
1914
- })
2817
+ });
1915
2818
 
1916
- .directive('pager', function() {
1917
- return {
1918
- restrict: 'E',
1919
- replace: true,
1920
- require: '^slideBox',
1921
- template: '<div class="slider-pager"><span class="slider-pager-page" ng-repeat="slide in numSlides() track by $index" ng-class="{active: $index == currentSlide}"><i class="icon ion-record"></i></span></div>',
1922
- link: function($scope, $element, $attr, slideBox) {
1923
- var selectPage = function(index) {
1924
- var children = $element[0].children;
1925
- var length = children.length;
1926
- for(var i = 0; i < length; i++) {
1927
- if(i == index) {
1928
- children[i].classList.add('active');
1929
- } else {
1930
- children[i].classList.remove('active');
1931
- }
1932
- }
1933
- };
2819
+ })(window.ionic);
2820
+ ;
1934
2821
 
1935
- $scope.numSlides = function() {
1936
- return new Array($scope.slideBox.getNumSlides());
1937
- };
2822
+ // Similar to Angular's ngTouch, however it uses Ionic's tap detection
2823
+ // and click simulation. ngClick
1938
2824
 
1939
- $scope.$watch('currentSlide', function(v) {
1940
- console.log('Current slide', v);
1941
- selectPage(v);
2825
+ (function(angular, ionic) {'use strict';
2826
+
2827
+
2828
+ angular.module('ionic.ui.touch', [])
2829
+
2830
+ .config(['$provide', function($provide) {
2831
+ $provide.decorator('ngClickDirective', ['$delegate', function($delegate) {
2832
+ // drop the default ngClick directive
2833
+ $delegate.shift();
2834
+ return $delegate;
2835
+ }]);
2836
+ }])
2837
+
2838
+ .directive('ngClick', ['$parse', function($parse) {
2839
+
2840
+ function onTap(e) {
2841
+ // wire this up to Ionic's tap/click simulation
2842
+ ionic.tapElement(e.target, e);
2843
+ }
2844
+
2845
+ // Actual linking function.
2846
+ return function(scope, element, attr) {
2847
+
2848
+ var clickHandler = $parse(attr.ngClick);
2849
+
2850
+ element.on('click', function(event) {
2851
+ scope.$apply(function() {
2852
+ clickHandler(scope, {$event: (event)});
2853
+ });
2854
+ });
2855
+
2856
+ ionic.on('tap', onTap, element[0]);
2857
+
2858
+ // Hack for iOS Safari's benefit. It goes searching for onclick handlers and is liable to click
2859
+ // something else nearby.
2860
+ element.onclick = function(event) { };
2861
+
2862
+ scope.$on('$destroy', function () {
2863
+ ionic.off('tap', onTap, element[0]);
1942
2864
  });
2865
+
2866
+ };
2867
+
2868
+ }])
2869
+
2870
+ .directive('ionStopEvent', function () {
2871
+ function stopEvent(e) {
2872
+ e.stopPropagation();
1943
2873
  }
1944
- };
2874
+ return {
2875
+ restrict: 'A',
2876
+ link: function (scope, element, attr) {
2877
+ element.bind(attr.ionStopEvent, stopEvent);
2878
+ }
2879
+ };
2880
+ });
1945
2881
 
1946
- });
1947
2882
 
1948
- })();
2883
+ })(window.angular, window.ionic);
1949
2884
  ;
1950
- angular.module('ionic.ui.tabs', ['ngAnimate'])
2885
+ (function() {
2886
+ 'use strict';
1951
2887
 
1952
2888
  /**
1953
2889
  * @description
2890
+ * The NavController is a navigation stack View Controller modelled off of
2891
+ * UINavigationController from Cocoa Touch. With the Nav Controller, you can
2892
+ * "push" new "pages" on to the navigation stack, and then pop them off to go
2893
+ * back. The NavController controls a navigation bar with a back button and title
2894
+ * which updates as the pages switch.
1954
2895
  *
1955
- * The Tab Controller renders a set of pages that switch based on taps
1956
- * on a tab bar. Modelled off of UITabBarController.
2896
+ * The NavController makes sure to not recycle scopes of old pages
2897
+ * so that a pop will still show the same state that the user left.
2898
+ *
2899
+ * However, once a page is popped, its scope is destroyed and will have to be
2900
+ * recreated then next time it is pushed.
2901
+ *
2902
+ */
2903
+
2904
+ angular.module('ionic.ui.viewState', ['ionic.service.view', 'ionic.service.gesture', 'ngSanitize'])
2905
+
2906
+ /**
2907
+ * Our Nav Bar directive which updates as the controller state changes.
1957
2908
  */
2909
+ .directive('ionNavBar', ['$ionicViewService', '$rootScope', '$animate', '$compile',
2910
+ function( $ionicViewService, $rootScope, $animate, $compile) {
1958
2911
 
1959
- .directive('tabs', function() {
1960
2912
  return {
1961
2913
  restrict: 'E',
1962
2914
  replace: true,
1963
- scope: true,
1964
- transclude: true,
1965
- controller: ['$scope', '$element', '$animate', function($scope, $element, $animate) {
1966
- var _this = this;
1967
-
1968
- angular.extend(this, ionic.controllers.TabBarController.prototype);
1969
-
1970
- ionic.controllers.TabBarController.call(this, {
1971
- controllerChanged: function(oldC, oldI, newC, newI) {
1972
- $scope.controllerChanged && $scope.controllerChanged({
1973
- oldController: oldC,
1974
- oldIndex: oldI,
1975
- newController: newC,
1976
- newIndex: newI
1977
- });
1978
- },
1979
- tabBar: {
1980
- tryTabSelect: function() {},
1981
- setSelectedItem: function(index) {},
1982
- addItem: function(item) {}
1983
- }
1984
- });
1985
-
1986
- this.add = function(controller) {
1987
- this.addController(controller);
1988
- this.select(0);
1989
- };
1990
-
1991
- this.select = function(controllerIndex) {
1992
- $scope.activeAnimation = $scope.animation;
1993
- _this.selectController(controllerIndex);
1994
- };
1995
-
1996
- $scope.controllers = this.controllers;
1997
-
1998
- $scope.tabsController = this;
1999
- }],
2000
- //templateUrl: 'ext/angular/tmpl/ionicTabBar.tmpl.html',
2001
- template: '<div class="view"><tab-controller-bar></tab-controller-bar></div>',
2002
- compile: function(element, attr, transclude, tabsCtrl) {
2003
- return function($scope, $element, $attr) {
2004
- var tabs = $element[0].querySelector('.tabs');
2005
-
2006
- $scope.tabsType = $attr.tabsType || 'tabs-positive';
2007
- $scope.tabsStyle = $attr.tabsStyle;
2008
- $scope.animation = $attr.animation;
2009
-
2010
- $scope.animateNav = $scope.$eval($attr.animateNav);
2011
- if($scope.animateNav !== false) {
2012
- $scope.animateNav = true;
2013
- }
2915
+ scope: {
2916
+ animation: '@',
2917
+ type: '@',
2918
+ backType: '@backButtonType',
2919
+ backLabel: '@backButtonLabel',
2920
+ backIcon: '@backButtonIcon',
2921
+ alignTitle: '@'
2922
+ },
2923
+ controller: function() {},
2924
+ template:
2925
+ '<header class="bar bar-header nav-bar{{navBarClass()}}">' +
2926
+ '<ion-nav-back-button ng-if="(backType || backLabel || backIcon)" ' +
2927
+ 'type="backType" label="backLabel" icon="backIcon" class="hide" ' +
2928
+ 'ng-class="{\'hide\': !backButtonEnabled}">' +
2929
+ '</ion-nav-back-button>' +
2930
+ '<div class="buttons left-buttons"> ' +
2931
+ '<button ng-click="button.tap($event)" ng-repeat="button in leftButtons" ' +
2932
+ 'class="button no-animation {{button.type}}" ng-bind-html="button.content">' +
2933
+ '</button>' +
2934
+ '</div>' +
2935
+
2936
+ '<h1 ng-bind-html="title" class="title"></h1>' +
2937
+
2938
+ '<div class="buttons right-buttons"> ' +
2939
+ '<button ng-click="button.tap($event)" ng-repeat="button in rightButtons" '+
2940
+ 'class="button no-animation {{button.type}}" ng-bind-html="button.content">' +
2941
+ '</button>' +
2942
+ '</div>' +
2943
+ '</header>',
2944
+ compile: function(tElement, tAttrs) {
2945
+
2946
+ return function link($scope, $element, $attr) {
2947
+ //defaults
2948
+ $scope.backButtonEnabled = false;
2949
+ $scope.animateEnabled = true;
2950
+ $scope.isReverse = false;
2951
+ $scope.isInvisible = true;
2952
+
2953
+ $scope.navBarClass = function() {
2954
+ return ($scope.type ? ' ' + $scope.type : '') +
2955
+ ($scope.isReverse ? ' reverse' : '') +
2956
+ ($scope.isInvisible ? ' invisible' : '') +
2957
+ (!$scope.animationDisabled && $scope.animation ? ' ' + $scope.animation : '');
2958
+ };
2014
2959
 
2015
- $attr.$observe('tabsStyle', function(val) {
2016
- if(tabs) {
2017
- angular.element(tabs).addClass($attr.tabsStyle);
2018
- }
2960
+ // Initialize our header bar view which will handle
2961
+ // resizing and aligning our title labels
2962
+ var hb = new ionic.views.HeaderBar({
2963
+ el: $element[0],
2964
+ alignTitle: $scope.alignTitle || 'center'
2019
2965
  });
2966
+ $scope.headerBarView = hb;
2020
2967
 
2021
- $attr.$observe('tabsType', function(val) {
2022
- if(tabs) {
2023
- angular.element(tabs).addClass($attr.tabsType);
2024
- }
2968
+ //Navbar events
2969
+ $scope.$on('viewState.viewEnter', function(e, data) {
2970
+ updateHeaderData(data);
2025
2971
  });
2026
-
2027
- $scope.$watch('activeAnimation', function(value) {
2028
- //$element.removeClass($scope.animation + ' ' + $scope.animation + '-reverse');
2029
- $element.addClass($scope.activeAnimation);
2972
+ $scope.$on('viewState.showNavBar', function(e, showNavBar) {
2973
+ $scope.isInvisible = !showNavBar;
2030
2974
  });
2031
- transclude($scope, function(cloned) {
2032
- $element.prepend(cloned);
2975
+
2976
+ // All of these these are emitted from children of a sibling scope,
2977
+ // so we listen on parent so we can catch them as they bubble up
2978
+ var unregisterEventListeners = [
2979
+ $scope.$parent.$on('$viewHistory.historyChange', function(e, data) {
2980
+ $scope.backButtonEnabled = !!data.showBack;
2981
+ }),
2982
+ $scope.$parent.$on('viewState.leftButtonsChanged', function(e, data) {
2983
+ $scope.leftButtons = data;
2984
+ }),
2985
+ $scope.$parent.$on('viewState.rightButtonsChanged', function(e, data) {
2986
+ $scope.rightButtons = data;
2987
+ }),
2988
+ $scope.$parent.$on('viewState.showBackButton', function(e, data) {
2989
+ $scope.backButtonEnabled = !!data;
2990
+ }),
2991
+ $scope.$parent.$on('viewState.titleUpdated', function(e, data) {
2992
+ $scope.title = data && data.title || '';
2993
+ })
2994
+ ];
2995
+ $scope.$on('$destroy', function() {
2996
+ for (var i=0; i<unregisterEventListeners.length; i++)
2997
+ unregisterEventListeners[i]();
2033
2998
  });
2034
- };
2035
- }
2036
- };
2037
- })
2038
2999
 
2039
- // Generic controller directive
2040
- .directive('tab', ['$animate', '$parse', function($animate, $parse) {
2041
- return {
2042
- restrict: 'E',
2043
- require: '^tabs',
2044
- scope: true,
2045
- transclude: 'element',
2046
- compile: function(element, attr, transclude) {
2047
- return function($scope, $element, $attr, tabsCtrl) {
2048
- var childScope, childElement;
3000
+ function updateHeaderData(data) {
2049
3001
 
2050
- $scope.title = $attr.title;
2051
- $scope.icon = $attr.icon;
2052
- $scope.iconOn = $attr.iconOn;
2053
- $scope.iconOff = $attr.iconOff;
3002
+ if (angular.isDefined(data.hideBackButton)) {
3003
+ $scope.backButtonEnabled = !!data.hideBackButton;
3004
+ }
3005
+ $scope.isReverse = data.navDirection == 'back';
3006
+ $scope.animateEnabled = !!(data.navDirection && data.animate !== false);
2054
3007
 
2055
- // Should we hide a back button when this tab is shown
2056
- $scope.hideBackButton = $scope.$eval($attr.hideBackButton);
3008
+ $scope.leftButtons = data.leftButtons;
3009
+ $scope.rightButtons = data.rightButtons;
3010
+ $scope.oldTitle = $scope.title;
3011
+ $scope.title = data && data.title || '';
2057
3012
 
2058
- if($scope.hideBackButton !== true) {
2059
- $scope.hideBackButton = false;
3013
+ //If no animation, we're done!
3014
+ if (!$scope.animateEnabled) {
3015
+ hb.align();
3016
+ return;
3017
+ } else {
3018
+ animateTitles();
3019
+ }
2060
3020
  }
2061
3021
 
2062
- // Whether we should animate on tab change, also impacts whether we
2063
- // tell any parent nav controller to animate
2064
- $scope.animate = $scope.$eval($attr.animate);
3022
+ function animateTitles() {
3023
+ var oldTitleEl, newTitleEl, currentTitles;
2065
3024
 
2066
- // Grab whether we should update any parent nav router on tab changes
2067
- $scope.doesUpdateNavRouter = $scope.$eval($attr.doesUpdateNavRouter);
2068
- if($scope.doesUpdateNavRouter !== false) {
2069
- $scope.doesUpdateNavRouter = true;
2070
- }
2071
-
2072
- var leftButtonsGet = $parse($attr.leftButtons);
2073
- $scope.$watch(leftButtonsGet, function(value) {
2074
- $scope.leftButtons = value;
2075
- if($scope.doesUpdateNavRouter) {
2076
- $scope.$emit('navRouter.leftButtonsChanged', $scope.rightButtons);
3025
+ //If we have any title right now (or more than one, they could be transitioning on switch),
3026
+ //replace the first one with an oldTitle element
3027
+ currentTitles = $element[0].querySelectorAll('.title');
3028
+ if (currentTitles.length) {
3029
+ oldTitleEl = $compile('<h1 ng-bind-html="oldTitle" class="title"></h1>')($scope);
3030
+ angular.element(currentTitles[0]).replaceWith(oldTitleEl);
2077
3031
  }
2078
- });
3032
+ //Compile new title
3033
+ newTitleEl = $compile('<h1 class="title invisible" ng-bind-html="title"></h1>')($scope);
2079
3034
 
2080
- var rightButtonsGet = $parse($attr.rightButtons);
2081
- $scope.$watch(rightButtonsGet, function(value) {
2082
- $scope.rightButtons = value;
2083
- });
3035
+ //Animate in one frame
3036
+ ionic.requestAnimationFrame(function() {
2084
3037
 
2085
- tabsCtrl.add($scope);
2086
-
2087
- $scope.$watch('isVisible', function(value) {
2088
- if(childElement) {
2089
- $animate.leave(childElement);
2090
- $scope.$broadcast('tab.hidden');
2091
- childElement = undefined;
2092
- }
2093
- if(childScope) {
2094
- childScope.$destroy();
2095
- childScope = undefined;
2096
- }
2097
- if(value) {
2098
- childScope = $scope.$new();
2099
- transclude(childScope, function(clone) {
2100
- childElement = clone;
3038
+ oldTitleEl && $animate.leave(angular.element(oldTitleEl));
2101
3039
 
2102
- clone.addClass('pane');
3040
+ var insert = oldTitleEl && angular.element(oldTitleEl) || null;
3041
+ $animate.enter(newTitleEl, $element, insert, function() {
3042
+ hb.align();
3043
+ });
2103
3044
 
2104
- $animate.enter(clone, $element.parent(), $element);
2105
-
2106
- if($scope.title) {
2107
- // Send the title up in case we are inside of a nav controller
2108
- if($scope.doesUpdateNavRouter) {
2109
- $scope.$emit('navRouter.pageShown', {
2110
- title: $scope.title,
2111
- rightButtons: $scope.rightButtons,
2112
- leftButtons: $scope.leftButtons,
2113
- hideBackButton: $scope.hideBackButton,
2114
- animate: $scope.animateNav
2115
- });
2116
- }
2117
- //$scope.$emit('navRouter.titleChanged', $scope.title);
3045
+ //Cleanup any old titles leftover (besides the one we already did replaceWith on)
3046
+ angular.forEach(currentTitles, function(el) {
3047
+ if (el && el.parentNode) {
3048
+ //Use .remove() to cleanup things like .data()
3049
+ angular.element(el).remove();
2118
3050
  }
2119
- $scope.$broadcast('tab.shown');
2120
3051
  });
2121
- }
2122
- });
3052
+
3053
+ //$apply so bindings fire
3054
+ $scope.$digest();
3055
+
3056
+ //Stop flicker of new title on ios7
3057
+ ionic.requestAnimationFrame(function() {
3058
+ newTitleEl[0].classList.remove('invisible');
3059
+ });
3060
+ });
3061
+ }
2123
3062
  };
2124
3063
  }
2125
3064
  };
2126
3065
  }])
2127
3066
 
2128
-
2129
- .directive('tabControllerBar', function() {
3067
+ .directive('ionNavBarTitle', function() {
2130
3068
  return {
2131
- restrict: 'E',
2132
- require: '^tabs',
2133
- transclude: true,
2134
- replace: true,
2135
- scope: true,
2136
- template: '<div class="tabs">' +
2137
- '<tab-controller-item title="{{controller.title}}" icon="{{controller.icon}}" icon-on="{{controller.iconOn}}" icon-off="{{controller.iconOff}}" active="controller.isVisible" index="$index" ng-repeat="controller in controllers"></tab-controller-item>' +
2138
- '</div>',
2139
- link: function($scope, $element, $attr, tabsCtrl) {
2140
- $element.addClass($scope.tabsType);
2141
- $element.addClass($scope.tabsStyle);
3069
+ restrict: 'A',
3070
+ require: '^ionNavBar',
3071
+ link: function($scope, $element, $attr, navBarCtrl) {
3072
+ $scope.headerBarView && $scope.headerBarView.align();
3073
+ $element.on('$animate:close', function() {
3074
+ $scope.headerBarView && $scope.headerBarView.align();
3075
+ });
2142
3076
  }
2143
3077
  };
2144
3078
  })
2145
3079
 
2146
- .directive('tabControllerItem', function() {
3080
+ /*
3081
+ * Directive to put on an element that has 'invisible' class when rendered.
3082
+ * This removes the visible class one frame later.
3083
+ * Fixes flickering in iOS7 and old android.
3084
+ * Used in title and back button
3085
+ */
3086
+ .directive('ionAsyncVisible', function() {
3087
+ return function($scope, $element) {
3088
+ ionic.requestAnimationFrame(function() {
3089
+ $element[0].classList.remove('invisible');
3090
+ });
3091
+ };
3092
+ })
3093
+
3094
+ .directive('ionView', ['$ionicViewService', '$rootScope', '$animate',
3095
+ function( $ionicViewService, $rootScope, $animate) {
2147
3096
  return {
2148
- restrict: 'E',
2149
- replace: true,
2150
- require: '^tabs',
3097
+ restrict: 'EA',
3098
+ priority: 1000,
2151
3099
  scope: {
2152
- title: '@',
3100
+ leftButtons: '=',
3101
+ rightButtons: '=',
3102
+ title: '=',
2153
3103
  icon: '@',
2154
3104
  iconOn: '@',
2155
3105
  iconOff: '@',
2156
- active: '=',
2157
- tabSelected: '@',
2158
- index: '='
3106
+ type: '@',
3107
+ alignTitle: '@',
3108
+ hideBackButton: '@',
3109
+ hideNavBar: '@',
3110
+ animation: '@'
2159
3111
  },
2160
- link: function(scope, element, attrs, tabsCtrl) {
2161
- if(attrs.icon) {
2162
- scope.iconOn = scope.iconOff = attrs.icon;
2163
- }
2164
- scope.selectTab = function(index) {
2165
- tabsCtrl.select(scope.index);
3112
+
3113
+ compile: function(tElement, tAttrs, transclude) {
3114
+ tElement.addClass('pane');
3115
+ tElement[0].removeAttribute('title');
3116
+
3117
+ return function link($scope, $element, $attr) {
3118
+
3119
+ $rootScope.$broadcast('viewState.viewEnter', {
3120
+ title: $scope.title,
3121
+ navDirection: $scope.$navDirection || $scope.$parent.$navDirection
3122
+ });
3123
+
3124
+ // Should we hide a back button when this tab is shown
3125
+ $scope.hideBackButton = $scope.$eval($scope.hideBackButton);
3126
+ if($scope.hideBackButton) {
3127
+ $rootScope.$broadcast('viewState.showBackButton', false);
3128
+ }
3129
+
3130
+ // Should the nav bar be hidden for this view or not?
3131
+ $rootScope.$broadcast('viewState.showNavBar', ($scope.hideNavBar !== 'true') );
3132
+
3133
+ // watch for changes in the left buttons
3134
+ $scope.$watch('leftButtons', function(value) {
3135
+ $scope.$emit('viewState.leftButtonsChanged', $scope.leftButtons);
3136
+ });
3137
+
3138
+ $scope.$watch('rightButtons', function(val) {
3139
+ $scope.$emit('viewState.rightButtonsChanged', $scope.rightButtons);
3140
+ });
3141
+
3142
+ // watch for changes in the title
3143
+ $scope.$watch('title', function(val) {
3144
+ $scope.$emit('viewState.titleUpdated', $scope);
3145
+ });
2166
3146
  };
2167
- },
2168
- template:
2169
- '<a ng-class="{active:active}" ng-click="selectTab()" class="tab-item">' +
2170
- '<i class="{{icon}}" ng-if="icon"></i>' +
2171
- '<i class="{{iconOn}}" ng-if="active"></i>' +
2172
- '<i class="{{iconOff}}" ng-if="!active"></i> {{title}}' +
2173
- '</a>'
3147
+ }
2174
3148
  };
2175
- })
3149
+ }])
3150
+
3151
+
3152
+ .directive('ionNavBackButton', ['$ionicViewService', '$rootScope',
3153
+ function($ionicViewService, $rootScope) {
3154
+
3155
+ function goBack(e) {
3156
+ var backView = $ionicViewService.getBackView();
3157
+ backView && backView.go();
3158
+ e.alreadyHandled = true;
3159
+ return false;
3160
+ }
2176
3161
 
2177
- .directive('tabBar', function() {
2178
3162
  return {
2179
3163
  restrict: 'E',
3164
+ scope: {
3165
+ type: '=',
3166
+ label: '=',
3167
+ icon: '='
3168
+ },
2180
3169
  replace: true,
2181
- transclude: true,
2182
- template: '<div class="tabs tabs-primary" ng-transclude>' +
2183
- '</div>'
3170
+ template:
3171
+ '<button ng-click="goBack($event)" class="button back-button {{type}} ' +
3172
+ '{{(icon && !label) ? \'icon \' + icon : \'\'}}">' +
3173
+ '<i ng-if="icon && label" class="icon {{icon}}"></i> ' +
3174
+ '{{label}}' +
3175
+ '</button>',
3176
+ link: function($scope) {
3177
+ $scope.goBack = goBack;
3178
+ }
2184
3179
  };
2185
- });
2186
-
2187
- ;
2188
- (function(ionic) {
2189
- 'use strict';
3180
+ }])
2190
3181
 
2191
- angular.module('ionic.ui.toggle', [])
3182
+ .directive('ionNavView', ['$ionicViewService', '$state', '$compile', '$controller', '$animate',
3183
+ function( $ionicViewService, $state, $compile, $controller, $animate) {
3184
+ // IONIC's fork of Angular UI Router, v0.2.7
3185
+ // the navView handles registering views in the history, which animation to use, and which
3186
+ var viewIsUpdating = false;
2192
3187
 
2193
- // The Toggle directive is a toggle switch that can be tapped to change
2194
- // its value
2195
- .directive('toggle', function() {
2196
- return {
3188
+ var directive = {
2197
3189
  restrict: 'E',
2198
- replace: true,
2199
- require: '?ngModel',
2200
- scope: {},
2201
- template: '<div ng-click="toggleIt($event)" class="toggle" skip-tap-poly><input type="checkbox"><div class="track"><div class="handle"></div></div></div>',
3190
+ terminal: true,
3191
+ priority: 2000,
3192
+ transclude: true,
3193
+ controller: ['$scope', function($scope) {
3194
+ this.setNextAnimation = function(anim) {
3195
+ $scope.$nextAnimation = anim;
3196
+ };
3197
+ }],
3198
+ compile: function (element, attr, transclude) {
3199
+ return function(scope, element, attr, navViewCtrl) {
3200
+ var viewScope, viewLocals,
3201
+ name = attr[directive.name] || attr.name || '',
3202
+ onloadExp = attr.onload || '',
3203
+ initialView = transclude(scope);
3204
+
3205
+ // Put back the compiled initial view
3206
+ element.append(initialView);
3207
+
3208
+ // Find the details of the parent view directive (if any) and use it
3209
+ // to derive our own qualified view name, then hang our own details
3210
+ // off the DOM so child directives can find it.
3211
+ var parent = element.parent().inheritedData('$uiView');
3212
+ if (name.indexOf('@') < 0) name = name + '@' + (parent ? parent.state.name : '');
3213
+ var view = { name: name, state: null };
3214
+ element.data('$uiView', view);
3215
+
3216
+ var eventHook = function() {
3217
+ if (viewIsUpdating) return;
3218
+ viewIsUpdating = true;
3219
+
3220
+ try { updateView(true); } catch (e) {
3221
+ viewIsUpdating = false;
3222
+ throw e;
3223
+ }
3224
+ viewIsUpdating = false;
3225
+ };
2202
3226
 
2203
- link: function($scope, $element, $attr, ngModel) {
2204
- var checkbox, handle;
3227
+ scope.$on('$stateChangeSuccess', eventHook);
3228
+ scope.$on('$viewContentLoading', eventHook);
3229
+ updateView(false);
2205
3230
 
2206
- if(!ngModel) { return; }
3231
+ function updateView(doAnimate) {
3232
+ //===false because $animate.enabled() is a noop without angular-animate included
3233
+ if ($animate.enabled() === false) {
3234
+ doAnimate = false;
3235
+ }
2207
3236
 
2208
- checkbox = $element.children().eq(0);
2209
- handle = $element.children().eq(1);
3237
+ var locals = $state.$current && $state.$current.locals[name];
3238
+ if (locals === viewLocals) return; // nothing to do
3239
+ var renderer = $ionicViewService.getRenderer(element, attr, scope);
2210
3240
 
2211
- if(!checkbox.length || !handle.length) { return; }
3241
+ // Destroy previous view scope
3242
+ if (viewScope) {
3243
+ viewScope.$destroy();
3244
+ viewScope = null;
3245
+ }
2212
3246
 
2213
- $scope.toggle = new ionic.views.Toggle({
2214
- el: $element[0],
2215
- checkbox: checkbox[0],
2216
- handle: handle[0]
2217
- });
3247
+ if (!locals) {
3248
+ viewLocals = null;
3249
+ view.state = null;
2218
3250
 
2219
- $scope.toggleIt = function(e) {
2220
- $scope.toggle.tap(e);
2221
- ngModel.$setViewValue(checkbox[0].checked);
2222
- };
3251
+ // Restore the initial view
3252
+ return element.append(initialView);
3253
+ }
3254
+
3255
+ var newElement = angular.element('<div></div>').html(locals.$template).contents();
3256
+ var viewRegisterData = renderer().register(newElement);
3257
+
3258
+ // Remove existing content
3259
+ renderer(doAnimate).leave();
3260
+
3261
+ viewLocals = locals;
3262
+ view.state = locals.$$state;
3263
+
3264
+ renderer(doAnimate).enter(newElement);
3265
+
3266
+ var link = $compile(newElement);
3267
+ viewScope = scope.$new();
3268
+
3269
+ viewScope.$navDirection = viewRegisterData.navDirection;
3270
+
3271
+ if (locals.$$controller) {
3272
+ locals.$scope = viewScope;
3273
+ var controller = $controller(locals.$$controller, locals);
3274
+ element.children().data('$ngControllerController', controller);
3275
+ }
3276
+ link(viewScope);
3277
+
3278
+ var viewHistoryData = $ionicViewService._getView(viewRegisterData.viewId) || {};
3279
+ viewScope.$broadcast('$viewContentLoaded', viewHistoryData);
2223
3280
 
2224
- ngModel.$render = function() {
2225
- $scope.toggle.val(ngModel.$viewValue);
3281
+ if (onloadExp) viewScope.$eval(onloadExp);
3282
+
3283
+ newElement = null;
3284
+ }
2226
3285
  };
2227
3286
  }
2228
3287
  };
2229
- });
3288
+ return directive;
3289
+ }]);
2230
3290
 
2231
- })(window.ionic);
3291
+ })();
2232
3292
  ;
2233
3293
  (function() {
2234
3294
  'use strict';
2235
3295
 
2236
3296
  angular.module('ionic.ui.virtRepeat', [])
2237
3297
 
2238
- .directive('virtRepeat', function() {
3298
+ .directive('ionVirtRepeat', function() {
2239
3299
  return {
2240
3300
  require: ['?ngModel', '^virtualList'],
2241
3301
  transclude: 'element',
@@ -2385,7 +3445,7 @@ angular.module('ionic.ui.virtualRepeat', [])
2385
3445
  * scrolling to only render items that are showing or will be showing
2386
3446
  * if a scroll is made.
2387
3447
  */
2388
- .directive('virtualRepeat', ['$log', function($log) {
3448
+ .directive('ionVirtualRepeat', ['$log', function($log) {
2389
3449
  return {
2390
3450
  require: ['?ngModel, ^virtualList'],
2391
3451
  transclude: 'element',
@@ -2581,3 +3641,64 @@ angular.module('ionic.ui.virtualRepeat', [])
2581
3641
  }]);
2582
3642
 
2583
3643
  })(ionic);
3644
+ ;
3645
+ (function() {
3646
+ 'use strict';
3647
+
3648
+ angular.module('ionic.ui.scroll')
3649
+
3650
+ .controller('$ionicScroll', ['$scope', 'scrollViewOptions', '$timeout', '$ionicScrollDelegate', '$window', function($scope, scrollViewOptions, $timeout, $ionicScrollDelegate, $window) {
3651
+
3652
+ var self = this;
3653
+
3654
+ var element = this.element = scrollViewOptions.el;
3655
+ var scrollView = this.scrollView = new ionic.views.Scroll(scrollViewOptions);
3656
+
3657
+ if (!angular.isDefined(scrollViewOptions.bouncing)) {
3658
+ ionic.Platform.ready(function() {
3659
+ scrollView.options.bouncing = !ionic.Platform.isAndroid();
3660
+ });
3661
+ }
3662
+
3663
+ var $element = this.$element = angular.element(element);
3664
+
3665
+ //Attach self to element as a controller so other directives can require this controller
3666
+ //through `require: '$ionicScroll'
3667
+ $element.data('$$ionicScrollController', this);
3668
+
3669
+ //Register delegate for event handling
3670
+ $ionicScrollDelegate.register($scope, $element, scrollView);
3671
+
3672
+ $window.addEventListener('resize', resize);
3673
+ $scope.$on('$destroy', function() {
3674
+ $window.removeEventListener('resize', resize);
3675
+ });
3676
+ function resize() {
3677
+ scrollView.resize();
3678
+ }
3679
+
3680
+ $timeout(function() {
3681
+ scrollView.run();
3682
+
3683
+ self.refresher = element.querySelector('.scroll-refresher');
3684
+
3685
+ // Activate pull-to-refresh
3686
+ if(self.refresher) {
3687
+ var refresherHeight = self.refresher.clientHeight || 0;
3688
+ scrollView.activatePullToRefresh(refresherHeight, function() {
3689
+ self.refresher.classList.add('active');
3690
+ $scope.$onRefreshOpening && $scope.$onRefreshOpening();
3691
+ }, function() {
3692
+ self.refresher.classList.remove('refreshing');
3693
+ self.refresher.classList.remove('active');
3694
+ }, function() {
3695
+ self.refresher.classList.add('refreshing');
3696
+ $scope.$onRefresh && $scope.$onRefresh();
3697
+ $scope.$parent.$broadcast('scroll.onRefresh');
3698
+ });
3699
+ }
3700
+ });
3701
+
3702
+ }]);
3703
+
3704
+ })();