diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss index 681502c38..861574862 100644 --- a/app/assets/stylesheets/application.scss +++ b/app/assets/stylesheets/application.scss @@ -16,7 +16,3 @@ @import "bootstrap-sprockets"; @import "bootstrap"; - -.cover-bg-blue{background-color:#2275CA;} -.cover-text-white{color:#FFFFFF;} -.a {color:#FFFFF;} \ No newline at end of file diff --git a/app/models/custom_widget.rb b/app/models/custom_widget.rb new file mode 100644 index 000000000..b4698bb5d --- /dev/null +++ b/app/models/custom_widget.rb @@ -0,0 +1,5 @@ +class CustomWidget < Widget + attribute :image, :reference + attribute :headline, :string + attribute :text, :string +end diff --git a/app/models/paragraph_widget.rb b/app/models/paragraph_widget.rb deleted file mode 100644 index d2075638e..000000000 --- a/app/models/paragraph_widget.rb +++ /dev/null @@ -1,6 +0,0 @@ -class ParagraphWidget < Widget - attribute :image, :reference - attribute :headline, :string - attribute :text, :string - attribute :color, :enum, values: %w[red green blue], default: 'blue' -end diff --git a/app/views/paragraph_widget/details.html.erb b/app/views/custom_widget/details.html.erb similarity index 56% rename from app/views/paragraph_widget/details.html.erb rename to app/views/custom_widget/details.html.erb index a63407642..7c9f3fc40 100644 --- a/app/views/paragraph_widget/details.html.erb +++ b/app/views/custom_widget/details.html.erb @@ -1,5 +1,5 @@ <%= scrivito_medium_dialog do %> - <%= scrivito_details_for ParagraphWidget.description_for_editor do %> + <%= scrivito_details_for CustomWidget.description_for_editor do %> A cover with head, text and image <% end %> <% end %> diff --git a/app/views/custom_widget/show.html.erb b/app/views/custom_widget/show.html.erb new file mode 100644 index 000000000..9e1fa1705 --- /dev/null +++ b/app/views/custom_widget/show.html.erb @@ -0,0 +1,115 @@ +
+ +

CoderDojo Japan

+

子どものためのプログラミング道場

+
+
+
+ +
+
+

CoderDojo は7〜17歳の子どもを対象にしたプログラミング道場です。2011年にアイルランドで始まり、世界では66カ国・1,150の道場、日本では全国に67以上の道場があります (2016年12月現在)。

+

各道場では、主に次のような内容を学ぶことができます。

+ +
+

各道場で学べる内容はそれぞれ異なりますが、CoderDojo の雰囲気は一様です。「どんな雰囲気か知りたい」という方は、下記のガイダンス資料をご参考にしてください。

+
+ +
スライドが見えない場合はコチラから閲覧してください
+
+

さらに詳しい道場の様子については、「CoderDojoとは? 運営者に話を聞いてみた! (バケモノ.jp)」や「子どものための無料プログラミング道場 CoderDojo をはじめてみませんか (ICT教育ニュース)」を読んでみてください。それぞれインタビュー形式で話が構成されているので、より具体的なイメージが掴めるかなと思います :)

+
+
+

全国の道場

+ +
道場情報を読み込み中...
+
+ +
+ CoderDojoに関するつぶやき + +
+
+
+

CoderDojo をもっと知りたい

+

下記ウェブサイトから、より詳細な情報や、最新の情報にアクセスできます。

+ +
+ +

本家サイトである CoderDojo (英語) では世界中の活動の様子が見れます。また、Facebook の CoderDojo Japan では日本全国の活動の様子を知ることができます。

+

「これまでの成り立ちを知りたい」「地元で CoderDojo を立ち上げてみたい」といった場合には CoderDojo Kata を参照してみてください。

+
+

お問い合わせ

+

取材や支援などのお問い合わせについては、下記フォームからお気軽にご連絡ください :)
+ メールをご希望の場合は、担当の安川要平まで (yohei@coderdojo.jp) ご連絡ください。

+ + +
+ +
HTML Forms powered by Wufoo.
+ +
+
+ + + + + + + diff --git a/app/views/custom_widget/thumbnail.html.erb b/app/views/custom_widget/thumbnail.html.erb new file mode 100644 index 000000000..311b663bb --- /dev/null +++ b/app/views/custom_widget/thumbnail.html.erb @@ -0,0 +1 @@ +<%= scrivito_thumbnail CustomWidget.description_for_editor %> diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb index 141e33e4e..395ed8070 100644 --- a/app/views/layouts/application.html.erb +++ b/app/views/layouts/application.html.erb @@ -1,16 +1,45 @@ - CoderdojoJp + + CoderDojo Japan - コーダー道場ジャパン <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> <%= csrf_meta_tags %> <%= scrivito_head_tags %> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + -
- +
<%= yield %> <%= scrivito_body_tags %>
diff --git a/app/views/paragraph_widget/show.html.erb b/app/views/paragraph_widget/show.html.erb deleted file mode 100644 index bbf768762..000000000 --- a/app/views/paragraph_widget/show.html.erb +++ /dev/null @@ -1,7 +0,0 @@ -
-
- <%= scrivito_image_tag widget, :image, class:'image-responsive'%> - <%= scrivito_tag :h1, widget, :headline, data: {newlines: false}, class:'cover-text-white' %> - <%= scrivito_tag :p, widget, :text, class:'cover-text-white', style:'padding-bottom:30px;'%> -
-
\ No newline at end of file diff --git a/app/views/paragraph_widget/thumbnail.html.erb b/app/views/paragraph_widget/thumbnail.html.erb deleted file mode 100644 index 0aabbb1de..000000000 --- a/app/views/paragraph_widget/thumbnail.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= scrivito_thumbnail ParagraphWidget.description_for_editor %> diff --git a/public/css/style.css b/public/css/style.css new file mode 100644 index 000000000..ef7a6ff62 --- /dev/null +++ b/public/css/style.css @@ -0,0 +1,355 @@ +.cd-fw{ + text-align: center; + background: #2275CA; +} + +.meetup-item a, .mentor-person > header > a { + text-decoration: none +} + +@font-face { + font-family: coderdojo; + src: url(../font/coderdojo.eot); + src: url(../font/coderdojo.eot?#iefix) format('eot'), url(../font/coderdojo.woff) format('woff'), url(../font/coderdojo.ttf) format('truetype'), url(../font/coderdojo.svg#coderdojo) format('svg'); + font-weight: 400; + font-style: normal +} + +.cd:before { + display: inline-block; + font-family: coderdojo; + font-style: normal; + font-weight: 400; + line-height: 1; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale +} + +.cd-lg { + font-size: 1.3333333333333333em; + line-height: .75em; + vertical-align: -15% +} + +.meetup-item, .mentor-person { + overflow: hidden; + vertical-align: top +} + +.cd-2x { + font-size: 2em +} + +.cd-3x { + font-size: 3em +} + +.cd-4x { + font-size: 4em +} + +.cd-5x { + font-size: 5em +} + +.cd-fw { + width: 1.2857142857142858em +} + +.cd-spin { + -webkit-animation: spin 2s infinite linear; + animation: spin 2s infinite linear +} + +.cd-clock:before { + content: "\EA01" +} + +.cd-connect:before { + content: "\EA02" +} + +.cd-glyph07:before { + content: "\EA03" +} + +.cd-glyph08:before { + content: "\EA04" +} + +.cd-logo-jp:before { + content: "\EA05" +} + +.cd-logo:before { + content: "\EA06" +} + +.cd-pin:before { + content: "\EA07" +} + +.cd-spinner:before { + content: "\EA08" +} + +body { + margin: 0; + padding: 0; + font-size: 16px +} + +a { + color: #2e9ad9 +} + +.under-construction { + color: #cd8585; + font-size: 75%; + margin: 0; + padding: .2em; + background: #FFF +} + +.footer-bottom { + margin: -.25em 0 0; + padding: 2em 3.5em; + color: #fff; + background: #2275CA +} + +.footer-bottom > h2 { + font-size: 87.5%; + margin-top: 0; + margin-bottom: 2em +} + +.footer-link { + color: #fff; + text-decoration: underline +} + +.footer-link:link { + color: #fff; + text-decoration: underline +} + +.footer-link:visited { + color: #fff; + text-decoration: underline +} + +.title, body > footer a:hover { + color: #fff +} + +.title{ + background: #2275CA; +} + +.title > i.cd:before { + font-size: 500%; + display: block +} + +.title > h1 { + font-size: 1.5em; + margin: .5em 0 1em +} + +.title > p { + padding: 0 1em; + margin: 0 auto; + max-width: 680px; + font-size: 130% +} + +@media only screen and (min-width:560px) { + body > footer > ul li { + border-radius: 3px; + line-height: 2.8em + } + + .title > i.cd:before { + font-size: 800% + } + + .title > h1 { + font-size: 1.7em; + margin-bottom: .5em + } + + .title > p { + padding: 0 3em; + font-size: 110% + } +} + +@media only screen and (min-width:720px) { + .title > i.cd:before { + font-size: 1000% + } + + .title > h1 { + font-size: 2.2em; + margin-bottom: .2em + } + + .title > p { + padding: 0 5em; + font-size: 90% + } +} + +@media only screen and (min-width:560px) { + + .introduction { + max-width: 560px; + margin: 0 auto; + padding: 1.5em; + background: #fff + } + + .detail-introduction { + max-width: 560px; + margin: 0 auto; + padding: 1.5em; + } +} + +.introduction { + max-width: 800px; + margin: 0 auto; + padding: 1.5em; + background: #fff +} + +.introduction > p { + margin-bottom: .8em; + text-align: center +} + +.detail-introduction { + max-width: 800px; + margin: auto; + padding: 0.5em 4em; + padding-bottom: 1.5em +} + +.list > ul { + display: inline-block; + text-align: left; + padding-left: 2em; + color: #666 +} + +.detail-introduction > h2 { + font-size: 120%; + color: #999; + margin-bottom: .4em +} + +.grayscale-bg { + background: #f7f7f7; + width: 120% +} + +.cover { + overflow: hidden; + min-height: 150px; + background: #999 +} + +.cover > img { + width: 100%; + max-width: 1000px; + margin-left: auto; + margin-right: auto; + display: block; + margin-top: -3% +} + +.mentor > ul { + text-align: center; + padding: 0 0 1.5em +} + +.mentor > h2 { + font-size: 120%; + color: #999; + padding-top: 1em; + margin: .4em +} + +.mentor-person { + display: inline-block; + width: 85%; + margin: 10px; + padding: 15px 0; + background: #fff; + position: relative; + text-align: center +} + +.mentor-person > p { + font-size: 90%; + padding: 5px 10px; + text-align: center; + margin: 5px 0 +} + +.mentor-person-picture { + display: block; + height: 96px; + width: 96px; + background-position: center center; + / / border-radius: 48px; + background-size: cover; + margin: 0 auto +} + +.mentor-person-name { + display: block; + margin: .5em 0 +} + +@media only screen and (min-width:560px) { + .mentor-person { + width: 220px; + height: 275px + } +} + +.spinner { + height: 3em; + color: #ccc +} + +.tags { + margin: 0 5px; + padding: 0; + list-style: none +} + +.tags > li { + display: inline-block; + font-size: 80%; + color: #fff; + border-radius: .3em; + padding: .2em; + margin: .1em +} + +body { + color: #f; + font-family: 'ヒラギノ角ゴ Pro W3', 'Hiragino Kaku Gothic Pro', 'メイリオ', Meiryo, 'MS Pゴシック', sans-serif +} + +.meetup-item > header time, .title > h1 { + font-family: 'Fjalla One', sans-serif +} + +.full-width{ + margin-left: -10%; + margin-right: -10%; +} diff --git a/public/font/coderdojo.eot b/public/font/coderdojo.eot new file mode 100644 index 000000000..80b44bf43 Binary files /dev/null and b/public/font/coderdojo.eot differ diff --git a/public/font/coderdojo.ttf b/public/font/coderdojo.ttf new file mode 100644 index 000000000..edfdaf358 Binary files /dev/null and b/public/font/coderdojo.ttf differ diff --git a/public/font/coderdojo.woff b/public/font/coderdojo.woff new file mode 100644 index 000000000..17fccd194 Binary files /dev/null and b/public/font/coderdojo.woff differ diff --git a/public/img/coderdojo-japan_cover.jpg b/public/img/coderdojo-japan_cover.jpg new file mode 100644 index 000000000..83c817ea0 Binary files /dev/null and b/public/img/coderdojo-japan_cover.jpg differ diff --git a/public/js/angular.min.js b/public/js/angular.min.js new file mode 100644 index 000000000..65223c3b8 --- /dev/null +++ b/public/js/angular.min.js @@ -0,0 +1,309 @@ +/* + AngularJS v1.5.2 + (c) 2010-2016 Google, Inc. http://angularjs.org + License: MIT +*/ +(function(N,Q,w){'use strict';function O(a){return function(){var b=arguments[0],d;d="["+(a?a+":":"")+b+"] http://errors.angularjs.org/1.5.2/"+(a?a+"/":"")+b;for(b=1;b").append(a).html();try{return a[0].nodeType===Na?E(d):d.match(/^(<[^>]+>)/)[1].replace(/^<([\w\-]+)/,function(a,b){return"<"+E(b)})}catch(c){return E(d)}}function vc(a){try{return decodeURIComponent(a)}catch(b){}} +function wc(a){var b={};p((a||"").split("&"),function(a){var c,e,f;a&&(e=a=a.replace(/\+/g,"%20"),c=a.indexOf("="),-1!==c&&(e=a.substring(0,c),f=a.substring(c+1)),e=vc(e),A(e)&&(f=A(f)?vc(f):!0,sa.call(b,e)?L(b[e])?b[e].push(f):b[e]=[b[e],f]:b[e]=f))});return b}function Rb(a){var b=[];p(a,function(a,c){L(a)?p(a,function(a){b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))}):b.push(ja(c,!0)+(!0===a?"":"="+ja(a,!0)))});return b.length?b.join("&"):""}function qb(a){return ja(a,!0).replace(/%26/gi,"&").replace(/%3D/gi, +"=").replace(/%2B/gi,"+")}function ja(a,b){return encodeURIComponent(a).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%3B/gi,";").replace(/%20/g,b?"%20":"+")}function ae(a,b){var d,c,e=Oa.length;for(c=0;c/,">"));}b=b||[];b.unshift(["$provide",function(b){b.value("$rootElement",a)}]);d.debugInfoEnabled&&b.push(["$compileProvider",function(a){a.debugInfoEnabled(!0)}]);b.unshift("ng");c=db(b,d.strictDi);c.invoke(["$rootScope", +"$rootElement","$compile","$injector",function(a,b,c,d){a.$apply(function(){b.data("$injector",d);c(b)(a)})}]);return c},e=/^NG_ENABLE_DEBUG_INFO!/,f=/^NG_DEFER_BOOTSTRAP!/;N&&e.test(N.name)&&(d.debugInfoEnabled=!0,N.name=N.name.replace(e,""));if(N&&!f.test(N.name))return c();N.name=N.name.replace(f,"");ea.resumeBootstrap=function(a){p(a,function(a){b.push(a)});return c()};H(ea.resumeDeferredBootstrap)&&ea.resumeDeferredBootstrap()}function ce(){N.name="NG_ENABLE_DEBUG_INFO!"+N.name;N.location.reload()} +function de(a){a=ea.element(a).injector();if(!a)throw Da("test");return a.get("$$testability")}function yc(a,b){b=b||"_";return a.replace(ee,function(a,c){return(c?b:"")+a.toLowerCase()})}function fe(){var a;if(!zc){var b=rb();(ra=v(b)?N.jQuery:b?N[b]:w)&&ra.fn.on?(G=ra,S(ra.fn,{scope:Pa.scope,isolateScope:Pa.isolateScope,controller:Pa.controller,injector:Pa.injector,inheritedData:Pa.inheritedData}),a=ra.cleanData,ra.cleanData=function(b){for(var c,e=0,f;null!=(f=b[e]);e++)(c=ra._data(f,"events"))&& +c.$destroy&&ra(f).triggerHandler("$destroy");a(b)}):G=Y;ea.element=G;zc=!0}}function sb(a,b,d){if(!a)throw Da("areq",b||"?",d||"required");return a}function Qa(a,b,d){d&&L(a)&&(a=a[a.length-1]);sb(H(a),b,"not a function, got "+(a&&"object"===typeof a?a.constructor.name||"Object":typeof a));return a}function Ra(a,b){if("hasOwnProperty"===a)throw Da("badname",b);}function Ac(a,b,d){if(!b)return a;b=b.split(".");for(var c,e=a,f=b.length,g=0;g")+c[2];for(c=c[0];c--;)d=d.lastChild;f=bb(f,d.childNodes);d=e.firstChild;d.textContent=""}else f.push(b.createTextNode(a));e.textContent="";e.innerHTML="";p(f,function(a){e.appendChild(a)});return e}function Lc(a, +b){var d=a.parentNode;d&&d.replaceChild(b,a);b.appendChild(a)}function Y(a){if(a instanceof Y)return a;var b;D(a)&&(a=Z(a),b=!0);if(!(this instanceof Y)){if(b&&"<"!=a.charAt(0))throw Ub("nosel");return new Y(a)}if(b){b=Q;var d;a=(d=Jf.exec(a))?[b.createElement(d[1])]:(d=Kc(a,b))?d.childNodes:[]}Mc(this,a)}function Vb(a){return a.cloneNode(!0)}function wb(a,b){b||fb(a);if(a.querySelectorAll)for(var d=a.querySelectorAll("*"),c=0,e=d.length;c=za?!1:"function"===typeof a&&/^(?:class\s|constructor\()/.test(Function.prototype.toString.call(a));return d?(c.unshift(null),new (Function.prototype.bind.apply(a,c))):a.apply(b,c)},instantiate:function(a,b,c){var d=L(a)?a[a.length-1]:a;a=e(a,b,c);a.unshift(null);return new (Function.prototype.bind.apply(d,a))},get:d,annotate:db.$$annotate,has:function(b){return n.hasOwnProperty(b+ +"Provider")||a.hasOwnProperty(b)}}}b=!0===b;var k={},l=[],m=new Sa([],!0),n={$provide:{provider:d(c),factory:d(f),service:d(function(a,b){return f(a,["$injector",function(a){return a.instantiate(b)}])}),value:d(function(a,b){return f(a,ia(b),!1)}),constant:d(function(a,b){Ra(a,"constant");n[a]=b;B[a]=b}),decorator:function(a,b){var c=y.get(a+"Provider"),d=c.$get;c.$get=function(){var a=x.invoke(d,c);return x.invoke(b,null,{$delegate:a})}}}},y=n.$injector=h(n,function(a,b){ea.isString(b)&&l.push(b); +throw Ga("unpr",l.join(" <- "));}),B={},K=h(B,function(a,b){var c=y.get(a+"Provider",b);return x.invoke(c.$get,c,w,a)}),x=K;n.$injectorProvider={$get:ia(K)};var q=g(a),x=K.get("$injector");x.strictDi=b;p(q,function(a){a&&x.invoke(a)});return x}function Te(){var a=!0;this.disableAutoScrolling=function(){a=!1};this.$get=["$window","$location","$rootScope",function(b,d,c){function e(a){var b=null;Array.prototype.some.call(a,function(a){if("a"===pa(a))return b=a,!0});return b}function f(a){if(a){a.scrollIntoView(); +var c;c=g.yOffset;H(c)?c=c():Ob(c)?(c=c[0],c="fixed"!==b.getComputedStyle(c).position?0:c.getBoundingClientRect().bottom):W(c)||(c=0);c&&(a=a.getBoundingClientRect().top,b.scrollBy(0,a-c))}else b.scrollTo(0,0)}function g(a){a=D(a)?a:d.hash();var b;a?(b=h.getElementById(a))?f(b):(b=e(h.getElementsByName(a)))?f(b):"top"===a&&f(null):f(null)}var h=b.document;a&&c.$watch(function(){return d.hash()},function(a,b){a===b&&""===a||Lf(function(){c.$evalAsync(g)})});return g}]}function hb(a,b){if(!a&&!b)return""; +if(!a)return b;if(!b)return a;L(a)&&(a=a.join(" "));L(b)&&(b=b.join(" "));return a+" "+b}function Uf(a){D(a)&&(a=a.split(" "));var b=X();p(a,function(a){a.length&&(b[a]=!0)});return b}function Ha(a){return I(a)?a:{}}function Vf(a,b,d,c){function e(a){try{a.apply(null,ya.call(arguments,1))}finally{if(K--,0===K)for(;x.length;)try{x.pop()()}catch(b){d.error(b)}}}function f(){M=null;g();h()}function g(){a:{try{q=m.state;break a}catch(a){}q=void 0}q=v(q)?null:q;oa(q,T)&&(q=T);T=q}function h(){if(u!==k.url()|| +t!==q)u=k.url(),t=q,p(C,function(a){a(k.url(),q)})}var k=this,l=a.location,m=a.history,n=a.setTimeout,y=a.clearTimeout,B={};k.isMock=!1;var K=0,x=[];k.$$completeOutstandingRequest=e;k.$$incOutstandingRequestCount=function(){K++};k.notifyWhenNoOutstandingRequests=function(a){0===K?a():x.push(a)};var q,t,u=l.href,s=b.find("base"),M=null;g();t=q;k.url=function(b,d,e){v(e)&&(e=null);l!==a.location&&(l=a.location);m!==a.history&&(m=a.history);if(b){var f=t===e;if(u===b&&(!c.history||f))return k;var h= +u&&Ia(u)===Ia(b);u=b;t=e;if(!c.history||h&&f){if(!h||M)M=b;d?l.replace(b):h?(d=l,e=b.indexOf("#"),e=-1===e?"":b.substr(e),d.hash=e):l.href=b;l.href!==b&&(M=b)}else m[d?"replaceState":"pushState"](e,"",b),g(),t=q;return k}return M||l.href.replace(/%27/g,"'")};k.state=function(){return q};var C=[],J=!1,T=null;k.onUrlChange=function(b){if(!J){if(c.history)G(a).on("popstate",f);G(a).on("hashchange",f);J=!0}C.push(b);return b};k.$$applicationDestroyed=function(){G(a).off("hashchange popstate",f)};k.$$checkUrlChange= +h;k.baseHref=function(){var a=s.attr("href");return a?a.replace(/^(https?\:)?\/\/[^\/]*/,""):""};k.defer=function(a,b){var c;K++;c=n(function(){delete B[c];e(a)},b||0);B[c]=!0;return c};k.defer.cancel=function(a){return B[a]?(delete B[a],y(a),e(z),!0):!1}}function $e(){this.$get=["$window","$log","$sniffer","$document",function(a,b,d,c){return new Vf(a,c,b,d)}]}function af(){this.$get=function(){function a(a,c){function e(a){a!=n&&(y?y==a&&(y=a.n):y=a,f(a.n,a.p),f(a,n),n=a,n.n=null)}function f(a, +b){a!=b&&(a&&(a.p=b),b&&(b.n=a))}if(a in b)throw O("$cacheFactory")("iid",a);var g=0,h=S({},c,{id:a}),k=X(),l=c&&c.capacity||Number.MAX_VALUE,m=X(),n=null,y=null;return b[a]={put:function(a,b){if(!v(b)){if(ll&&this.remove(y.key);return b}},get:function(a){if(l";b=fa.firstChild.attributes;var d=b[0];b.removeNamedItem(d.name);d.value=c;a.attributes.setNamedItem(d)}function $(a,b){try{a.addClass(b)}catch(c){}}function R(a,b,c,d,e){a instanceof G||(a=G(a));for(var f=/\S+/,g=0,h=a.length;g").append(a).html())):c?Pa.clone.call(a):a;if(g)for(var h in g)d.data("$"+h+"Controller",g[h].instance);R.$$addScopeInfo(d, +b);c&&c(d,b);l&&l(b,d,d,f);return d}}function P(a,b,c,d,e,f){function g(a,c,d,e){var f,k,l,m,n,C,u;if(q)for(u=Array(c.length),m=0;ms.priority)break;if(E=s.scope)s.templateUrl||(I(E)?(aa("new/isolated scope",t||C,s,P),t=s):aa("new/isolated scope",t,s,P)),C=C||s;K=s.name;if(!va&&(s.replace&&(s.templateUrl||s.template)||s.transclude&& +!s.$$tlb)){for(E=Q+1;va=a[E++];)if(va.transclude&&!va.$$tlb||va.replace&&(va.templateUrl||va.template)){N=!0;break}va=!0}!s.templateUrl&&s.controller&&(E=s.controller,u=u||X(),aa("'"+K+"' controller",u[K],s,P),u[K]=s);if(E=s.transclude)if(F=!0,s.$$tlb||(aa("transclusion",B,s,P),B=s),"element"==E)M=!0,q=s.priority,ca=P,P=d.$$element=G(R.$$createComment(K,d[K])),b=P[0],ga(f,ya.call(ca,0),b),z=Yb(N,ca,e,q,g&&g.name,{nonTlbTranscludeDirective:B});else{var U=X();ca=G(Vb(b)).contents();if(I(E)){ca=[];var ea= +X(),fa=X();p(E,function(a,b){var c="?"===a.charAt(0);a=c?a.substring(1):a;ea[a]=b;U[b]=null;fa[b]=c});p(P.contents(),function(a){var b=ea[ua(pa(a))];b?(fa[b]=!0,U[b]=U[b]||[],U[b].push(a)):ca.push(a)});p(fa,function(a,b){if(!a)throw la("reqslot",b);});for(var ha in U)U[ha]&&(U[ha]=Yb(N,U[ha],e))}P.empty();z=Yb(N,ca,e,w,w,{needsNewScope:s.$$isolateScope||s.$$newScope});z.$$slots=U}if(s.template)if($=!0,aa("template",J,s,P),J=s,E=H(s.template)?s.template(P,d):s.template,E=ra(E),s.replace){g=s;ca=Tb.test(E)? +Vc(da(s.templateNamespace,Z(E))):[];b=ca[0];if(1!=ca.length||1!==b.nodeType)throw la("tplrt",K,"");ga(f,P,b);Ua={$attr:{}};E=Aa(b,[],Ua);var ka=a.splice(Q+1,a.length-(Q+1));(t||C)&&Wc(E,t,C);a=a.concat(E).concat(ka);W(d,Ua);Ua=a.length}else P.html(E);if(s.templateUrl)$=!0,aa("template",J,s,P),J=s,s.replace&&(g=s),n=Y(a.splice(Q,a.length-Q),P,d,f,F&&z,h,k,{controllerDirectives:u,newScopeDirective:C!==s&&C,newIsolateScopeDirective:t,templateDirective:J,nonTlbTranscludeDirective:B}),Ua=a.length;else if(s.compile)try{D= +s.compile(P,d,z),H(D)?m(null,D,V,ba):D&&m(D.pre,D.post,V,ba)}catch(Wf){c(Wf,ta(P))}s.terminal&&(n.terminal=!0,q=Math.max(q,s.priority))}n.scope=C&&!0===C.scope;n.transcludeOnThisElement=F;n.templateOnThisElement=$;n.transclude=z;l.hasElementTranscludeDirective=M;return n}function ib(a,b,c,d){var e;if(D(b)){var f=b.match(k);b=b.substring(f[0].length);var g=f[1]||f[3],f="?"===f[2];"^^"===g?c=c.parent():e=(e=d&&d[b])&&e.instance;if(!e){var h="$"+b+"Controller";e=g?c.inheritedData(h):c.data(h)}if(!e&& +!f)throw la("ctreq",b,a);}else if(L(b))for(e=[],g=0,f=b.length;gn.priority)&&-1!=n.restrict.indexOf(g)){l&&(n=Pb(n,{$$start:l,$$end:m}));if(!n.$$bindings){var u=n,t=n,J=n.name,s={isolateScope:null,bindToController:null};I(t.scope)&&(!0===t.bindToController?(s.bindToController=d(t.scope,J,!0),s.isolateScope={}):s.isolateScope=d(t.scope,J,!1));I(t.bindToController)&&(s.bindToController=d(t.bindToController, +J,!0));if(I(s.bindToController)){var F=t.controller,$=t.controllerAs;if(!F)throw la("noctrl",J);if(!Tc(F,$))throw la("noident",J);}var T=u.$$bindings=s;I(T.isolateScope)&&(n.$$isolateBindings=T.isolateScope)}b.push(n);k=n}}catch(P){c(P)}}return k}function va(b){if(e.hasOwnProperty(b))for(var c=a.get(b+"Directive"),d=0,f=c.length;d"+b+"";return c.childNodes[0].childNodes;default:return b}}function U(a,b){if("srcdoc"==b)return M.HTML;var c=pa(a);if("xlinkHref"== +b||"form"==c&&"action"==b||"img"!=c&&("src"==b||"ngSrc"==b))return M.RESOURCE_URL}function ea(a,c,d,e,f){var g=U(a,e);f=h[e]||f;var k=b(d,!0,g,f);if(k){if("multiple"===e&&"select"===pa(a))throw la("selmulti",ta(a));c.push({priority:100,compile:function(){return{pre:function(a,c,h){c=h.$$observers||(h.$$observers=X());if(l.test(e))throw la("nodomevents");var m=h[e];m!==d&&(k=m&&b(m,!0,g,f),d=m);k&&(h[e]=k(a),(c[e]||(c[e]=[])).$$inter=!0,(h.$$observers&&h.$$observers[e].$$scope||a).$watch(k,function(a, +b){"class"===e&&a!=b?h.$updateClass(a,b):h.$set(e,a)}))}}}})}}function ga(a,b,c){var d=b[0],e=b.length,f=d.parentNode,g,h;if(a)for(g=0,h=a.length;g=b)return a;for(;b--;)8===a[b].nodeType&&Yf.call(a,b,1);return a}function Tc(a,b){if(b&&D(b))return b;if(D(a)){var d=Zc.exec(a);if(d)return d[3]}}function bf(){var a={},b=!1;this.has=function(b){return a.hasOwnProperty(b)};this.register= +function(b,c){Ra(b,"controller");I(b)?S(a,b):a[b]=c};this.allowGlobals=function(){b=!0};this.$get=["$injector","$window",function(d,c){function e(a,b,c,d){if(!a||!I(a.$scope))throw O("$controller")("noscp",d,b);a.$scope[b]=c}return function(f,g,h,k){var l,m,n;h=!0===h;k&&D(k)&&(n=k);if(D(f)){k=f.match(Zc);if(!k)throw Zf("ctrlfmt",f);m=k[1];n=n||k[3];f=a.hasOwnProperty(m)?a[m]:Ac(g.$scope,m,!0)||(b?Ac(c,m,!0):w);Qa(f,m,!0)}if(h)return h=(L(f)?f[f.length-1]:f).prototype,l=Object.create(h||null),n&& +e(g,n,l,m||f.name),S(function(){var a=d.invoke(f,l,g,m);a!==l&&(I(a)||H(a))&&(l=a,n&&e(g,n,l,m||f.name));return l},{instance:l,identifier:n});l=d.instantiate(f,g,m);n&&e(g,n,l,m||f.name);return l}}]}function cf(){this.$get=["$window",function(a){return G(a.document)}]}function df(){this.$get=["$log",function(a){return function(b,d){a.error.apply(a,arguments)}}]}function Zb(a){return I(a)?ga(a)?a.toISOString():cb(a):a}function jf(){this.$get=function(){return function(a){if(!a)return"";var b=[];oc(a, +function(a,c){null===a||v(a)||(L(a)?p(a,function(a){b.push(ja(c)+"="+ja(Zb(a)))}):b.push(ja(c)+"="+ja(Zb(a))))});return b.join("&")}}}function kf(){this.$get=function(){return function(a){function b(a,e,f){null===a||v(a)||(L(a)?p(a,function(a,c){b(a,e+"["+(I(a)?c:"")+"]")}):I(a)&&!ga(a)?oc(a,function(a,c){b(a,e+(f?"":"[")+c+(f?"":"]"))}):d.push(ja(e)+"="+ja(Zb(a))))}if(!a)return"";var d=[];b(a,"",!0);return d.join("&")}}}function $b(a,b){if(D(a)){var d=a.replace($f,"").trim();if(d){var c=b("Content-Type"); +(c=c&&0===c.indexOf($c))||(c=(c=d.match(ag))&&bg[c[0]].test(d));c&&(a=tc(d))}}return a}function ad(a){var b=X(),d;D(a)?p(a.split("\n"),function(a){d=a.indexOf(":");var e=E(Z(a.substr(0,d)));a=Z(a.substr(d+1));e&&(b[e]=b[e]?b[e]+", "+a:a)}):I(a)&&p(a,function(a,d){var f=E(d),g=Z(a);f&&(b[f]=b[f]?b[f]+", "+g:g)});return b}function bd(a){var b;return function(d){b||(b=ad(a));return d?(d=b[E(d)],void 0===d&&(d=null),d):b}}function cd(a,b,d,c){if(H(c))return c(a,b,d);p(c,function(c){a=c(a,b,d)});return a} +function hf(){var a=this.defaults={transformResponse:[$b],transformRequest:[function(a){return I(a)&&"[object File]"!==ha.call(a)&&"[object Blob]"!==ha.call(a)&&"[object FormData]"!==ha.call(a)?cb(a):a}],headers:{common:{Accept:"application/json, text/plain, */*"},post:ma(ac),put:ma(ac),patch:ma(ac)},xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",paramSerializer:"$httpParamSerializer"},b=!1;this.useApplyAsync=function(a){return A(a)?(b=!!a,this):b};var d=!0;this.useLegacyPromiseExtensions= +function(a){return A(a)?(d=!!a,this):d};var c=this.interceptors=[];this.$get=["$httpBackend","$$cookieReader","$cacheFactory","$rootScope","$q","$injector",function(e,f,g,h,k,l){function m(b){function c(a){var b=S({},a);b.data=cd(a.data,a.headers,a.status,f.transformResponse);a=a.status;return 200<=a&&300>a?b:k.reject(b)}function e(a,b){var c,d={};p(a,function(a,e){H(a)?(c=a(b),null!=c&&(d[e]=c)):d[e]=a});return d}if(!I(b))throw O("$http")("badreq",b);if(!D(b.url))throw O("$http")("badreq",b.url); +var f=S({method:"get",transformRequest:a.transformRequest,transformResponse:a.transformResponse,paramSerializer:a.paramSerializer},b);f.headers=function(b){var c=a.headers,d=S({},b.headers),f,g,h,c=S({},c.common,c[E(b.method)]);a:for(f in c){g=E(f);for(h in d)if(E(h)===g)continue a;d[f]=c[f]}return e(d,ma(b))}(b);f.method=ub(f.method);f.paramSerializer=D(f.paramSerializer)?l.get(f.paramSerializer):f.paramSerializer;var g=[function(b){var d=b.headers,e=cd(b.data,bd(d),w,b.transformRequest);v(e)&&p(d, +function(a,b){"content-type"===E(b)&&delete d[b]});v(b.withCredentials)&&!v(a.withCredentials)&&(b.withCredentials=a.withCredentials);return n(b,e).then(c,c)},w],h=k.when(f);for(p(K,function(a){(a.request||a.requestError)&&g.unshift(a.request,a.requestError);(a.response||a.responseError)&&g.push(a.response,a.responseError)});g.length;){b=g.shift();var m=g.shift(),h=h.then(b,m)}d?(h.success=function(a){Qa(a,"fn");h.then(function(b){a(b.data,b.status,b.headers,f)});return h},h.error=function(a){Qa(a, +"fn");h.then(null,function(b){a(b.data,b.status,b.headers,f)});return h}):(h.success=dd("success"),h.error=dd("error"));return h}function n(c,d){function g(a,c,d,e){function f(){l(c,a,d,e)}T&&(200<=a&&300>a?T.put(R,[a,c,ad(d),e]):T.remove(R));b?h.$applyAsync(f):(f(),h.$$phase||h.$apply())}function l(a,b,d,e){b=-1<=b?b:0;(200<=b&&300>b?C.resolve:C.reject)({data:a,status:b,headers:bd(d),config:c,statusText:e})}function n(a){l(a.data,a.status,ma(a.headers()),a.statusText)}function K(){var a=m.pendingRequests.indexOf(c); +-1!==a&&m.pendingRequests.splice(a,1)}var C=k.defer(),J=C.promise,T,F,$=c.headers,R=y(c.url,c.paramSerializer(c.params));m.pendingRequests.push(c);J.then(K,K);!c.cache&&!a.cache||!1===c.cache||"GET"!==c.method&&"JSONP"!==c.method||(T=I(c.cache)?c.cache:I(a.cache)?a.cache:B);T&&(F=T.get(R),A(F)?F&&H(F.then)?F.then(n,n):L(F)?l(F[1],F[0],ma(F[2]),F[3]):l(F,200,{},"OK"):T.put(R,J));v(F)&&((F=ed(c.url)?f()[c.xsrfCookieName||a.xsrfCookieName]:w)&&($[c.xsrfHeaderName||a.xsrfHeaderName]=F),e(c.method,R,d, +g,$,c.timeout,c.withCredentials,c.responseType));return J}function y(a,b){0=l&&(u.resolve(q),x(s.$$intervalId),delete g[s.$$intervalId]);t||a.$apply()},k);g[s.$$intervalId]=u;return s}var g={};f.cancel=function(a){return a&&a.$$intervalId in g?(g[a.$$intervalId].reject("canceled"),b.clearInterval(a.$$intervalId),delete g[a.$$intervalId],!0):!1};return f}]}function bc(a){a=a.split("/");for(var b=a.length;b--;)a[b]=qb(a[b]);return a.join("/")}function fd(a,b){var d=wa(a);b.$$protocol=d.protocol;b.$$host=d.hostname; +b.$$port=da(d.port)||dg[d.protocol]||null}function gd(a,b){var d="/"!==a.charAt(0);d&&(a="/"+a);var c=wa(a);b.$$path=decodeURIComponent(d&&"/"===c.pathname.charAt(0)?c.pathname.substring(1):c.pathname);b.$$search=wc(c.search);b.$$hash=decodeURIComponent(c.hash);b.$$path&&"/"!=b.$$path.charAt(0)&&(b.$$path="/"+b.$$path)}function na(a,b){if(0===b.indexOf(a))return b.substr(a.length)}function Ia(a){var b=a.indexOf("#");return-1==b?a:a.substr(0,b)}function jb(a){return a.replace(/(#.+)|#$/,"$1")}function cc(a, +b,d){this.$$html5=!0;d=d||"";fd(a,this);this.$$parse=function(a){var d=na(b,a);if(!D(d))throw Eb("ipthprfx",a,b);gd(d,this);this.$$path||(this.$$path="/");this.$$compose()};this.$$compose=function(){var a=Rb(this.$$search),d=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+(a?"?"+a:"")+d;this.$$absUrl=b+this.$$url.substr(1)};this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;A(f=na(a,c))?(g=f,g=A(f=na(d,f))?b+(na("/",f)||f):a+g):A(f=na(b,c))?g= +b+f:b==c+"/"&&(g=b);g&&this.$$parse(g);return!!g}}function dc(a,b,d){fd(a,this);this.$$parse=function(c){var e=na(a,c)||na(b,c),f;v(e)||"#"!==e.charAt(0)?this.$$html5?f=e:(f="",v(e)&&(a=c,this.replace())):(f=na(d,e),v(f)&&(f=e));gd(f,this);c=this.$$path;var e=a,g=/^\/[A-Z]:(\/.*)/;0===f.indexOf(e)&&(f=f.replace(e,""));g.exec(f)||(c=(f=g.exec(c))?f[1]:c);this.$$path=c;this.$$compose()};this.$$compose=function(){var b=Rb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+ +(b?"?"+b:"")+e;this.$$absUrl=a+(this.$$url?d+this.$$url:"")};this.$$parseLinkUrl=function(b,d){return Ia(a)==Ia(b)?(this.$$parse(b),!0):!1}}function hd(a,b,d){this.$$html5=!0;dc.apply(this,arguments);this.$$parseLinkUrl=function(c,e){if(e&&"#"===e[0])return this.hash(e.slice(1)),!0;var f,g;a==Ia(c)?f=c:(g=na(b,c))?f=a+d+g:b===c+"/"&&(f=b);f&&this.$$parse(f);return!!f};this.$$compose=function(){var b=Rb(this.$$search),e=this.$$hash?"#"+qb(this.$$hash):"";this.$$url=bc(this.$$path)+(b?"?"+b:"")+e;this.$$absUrl= +a+d+this.$$url}}function Fb(a){return function(){return this[a]}}function id(a,b){return function(d){if(v(d))return this[a];this[a]=b(d);this.$$compose();return this}}function nf(){var a="",b={enabled:!1,requireBase:!0,rewriteLinks:!0};this.hashPrefix=function(b){return A(b)?(a=b,this):a};this.html5Mode=function(a){return Ma(a)?(b.enabled=a,this):I(a)?(Ma(a.enabled)&&(b.enabled=a.enabled),Ma(a.requireBase)&&(b.requireBase=a.requireBase),Ma(a.rewriteLinks)&&(b.rewriteLinks=a.rewriteLinks),this):b}; +this.$get=["$rootScope","$browser","$sniffer","$rootElement","$window",function(d,c,e,f,g){function h(a,b,d){var e=l.url(),f=l.$$state;try{c.url(a,b,d),l.$$state=c.state()}catch(g){throw l.url(e),l.$$state=f,g;}}function k(a,b){d.$broadcast("$locationChangeSuccess",l.absUrl(),a,l.$$state,b)}var l,m;m=c.baseHref();var n=c.url(),y;if(b.enabled){if(!m&&b.requireBase)throw Eb("nobase");y=n.substring(0,n.indexOf("/",n.indexOf("//")+2))+(m||"/");m=e.history?cc:hd}else y=Ia(n),m=dc;var B=y.substr(0,Ia(y).lastIndexOf("/")+ +1);l=new m(y,B,"#"+a);l.$$parseLinkUrl(n,n);l.$$state=c.state();var p=/^\s*(javascript|mailto):/i;f.on("click",function(a){if(b.rewriteLinks&&!a.ctrlKey&&!a.metaKey&&!a.shiftKey&&2!=a.which&&2!=a.button){for(var e=G(a.target);"a"!==pa(e[0]);)if(e[0]===f[0]||!(e=e.parent())[0])return;var h=e.prop("href"),k=e.attr("href")||e.attr("xlink:href");I(h)&&"[object SVGAnimatedString]"===h.toString()&&(h=wa(h.animVal).href);p.test(h)||!h||e.attr("target")||a.isDefaultPrevented()||!l.$$parseLinkUrl(h,k)||(a.preventDefault(), +l.absUrl()!=c.url()&&(d.$apply(),g.angular["ff-684208-preventDefault"]=!0))}});jb(l.absUrl())!=jb(n)&&c.url(l.absUrl(),!0);var x=!0;c.onUrlChange(function(a,b){v(na(B,a))?g.location.href=a:(d.$evalAsync(function(){var c=l.absUrl(),e=l.$$state,f;a=jb(a);l.$$parse(a);l.$$state=b;f=d.$broadcast("$locationChangeStart",a,c,b,e).defaultPrevented;l.absUrl()===a&&(f?(l.$$parse(c),l.$$state=e,h(c,!1,e)):(x=!1,k(c,e)))}),d.$$phase||d.$digest())});d.$watch(function(){var a=jb(c.url()),b=jb(l.absUrl()),f=c.state(), +g=l.$$replace,m=a!==b||l.$$html5&&e.history&&f!==l.$$state;if(x||m)x=!1,d.$evalAsync(function(){var b=l.absUrl(),c=d.$broadcast("$locationChangeStart",b,a,l.$$state,f).defaultPrevented;l.absUrl()===b&&(c?(l.$$parse(a),l.$$state=f):(m&&h(b,g,f===l.$$state?null:l.$$state),k(a,f)))});l.$$replace=!1});return l}]}function of(){var a=!0,b=this;this.debugEnabled=function(b){return A(b)?(a=b,this):a};this.$get=["$window",function(d){function c(a){a instanceof Error&&(a.stack?a=a.message&&-1===a.stack.indexOf(a.message)? +"Error: "+a.message+"\n"+a.stack:a.stack:a.sourceURL&&(a=a.message+"\n"+a.sourceURL+":"+a.line));return a}function e(a){var b=d.console||{},e=b[a]||b.log||z;a=!1;try{a=!!e.apply}catch(k){}return a?function(){var a=[];p(arguments,function(b){a.push(c(b))});return e.apply(b,a)}:function(a,b){e(a,null==b?"":b)}}return{log:e("log"),info:e("info"),warn:e("warn"),error:e("error"),debug:function(){var c=e("debug");return function(){a&&c.apply(b,arguments)}}()}}]}function Va(a,b){if("__defineGetter__"=== +a||"__defineSetter__"===a||"__lookupGetter__"===a||"__lookupSetter__"===a||"__proto__"===a)throw U("isecfld",b);return a}function eg(a){return a+""}function xa(a,b){if(a){if(a.constructor===a)throw U("isecfn",b);if(a.window===a)throw U("isecwindow",b);if(a.children&&(a.nodeName||a.prop&&a.attr&&a.find))throw U("isecdom",b);if(a===Object)throw U("isecobj",b);}return a}function jd(a,b){if(a){if(a.constructor===a)throw U("isecfn",b);if(a===fg||a===gg||a===hg)throw U("isecff",b);}}function Gb(a,b){if(a&& +(a===(0).constructor||a===(!1).constructor||a==="".constructor||a==={}.constructor||a===[].constructor||a===Function.constructor))throw U("isecaf",b);}function ig(a,b){return"undefined"!==typeof a?a:b}function kd(a,b){return"undefined"===typeof a?b:"undefined"===typeof b?a:a+b}function V(a,b){var d,c;switch(a.type){case r.Program:d=!0;p(a.body,function(a){V(a.expression,b);d=d&&a.expression.constant});a.constant=d;break;case r.Literal:a.constant=!0;a.toWatch=[];break;case r.UnaryExpression:V(a.argument, +b);a.constant=a.argument.constant;a.toWatch=a.argument.toWatch;break;case r.BinaryExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.left.toWatch.concat(a.right.toWatch);break;case r.LogicalExpression:V(a.left,b);V(a.right,b);a.constant=a.left.constant&&a.right.constant;a.toWatch=a.constant?[]:[a];break;case r.ConditionalExpression:V(a.test,b);V(a.alternate,b);V(a.consequent,b);a.constant=a.test.constant&&a.alternate.constant&&a.consequent.constant;a.toWatch= +a.constant?[]:[a];break;case r.Identifier:a.constant=!1;a.toWatch=[a];break;case r.MemberExpression:V(a.object,b);a.computed&&V(a.property,b);a.constant=a.object.constant&&(!a.computed||a.property.constant);a.toWatch=[a];break;case r.CallExpression:d=a.filter?!b(a.callee.name).$stateful:!1;c=[];p(a.arguments,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=a.filter&&!b(a.callee.name).$stateful?c:[a];break;case r.AssignmentExpression:V(a.left,b);V(a.right, +b);a.constant=a.left.constant&&a.right.constant;a.toWatch=[a];break;case r.ArrayExpression:d=!0;c=[];p(a.elements,function(a){V(a,b);d=d&&a.constant;a.constant||c.push.apply(c,a.toWatch)});a.constant=d;a.toWatch=c;break;case r.ObjectExpression:d=!0;c=[];p(a.properties,function(a){V(a.value,b);d=d&&a.value.constant;a.value.constant||c.push.apply(c,a.value.toWatch)});a.constant=d;a.toWatch=c;break;case r.ThisExpression:a.constant=!1;a.toWatch=[];break;case r.LocalsExpression:a.constant=!1,a.toWatch= +[]}}function ld(a){if(1==a.length){a=a[0].expression;var b=a.toWatch;return 1!==b.length?b:b[0]!==a?b:w}}function md(a){return a.type===r.Identifier||a.type===r.MemberExpression}function nd(a){if(1===a.body.length&&md(a.body[0].expression))return{type:r.AssignmentExpression,left:a.body[0].expression,right:{type:r.NGValueParameter},operator:"="}}function od(a){return 0===a.body.length||1===a.body.length&&(a.body[0].expression.type===r.Literal||a.body[0].expression.type===r.ArrayExpression||a.body[0].expression.type=== +r.ObjectExpression)}function pd(a,b){this.astBuilder=a;this.$filter=b}function qd(a,b){this.astBuilder=a;this.$filter=b}function Hb(a){return"constructor"==a}function ec(a){return H(a.valueOf)?a.valueOf():jg.call(a)}function pf(){var a=X(),b=X(),d={"true":!0,"false":!1,"null":null,undefined:w};this.addLiteral=function(a,b){d[a]=b};this.$get=["$filter",function(c){function e(d,e,g){var y,p,C;g=g||x;switch(typeof d){case "string":C=d=d.trim();var J=g?b:a;y=J[C];if(!y){":"===d.charAt(0)&&":"===d.charAt(1)&& +(p=!0,d=d.substring(2));y=g?K:B;var T=new fc(y);y=(new gc(T,c,y)).parse(d);y.constant?y.$$watchDelegate=m:p?y.$$watchDelegate=y.literal?l:k:y.inputs&&(y.$$watchDelegate=h);g&&(y=f(y));J[C]=y}return n(y,e);case "function":return n(d,e);default:return n(z,e)}}function f(a){function b(c,d,e,f){var g=x;x=!0;try{return a(c,d,e,f)}finally{x=g}}if(!a)return a;b.$$watchDelegate=a.$$watchDelegate;b.assign=f(a.assign);b.constant=a.constant;b.literal=a.literal;for(var c=0;a.inputs&&c=this.promise.$$state.status&&d&&d.length&&a(function(){for(var a,e,f=0,g=d.length;fa)for(b in l++,f)sa.call(e,b)||(u--,delete f[b])}else f!==e&&(f=e,l++);return l}}c.$stateful=!0;var d=this,e,f,h,k=1p&&(A=4-p,w[A]||(w[A]=[]),w[A].push({msg:H(a.exp)?"fn: "+(a.exp.name||a.exp.toString()):a.exp,newVal:g,oldVal:k}));else if(a===c){B=!1;break a}}catch(G){f(G)}if(!(y=M.$$watchersCount&&M.$$childHead||M!==this&&M.$$nextSibling))for(;M!==this&&!(y=M.$$nextSibling);)M=M.$parent}while(M=y);if((B||u.length)&&!p--)throw t.$$phase=null,d("infdig",b,w);}while(B||u.length); +for(t.$$phase=null;s.length;)try{s.shift()()}catch(D){f(D)}},$destroy:function(){if(!this.$$destroyed){var a=this.$parent;this.$broadcast("$destroy");this.$$destroyed=!0;this===t&&h.$$applicationDestroyed();y(this,-this.$$watchersCount);for(var b in this.$$listenerCount)B(this,this.$$listenerCount[b],b);a&&a.$$childHead==this&&(a.$$childHead=this.$$nextSibling);a&&a.$$childTail==this&&(a.$$childTail=this.$$prevSibling);this.$$prevSibling&&(this.$$prevSibling.$$nextSibling=this.$$nextSibling);this.$$nextSibling&& +(this.$$nextSibling.$$prevSibling=this.$$prevSibling);this.$destroy=this.$digest=this.$apply=this.$evalAsync=this.$applyAsync=z;this.$on=this.$watch=this.$watchGroup=function(){return z};this.$$listeners={};this.$$nextSibling=null;l(this)}},$eval:function(a,b){return g(a)(this,b)},$evalAsync:function(a,b){t.$$phase||u.length||h.defer(function(){u.length&&t.$digest()});u.push({scope:this,expression:g(a),locals:b})},$$postDigest:function(a){s.push(a)},$apply:function(a){try{n("$apply");try{return this.$eval(a)}finally{t.$$phase= +null}}catch(b){f(b)}finally{try{t.$digest()}catch(c){throw f(c),c;}}},$applyAsync:function(a){function b(){c.$eval(a)}var c=this;a&&M.push(b);a=g(a);q()},$on:function(a,b){var c=this.$$listeners[a];c||(this.$$listeners[a]=c=[]);c.push(b);var d=this;do d.$$listenerCount[a]||(d.$$listenerCount[a]=0),d.$$listenerCount[a]++;while(d=d.$parent);var e=this;return function(){var d=c.indexOf(b);-1!==d&&(c[d]=null,B(e,1,a))}},$emit:function(a,b){var c=[],d,e=this,g=!1,h={name:a,targetScope:e,stopPropagation:function(){g= +!0},preventDefault:function(){h.defaultPrevented=!0},defaultPrevented:!1},k=bb([h],arguments,1),l,m;do{d=e.$$listeners[a]||c;h.currentScope=e;l=0;for(m=d.length;lza)throw Ba("iequirks");var c=ma(fa);c.isEnabled=function(){return a};c.trustAs=d.trustAs;c.getTrusted=d.getTrusted;c.valueOf=d.valueOf;a||(c.trustAs=c.getTrusted=function(a,b){return b},c.valueOf=Za);c.parseAs=function(a,d){var e=b(d);return e.literal&&e.constant?e:b(d,function(b){return c.getTrusted(a,b)})};var e=c.parseAs,f=c.getTrusted,g=c.trustAs;p(fa,function(a,b){var d=E(b);c[eb("parse_as_"+d)]=function(b){return e(a, +b)};c[eb("get_trusted_"+d)]=function(b){return f(a,b)};c[eb("trust_as_"+d)]=function(b){return g(a,b)}});return c}]}function vf(){this.$get=["$window","$document",function(a,b){var d={},c=da((/android (\d+)/.exec(E((a.navigator||{}).userAgent))||[])[1]),e=/Boxee/i.test((a.navigator||{}).userAgent),f=b[0]||{},g,h=/^(Moz|webkit|ms)(?=[A-Z])/,k=f.body&&f.body.style,l=!1,m=!1;if(k){for(var n in k)if(l=h.exec(n)){g=l[0];g=g.substr(0,1).toUpperCase()+g.substr(1);break}g||(g="WebkitOpacity"in k&&"webkit"); +l=!!("transition"in k||g+"Transition"in k);m=!!("animation"in k||g+"Animation"in k);!c||l&&m||(l=D(k.webkitTransition),m=D(k.webkitAnimation))}return{history:!(!a.history||!a.history.pushState||4>c||e),hasEvent:function(a){if("input"===a&&11>=za)return!1;if(v(d[a])){var b=f.createElement("div");d[a]="on"+a in b}return d[a]},csp:Ea(),vendorPrefix:g,transitions:l,animations:m,android:c}}]}function xf(){var a;this.httpOptions=function(b){return b?(a=b,this):a};this.$get=["$templateCache","$http","$q", +"$sce",function(b,d,c,e){function f(g,h){f.totalPendingRequests++;D(g)&&b.get(g)||(g=e.getTrustedResourceUrl(g));var k=d.defaults&&d.defaults.transformResponse;L(k)?k=k.filter(function(a){return a!==$b}):k===$b&&(k=null);return d.get(g,S({cache:b,transformResponse:k},a))["finally"](function(){f.totalPendingRequests--}).then(function(a){b.put(g,a.data);return a.data},function(a){if(!h)throw lg("tpload",g,a.status,a.statusText);return c.reject(a)})}f.totalPendingRequests=0;return f}]}function yf(){this.$get= +["$rootScope","$browser","$location",function(a,b,d){return{findBindings:function(a,b,d){a=a.getElementsByClassName("ng-binding");var g=[];p(a,function(a){var c=ea.element(a).data("$binding");c&&p(c,function(c){d?(new RegExp("(^|\\s)"+sd(b)+"(\\s|\\||$)")).test(c)&&g.push(a):-1!=c.indexOf(b)&&g.push(a)})});return g},findModels:function(a,b,d){for(var g=["ng-","data-ng-","ng\\:"],h=0;hc&&(c=e),c+=+a.slice(e+1),a=a.substring(0,e)):0>c&&(c=a.length);for(e=0;a.charAt(e)==ic;e++);if(e==(g=a.length))d=[0],c=1;else{for(g--;a.charAt(g)==ic;)g--;c-=e;d=[];for(f=0;e<=g;e++,f++)d[f]=+a.charAt(e)}c>Cd&&(d=d.splice(0,Cd-1),b=c-1, +c=1);return{d:d,e:b,i:c}}function tg(a,b,d,c){var e=a.d,f=e.length-a.i;b=v(b)?Math.min(Math.max(d,f),c):+b;d=b+a.i;c=e[d];if(0d-1){for(c=0;c>d;c--)e.unshift(0),a.i++;e.unshift(1);a.i++}else e[d-1]++;for(;fh;)k.unshift(0),h++;0b.lgSize&&h.unshift(k.splice(-b.lgSize).join(""));k.length>b.gSize;)h.unshift(k.splice(-b.gSize).join(""));k.length&&h.unshift(k.join(""));k=h.join(d);f.length&&(k+=c+f.join(""));e&&(k+="e+"+e)}return 0>a&&!g?b.negPre+ +k+b.negSuf:b.posPre+k+b.posSuf}function Ib(a,b,d,c){var e="";if(0>a||c&&0>=a)c?a=-a+1:(a=-a,e="-");for(a=""+a;a.length-d)f+=d;0===f&&-12==d&&(f=12);return Ib(f,b,c,e)}}function kb(a,b,d){return function(c,e){var f=c["get"+a](),g=ub((d?"STANDALONE":"")+(b?"SHORT":"")+a);return e[g][f]}}function Dd(a){var b=(new Date(a,0,1)).getDay();return new Date(a,0,(4>=b?5:12)-b)}function Ed(a){return function(b){var d= +Dd(b.getFullYear());b=+new Date(b.getFullYear(),b.getMonth(),b.getDate()+(4-b.getDay()))-+d;b=1+Math.round(b/6048E5);return Ib(b,a)}}function jc(a,b){return 0>=a.getFullYear()?b.ERAS[0]:b.ERAS[1]}function xd(a){function b(a){var b;if(b=a.match(d)){a=new Date(0);var f=0,g=0,h=b[8]?a.setUTCFullYear:a.setFullYear,k=b[8]?a.setUTCHours:a.setHours;b[9]&&(f=da(b[9]+b[10]),g=da(b[9]+b[11]));h.call(a,da(b[1]),da(b[2])-1,da(b[3]));f=da(b[4]||0)-f;g=da(b[5]||0)-g;h=da(b[6]||0);b=Math.round(1E3*parseFloat("0."+ +(b[7]||0)));k.call(a,f,g,h,b)}return a}var d=/^(\d{4})-?(\d\d)-?(\d\d)(?:T(\d\d)(?::?(\d\d)(?::?(\d\d)(?:\.(\d+))?)?)?(Z|([+-])(\d\d):?(\d\d))?)?$/;return function(c,d,f){var g="",h=[],k,l;d=d||"mediumDate";d=a.DATETIME_FORMATS[d]||d;D(c)&&(c=ug.test(c)?da(c):b(c));W(c)&&(c=new Date(c));if(!ga(c)||!isFinite(c.getTime()))return c;for(;d;)(l=vg.exec(d))?(h=bb(h,l,1),d=h.pop()):(h.push(d),d=null);var m=c.getTimezoneOffset();f&&(m=uc(f,m),c=Qb(c,f,!0));p(h,function(b){k=wg[b];g+=k?k(c,a.DATETIME_FORMATS, +m):"''"===b?"'":b.replace(/(^'|'$)/g,"").replace(/''/g,"'")});return g}}function ng(){return function(a,b){v(b)&&(b=2);return cb(a,b)}}function og(){return function(a,b,d){b=Infinity===Math.abs(Number(b))?Number(b):da(b);if(isNaN(b))return a;W(a)&&(a=a.toString());if(!L(a)&&!D(a))return a;d=!d||isNaN(d)?0:da(d);d=0>d?Math.max(0,a.length+d):d;return 0<=b?a.slice(d,d+b):0===d?a.slice(b,a.length):a.slice(Math.max(0,d+b),d)}}function zd(a){function b(b,d){d=d?-1:1;return b.map(function(b){var c=1,h=Za; +if(H(b))h=b;else if(D(b)){if("+"==b.charAt(0)||"-"==b.charAt(0))c="-"==b.charAt(0)?-1:1,b=b.substring(1);if(""!==b&&(h=a(b),h.constant))var k=h(),h=function(a){return a[k]}}return{get:h,descending:c*d}})}function d(a){switch(typeof a){case "number":case "boolean":case "string":return!0;default:return!1}}return function(a,e,f){if(null==a)return a;if(!Ca(a))throw O("orderBy")("notarray",a);L(e)||(e=[e]);0===e.length&&(e=["+"]);var g=b(e,f);g.push({get:function(){return{}},descending:f?-1:1});a=Array.prototype.map.call(a, +function(a,b){return{value:a,predicateValues:g.map(function(c){var e=c.get(a);c=typeof e;if(null===e)c="string",e="null";else if("string"===c)e=e.toLowerCase();else if("object"===c)a:{if("function"===typeof e.valueOf&&(e=e.valueOf(),d(e)))break a;if(qc(e)&&(e=e.toString(),d(e)))break a;e=b}return{value:e,type:c}})}});a.sort(function(a,b){for(var c=0,d=0,e=g.length;db||37<=b&&40>=b||m(a,this,this.value)});if(e.hasEvent("paste"))b.on("paste cut",m)}b.on("change",l);if(Hd[g]&&c.$$hasNativeValidators&&g===d.type)b.on("keydown wheel mousedown",function(a){if(!k){var b=this.validity,c=b.badInput,d=b.typeMismatch;k=f.defer(function(){k=null;b.badInput===c&&b.typeMismatch===d||l(a)})}});c.$render=function(){var a=c.$isEmpty(c.$viewValue)? +"":c.$viewValue;b.val()!==a&&b.val(a)}}function Lb(a,b){return function(d,c){var e,f;if(ga(d))return d;if(D(d)){'"'==d.charAt(0)&&'"'==d.charAt(d.length-1)&&(d=d.substring(1,d.length-1));if(xg.test(d))return new Date(d);a.lastIndex=0;if(e=a.exec(d))return e.shift(),f=c?{yyyy:c.getFullYear(),MM:c.getMonth()+1,dd:c.getDate(),HH:c.getHours(),mm:c.getMinutes(),ss:c.getSeconds(),sss:c.getMilliseconds()/1E3}:{yyyy:1970,MM:1,dd:1,HH:0,mm:0,ss:0,sss:0},p(e,function(a,c){c=x};g.$observe("min",function(a){x=y(a);h.$validate()})}if(A(g.max)||g.ngMax){var q;h.$validators.max=function(a){return!n(a)||v(q)||d(a)<=q};g.$observe("max",function(a){q=y(a);h.$validate()})}}}function Id(a,b,d,c){(c.$$hasNativeValidators=I(b[0].validity))&&c.$parsers.push(function(a){var c=b.prop("validity")||{};return c.badInput||c.typeMismatch?w:a})}function Jd(a,b,d,c,e){if(A(c)){a= +a(c);if(!a.constant)throw nb("constexpr",d,c);return a(b)}return e}function lc(a,b){a="ngClass"+a;return["$animate",function(d){function c(a,b){var c=[],d=0;a:for(;d(?:<\/\1>|)$/,Tb=/<|&#?\w+;/,Hf=/<([\w:-]+)/,If=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:-]+)[^>]*)\/>/gi, +ka={option:[1,'"],thead:[1,"","
"],col:[2,"","
"],tr:[2,"","
"],td:[3,"","
"],_default:[0,"",""]};ka.optgroup=ka.option;ka.tbody=ka.tfoot=ka.colgroup=ka.caption=ka.thead;ka.th=ka.td;var Pf=Node.prototype.contains||function(a){return!!(this.compareDocumentPosition(a)&16)},Pa=Y.prototype={ready:function(a){function b(){d||(d=!0,a())}var d=!1;"complete"=== +Q.readyState?setTimeout(b):(this.on("DOMContentLoaded",b),Y(N).on("load",b))},toString:function(){var a=[];p(this,function(b){a.push(""+b)});return"["+a.join(", ")+"]"},eq:function(a){return 0<=a?G(this[a]):G(this[this.length+a])},length:0,push:zg,sort:[].sort,splice:[].splice},Db={};p("multiple selected checked disabled readOnly required open".split(" "),function(a){Db[E(a)]=a});var Rc={};p("input select option textarea button form details".split(" "),function(a){Rc[a]=!0});var Yc={ngMinlength:"minlength", +ngMaxlength:"maxlength",ngMin:"min",ngMax:"max",ngPattern:"pattern"};p({data:Wb,removeData:fb,hasData:function(a){for(var b in gb[a.ng339])return!0;return!1},cleanData:function(a){for(var b=0,d=a.length;b/,Sf=/^[^\(]*\(\s*([^\)]*)\)/m,Ag=/,/,Bg=/^\s*(_?)(\S+?)\1\s*$/,Qf=/((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg,Ga=O("$injector");db.$$annotate=function(a,b,d){var c;if("function"===typeof a){if(!(c=a.$inject)){c=[];if(a.length){if(b)throw D(d)&&d||(d=a.name||Tf(a)),Ga("strictdi",d); +b=Sc(a);p(b[1].split(Ag),function(a){a.replace(Bg,function(a,b,d){c.push(d)})})}a.$inject=c}}else L(a)?(b=a.length-1,Qa(a[b],"fn"),c=a.slice(0,b)):Qa(a,"fn",!0);return c};var Nd=O("$animate"),We=function(){this.$get=z},Xe=function(){var a=new Sa,b=[];this.$get=["$$AnimateRunner","$rootScope",function(d,c){function e(a,b,c){var d=!1;b&&(b=D(b)?b.split(" "):L(b)?b:[],p(b,function(b){b&&(d=!0,a[b]=c)}));return d}function f(){p(b,function(b){var c=a.get(b);if(c){var d=Uf(b.attr("class")),e="",f="";p(c, +function(a,b){a!==!!d[b]&&(a?e+=(e.length?" ":"")+b:f+=(f.length?" ":"")+b)});p(b,function(a){e&&Bb(a,e);f&&Ab(a,f)});a.remove(b)}});b.length=0}return{enabled:z,on:z,off:z,pin:z,push:function(g,h,k,l){l&&l();k=k||{};k.from&&g.css(k.from);k.to&&g.css(k.to);if(k.addClass||k.removeClass)if(h=k.addClass,l=k.removeClass,k=a.get(g)||{},h=e(k,h,!0),l=e(k,l,!1),h||l)a.put(g,k),b.push(g),1===b.length&&c.$$postDigest(f);g=new d;g.complete();return g}}}]},Ue=["$provide",function(a){var b=this;this.$$registeredAnimations= +Object.create(null);this.register=function(d,c){if(d&&"."!==d.charAt(0))throw Nd("notcsel",d);var e=d+"-animation";b.$$registeredAnimations[d.substr(1)]=e;a.factory(e,c)};this.classNameFilter=function(a){if(1===arguments.length&&(this.$$classNameFilter=a instanceof RegExp?a:null)&&/(\s+|\/)ng-animate(\s+|\/)/.test(this.$$classNameFilter.toString()))throw Nd("nongcls","ng-animate");return this.$$classNameFilter};this.$get=["$$animateQueue",function(a){function b(a,c,d){if(d){var h;a:{for(h=0;h <= >= && || ! = |".split(" "),function(a){Mb[a]=!0});var Fg={n:"\n",f:"\f",r:"\r",t:"\t",v:"\v","'":"'",'"':'"'},fc=function(a){this.options= +a};fc.prototype={constructor:fc,lex:function(a){this.text=a;this.index=0;for(this.tokens=[];this.index=a&&"string"===typeof a},isWhitespace:function(a){return" "===a||"\r"===a||"\t"===a||"\n"===a||"\v"===a||"\u00a0"===a},isIdent:function(a){return"a"<= +a&&"z">=a||"A"<=a&&"Z">=a||"_"===a||"$"===a},isExpOperator:function(a){return"-"===a||"+"===a||this.isNumber(a)},throwError:function(a,b,d){d=d||this.index;b=A(b)?"s "+b+"-"+this.index+" ["+this.text.substring(b,d)+"]":" "+d;throw U("lexerr",a,b,this.text);},readNumber:function(){for(var a="",b=this.index;this.index","<=",">=");)a={type:r.BinaryExpression,operator:b.text,left:a,right:this.additive()};return a},additive:function(){for(var a=this.multiplicative(),b;b=this.expect("+","-");)a={type:r.BinaryExpression,operator:b.text,left:a,right:this.multiplicative()};return a},multiplicative:function(){for(var a=this.unary(),b;b=this.expect("*","/","%");)a={type:r.BinaryExpression,operator:b.text,left:a,right:this.unary()};return a},unary:function(){var a;return(a=this.expect("+", +"-","!"))?{type:r.UnaryExpression,operator:a.text,prefix:!0,argument:this.unary()}:this.primary()},primary:function(){var a;this.expect("(")?(a=this.filterChain(),this.consume(")")):this.expect("[")?a=this.arrayDeclaration():this.expect("{")?a=this.object():this.selfReferential.hasOwnProperty(this.peek().text)?a=qa(this.selfReferential[this.consume().text]):this.options.literals.hasOwnProperty(this.peek().text)?a={type:r.Literal,value:this.options.literals[this.consume().text]}:this.peek().identifier? +a=this.identifier():this.peek().constant?a=this.constant():this.throwError("not a primary expression",this.peek());for(var b;b=this.expect("(","[",".");)"("===b.text?(a={type:r.CallExpression,callee:a,arguments:this.parseArguments()},this.consume(")")):"["===b.text?(a={type:r.MemberExpression,object:a,property:this.expression(),computed:!0},this.consume("]")):"."===b.text?a={type:r.MemberExpression,object:a,property:this.identifier(),computed:!1}:this.throwError("IMPOSSIBLE");return a},filter:function(a){a= +[a];for(var b={type:r.CallExpression,callee:this.identifier(),arguments:a,filter:!0};this.expect(":");)a.push(this.expression());return b},parseArguments:function(){var a=[];if(")"!==this.peekToken().text){do a.push(this.expression());while(this.expect(","))}return a},identifier:function(){var a=this.consume();a.identifier||this.throwError("is not a valid identifier",a);return{type:r.Identifier,name:a.text}},constant:function(){return{type:r.Literal,value:this.consume().value}},arrayDeclaration:function(){var a= +[];if("]"!==this.peekToken().text){do{if(this.peek("]"))break;a.push(this.expression())}while(this.expect(","))}this.consume("]");return{type:r.ArrayExpression,elements:a}},object:function(){var a=[],b;if("}"!==this.peekToken().text){do{if(this.peek("}"))break;b={type:r.Property,kind:"init"};this.peek().constant?b.key=this.constant():this.peek().identifier?b.key=this.identifier():this.throwError("invalid key",this.peek());this.consume(":");b.value=this.expression();a.push(b)}while(this.expect(",")) +}this.consume("}");return{type:r.ObjectExpression,properties:a}},throwError:function(a,b){throw U("syntax",b.text,a,b.index+1,this.text,this.text.substring(b.index));},consume:function(a){if(0===this.tokens.length)throw U("ueoe",this.text);var b=this.expect(a);b||this.throwError("is unexpected, expecting ["+a+"]",this.peek());return b},peekToken:function(){if(0===this.tokens.length)throw U("ueoe",this.text);return this.tokens[0]},peek:function(a,b,d,c){return this.peekAhead(0,a,b,d,c)},peekAhead:function(a, +b,d,c,e){if(this.tokens.length>a){a=this.tokens[a];var f=a.text;if(f===b||f===d||f===c||f===e||!(b||d||c||e))return a}return!1},expect:function(a,b,d,c){return(a=this.peek(a,b,d,c))?(this.tokens.shift(),a):!1},selfReferential:{"this":{type:r.ThisExpression},$locals:{type:r.LocalsExpression}}};pd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.state={nextId:0,filters:{},expensiveChecks:b,fn:{vars:[],body:[],own:{}},assign:{vars:[],body:[],own:{}},inputs:[]};V(c,d.$filter); +var e="",f;this.stage="assign";if(f=nd(c))this.state.computing="assign",e=this.nextId(),this.recurse(f,e),this.return_(e),e="fn.assign="+this.generateFunction("assign","s,v,l");f=ld(c.body);d.stage="inputs";p(f,function(a,b){var c="fn"+b;d.state[c]={vars:[],body:[],own:{}};d.state.computing=c;var e=d.nextId();d.recurse(a,e);d.return_(e);d.state.inputs.push(c);a.watchId=b});this.state.computing="fn";this.stage="main";this.recurse(c);e='"'+this.USE+" "+this.STRICT+'";\n'+this.filterPrefix()+"var fn="+ +this.generateFunction("fn","s,l,a,i")+e+this.watchFns()+"return fn;";e=(new Function("$filter","ensureSafeMemberName","ensureSafeObject","ensureSafeFunction","getStringValue","ensureSafeAssignContext","ifDefined","plus","text",e))(this.$filter,Va,xa,jd,eg,Gb,ig,kd,a);this.state=this.stage=w;e.literal=od(c);e.constant=c.constant;return e},USE:"use",STRICT:"strict",watchFns:function(){var a=[],b=this.state.inputs,d=this;p(b,function(b){a.push("var "+b+"="+d.generateFunction(b,"s"))});b.length&&a.push("fn.inputs=["+ +b.join(",")+"];");return a.join("")},generateFunction:function(a,b){return"function("+b+"){"+this.varsPrefix(a)+this.body(a)+"};"},filterPrefix:function(){var a=[],b=this;p(this.state.filters,function(d,c){a.push(d+"=$filter("+b.escape(c)+")")});return a.length?"var "+a.join(",")+";":""},varsPrefix:function(a){return this.state[a].vars.length?"var "+this.state[a].vars.join(",")+";":""},body:function(a){return this.state[a].body.join("")},recurse:function(a,b,d,c,e,f){var g,h,k=this,l,m;c=c||z;if(!f&& +A(a.watchId))b=b||this.nextId(),this.if_("i",this.lazyAssign(b,this.computedMember("i",a.watchId)),this.lazyRecurse(a,b,d,c,e,!0));else switch(a.type){case r.Program:p(a.body,function(b,c){k.recurse(b.expression,w,w,function(a){h=a});c!==a.body.length-1?k.current().body.push(h,";"):k.return_(h)});break;case r.Literal:m=this.escape(a.value);this.assign(b,m);c(m);break;case r.UnaryExpression:this.recurse(a.argument,w,w,function(a){h=a});m=a.operator+"("+this.ifDefined(h,0)+")";this.assign(b,m);c(m); +break;case r.BinaryExpression:this.recurse(a.left,w,w,function(a){g=a});this.recurse(a.right,w,w,function(a){h=a});m="+"===a.operator?this.plus(g,h):"-"===a.operator?this.ifDefined(g,0)+a.operator+this.ifDefined(h,0):"("+g+")"+a.operator+"("+h+")";this.assign(b,m);c(m);break;case r.LogicalExpression:b=b||this.nextId();k.recurse(a.left,b);k.if_("&&"===a.operator?b:k.not(b),k.lazyRecurse(a.right,b));c(b);break;case r.ConditionalExpression:b=b||this.nextId();k.recurse(a.test,b);k.if_(b,k.lazyRecurse(a.alternate, +b),k.lazyRecurse(a.consequent,b));c(b);break;case r.Identifier:b=b||this.nextId();d&&(d.context="inputs"===k.stage?"s":this.assign(this.nextId(),this.getHasOwnProperty("l",a.name)+"?l:s"),d.computed=!1,d.name=a.name);Va(a.name);k.if_("inputs"===k.stage||k.not(k.getHasOwnProperty("l",a.name)),function(){k.if_("inputs"===k.stage||"s",function(){e&&1!==e&&k.if_(k.not(k.nonComputedMember("s",a.name)),k.lazyAssign(k.nonComputedMember("s",a.name),"{}"));k.assign(b,k.nonComputedMember("s",a.name))})},b&& +k.lazyAssign(b,k.nonComputedMember("l",a.name)));(k.state.expensiveChecks||Hb(a.name))&&k.addEnsureSafeObject(b);c(b);break;case r.MemberExpression:g=d&&(d.context=this.nextId())||this.nextId();b=b||this.nextId();k.recurse(a.object,g,w,function(){k.if_(k.notNull(g),function(){e&&1!==e&&k.addEnsureSafeAssignContext(g);if(a.computed)h=k.nextId(),k.recurse(a.property,h),k.getStringValue(h),k.addEnsureSafeMemberName(h),e&&1!==e&&k.if_(k.not(k.computedMember(g,h)),k.lazyAssign(k.computedMember(g,h),"{}")), +m=k.ensureSafeObject(k.computedMember(g,h)),k.assign(b,m),d&&(d.computed=!0,d.name=h);else{Va(a.property.name);e&&1!==e&&k.if_(k.not(k.nonComputedMember(g,a.property.name)),k.lazyAssign(k.nonComputedMember(g,a.property.name),"{}"));m=k.nonComputedMember(g,a.property.name);if(k.state.expensiveChecks||Hb(a.property.name))m=k.ensureSafeObject(m);k.assign(b,m);d&&(d.computed=!1,d.name=a.property.name)}},function(){k.assign(b,"undefined")});c(b)},!!e);break;case r.CallExpression:b=b||this.nextId();a.filter? +(h=k.filter(a.callee.name),l=[],p(a.arguments,function(a){var b=k.nextId();k.recurse(a,b);l.push(b)}),m=h+"("+l.join(",")+")",k.assign(b,m),c(b)):(h=k.nextId(),g={},l=[],k.recurse(a.callee,h,g,function(){k.if_(k.notNull(h),function(){k.addEnsureSafeFunction(h);p(a.arguments,function(a){k.recurse(a,k.nextId(),w,function(a){l.push(k.ensureSafeObject(a))})});g.name?(k.state.expensiveChecks||k.addEnsureSafeObject(g.context),m=k.member(g.context,g.name,g.computed)+"("+l.join(",")+")"):m=h+"("+l.join(",")+ +")";m=k.ensureSafeObject(m);k.assign(b,m)},function(){k.assign(b,"undefined")});c(b)}));break;case r.AssignmentExpression:h=this.nextId();g={};if(!md(a.left))throw U("lval");this.recurse(a.left,w,g,function(){k.if_(k.notNull(g.context),function(){k.recurse(a.right,h);k.addEnsureSafeObject(k.member(g.context,g.name,g.computed));k.addEnsureSafeAssignContext(g.context);m=k.member(g.context,g.name,g.computed)+a.operator+h;k.assign(b,m);c(b||m)})},1);break;case r.ArrayExpression:l=[];p(a.elements,function(a){k.recurse(a, +k.nextId(),w,function(a){l.push(a)})});m="["+l.join(",")+"]";this.assign(b,m);c(m);break;case r.ObjectExpression:l=[];p(a.properties,function(a){k.recurse(a.value,k.nextId(),w,function(b){l.push(k.escape(a.key.type===r.Identifier?a.key.name:""+a.key.value)+":"+b)})});m="{"+l.join(",")+"}";this.assign(b,m);c(m);break;case r.ThisExpression:this.assign(b,"s");c("s");break;case r.LocalsExpression:this.assign(b,"l");c("l");break;case r.NGValueParameter:this.assign(b,"v"),c("v")}},getHasOwnProperty:function(a, +b){var d=a+"."+b,c=this.current().own;c.hasOwnProperty(d)||(c[d]=this.nextId(!1,a+"&&("+this.escape(b)+" in "+a+")"));return c[d]},assign:function(a,b){if(a)return this.current().body.push(a,"=",b,";"),a},filter:function(a){this.state.filters.hasOwnProperty(a)||(this.state.filters[a]=this.nextId(!0));return this.state.filters[a]},ifDefined:function(a,b){return"ifDefined("+a+","+this.escape(b)+")"},plus:function(a,b){return"plus("+a+","+b+")"},return_:function(a){this.current().body.push("return ", +a,";")},if_:function(a,b,d){if(!0===a)b();else{var c=this.current().body;c.push("if(",a,"){");b();c.push("}");d&&(c.push("else{"),d(),c.push("}"))}},not:function(a){return"!("+a+")"},notNull:function(a){return a+"!=null"},nonComputedMember:function(a,b){return a+"."+b},computedMember:function(a,b){return a+"["+b+"]"},member:function(a,b,d){return d?this.computedMember(a,b):this.nonComputedMember(a,b)},addEnsureSafeObject:function(a){this.current().body.push(this.ensureSafeObject(a),";")},addEnsureSafeMemberName:function(a){this.current().body.push(this.ensureSafeMemberName(a), +";")},addEnsureSafeFunction:function(a){this.current().body.push(this.ensureSafeFunction(a),";")},addEnsureSafeAssignContext:function(a){this.current().body.push(this.ensureSafeAssignContext(a),";")},ensureSafeObject:function(a){return"ensureSafeObject("+a+",text)"},ensureSafeMemberName:function(a){return"ensureSafeMemberName("+a+",text)"},ensureSafeFunction:function(a){return"ensureSafeFunction("+a+",text)"},getStringValue:function(a){this.assign(a,"getStringValue("+a+")")},ensureSafeAssignContext:function(a){return"ensureSafeAssignContext("+ +a+",text)"},lazyRecurse:function(a,b,d,c,e,f){var g=this;return function(){g.recurse(a,b,d,c,e,f)}},lazyAssign:function(a,b){var d=this;return function(){d.assign(a,b)}},stringEscapeRegex:/[^ a-zA-Z0-9]/g,stringEscapeFn:function(a){return"\\u"+("0000"+a.charCodeAt(0).toString(16)).slice(-4)},escape:function(a){if(D(a))return"'"+a.replace(this.stringEscapeRegex,this.stringEscapeFn)+"'";if(W(a))return a.toString();if(!0===a)return"true";if(!1===a)return"false";if(null===a)return"null";if("undefined"=== +typeof a)return"undefined";throw U("esc");},nextId:function(a,b){var d="v"+this.state.nextId++;a||this.current().vars.push(d+(b?"="+b:""));return d},current:function(){return this.state[this.state.computing]}};qd.prototype={compile:function(a,b){var d=this,c=this.astBuilder.ast(a);this.expression=a;this.expensiveChecks=b;V(c,d.$filter);var e,f;if(e=nd(c))f=this.recurse(e);e=ld(c.body);var g;e&&(g=[],p(e,function(a,b){var c=d.recurse(a);a.input=c;g.push(c);a.watchId=b}));var h=[];p(c.body,function(a){h.push(d.recurse(a.expression))}); +e=0===c.body.length?z:1===c.body.length?h[0]:function(a,b){var c;p(h,function(d){c=d(a,b)});return c};f&&(e.assign=function(a,b,c){return f(a,c,b)});g&&(e.inputs=g);e.literal=od(c);e.constant=c.constant;return e},recurse:function(a,b,d){var c,e,f=this,g;if(a.input)return this.inputs(a.input,a.watchId);switch(a.type){case r.Literal:return this.value(a.value,b);case r.UnaryExpression:return e=this.recurse(a.argument),this["unary"+a.operator](e,b);case r.BinaryExpression:return c=this.recurse(a.left), +e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case r.LogicalExpression:return c=this.recurse(a.left),e=this.recurse(a.right),this["binary"+a.operator](c,e,b);case r.ConditionalExpression:return this["ternary?:"](this.recurse(a.test),this.recurse(a.alternate),this.recurse(a.consequent),b);case r.Identifier:return Va(a.name,f.expression),f.identifier(a.name,f.expensiveChecks||Hb(a.name),b,d,f.expression);case r.MemberExpression:return c=this.recurse(a.object,!1,!!d),a.computed||(Va(a.property.name, +f.expression),e=a.property.name),a.computed&&(e=this.recurse(a.property)),a.computed?this.computedMember(c,e,b,d,f.expression):this.nonComputedMember(c,e,f.expensiveChecks,b,d,f.expression);case r.CallExpression:return g=[],p(a.arguments,function(a){g.push(f.recurse(a))}),a.filter&&(e=this.$filter(a.callee.name)),a.filter||(e=this.recurse(a.callee,!0)),a.filter?function(a,c,d,f){for(var n=[],p=0;p":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>b(c,e,f,g);return d?{value:c}:c}},"binary<=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f, +g)<=b(c,e,f,g);return d?{value:c}:c}},"binary>=":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)>=b(c,e,f,g);return d?{value:c}:c}},"binary&&":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)&&b(c,e,f,g);return d?{value:c}:c}},"binary||":function(a,b,d){return function(c,e,f,g){c=a(c,e,f,g)||b(c,e,f,g);return d?{value:c}:c}},"ternary?:":function(a,b,d,c){return function(e,f,g,h){e=a(e,f,g,h)?b(e,f,g,h):d(e,f,g,h);return c?{value:e}:e}},value:function(a,b){return function(){return b?{context:w, +name:w,value:a}:a}},identifier:function(a,b,d,c,e){return function(f,g,h,k){f=g&&a in g?g:f;c&&1!==c&&f&&!f[a]&&(f[a]={});g=f?f[a]:w;b&&xa(g,e);return d?{context:f,name:a,value:g}:g}},computedMember:function(a,b,d,c,e){return function(f,g,h,k){var l=a(f,g,h,k),m,n;null!=l&&(m=b(f,g,h,k),m+="",Va(m,e),c&&1!==c&&(Gb(l),l&&!l[m]&&(l[m]={})),n=l[m],xa(n,e));return d?{context:l,name:m,value:n}:n}},nonComputedMember:function(a,b,d,c,e,f){return function(g,h,k,l){g=a(g,h,k,l);e&&1!==e&&(Gb(g),g&&!g[b]&& +(g[b]={}));h=null!=g?g[b]:w;(d||Hb(b))&&xa(h,f);return c?{context:g,name:b,value:h}:h}},inputs:function(a,b){return function(d,c,e,f){return f?f[b]:a(d,c,e)}}};var gc=function(a,b,d){this.lexer=a;this.$filter=b;this.options=d;this.ast=new r(a,d);this.astCompiler=d.csp?new qd(this.ast,b):new pd(this.ast,b)};gc.prototype={constructor:gc,parse:function(a){return this.astCompiler.compile(a,this.options.expensiveChecks)}};var jg=Object.prototype.valueOf,Ba=O("$sce"),fa={HTML:"html",CSS:"css",URL:"url", +RESOURCE_URL:"resourceUrl",JS:"js"},lg=O("$compile"),aa=Q.createElement("a"),ud=wa(N.location.href);vd.$inject=["$document"];Ic.$inject=["$provide"];var Cd=22,Bd=".",ic="0";wd.$inject=["$locale"];yd.$inject=["$locale"];var wg={yyyy:ba("FullYear",4,0,!1,!0),yy:ba("FullYear",2,0,!0,!0),y:ba("FullYear",1,0,!1,!0),MMMM:kb("Month"),MMM:kb("Month",!0),MM:ba("Month",2,1),M:ba("Month",1,1),LLLL:kb("Month",!1,!0),dd:ba("Date",2),d:ba("Date",1),HH:ba("Hours",2),H:ba("Hours",1),hh:ba("Hours",2,-12),h:ba("Hours", +1,-12),mm:ba("Minutes",2),m:ba("Minutes",1),ss:ba("Seconds",2),s:ba("Seconds",1),sss:ba("Milliseconds",3),EEEE:kb("Day"),EEE:kb("Day",!0),a:function(a,b){return 12>a.getHours()?b.AMPMS[0]:b.AMPMS[1]},Z:function(a,b,d){a=-1*d;return a=(0<=a?"+":"")+(Ib(Math[0=a.getFullYear()?b.ERANAMES[0]:b.ERANAMES[1]}},vg=/((?:[^yMLdHhmsaZEwG']+)|(?:'(?:[^']|'')*')|(?:E+|y+|M+|L+|d+|H+|h+|m+|s+|a|Z|G+|w+))(.*)/, +ug=/^\-?\d+$/;xd.$inject=["$locale"];var pg=ia(E),qg=ia(ub);zd.$inject=["$parse"];var ke=ia({restrict:"E",compile:function(a,b){if(!b.href&&!b.xlinkHref)return function(a,b){if("a"===b[0].nodeName.toLowerCase()){var e="[object SVGAnimatedString]"===ha.call(b.prop("href"))?"xlink:href":"href";b.on("click",function(a){b.attr(e)||a.preventDefault()})}}}}),vb={};p(Db,function(a,b){function d(a,d,e){a.$watch(e[c],function(a){e.$set(b,!!a)})}if("multiple"!=a){var c=ua("ng-"+b),e=d;"checked"===a&&(e=function(a, +b,e){e.ngModel!==e[c]&&d(a,b,e)});vb[c]=function(){return{restrict:"A",priority:100,link:e}}}});p(Yc,function(a,b){vb[b]=function(){return{priority:100,link:function(a,c,e){if("ngPattern"===b&&"/"==e.ngPattern.charAt(0)&&(c=e.ngPattern.match(yg))){e.$set("ngPattern",new RegExp(c[1],c[2]));return}a.$watch(e[b],function(a){e.$set(b,a)})}}}});p(["src","srcset","href"],function(a){var b=ua("ng-"+a);vb[b]=function(){return{priority:99,link:function(d,c,e){var f=a,g=a;"href"===a&&"[object SVGAnimatedString]"=== +ha.call(c.prop("href"))&&(g="xlinkHref",e.$attr[g]="xlink:href",f=null);e.$observe(b,function(b){b?(e.$set(g,b),za&&f&&c.prop(f,e[g])):"href"===a&&e.$set(g,null)})}}}});var Jb={$addControl:z,$$renameControl:function(a,b){a.$name=b},$removeControl:z,$setValidity:z,$setDirty:z,$setPristine:z,$setSubmitted:z};Fd.$inject=["$element","$attrs","$scope","$animate","$interpolate"];var Od=function(a){return["$timeout","$parse",function(b,d){function c(a){return""===a?d('this[""]').assign:d(a).assign||z}return{name:"form", +restrict:a?"EAC":"E",require:["form","^^?form"],controller:Fd,compile:function(d,f){d.addClass(Wa).addClass(ob);var g=f.name?"name":a&&f.ngForm?"ngForm":!1;return{pre:function(a,d,e,f){var n=f[0];if(!("action"in e)){var p=function(b){a.$apply(function(){n.$commitViewValue();n.$setSubmitted()});b.preventDefault()};d[0].addEventListener("submit",p,!1);d.on("$destroy",function(){b(function(){d[0].removeEventListener("submit",p,!1)},0,!1)})}(f[1]||n.$$parentForm).$addControl(n);var r=g?c(n.$name):z;g&& +(r(a,n),e.$observe(g,function(b){n.$name!==b&&(r(a,w),n.$$parentForm.$$renameControl(n,b),r=c(n.$name),r(a,n))}));d.on("$destroy",function(){n.$$parentForm.$removeControl(n);r(a,w);S(n,Jb)})}}}}}]},le=Od(),ye=Od(!0),xg=/^\d{4,}-[01]\d-[0-3]\dT[0-2]\d:[0-5]\d:[0-5]\d\.\d+(?:[+-][0-2]\d:[0-5]\d|Z)$/,Gg=/^[a-z][a-z\d.+-]*:\/*(?:[^:@]+(?::[^@]+)?@)?(?:[^\s:/?#]+|\[[a-f\d:]+\])(?::\d+)?(?:\/[^?#]*)?(?:\?[^#]*)?(?:#.*)?$/i,Hg=/^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i, +Ig=/^\s*(\-|\+)?(\d+|(\d*(\.\d*)))([eE][+-]?\d+)?\s*$/,Pd=/^(\d{4,})-(\d{2})-(\d{2})$/,Qd=/^(\d{4,})-(\d\d)-(\d\d)T(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,mc=/^(\d{4,})-W(\d\d)$/,Rd=/^(\d{4,})-(\d\d)$/,Sd=/^(\d\d):(\d\d)(?::(\d\d)(\.\d{1,3})?)?$/,Hd=X();p(["date","datetime-local","month","time","week"],function(a){Hd[a]=!0});var Td={text:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);kc(c)},date:mb("date",Pd,Lb(Pd,["yyyy","MM","dd"]),"yyyy-MM-dd"),"datetime-local":mb("datetimelocal",Qd,Lb(Qd,"yyyy MM dd HH mm ss sss".split(" ")), +"yyyy-MM-ddTHH:mm:ss.sss"),time:mb("time",Sd,Lb(Sd,["HH","mm","ss","sss"]),"HH:mm:ss.sss"),week:mb("week",mc,function(a,b){if(ga(a))return a;if(D(a)){mc.lastIndex=0;var d=mc.exec(a);if(d){var c=+d[1],e=+d[2],f=d=0,g=0,h=0,k=Dd(c),e=7*(e-1);b&&(d=b.getHours(),f=b.getMinutes(),g=b.getSeconds(),h=b.getMilliseconds());return new Date(c,0,k.getDate()+e,d,f,g,h)}}return NaN},"yyyy-Www"),month:mb("month",Rd,Lb(Rd,["yyyy","MM"]),"yyyy-MM"),number:function(a,b,d,c,e,f){Id(a,b,d,c);lb(a,b,d,c,e,f);c.$$parserName= +"number";c.$parsers.push(function(a){return c.$isEmpty(a)?null:Ig.test(a)?parseFloat(a):w});c.$formatters.push(function(a){if(!c.$isEmpty(a)){if(!W(a))throw nb("numfmt",a);a=a.toString()}return a});if(A(d.min)||d.ngMin){var g;c.$validators.min=function(a){return c.$isEmpty(a)||v(g)||a>=g};d.$observe("min",function(a){A(a)&&!W(a)&&(a=parseFloat(a,10));g=W(a)&&!isNaN(a)?a:w;c.$validate()})}if(A(d.max)||d.ngMax){var h;c.$validators.max=function(a){return c.$isEmpty(a)||v(h)||a<=h};d.$observe("max",function(a){A(a)&& +!W(a)&&(a=parseFloat(a,10));h=W(a)&&!isNaN(a)?a:w;c.$validate()})}},url:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);kc(c);c.$$parserName="url";c.$validators.url=function(a,b){var d=a||b;return c.$isEmpty(d)||Gg.test(d)}},email:function(a,b,d,c,e,f){lb(a,b,d,c,e,f);kc(c);c.$$parserName="email";c.$validators.email=function(a,b){var d=a||b;return c.$isEmpty(d)||Hg.test(d)}},radio:function(a,b,d,c){v(d.name)&&b.attr("name",++pb);b.on("click",function(a){b[0].checked&&c.$setViewValue(d.value,a&&a.type)});c.$render= +function(){b[0].checked=d.value==c.$viewValue};d.$observe("value",c.$render)},checkbox:function(a,b,d,c,e,f,g,h){var k=Jd(h,a,"ngTrueValue",d.ngTrueValue,!0),l=Jd(h,a,"ngFalseValue",d.ngFalseValue,!1);b.on("click",function(a){c.$setViewValue(b[0].checked,a&&a.type)});c.$render=function(){b[0].checked=c.$viewValue};c.$isEmpty=function(a){return!1===a};c.$formatters.push(function(a){return oa(a,k)});c.$parsers.push(function(a){return a?k:l})},hidden:z,button:z,submit:z,reset:z,file:z},Cc=["$browser", +"$sniffer","$filter","$parse",function(a,b,d,c){return{restrict:"E",require:["?ngModel"],link:{pre:function(e,f,g,h){h[0]&&(Td[E(g.type)]||Td.text)(e,f,g,h[0],b,a,d,c)}}}}],Jg=/^(true|false|\d+)$/,Qe=function(){return{restrict:"A",priority:100,compile:function(a,b){return Jg.test(b.ngValue)?function(a,b,e){e.$set("value",a.$eval(e.ngValue))}:function(a,b,e){a.$watch(e.ngValue,function(a){e.$set("value",a)})}}}},qe=["$compile",function(a){return{restrict:"AC",compile:function(b){a.$$addBindingClass(b); +return function(b,c,e){a.$$addBindingInfo(c,e.ngBind);c=c[0];b.$watch(e.ngBind,function(a){c.textContent=v(a)?"":a})}}}}],se=["$interpolate","$compile",function(a,b){return{compile:function(d){b.$$addBindingClass(d);return function(c,d,f){c=a(d.attr(f.$attr.ngBindTemplate));b.$$addBindingInfo(d,c.expressions);d=d[0];f.$observe("ngBindTemplate",function(a){d.textContent=v(a)?"":a})}}}}],re=["$sce","$parse","$compile",function(a,b,d){return{restrict:"A",compile:function(c,e){var f=b(e.ngBindHtml),g= +b(e.ngBindHtml,function(a){return(a||"").toString()});d.$$addBindingClass(c);return function(b,c,e){d.$$addBindingInfo(c,e.ngBindHtml);b.$watch(g,function(){c.html(a.getTrustedHtml(f(b))||"")})}}}}],Pe=ia({restrict:"A",require:"ngModel",link:function(a,b,d,c){c.$viewChangeListeners.push(function(){a.$eval(d.ngChange)})}}),te=lc("",!0),ve=lc("Odd",0),ue=lc("Even",1),we=La({compile:function(a,b){b.$set("ngCloak",w);a.removeClass("ng-cloak")}}),xe=[function(){return{restrict:"A",scope:!0,controller:"@", +priority:500}}],Hc={},Kg={blur:!0,focus:!0};p("click dblclick mousedown mouseup mouseover mouseout mousemove mouseenter mouseleave keydown keyup keypress submit focus blur copy cut paste".split(" "),function(a){var b=ua("ng-"+a);Hc[b]=["$parse","$rootScope",function(d,c){return{restrict:"A",compile:function(e,f){var g=d(f[b],null,!0);return function(b,d){d.on(a,function(d){var e=function(){g(b,{$event:d})};Kg[a]&&c.$$phase?b.$evalAsync(e):b.$apply(e)})}}}}]});var Ae=["$animate","$compile",function(a, +b){return{multiElement:!0,transclude:"element",priority:600,terminal:!0,restrict:"A",$$tlb:!0,link:function(d,c,e,f,g){var h,k,l;d.$watch(e.ngIf,function(d){d?k||g(function(d,f){k=f;d[d.length++]=b.$$createComment("end ngIf",e.ngIf);h={clone:d};a.enter(d,c.parent(),c)}):(l&&(l.remove(),l=null),k&&(k.$destroy(),k=null),h&&(l=tb(h.clone),a.leave(l).then(function(){l=null}),h=null))})}}}],Be=["$templateRequest","$anchorScroll","$animate",function(a,b,d){return{restrict:"ECA",priority:400,terminal:!0, +transclude:"element",controller:ea.noop,compile:function(c,e){var f=e.ngInclude||e.src,g=e.onload||"",h=e.autoscroll;return function(c,e,m,n,p){var r=0,w,x,q,t=function(){x&&(x.remove(),x=null);w&&(w.$destroy(),w=null);q&&(d.leave(q).then(function(){x=null}),x=q,q=null)};c.$watch(f,function(f){var m=function(){!A(h)||h&&!c.$eval(h)||b()},x=++r;f?(a(f,!0).then(function(a){if(!c.$$destroyed&&x===r){var b=c.$new();n.template=a;a=p(b,function(a){t();d.enter(a,null,e).then(m)});w=b;q=a;w.$emit("$includeContentLoaded", +f);c.$eval(g)}},function(){c.$$destroyed||x!==r||(t(),c.$emit("$includeContentError",f))}),c.$emit("$includeContentRequested",f)):(t(),n.template=null)})}}}}],Se=["$compile",function(a){return{restrict:"ECA",priority:-400,require:"ngInclude",link:function(b,d,c,e){ha.call(d[0]).match(/SVG/)?(d.empty(),a(Kc(e.template,Q).childNodes)(b,function(a){d.append(a)},{futureParentElement:d})):(d.html(e.template),a(d.contents())(b))}}}],Ce=La({priority:450,compile:function(){return{pre:function(a,b,d){a.$eval(d.ngInit)}}}}), +Oe=function(){return{restrict:"A",priority:100,require:"ngModel",link:function(a,b,d,c){var e=b.attr(d.$attr.ngList)||", ",f="false"!==d.ngTrim,g=f?Z(e):e;c.$parsers.push(function(a){if(!v(a)){var b=[];a&&p(a.split(g),function(a){a&&b.push(f?Z(a):a)});return b}});c.$formatters.push(function(a){return L(a)?a.join(e):w});c.$isEmpty=function(a){return!a||!a.length}}}},ob="ng-valid",Kd="ng-invalid",Wa="ng-pristine",Kb="ng-dirty",Md="ng-pending",nb=O("ngModel"),Lg=["$scope","$exceptionHandler","$attrs", +"$element","$parse","$animate","$timeout","$rootScope","$q","$interpolate",function(a,b,d,c,e,f,g,h,k,l){this.$modelValue=this.$viewValue=Number.NaN;this.$$rawModelValue=w;this.$validators={};this.$asyncValidators={};this.$parsers=[];this.$formatters=[];this.$viewChangeListeners=[];this.$untouched=!0;this.$touched=!1;this.$pristine=!0;this.$dirty=!1;this.$valid=!0;this.$invalid=!1;this.$error={};this.$$success={};this.$pending=w;this.$name=l(d.name||"",!1)(a);this.$$parentForm=Jb;var m=e(d.ngModel), +n=m.assign,r=m,B=n,K=null,x,q=this;this.$$setOptions=function(a){if((q.$options=a)&&a.getterSetter){var b=e(d.ngModel+"()"),f=e(d.ngModel+"($$$p)");r=function(a){var c=m(a);H(c)&&(c=b(a));return c};B=function(a,b){H(m(a))?f(a,{$$$p:b}):n(a,b)}}else if(!m.assign)throw nb("nonassign",d.ngModel,ta(c));};this.$render=z;this.$isEmpty=function(a){return v(a)||""===a||null===a||a!==a};this.$$updateEmptyClasses=function(a){q.$isEmpty(a)?(f.removeClass(c,"ng-not-empty"),f.addClass(c,"ng-empty")):(f.removeClass(c, +"ng-empty"),f.addClass(c,"ng-not-empty"))};var t=0;Gd({ctrl:this,$element:c,set:function(a,b){a[b]=!0},unset:function(a,b){delete a[b]},$animate:f});this.$setPristine=function(){q.$dirty=!1;q.$pristine=!0;f.removeClass(c,Kb);f.addClass(c,Wa)};this.$setDirty=function(){q.$dirty=!0;q.$pristine=!1;f.removeClass(c,Wa);f.addClass(c,Kb);q.$$parentForm.$setDirty()};this.$setUntouched=function(){q.$touched=!1;q.$untouched=!0;f.setClass(c,"ng-untouched","ng-touched")};this.$setTouched=function(){q.$touched= +!0;q.$untouched=!1;f.setClass(c,"ng-touched","ng-untouched")};this.$rollbackViewValue=function(){g.cancel(K);q.$viewValue=q.$$lastCommittedViewValue;q.$render()};this.$validate=function(){if(!W(q.$modelValue)||!isNaN(q.$modelValue)){var a=q.$$rawModelValue,b=q.$valid,c=q.$modelValue,d=q.$options&&q.$options.allowInvalid;q.$$runValidators(a,q.$$lastCommittedViewValue,function(e){d||b===e||(q.$modelValue=e?a:w,q.$modelValue!==c&&q.$$writeModelToScope())})}};this.$$runValidators=function(a,b,c){function d(){var c= +!0;p(q.$validators,function(d,e){var g=d(a,b);c=c&&g;f(e,g)});return c?!0:(p(q.$asyncValidators,function(a,b){f(b,null)}),!1)}function e(){var c=[],d=!0;p(q.$asyncValidators,function(e,g){var h=e(a,b);if(!h||!H(h.then))throw nb("nopromise",h);f(g,w);c.push(h.then(function(){f(g,!0)},function(){d=!1;f(g,!1)}))});c.length?k.all(c).then(function(){g(d)},z):g(!0)}function f(a,b){h===t&&q.$setValidity(a,b)}function g(a){h===t&&c(a)}t++;var h=t;(function(){var a=q.$$parserName||"parse";if(v(x))f(a,null); +else return x||(p(q.$validators,function(a,b){f(b,null)}),p(q.$asyncValidators,function(a,b){f(b,null)})),f(a,x),x;return!0})()?d()?e():g(!1):g(!1)};this.$commitViewValue=function(){var a=q.$viewValue;g.cancel(K);if(q.$$lastCommittedViewValue!==a||""===a&&q.$$hasNativeValidators)q.$$updateEmptyClasses(a),q.$$lastCommittedViewValue=a,q.$pristine&&this.$setDirty(),this.$$parseAndValidate()};this.$$parseAndValidate=function(){var b=q.$$lastCommittedViewValue;if(x=v(b)?w:!0)for(var c=0;ce||c.$isEmpty(b)|| +b.length<=e}}}}},Fc=function(){return{restrict:"A",require:"?ngModel",link:function(a,b,d,c){if(c){var e=0;d.$observe("minlength",function(a){e=da(a)||0;c.$validate()});c.$validators.minlength=function(a,b){return c.$isEmpty(b)||b.length>=e}}}}};N.angular.bootstrap?N.console&&console.log("WARNING: Tried to load angular more than once."):(fe(),he(ea),ea.module("ngLocale",[],["$provide",function(a){function b(a){a+="";var b=a.indexOf(".");return-1==b?0:a.length-b-1}a.value("$locale",{DATETIME_FORMATS:{AMPMS:["AM", +"PM"],DAY:"Sunday Monday Tuesday Wednesday Thursday Friday Saturday".split(" "),ERANAMES:["Before Christ","Anno Domini"],ERAS:["BC","AD"],FIRSTDAYOFWEEK:6,MONTH:"January February March April May June July August September October November December".split(" "),SHORTDAY:"Sun Mon Tue Wed Thu Fri Sat".split(" "),SHORTMONTH:"Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec".split(" "),STANDALONEMONTH:"January February March April May June July August September October November December".split(" "),WEEKENDRANGE:[5, +6],fullDate:"EEEE, MMMM d, y",longDate:"MMMM d, y",medium:"MMM d, y h:mm:ss a",mediumDate:"MMM d, y",mediumTime:"h:mm:ss a","short":"M/d/yy h:mm a",shortDate:"M/d/yy",shortTime:"h:mm a"},NUMBER_FORMATS:{CURRENCY_SYM:"$",DECIMAL_SEP:".",GROUP_SEP:",",PATTERNS:[{gSize:3,lgSize:3,maxFrac:3,minFrac:0,minInt:1,negPre:"-",negSuf:"",posPre:"",posSuf:""},{gSize:3,lgSize:3,maxFrac:2,minFrac:2,minInt:1,negPre:"-\u00a4",negSuf:"",posPre:"\u00a4",posSuf:""}]},id:"en-us",localeID:"en_US",pluralCat:function(a, +c){var e=a|0,f=c;w===f&&(f=Math.min(b(a),3));Math.pow(10,f);return 1==e&&0==f?"one":"other"}})}]),G(Q).ready(function(){be(Q,xc)}))})(window,document);!window.angular.$$csp().noInlineStyle&&window.angular.element(document.head).prepend(''); +//# sourceMappingURL=angular.min.js.map diff --git a/public/js/index.js b/public/js/index.js new file mode 100644 index 000000000..5c0c9324d --- /dev/null +++ b/public/js/index.js @@ -0,0 +1 @@ +!function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a="function"==typeof require&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}for(var i="function"==typeof require&&require,o=0;oi;i++)row=result[i],results.push(row.attributes);return results}(),$scope.loaded=!0,$scope.$apply()})}},{"../../parse/class/Meetup":4}],3:[function(require,module,exports){var Mentor;Mentor=require("../../parse/class/Mentor"),module.exports=function($scope,$http){return $scope.loaded=!1,$scope.mentors=[],new Parse.Query(Mentor).ascending("order").find().then(function(result){var row;return $scope.mentors=function(){var i,len,results;for(results=[],i=0,len=result.length;len>i;i++)row=result[i],results.push(row.attributes);return results}(),$scope.loaded=!0,$scope.$apply()})}},{"../../parse/class/Mentor":5}],4:[function(require,module,exports){module.exports=Parse.Object.extend("Meetup",{initialize:function(attrs){return attrs&&this.setValues(attrs),this},setValues:function(attrs){var ref,ref1,ref2;return(null!=attrs.source||null!=attrs.id)&&this.set("source",attrs.source||attrs.id),null!=attrs.name&&this.set("name",attrs.name),null!=attrs.location&&this.set("location",attrs.location),((null!=(ref=attrs.venue)?ref.id:void 0)||attrs.venue_id)&&this.set("venue_id",(null!=(ref1=attrs.venue)?ref1.id:void 0)||attrs.venue_id),null!=attrs.description&&this.set("description",attrs.description),null!=attrs.start_time&&("string"==typeof attrs.start_time?this.set("start_time",new Date(attrs.start_time)):this.set("start_time",attrs.start_time)),null!=attrs.end_time&&("string"==typeof attrs.end_time?this.set("end_time",new Date(attrs.end_time)):this.set("end_time",attrs.end_time)),null!=(null!=(ref2=attrs.cover)?ref2.source:void 0)&&this.set("cover_source",attrs.cover.source),this}})},{}],5:[function(require,module,exports){module.exports=Parse.Object.extend("Mentor",{initialize:function(attrs){return attrs&&this.setValues(attrs),this},setValues:function(attrs){return(null!=attrs.source||null!=attrs.id)&&this.set("source",attrs.source||attrs.id),null!=attrs.last_name&&this.set("last_name",attrs.last_name),null!=attrs.first_name&&this.set("first_name",attrs.first_name),null!=attrs.description&&this.set("description",attrs.description),null!=attrs.last_attended&&("string"==typeof attrs.last_attended?this.set("last_attended",new Date(attrs.last_attended)):this.set("last_attended",attrs.last_attended)),this}})},{}]},{},[1]); diff --git a/public/js/parse.js b/public/js/parse.js new file mode 100644 index 000000000..70edf5939 --- /dev/null +++ b/public/js/parse.js @@ -0,0 +1,9629 @@ +/*! + * Parse JavaScript SDK + * Version: 1.5.0 + * Built: Fri Jul 10 2015 17:05:46 + * http://parse.com + * + * Copyright 2015 Parse, LLC + * + * Includes: Underscore.js + * Copyright 2009-2012 Jeremy Ashkenas, DocumentCloud Inc. + * Released under the MIT license. + */ +(function(root) { + root.Parse = root.Parse || {}; + root.Parse.VERSION = "js1.5.0"; +}(this)); +// Underscore.js 1.4.4 +// http://underscorejs.org +// (c) 2009-2013 Jeremy Ashkenas, DocumentCloud Inc. +// Underscore may be freely distributed under the MIT license. + +(function() { + + // Baseline setup + // -------------- + + // Establish the root object, `window` in the browser, or `global` on the server. + var root = this; + + // Save the previous value of the `_` variable. + var previousUnderscore = root._; + + // Establish the object that gets returned to break out of a loop iteration. + var breaker = {}; + + // Save bytes in the minified (but not gzipped) version: + var ArrayProto = Array.prototype, ObjProto = Object.prototype, FuncProto = Function.prototype; + + // Create quick reference variables for speed access to core prototypes. + var push = ArrayProto.push, + slice = ArrayProto.slice, + concat = ArrayProto.concat, + toString = ObjProto.toString, + hasOwnProperty = ObjProto.hasOwnProperty; + + // All **ECMAScript 5** native function implementations that we hope to use + // are declared here. + var + nativeForEach = ArrayProto.forEach, + nativeMap = ArrayProto.map, + nativeReduce = ArrayProto.reduce, + nativeReduceRight = ArrayProto.reduceRight, + nativeFilter = ArrayProto.filter, + nativeEvery = ArrayProto.every, + nativeSome = ArrayProto.some, + nativeIndexOf = ArrayProto.indexOf, + nativeLastIndexOf = ArrayProto.lastIndexOf, + nativeIsArray = Array.isArray, + nativeKeys = Object.keys, + nativeBind = FuncProto.bind; + + // Create a safe reference to the Underscore object for use below. + var _ = function(obj) { + if (obj instanceof _) return obj; + if (!(this instanceof _)) return new _(obj); + this._wrapped = obj; + }; + + // Export the Underscore object for **Node.js**, with + // backwards-compatibility for the old `require()` API. If we're in + // the browser, add `_` as a global object via a string identifier, + // for Closure Compiler "advanced" mode. + if (typeof exports !== 'undefined') { + if (typeof module !== 'undefined' && module.exports) { + exports = module.exports = _; + } + exports._ = _; + } else { + root._ = _; + } + + // Current version. + _.VERSION = '1.4.4'; + + // Collection Functions + // -------------------- + + // The cornerstone, an `each` implementation, aka `forEach`. + // Handles objects with the built-in `forEach`, arrays, and raw objects. + // Delegates to **ECMAScript 5**'s native `forEach` if available. + var each = _.each = _.forEach = function(obj, iterator, context) { + if (obj == null) return; + if (nativeForEach && obj.forEach === nativeForEach) { + obj.forEach(iterator, context); + } else if (obj.length === +obj.length) { + for (var i = 0, l = obj.length; i < l; i++) { + if (iterator.call(context, obj[i], i, obj) === breaker) return; + } + } else { + for (var key in obj) { + if (_.has(obj, key)) { + if (iterator.call(context, obj[key], key, obj) === breaker) return; + } + } + } + }; + + // Return the results of applying the iterator to each element. + // Delegates to **ECMAScript 5**'s native `map` if available. + _.map = _.collect = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeMap && obj.map === nativeMap) return obj.map(iterator, context); + each(obj, function(value, index, list) { + results[results.length] = iterator.call(context, value, index, list); + }); + return results; + }; + + var reduceError = 'Reduce of empty array with no initial value'; + + // **Reduce** builds up a single result from a list of values, aka `inject`, + // or `foldl`. Delegates to **ECMAScript 5**'s native `reduce` if available. + _.reduce = _.foldl = _.inject = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduce && obj.reduce === nativeReduce) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduce(iterator, memo) : obj.reduce(iterator); + } + each(obj, function(value, index, list) { + if (!initial) { + memo = value; + initial = true; + } else { + memo = iterator.call(context, memo, value, index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // The right-associative version of reduce, also known as `foldr`. + // Delegates to **ECMAScript 5**'s native `reduceRight` if available. + _.reduceRight = _.foldr = function(obj, iterator, memo, context) { + var initial = arguments.length > 2; + if (obj == null) obj = []; + if (nativeReduceRight && obj.reduceRight === nativeReduceRight) { + if (context) iterator = _.bind(iterator, context); + return initial ? obj.reduceRight(iterator, memo) : obj.reduceRight(iterator); + } + var length = obj.length; + if (length !== +length) { + var keys = _.keys(obj); + length = keys.length; + } + each(obj, function(value, index, list) { + index = keys ? keys[--length] : --length; + if (!initial) { + memo = obj[index]; + initial = true; + } else { + memo = iterator.call(context, memo, obj[index], index, list); + } + }); + if (!initial) throw new TypeError(reduceError); + return memo; + }; + + // Return the first value which passes a truth test. Aliased as `detect`. + _.find = _.detect = function(obj, iterator, context) { + var result; + any(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) { + result = value; + return true; + } + }); + return result; + }; + + // Return all the elements that pass a truth test. + // Delegates to **ECMAScript 5**'s native `filter` if available. + // Aliased as `select`. + _.filter = _.select = function(obj, iterator, context) { + var results = []; + if (obj == null) return results; + if (nativeFilter && obj.filter === nativeFilter) return obj.filter(iterator, context); + each(obj, function(value, index, list) { + if (iterator.call(context, value, index, list)) results[results.length] = value; + }); + return results; + }; + + // Return all the elements for which a truth test fails. + _.reject = function(obj, iterator, context) { + return _.filter(obj, function(value, index, list) { + return !iterator.call(context, value, index, list); + }, context); + }; + + // Determine whether all of the elements match a truth test. + // Delegates to **ECMAScript 5**'s native `every` if available. + // Aliased as `all`. + _.every = _.all = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = true; + if (obj == null) return result; + if (nativeEvery && obj.every === nativeEvery) return obj.every(iterator, context); + each(obj, function(value, index, list) { + if (!(result = result && iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if at least one element in the object matches a truth test. + // Delegates to **ECMAScript 5**'s native `some` if available. + // Aliased as `any`. + var any = _.some = _.any = function(obj, iterator, context) { + iterator || (iterator = _.identity); + var result = false; + if (obj == null) return result; + if (nativeSome && obj.some === nativeSome) return obj.some(iterator, context); + each(obj, function(value, index, list) { + if (result || (result = iterator.call(context, value, index, list))) return breaker; + }); + return !!result; + }; + + // Determine if the array or object contains a given value (using `===`). + // Aliased as `include`. + _.contains = _.include = function(obj, target) { + if (obj == null) return false; + if (nativeIndexOf && obj.indexOf === nativeIndexOf) return obj.indexOf(target) != -1; + return any(obj, function(value) { + return value === target; + }); + }; + + // Invoke a method (with arguments) on every item in a collection. + _.invoke = function(obj, method) { + var args = slice.call(arguments, 2); + var isFunc = _.isFunction(method); + return _.map(obj, function(value) { + return (isFunc ? method : value[method]).apply(value, args); + }); + }; + + // Convenience version of a common use case of `map`: fetching a property. + _.pluck = function(obj, key) { + return _.map(obj, function(value){ return value[key]; }); + }; + + // Convenience version of a common use case of `filter`: selecting only objects + // containing specific `key:value` pairs. + _.where = function(obj, attrs, first) { + if (_.isEmpty(attrs)) return first ? null : []; + return _[first ? 'find' : 'filter'](obj, function(value) { + for (var key in attrs) { + if (attrs[key] !== value[key]) return false; + } + return true; + }); + }; + + // Convenience version of a common use case of `find`: getting the first object + // containing specific `key:value` pairs. + _.findWhere = function(obj, attrs) { + return _.where(obj, attrs, true); + }; + + // Return the maximum element or (element-based computation). + // Can't optimize arrays of integers longer than 65,535 elements. + // See: https://bugs.webkit.org/show_bug.cgi?id=80797 + _.max = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.max.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return -Infinity; + var result = {computed : -Infinity, value: -Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed >= result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Return the minimum element (or element-based computation). + _.min = function(obj, iterator, context) { + if (!iterator && _.isArray(obj) && obj[0] === +obj[0] && obj.length < 65535) { + return Math.min.apply(Math, obj); + } + if (!iterator && _.isEmpty(obj)) return Infinity; + var result = {computed : Infinity, value: Infinity}; + each(obj, function(value, index, list) { + var computed = iterator ? iterator.call(context, value, index, list) : value; + computed < result.computed && (result = {value : value, computed : computed}); + }); + return result.value; + }; + + // Shuffle an array. + _.shuffle = function(obj) { + var rand; + var index = 0; + var shuffled = []; + each(obj, function(value) { + rand = _.random(index++); + shuffled[index - 1] = shuffled[rand]; + shuffled[rand] = value; + }); + return shuffled; + }; + + // An internal function to generate lookup iterators. + var lookupIterator = function(value) { + return _.isFunction(value) ? value : function(obj){ return obj[value]; }; + }; + + // Sort the object's values by a criterion produced by an iterator. + _.sortBy = function(obj, value, context) { + var iterator = lookupIterator(value); + return _.pluck(_.map(obj, function(value, index, list) { + return { + value : value, + index : index, + criteria : iterator.call(context, value, index, list) + }; + }).sort(function(left, right) { + var a = left.criteria; + var b = right.criteria; + if (a !== b) { + if (a > b || a === void 0) return 1; + if (a < b || b === void 0) return -1; + } + return left.index < right.index ? -1 : 1; + }), 'value'); + }; + + // An internal function used for aggregate "group by" operations. + var group = function(obj, value, context, behavior) { + var result = {}; + var iterator = lookupIterator(value || _.identity); + each(obj, function(value, index) { + var key = iterator.call(context, value, index, obj); + behavior(result, key, value); + }); + return result; + }; + + // Groups the object's values by a criterion. Pass either a string attribute + // to group by, or a function that returns the criterion. + _.groupBy = function(obj, value, context) { + return group(obj, value, context, function(result, key, value) { + (_.has(result, key) ? result[key] : (result[key] = [])).push(value); + }); + }; + + // Counts instances of an object that group by a certain criterion. Pass + // either a string attribute to count by, or a function that returns the + // criterion. + _.countBy = function(obj, value, context) { + return group(obj, value, context, function(result, key) { + if (!_.has(result, key)) result[key] = 0; + result[key]++; + }); + }; + + // Use a comparator function to figure out the smallest index at which + // an object should be inserted so as to maintain order. Uses binary search. + _.sortedIndex = function(array, obj, iterator, context) { + iterator = iterator == null ? _.identity : lookupIterator(iterator); + var value = iterator.call(context, obj); + var low = 0, high = array.length; + while (low < high) { + var mid = (low + high) >>> 1; + iterator.call(context, array[mid]) < value ? low = mid + 1 : high = mid; + } + return low; + }; + + // Safely convert anything iterable into a real, live array. + _.toArray = function(obj) { + if (!obj) return []; + if (_.isArray(obj)) return slice.call(obj); + if (obj.length === +obj.length) return _.map(obj, _.identity); + return _.values(obj); + }; + + // Return the number of elements in an object. + _.size = function(obj) { + if (obj == null) return 0; + return (obj.length === +obj.length) ? obj.length : _.keys(obj).length; + }; + + // Array Functions + // --------------- + + // Get the first element of an array. Passing **n** will return the first N + // values in the array. Aliased as `head` and `take`. The **guard** check + // allows it to work with `_.map`. + _.first = _.head = _.take = function(array, n, guard) { + if (array == null) return void 0; + return (n != null) && !guard ? slice.call(array, 0, n) : array[0]; + }; + + // Returns everything but the last entry of the array. Especially useful on + // the arguments object. Passing **n** will return all the values in + // the array, excluding the last N. The **guard** check allows it to work with + // `_.map`. + _.initial = function(array, n, guard) { + return slice.call(array, 0, array.length - ((n == null) || guard ? 1 : n)); + }; + + // Get the last element of an array. Passing **n** will return the last N + // values in the array. The **guard** check allows it to work with `_.map`. + _.last = function(array, n, guard) { + if (array == null) return void 0; + if ((n != null) && !guard) { + return slice.call(array, Math.max(array.length - n, 0)); + } else { + return array[array.length - 1]; + } + }; + + // Returns everything but the first entry of the array. Aliased as `tail` and `drop`. + // Especially useful on the arguments object. Passing an **n** will return + // the rest N values in the array. The **guard** + // check allows it to work with `_.map`. + _.rest = _.tail = _.drop = function(array, n, guard) { + return slice.call(array, (n == null) || guard ? 1 : n); + }; + + // Trim out all falsy values from an array. + _.compact = function(array) { + return _.filter(array, _.identity); + }; + + // Internal implementation of a recursive `flatten` function. + var flatten = function(input, shallow, output) { + each(input, function(value) { + if (_.isArray(value)) { + shallow ? push.apply(output, value) : flatten(value, shallow, output); + } else { + output.push(value); + } + }); + return output; + }; + + // Return a completely flattened version of an array. + _.flatten = function(array, shallow) { + return flatten(array, shallow, []); + }; + + // Return a version of the array that does not contain the specified value(s). + _.without = function(array) { + return _.difference(array, slice.call(arguments, 1)); + }; + + // Produce a duplicate-free version of the array. If the array has already + // been sorted, you have the option of using a faster algorithm. + // Aliased as `unique`. + _.uniq = _.unique = function(array, isSorted, iterator, context) { + if (_.isFunction(isSorted)) { + context = iterator; + iterator = isSorted; + isSorted = false; + } + var initial = iterator ? _.map(array, iterator, context) : array; + var results = []; + var seen = []; + each(initial, function(value, index) { + if (isSorted ? (!index || seen[seen.length - 1] !== value) : !_.contains(seen, value)) { + seen.push(value); + results.push(array[index]); + } + }); + return results; + }; + + // Produce an array that contains the union: each distinct element from all of + // the passed-in arrays. + _.union = function() { + return _.uniq(concat.apply(ArrayProto, arguments)); + }; + + // Produce an array that contains every item shared between all the + // passed-in arrays. + _.intersection = function(array) { + var rest = slice.call(arguments, 1); + return _.filter(_.uniq(array), function(item) { + return _.every(rest, function(other) { + return _.indexOf(other, item) >= 0; + }); + }); + }; + + // Take the difference between one array and a number of other arrays. + // Only the elements present in just the first array will remain. + _.difference = function(array) { + var rest = concat.apply(ArrayProto, slice.call(arguments, 1)); + return _.filter(array, function(value){ return !_.contains(rest, value); }); + }; + + // Zip together multiple lists into a single array -- elements that share + // an index go together. + _.zip = function() { + var args = slice.call(arguments); + var length = _.max(_.pluck(args, 'length')); + var results = new Array(length); + for (var i = 0; i < length; i++) { + results[i] = _.pluck(args, "" + i); + } + return results; + }; + + // Converts lists into objects. Pass either a single array of `[key, value]` + // pairs, or two parallel arrays of the same length -- one of keys, and one of + // the corresponding values. + _.object = function(list, values) { + if (list == null) return {}; + var result = {}; + for (var i = 0, l = list.length; i < l; i++) { + if (values) { + result[list[i]] = values[i]; + } else { + result[list[i][0]] = list[i][1]; + } + } + return result; + }; + + // If the browser doesn't supply us with indexOf (I'm looking at you, **MSIE**), + // we need this function. Return the position of the first occurrence of an + // item in an array, or -1 if the item is not included in the array. + // Delegates to **ECMAScript 5**'s native `indexOf` if available. + // If the array is large and already in sort order, pass `true` + // for **isSorted** to use binary search. + _.indexOf = function(array, item, isSorted) { + if (array == null) return -1; + var i = 0, l = array.length; + if (isSorted) { + if (typeof isSorted == 'number') { + i = (isSorted < 0 ? Math.max(0, l + isSorted) : isSorted); + } else { + i = _.sortedIndex(array, item); + return array[i] === item ? i : -1; + } + } + if (nativeIndexOf && array.indexOf === nativeIndexOf) return array.indexOf(item, isSorted); + for (; i < l; i++) if (array[i] === item) return i; + return -1; + }; + + // Delegates to **ECMAScript 5**'s native `lastIndexOf` if available. + _.lastIndexOf = function(array, item, from) { + if (array == null) return -1; + var hasIndex = from != null; + if (nativeLastIndexOf && array.lastIndexOf === nativeLastIndexOf) { + return hasIndex ? array.lastIndexOf(item, from) : array.lastIndexOf(item); + } + var i = (hasIndex ? from : array.length); + while (i--) if (array[i] === item) return i; + return -1; + }; + + // Generate an integer Array containing an arithmetic progression. A port of + // the native Python `range()` function. See + // [the Python documentation](http://docs.python.org/library/functions.html#range). + _.range = function(start, stop, step) { + if (arguments.length <= 1) { + stop = start || 0; + start = 0; + } + step = arguments[2] || 1; + + var len = Math.max(Math.ceil((stop - start) / step), 0); + var idx = 0; + var range = new Array(len); + + while(idx < len) { + range[idx++] = start; + start += step; + } + + return range; + }; + + // Function (ahem) Functions + // ------------------ + + // Create a function bound to a given object (assigning `this`, and arguments, + // optionally). Delegates to **ECMAScript 5**'s native `Function.bind` if + // available. + _.bind = function(func, context) { + if (func.bind === nativeBind && nativeBind) return nativeBind.apply(func, slice.call(arguments, 1)); + var args = slice.call(arguments, 2); + return function() { + return func.apply(context, args.concat(slice.call(arguments))); + }; + }; + + // Partially apply a function by creating a version that has had some of its + // arguments pre-filled, without changing its dynamic `this` context. + _.partial = function(func) { + var args = slice.call(arguments, 1); + return function() { + return func.apply(this, args.concat(slice.call(arguments))); + }; + }; + + // Bind all of an object's methods to that object. Useful for ensuring that + // all callbacks defined on an object belong to it. + _.bindAll = function(obj) { + var funcs = slice.call(arguments, 1); + if (funcs.length === 0) funcs = _.functions(obj); + each(funcs, function(f) { obj[f] = _.bind(obj[f], obj); }); + return obj; + }; + + // Memoize an expensive function by storing its results. + _.memoize = function(func, hasher) { + var memo = {}; + hasher || (hasher = _.identity); + return function() { + var key = hasher.apply(this, arguments); + return _.has(memo, key) ? memo[key] : (memo[key] = func.apply(this, arguments)); + }; + }; + + // Delays a function for the given number of milliseconds, and then calls + // it with the arguments supplied. + _.delay = function(func, wait) { + var args = slice.call(arguments, 2); + return setTimeout(function(){ return func.apply(null, args); }, wait); + }; + + // Defers a function, scheduling it to run after the current call stack has + // cleared. + _.defer = function(func) { + return _.delay.apply(_, [func, 1].concat(slice.call(arguments, 1))); + }; + + // Returns a function, that, when invoked, will only be triggered at most once + // during a given window of time. + _.throttle = function(func, wait) { + var context, args, timeout, result; + var previous = 0; + var later = function() { + previous = new Date; + timeout = null; + result = func.apply(context, args); + }; + return function() { + var now = new Date; + var remaining = wait - (now - previous); + context = this; + args = arguments; + if (remaining <= 0) { + clearTimeout(timeout); + timeout = null; + previous = now; + result = func.apply(context, args); + } else if (!timeout) { + timeout = setTimeout(later, remaining); + } + return result; + }; + }; + + // Returns a function, that, as long as it continues to be invoked, will not + // be triggered. The function will be called after it stops being called for + // N milliseconds. If `immediate` is passed, trigger the function on the + // leading edge, instead of the trailing. + _.debounce = function(func, wait, immediate) { + var timeout, result; + return function() { + var context = this, args = arguments; + var later = function() { + timeout = null; + if (!immediate) result = func.apply(context, args); + }; + var callNow = immediate && !timeout; + clearTimeout(timeout); + timeout = setTimeout(later, wait); + if (callNow) result = func.apply(context, args); + return result; + }; + }; + + // Returns a function that will be executed at most one time, no matter how + // often you call it. Useful for lazy initialization. + _.once = function(func) { + var ran = false, memo; + return function() { + if (ran) return memo; + ran = true; + memo = func.apply(this, arguments); + func = null; + return memo; + }; + }; + + // Returns the first function passed as an argument to the second, + // allowing you to adjust arguments, run code before and after, and + // conditionally execute the original function. + _.wrap = function(func, wrapper) { + return function() { + var args = [func]; + push.apply(args, arguments); + return wrapper.apply(this, args); + }; + }; + + // Returns a function that is the composition of a list of functions, each + // consuming the return value of the function that follows. + _.compose = function() { + var funcs = arguments; + return function() { + var args = arguments; + for (var i = funcs.length - 1; i >= 0; i--) { + args = [funcs[i].apply(this, args)]; + } + return args[0]; + }; + }; + + // Returns a function that will only be executed after being called N times. + _.after = function(times, func) { + if (times <= 0) return func(); + return function() { + if (--times < 1) { + return func.apply(this, arguments); + } + }; + }; + + // Object Functions + // ---------------- + + // Retrieve the names of an object's properties. + // Delegates to **ECMAScript 5**'s native `Object.keys` + _.keys = nativeKeys || function(obj) { + if (obj !== Object(obj)) throw new TypeError('Invalid object'); + var keys = []; + for (var key in obj) if (_.has(obj, key)) keys[keys.length] = key; + return keys; + }; + + // Retrieve the values of an object's properties. + _.values = function(obj) { + var values = []; + for (var key in obj) if (_.has(obj, key)) values.push(obj[key]); + return values; + }; + + // Convert an object into a list of `[key, value]` pairs. + _.pairs = function(obj) { + var pairs = []; + for (var key in obj) if (_.has(obj, key)) pairs.push([key, obj[key]]); + return pairs; + }; + + // Invert the keys and values of an object. The values must be serializable. + _.invert = function(obj) { + var result = {}; + for (var key in obj) if (_.has(obj, key)) result[obj[key]] = key; + return result; + }; + + // Return a sorted list of the function names available on the object. + // Aliased as `methods` + _.functions = _.methods = function(obj) { + var names = []; + for (var key in obj) { + if (_.isFunction(obj[key])) names.push(key); + } + return names.sort(); + }; + + // Extend a given object with all the properties in passed-in object(s). + _.extend = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Return a copy of the object only containing the whitelisted properties. + _.pick = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + each(keys, function(key) { + if (key in obj) copy[key] = obj[key]; + }); + return copy; + }; + + // Return a copy of the object without the blacklisted properties. + _.omit = function(obj) { + var copy = {}; + var keys = concat.apply(ArrayProto, slice.call(arguments, 1)); + for (var key in obj) { + if (!_.contains(keys, key)) copy[key] = obj[key]; + } + return copy; + }; + + // Fill in a given object with default properties. + _.defaults = function(obj) { + each(slice.call(arguments, 1), function(source) { + if (source) { + for (var prop in source) { + if (obj[prop] == null) obj[prop] = source[prop]; + } + } + }); + return obj; + }; + + // Create a (shallow-cloned) duplicate of an object. + _.clone = function(obj) { + if (!_.isObject(obj)) return obj; + return _.isArray(obj) ? obj.slice() : _.extend({}, obj); + }; + + // Invokes interceptor with the obj, and then returns obj. + // The primary purpose of this method is to "tap into" a method chain, in + // order to perform operations on intermediate results within the chain. + _.tap = function(obj, interceptor) { + interceptor(obj); + return obj; + }; + + // Internal recursive comparison function for `isEqual`. + var eq = function(a, b, aStack, bStack) { + // Identical objects are equal. `0 === -0`, but they aren't identical. + // See the Harmony `egal` proposal: http://wiki.ecmascript.org/doku.php?id=harmony:egal. + if (a === b) return a !== 0 || 1 / a == 1 / b; + // A strict comparison is necessary because `null == undefined`. + if (a == null || b == null) return a === b; + // Unwrap any wrapped objects. + if (a instanceof _) a = a._wrapped; + if (b instanceof _) b = b._wrapped; + // Compare `[[Class]]` names. + var className = toString.call(a); + if (className != toString.call(b)) return false; + switch (className) { + // Strings, numbers, dates, and booleans are compared by value. + case '[object String]': + // Primitives and their corresponding object wrappers are equivalent; thus, `"5"` is + // equivalent to `new String("5")`. + return a == String(b); + case '[object Number]': + // `NaN`s are equivalent, but non-reflexive. An `egal` comparison is performed for + // other numeric values. + return a != +a ? b != +b : (a == 0 ? 1 / a == 1 / b : a == +b); + case '[object Date]': + case '[object Boolean]': + // Coerce dates and booleans to numeric primitive values. Dates are compared by their + // millisecond representations. Note that invalid dates with millisecond representations + // of `NaN` are not equivalent. + return +a == +b; + // RegExps are compared by their source patterns and flags. + case '[object RegExp]': + return a.source == b.source && + a.global == b.global && + a.multiline == b.multiline && + a.ignoreCase == b.ignoreCase; + } + if (typeof a != 'object' || typeof b != 'object') return false; + // Assume equality for cyclic structures. The algorithm for detecting cyclic + // structures is adapted from ES 5.1 section 15.12.3, abstract operation `JO`. + var length = aStack.length; + while (length--) { + // Linear search. Performance is inversely proportional to the number of + // unique nested structures. + if (aStack[length] == a) return bStack[length] == b; + } + // Add the first object to the stack of traversed objects. + aStack.push(a); + bStack.push(b); + var size = 0, result = true; + // Recursively compare objects and arrays. + if (className == '[object Array]') { + // Compare array lengths to determine if a deep comparison is necessary. + size = a.length; + result = size == b.length; + if (result) { + // Deep compare the contents, ignoring non-numeric properties. + while (size--) { + if (!(result = eq(a[size], b[size], aStack, bStack))) break; + } + } + } else { + // Objects with different constructors are not equivalent, but `Object`s + // from different frames are. + var aCtor = a.constructor, bCtor = b.constructor; + if (aCtor !== bCtor && !(_.isFunction(aCtor) && (aCtor instanceof aCtor) && + _.isFunction(bCtor) && (bCtor instanceof bCtor))) { + return false; + } + // Deep compare objects. + for (var key in a) { + if (_.has(a, key)) { + // Count the expected number of properties. + size++; + // Deep compare each member. + if (!(result = _.has(b, key) && eq(a[key], b[key], aStack, bStack))) break; + } + } + // Ensure that both objects contain the same number of properties. + if (result) { + for (key in b) { + if (_.has(b, key) && !(size--)) break; + } + result = !size; + } + } + // Remove the first object from the stack of traversed objects. + aStack.pop(); + bStack.pop(); + return result; + }; + + // Perform a deep comparison to check if two objects are equal. + _.isEqual = function(a, b) { + return eq(a, b, [], []); + }; + + // Is a given array, string, or object empty? + // An "empty" object has no enumerable own-properties. + _.isEmpty = function(obj) { + if (obj == null) return true; + if (_.isArray(obj) || _.isString(obj)) return obj.length === 0; + for (var key in obj) if (_.has(obj, key)) return false; + return true; + }; + + // Is a given value a DOM element? + _.isElement = function(obj) { + return !!(obj && obj.nodeType === 1); + }; + + // Is a given value an array? + // Delegates to ECMA5's native Array.isArray + _.isArray = nativeIsArray || function(obj) { + return toString.call(obj) == '[object Array]'; + }; + + // Is a given variable an object? + _.isObject = function(obj) { + return obj === Object(obj); + }; + + // Add some isType methods: isArguments, isFunction, isString, isNumber, isDate, isRegExp. + each(['Arguments', 'Function', 'String', 'Number', 'Date', 'RegExp'], function(name) { + _['is' + name] = function(obj) { + return toString.call(obj) == '[object ' + name + ']'; + }; + }); + + // Define a fallback version of the method in browsers (ahem, IE), where + // there isn't any inspectable "Arguments" type. + if (!_.isArguments(arguments)) { + _.isArguments = function(obj) { + return !!(obj && _.has(obj, 'callee')); + }; + } + + // Optimize `isFunction` if appropriate. + if (typeof (/./) !== 'function') { + _.isFunction = function(obj) { + return typeof obj === 'function'; + }; + } + + // Is a given object a finite number? + _.isFinite = function(obj) { + return isFinite(obj) && !isNaN(parseFloat(obj)); + }; + + // Is the given value `NaN`? (NaN is the only number which does not equal itself). + _.isNaN = function(obj) { + return _.isNumber(obj) && obj != +obj; + }; + + // Is a given value a boolean? + _.isBoolean = function(obj) { + return obj === true || obj === false || toString.call(obj) == '[object Boolean]'; + }; + + // Is a given value equal to null? + _.isNull = function(obj) { + return obj === null; + }; + + // Is a given variable undefined? + _.isUndefined = function(obj) { + return obj === void 0; + }; + + // Shortcut function for checking if an object has a given property directly + // on itself (in other words, not on a prototype). + _.has = function(obj, key) { + return hasOwnProperty.call(obj, key); + }; + + // Utility Functions + // ----------------- + + // Run Underscore.js in *noConflict* mode, returning the `_` variable to its + // previous owner. Returns a reference to the Underscore object. + _.noConflict = function() { + root._ = previousUnderscore; + return this; + }; + + // Keep the identity function around for default iterators. + _.identity = function(value) { + return value; + }; + + // Run a function **n** times. + _.times = function(n, iterator, context) { + var accum = Array(n); + for (var i = 0; i < n; i++) accum[i] = iterator.call(context, i); + return accum; + }; + + // Return a random integer between min and max (inclusive). + _.random = function(min, max) { + if (max == null) { + max = min; + min = 0; + } + return min + Math.floor(Math.random() * (max - min + 1)); + }; + + // List of HTML entities for escaping. + var entityMap = { + escape: { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + "'": ''', + '/': '/' + } + }; + entityMap.unescape = _.invert(entityMap.escape); + + // Regexes containing the keys and values listed immediately above. + var entityRegexes = { + escape: new RegExp('[' + _.keys(entityMap.escape).join('') + ']', 'g'), + unescape: new RegExp('(' + _.keys(entityMap.unescape).join('|') + ')', 'g') + }; + + // Functions for escaping and unescaping strings to/from HTML interpolation. + _.each(['escape', 'unescape'], function(method) { + _[method] = function(string) { + if (string == null) return ''; + return ('' + string).replace(entityRegexes[method], function(match) { + return entityMap[method][match]; + }); + }; + }); + + // If the value of the named property is a function then invoke it; + // otherwise, return it. + _.result = function(object, property) { + if (object == null) return null; + var value = object[property]; + return _.isFunction(value) ? value.call(object) : value; + }; + + // Add your own custom functions to the Underscore object. + _.mixin = function(obj) { + each(_.functions(obj), function(name){ + var func = _[name] = obj[name]; + _.prototype[name] = function() { + var args = [this._wrapped]; + push.apply(args, arguments); + return result.call(this, func.apply(_, args)); + }; + }); + }; + + // Generate a unique integer id (unique within the entire client session). + // Useful for temporary DOM ids. + var idCounter = 0; + _.uniqueId = function(prefix) { + var id = ++idCounter + ''; + return prefix ? prefix + id : id; + }; + + // By default, Underscore uses ERB-style template delimiters, change the + // following template settings to use alternative delimiters. + _.templateSettings = { + evaluate : /<%([\s\S]+?)%>/g, + interpolate : /<%=([\s\S]+?)%>/g, + escape : /<%-([\s\S]+?)%>/g + }; + + // When customizing `templateSettings`, if you don't want to define an + // interpolation, evaluation or escaping regex, we need one that is + // guaranteed not to match. + var noMatch = /(.)^/; + + // Certain characters need to be escaped so that they can be put into a + // string literal. + var escapes = { + "'": "'", + '\\': '\\', + '\r': 'r', + '\n': 'n', + '\t': 't', + '\u2028': 'u2028', + '\u2029': 'u2029' + }; + + var escaper = /\\|'|\r|\n|\t|\u2028|\u2029/g; + + // JavaScript micro-templating, similar to John Resig's implementation. + // Underscore templating handles arbitrary delimiters, preserves whitespace, + // and correctly escapes quotes within interpolated code. + _.template = function(text, data, settings) { + var render; + settings = _.defaults({}, settings, _.templateSettings); + + // Combine delimiters into one regular expression via alternation. + var matcher = new RegExp([ + (settings.escape || noMatch).source, + (settings.interpolate || noMatch).source, + (settings.evaluate || noMatch).source + ].join('|') + '|$', 'g'); + + // Compile the template source, escaping string literals appropriately. + var index = 0; + var source = "__p+='"; + text.replace(matcher, function(match, escape, interpolate, evaluate, offset) { + source += text.slice(index, offset) + .replace(escaper, function(match) { return '\\' + escapes[match]; }); + + if (escape) { + source += "'+\n((__t=(" + escape + "))==null?'':_.escape(__t))+\n'"; + } + if (interpolate) { + source += "'+\n((__t=(" + interpolate + "))==null?'':__t)+\n'"; + } + if (evaluate) { + source += "';\n" + evaluate + "\n__p+='"; + } + index = offset + match.length; + return match; + }); + source += "';\n"; + + // If a variable is not specified, place data values in local scope. + if (!settings.variable) source = 'with(obj||{}){\n' + source + '}\n'; + + source = "var __t,__p='',__j=Array.prototype.join," + + "print=function(){__p+=__j.call(arguments,'');};\n" + + source + "return __p;\n"; + + try { + render = new Function(settings.variable || 'obj', '_', source); + } catch (e) { + e.source = source; + throw e; + } + + if (data) return render(data, _); + var template = function(data) { + return render.call(this, data, _); + }; + + // Provide the compiled function source as a convenience for precompilation. + template.source = 'function(' + (settings.variable || 'obj') + '){\n' + source + '}'; + + return template; + }; + + // Add a "chain" function, which will delegate to the wrapper. + _.chain = function(obj) { + return _(obj).chain(); + }; + + // OOP + // --------------- + // If Underscore is called as a function, it returns a wrapped object that + // can be used OO-style. This wrapper holds altered versions of all the + // underscore functions. Wrapped objects may be chained. + + // Helper function to continue chaining intermediate results. + var result = function(obj) { + return this._chain ? _(obj).chain() : obj; + }; + + // Add all of the Underscore functions to the wrapper object. + _.mixin(_); + + // Add all mutator Array functions to the wrapper. + each(['pop', 'push', 'reverse', 'shift', 'sort', 'splice', 'unshift'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + var obj = this._wrapped; + method.apply(obj, arguments); + if ((name == 'shift' || name == 'splice') && obj.length === 0) delete obj[0]; + return result.call(this, obj); + }; + }); + + // Add all accessor Array functions to the wrapper. + each(['concat', 'join', 'slice'], function(name) { + var method = ArrayProto[name]; + _.prototype[name] = function() { + return result.call(this, method.apply(this._wrapped, arguments)); + }; + }); + + _.extend(_.prototype, { + + // Start chaining a wrapped Underscore object. + chain: function() { + this._chain = true; + return this; + }, + + // Extracts the result from a wrapped and chained object. + value: function() { + return this._wrapped; + } + + }); + +}).call(this); + +/*global _: false, $: false, localStorage: false, process: true, + XMLHttpRequest: false, XDomainRequest: false, exports: false, + require: false, setTimeout: true */ +(function(root) { + root.Parse = root.Parse || {}; + /** + * Contains all Parse API classes and functions. + * @name Parse + * @namespace + * + * Contains all Parse API classes and functions. + */ + var Parse = root.Parse; + + var req = typeof(require) === 'function' ? require : null; + // Load references to other dependencies + if (typeof(XMLHttpRequest) !== 'undefined') { + Parse.XMLHttpRequest = XMLHttpRequest; + } else if (typeof(require) === 'function' && + typeof(require.ensure) === 'undefined') { + Parse.XMLHttpRequest = req('xmlhttprequest').XMLHttpRequest; + } + // Import Parse's local copy of underscore. + if (typeof(exports) !== 'undefined' && exports._) { + // We're running in a CommonJS environment + Parse._ = exports._.noConflict(); + exports.Parse = Parse; + } else { + Parse._ = _.noConflict(); + } + + // If jQuery or Zepto has been included, grab a reference to it. + if (typeof($) !== "undefined") { + Parse.$ = $; + } + + // Helpers + // ------- + + // Shared empty constructor function to aid in prototype-chain creation. + var EmptyConstructor = function() {}; + + + // Helper function to correctly set up the prototype chain, for subclasses. + // Similar to `goog.inherits`, but uses a hash of prototype properties and + // class properties to be extended. + var inherits = function(parent, protoProps, staticProps) { + var child; + + // The constructor function for the new subclass is either defined by you + // (the "constructor" property in your `extend` definition), or defaulted + // by us to simply call the parent's constructor. + if (protoProps && protoProps.hasOwnProperty('constructor')) { + child = protoProps.constructor; + } else { + /** @ignore */ + child = function(){ parent.apply(this, arguments); }; + } + + // Inherit class (static) properties from parent. + Parse._.extend(child, parent); + + // Set the prototype chain to inherit from `parent`, without calling + // `parent`'s constructor function. + EmptyConstructor.prototype = parent.prototype; + child.prototype = new EmptyConstructor(); + + // Add prototype properties (instance properties) to the subclass, + // if supplied. + if (protoProps) { + Parse._.extend(child.prototype, protoProps); + } + + // Add static properties to the constructor function, if supplied. + if (staticProps) { + Parse._.extend(child, staticProps); + } + + // Correctly set child's `prototype.constructor`. + child.prototype.constructor = child; + + // Set a convenience property in case the parent's prototype is + // needed later. + child.__super__ = parent.prototype; + + return child; + }; + + // Set the server for Parse to talk to. + Parse.serverURL = "https://api.parse.com"; + + // Check whether we are running in Node.js. + if (typeof(process) !== "undefined" && + process.versions && + process.versions.node) { + Parse._isNode = true; + } + + /** + * Call this method first to set up your authentication tokens for Parse. + * You can get your keys from the Data Browser on parse.com. + * @param {String} applicationId Your Parse Application ID. + * @param {String} javaScriptKey Your Parse JavaScript Key. + * @param {String} masterKey (optional) Your Parse Master Key. (Node.js only!) + */ + Parse.initialize = function(applicationId, javaScriptKey, masterKey) { + if (masterKey) { + throw "Parse.initialize() was passed a Master Key, which is only " + + "allowed from within Node.js."; + } + Parse._initialize(applicationId, javaScriptKey); + }; + + /** + * Call this method first to set up master authentication tokens for Parse. + * This method is for Parse's own private use. + * @param {String} applicationId Your Parse Application ID. + * @param {String} javaScriptKey Your Parse JavaScript Key. + * @param {String} masterKey Your Parse Master Key. + */ + Parse._initialize = function(applicationId, javaScriptKey, masterKey) { + Parse.applicationId = applicationId; + Parse.javaScriptKey = javaScriptKey; + Parse.masterKey = masterKey; + Parse._useMasterKey = false; + }; + + // If we're running in node.js, allow using the master key. + if (Parse._isNode) { + Parse.initialize = Parse._initialize; + + Parse.Cloud = Parse.Cloud || {}; + /** + * Switches the Parse SDK to using the Master key. The Master key grants + * priveleged access to the data in Parse and can be used to bypass ACLs and + * other restrictions that are applied to the client SDKs. + *

Available in Cloud Code and Node.js only. + *

+ */ + Parse.Cloud.useMasterKey = function() { + Parse._useMasterKey = true; + }; + } + + /** + * Returns prefix for Storage keys used by this instance of Parse. + * @param {String} path The relative suffix to append to it. + * null or undefined is treated as the empty string. + * @return {String} The full key name. + */ + Parse._getParsePath = function(path) { + if (!Parse.applicationId) { + throw "You need to call Parse.initialize before using Parse."; + } + if (!path) { + path = ""; + } + if (!Parse._.isString(path)) { + throw "Tried to get a Storage path that wasn't a String."; + } + if (path[0] === "/") { + path = path.substring(1); + } + return "Parse/" + Parse.applicationId + "/" + path; + }; + + /** + * Returns a Promise that is resolved with the unique string for this app on + * this machine. + * Gets reset when Storage is cleared. + */ + Parse._installationId = null; + Parse._getInstallationId = function() { + // See if it's cached in RAM. + if (Parse._installationId) { + return Parse.Promise.as(Parse._installationId); + } + + // Try to get it from Storage. + var path = Parse._getParsePath("installationId"); + return (Parse.Storage.getItemAsync(path) + .then(function(value) { + Parse._installationId = value; + + if (!Parse._installationId || Parse._installationId === "") { + // It wasn't in Storage, so create a new one. + var hexOctet = function() { + return ( + Math.floor((1+Math.random())*0x10000).toString(16).substring(1) + ); + }; + Parse._installationId = ( + hexOctet() + hexOctet() + "-" + + hexOctet() + "-" + + hexOctet() + "-" + + hexOctet() + "-" + + hexOctet() + hexOctet() + hexOctet()); + return Parse.Storage.setItemAsync(path, Parse._installationId); + } + + return Parse.Promise.as(Parse._installationId); + }) + ); + }; + + Parse._parseDate = function(iso8601) { + var regexp = new RegExp( + "^([0-9]{1,4})-([0-9]{1,2})-([0-9]{1,2})" + "T" + + "([0-9]{1,2}):([0-9]{1,2}):([0-9]{1,2})" + + "(.([0-9]+))?" + "Z$"); + var match = regexp.exec(iso8601); + if (!match) { + return null; + } + + var year = match[1] || 0; + var month = (match[2] || 1) - 1; + var day = match[3] || 0; + var hour = match[4] || 0; + var minute = match[5] || 0; + var second = match[6] || 0; + var milli = match[8] || 0; + + return new Date(Date.UTC(year, month, day, hour, minute, second, milli)); + }; + + Parse._ajaxIE8 = function(method, url, data) { + var promise = new Parse.Promise(); + var xdr = new XDomainRequest(); + xdr.onload = function() { + var response; + try { + response = JSON.parse(xdr.responseText); + } catch (e) { + promise.reject(e); + } + if (response) { + promise.resolve(response); + } + }; + xdr.onerror = xdr.ontimeout = function() { + // Let's fake a real error message. + var fakeResponse = { + responseText: JSON.stringify({ + code: Parse.Error.X_DOMAIN_REQUEST, + error: "IE's XDomainRequest does not supply error info." + }) + }; + promise.reject(fakeResponse); + }; + xdr.onprogress = function() {}; + xdr.open(method, url); + xdr.send(data); + return promise; + }; + + Parse._useXDomainRequest = function() { + if (typeof(XDomainRequest) !== "undefined") { + // We're in IE 8+. + if ('withCredentials' in new XMLHttpRequest()) { + // We're in IE 10+. + return false; + } + return true; + } + return false; + }; + + + Parse._ajax = function(method, url, data, success, error) { + var options = { + success: success, + error: error + }; + + if (Parse._useXDomainRequest()) { + return Parse._ajaxIE8(method, url, data)._thenRunCallbacks(options); + } + + var promise = new Parse.Promise(); + var attempts = 0; + + var dispatch = function() { + var handled = false; + var xhr = new Parse.XMLHttpRequest(); + + xhr.onreadystatechange = function() { + if (xhr.readyState === 4) { + if (handled) { + return; + } + handled = true; + + if (xhr.status >= 200 && xhr.status < 300) { + var response; + try { + response = JSON.parse(xhr.responseText); + } catch (e) { + promise.reject(e); + } + if (response) { + promise.resolve(response, xhr.status, xhr); + } + } else if (xhr.status >= 500) { // Retry on 5XX + if (++attempts < 5) { + // Exponentially-growing delay + var delay = Math.round( + Math.random() * 125 * Math.pow(2, attempts) + ); + setTimeout(dispatch, delay); + } else { + // After 5 retries, fail + promise.reject(xhr); + } + } else { + promise.reject(xhr); + } + } + }; + + xhr.open(method, url, true); + xhr.setRequestHeader('Content-Type', 'text/plain'); // avoid pre-flight. + if (Parse._isNode) { + // Add a special user agent just for request from node.js. + xhr.setRequestHeader("User-Agent", + "Parse/" + Parse.VERSION + + " (NodeJS " + process.versions.node + ")"); + } + xhr.send(data); + }; + + dispatch(); + return promise._thenRunCallbacks(options); + }; + + // A self-propagating extend function. + Parse._extend = function(protoProps, classProps) { + var child = inherits(this, protoProps, classProps); + child.extend = this.extend; + return child; + }; + + /** + * Options: + * route: is classes, users, login, etc. + * objectId: null if there is no associated objectId. + * method: the http method for the REST API. + * dataObject: the payload as an object, or null if there is none. + * useMasterKey: overrides whether to use the master key if set. + * @ignore + */ + Parse._request = function(options) { + var route = options.route; + var className = options.className; + var objectId = options.objectId; + var method = options.method; + var useMasterKey = options.useMasterKey; + var sessionToken = options.sessionToken; + var dataObject = options.data; + + if (!Parse.applicationId) { + throw "You must specify your applicationId using Parse.initialize."; + } + + if (!Parse.javaScriptKey && !Parse.masterKey) { + throw "You must specify a key using Parse.initialize."; + } + + + if (route !== "batch" && + route !== "classes" && + route !== "events" && + route !== "files" && + route !== "functions" && + route !== "login" && + route !== "logout" && + route !== "push" && + route !== "requestPasswordReset" && + route !== "rest_verify_analytics" && + route !== "users" && + route !== "jobs" && + route !== "config" && + route !== "sessions" && + route !== "upgradeToRevocableSession") { + throw "Bad route: '" + route + "'."; + } + + var url = Parse.serverURL; + if (url.charAt(url.length - 1) !== "/") { + url += "/"; + } + url += "1/" + route; + if (className) { + url += "/" + className; + } + if (objectId) { + url += "/" + objectId; + } + + dataObject = Parse._.clone(dataObject || {}); + if (method !== "POST") { + dataObject._method = method; + method = "POST"; + } + + if (Parse._.isUndefined(useMasterKey)) { + useMasterKey = Parse._useMasterKey; + } + + dataObject._ApplicationId = Parse.applicationId; + if (!useMasterKey) { + dataObject._JavaScriptKey = Parse.javaScriptKey; + } else if (!Parse.masterKey) { + throw new Error('Cannot use the Master Key, it has not been provided.'); + } else { + dataObject._MasterKey = Parse.masterKey; + } + + dataObject._ClientVersion = Parse.VERSION; + + return Parse._getInstallationId().then(function(iid) { + dataObject._InstallationId = iid; + + if (sessionToken) { + return Parse.Promise.as({ _sessionToken: sessionToken }); + } + if (!Parse.User._canUseCurrentUser()) { + return Parse.Promise.as(null); + } + + return Parse.User._currentAsync(); + }).then(function(currentUser) { + if (currentUser && currentUser._sessionToken) { + dataObject._SessionToken = currentUser._sessionToken; + } + + if (Parse.User._isRevocableSessionEnabled) { + dataObject._RevocableSession = '1'; + } + + var data = JSON.stringify(dataObject); + + return Parse._ajax(method, url, data); + }).then(null, function(response) { + // Transform the error into an instance of Parse.Error by trying to parse + // the error string as JSON. + var error; + if (response && response.responseText) { + try { + var errorJSON = JSON.parse(response.responseText); + error = new Parse.Error(errorJSON.code, errorJSON.error); + } catch (e) { + // If we fail to parse the error text, that's okay. + error = new Parse.Error( + Parse.Error.INVALID_JSON, + "Received an error with invalid JSON from Parse: " + + response.responseText); + } + } else { + error = new Parse.Error( + Parse.Error.CONNECTION_FAILED, + "XMLHttpRequest failed: " + JSON.stringify(response)); + } + // By explicitly returning a rejected Promise, this will work with + // either jQuery or Promises/A semantics. + return Parse.Promise.error(error); + }); + }; + + // Helper function to get a value from a Backbone object as a property + // or as a function. + Parse._getValue = function(object, prop) { + if (!(object && object[prop])) { + return null; + } + return Parse._.isFunction(object[prop]) ? object[prop]() : object[prop]; + }; + + /** + * Converts a value in a Parse Object into the appropriate representation. + * This is the JS equivalent of Java's Parse.maybeReferenceAndEncode(Object) + * if seenObjects is falsey. Otherwise any Parse.Objects not in + * seenObjects will be fully embedded rather than encoded + * as a pointer. This array will be used to prevent going into an infinite + * loop because we have circular references. If seenObjects + * is set, then none of the Parse Objects that are serialized can be dirty. + */ + Parse._encode = function(value, seenObjects, disallowObjects) { + var _ = Parse._; + if (value instanceof Parse.Object) { + if (disallowObjects) { + throw "Parse.Objects not allowed here"; + } + if (!seenObjects || _.include(seenObjects, value) || !value._hasData) { + return value._toPointer(); + } + if (!value.dirty()) { + seenObjects = seenObjects.concat(value); + return Parse._encode(value._toFullJSON(seenObjects), + seenObjects, + disallowObjects); + } + throw "Tried to save an object with a pointer to a new, unsaved object."; + } + if (value instanceof Parse.ACL) { + return value.toJSON(); + } + if (_.isDate(value)) { + if (isNaN(value)) { + throw new Error('Cannot encode invalid Date'); + } + return { "__type": "Date", "iso": value.toJSON() }; + } + if (value instanceof Parse.GeoPoint) { + return value.toJSON(); + } + if (_.isArray(value)) { + return _.map(value, function(x) { + return Parse._encode(x, seenObjects, disallowObjects); + }); + } + if (_.isRegExp(value)) { + return value.source; + } + if (value instanceof Parse.Relation) { + return value.toJSON(); + } + if (value instanceof Parse.Op) { + return value.toJSON(); + } + if (value instanceof Parse.File) { + if (!value.url()) { + throw "Tried to save an object containing an unsaved file."; + } + return { + __type: "File", + name: value.name(), + url: value.url() + }; + } + if (_.isObject(value)) { + var output = {}; + Parse._objectEach(value, function(v, k) { + output[k] = Parse._encode(v, seenObjects, disallowObjects); + }); + return output; + } + return value; + }; + + /** + * The inverse function of Parse._encode. + * TODO: make decode not mutate value. + */ + Parse._decode = function(key, value) { + var _ = Parse._; + if (!_.isObject(value)) { + return value; + } + if (_.isArray(value)) { + Parse._arrayEach(value, function(v, k) { + value[k] = Parse._decode(k, v); + }); + return value; + } + if (value instanceof Parse.Object) { + return value; + } + if (value instanceof Parse.File) { + return value; + } + if (value instanceof Parse.Op) { + return value; + } + if (value.__op) { + return Parse.Op._decode(value); + } + if (value.__type === "Pointer" && value.className) { + var pointer = Parse.Object._create(value.className); + pointer._finishFetch({ objectId: value.objectId }, false); + return pointer; + } + if (value.__type === "Object" && value.className) { + // It's an Object included in a query result. + var className = value.className; + delete value.__type; + delete value.className; + var object = Parse.Object._create(className); + object._finishFetch(value, true); + return object; + } + if (value.__type === "Date") { + return Parse._parseDate(value.iso); + } + if (value.__type === "GeoPoint") { + return new Parse.GeoPoint({ + latitude: value.latitude, + longitude: value.longitude + }); + } + if (key === "ACL") { + if (value instanceof Parse.ACL) { + return value; + } + return new Parse.ACL(value); + } + if (value.__type === "Relation") { + var relation = new Parse.Relation(null, key); + relation.targetClassName = value.className; + return relation; + } + if (value.__type === "File") { + var file = new Parse.File(value.name); + file._url = value.url; + return file; + } + Parse._objectEach(value, function(v, k) { + value[k] = Parse._decode(k, v); + }); + return value; + }; + + Parse._arrayEach = Parse._.each; + + /** + * Does a deep traversal of every item in object, calling func on every one. + * @param {Object} object The object or array to traverse deeply. + * @param {Function} func The function to call for every item. It will + * be passed the item as an argument. If it returns a truthy value, that + * value will replace the item in its parent container. + * @returns {} the result of calling func on the top-level object itself. + */ + Parse._traverse = function(object, func, seen) { + if (object instanceof Parse.Object) { + seen = seen || []; + if (Parse._.indexOf(seen, object) >= 0) { + // We've already visited this object in this call. + return; + } + seen.push(object); + Parse._traverse(object.attributes, func, seen); + return func(object); + } + if (object instanceof Parse.Relation || object instanceof Parse.File) { + // Nothing needs to be done, but we don't want to recurse into the + // object's parent infinitely, so we catch this case. + return func(object); + } + if (Parse._.isArray(object)) { + Parse._.each(object, function(child, index) { + var newChild = Parse._traverse(child, func, seen); + if (newChild) { + object[index] = newChild; + } + }); + return func(object); + } + if (Parse._.isObject(object)) { + Parse._each(object, function(child, key) { + var newChild = Parse._traverse(child, func, seen); + if (newChild) { + object[key] = newChild; + } + }); + return func(object); + } + return func(object); + }; + + /** + * This is like _.each, except: + * * it doesn't work for so-called array-like objects, + * * it does work for dictionaries with a "length" attribute. + */ + Parse._objectEach = Parse._each = function(obj, callback) { + var _ = Parse._; + if (_.isObject(obj)) { + _.each(_.keys(obj), function(key) { + callback(obj[key], key); + }); + } else { + _.each(obj, callback); + } + }; + + // Helper function to check null or undefined. + Parse._isNullOrUndefined = function(x) { + return Parse._.isNull(x) || Parse._.isUndefined(x); + }; +}(this)); + +/* global require: false, localStorage: false */ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + + var Storage = { + async: false, + }; + + var hasLocalStorage = (typeof localStorage !== 'undefined'); + if (hasLocalStorage) { + try { + localStorage.setItem('supported', true); + localStorage.removeItem('supported'); + } catch(e) { + hasLocalStorage = false; + } + } + if (hasLocalStorage) { + Storage.getItem = function(path) { + return localStorage.getItem(path); + }; + + Storage.setItem = function(path, value) { + return localStorage.setItem(path, value); + }; + + Storage.removeItem = function(path) { + return localStorage.removeItem(path); + }; + + Storage.clear = function() { + return localStorage.clear(); + }; + } else if (typeof require === 'function') { + var AsyncStorage; + try { + AsyncStorage = eval("require('AsyncStorage')"); // jshint ignore:line + + Storage.async = true; + + Storage.getItemAsync = function(path) { + var p = new Parse.Promise(); + AsyncStorage.getItem(path, function(err, value) { + if (err) { + p.reject(err); + } else { + p.resolve(value); + } + }); + return p; + }; + + Storage.setItemAsync = function(path, value) { + var p = new Parse.Promise(); + AsyncStorage.setItem(path, value, function(err) { + if (err) { + p.reject(err); + } else { + p.resolve(value); + } + }); + return p; + }; + + Storage.removeItemAsync = function(path) { + var p = new Parse.Promise(); + AsyncStorage.removeItem(path, function(err) { + if (err) { + p.reject(err); + } else { + p.resolve(); + } + }); + return p; + }; + + Storage.clear = function() { + AsyncStorage.clear(); + }; + } catch (e) { } + } + if (!Storage.async && !Storage.getItem) { + var memMap = Storage.inMemoryMap = {}; + Storage.getItem = function(path) { + if (memMap.hasOwnProperty(path)) { + return memMap[path]; + } + return null; + }; + + Storage.setItem = function(path, value) { + memMap[path] = String(value); + }; + + Storage.removeItem = function(path) { + delete memMap[path]; + }; + + Storage.clear = function() { + for (var key in memMap) { + if (memMap.hasOwnProperty(key)) { + delete memMap[key]; + } + } + }; + } + + // We can use synchronous methods from async scenarios, but not vice-versa + if (!Storage.async) { + Storage.getItemAsync = function(path) { + return Parse.Promise.as( + Storage.getItem(path) + ); + }; + + Storage.setItemAsync = function(path, value) { + Storage.setItem(path, value); + return Parse.Promise.as(value); + }; + + Storage.removeItemAsync = function(path) { + return Parse.Promise.as( + Storage.removeItem(path) + ); + }; + } + + Parse.Storage = Storage; + +})(this); + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * @namespace Provides an interface to Parse's logging and analytics backend. + */ + Parse.Analytics = Parse.Analytics || {}; + + _.extend(Parse.Analytics, /** @lends Parse.Analytics */ { + /** + * Tracks the occurrence of a custom event with additional dimensions. + * Parse will store a data point at the time of invocation with the given + * event name. + * + * Dimensions will allow segmentation of the occurrences of this custom + * event. Keys and values should be {@code String}s, and will throw + * otherwise. + * + * To track a user signup along with additional metadata, consider the + * following: + *
+     * var dimensions = {
+     *  gender: 'm',
+     *  source: 'web',
+     *  dayType: 'weekend'
+     * };
+     * Parse.Analytics.track('signup', dimensions);
+     * 
+ * + * There is a default limit of 8 dimensions per event tracked. + * + * @param {String} name The name of the custom event to report to Parse as + * having happened. + * @param {Object} dimensions The dictionary of information by which to + * segment this event. + * @param {Object} options A Backbone-style callback object. + * @return {Parse.Promise} A promise that is resolved when the round-trip + * to the server completes. + */ + track: function(name, dimensions, options) { + name = name || ''; + name = name.replace(/^\s*/, ''); + name = name.replace(/\s*$/, ''); + if (name.length === 0) { + throw 'A name for the custom event must be provided'; + } + + _.each(dimensions, function(val, key) { + if (!_.isString(key) || !_.isString(val)) { + throw 'track() dimensions expects keys and values of type "string".'; + } + }); + + options = options || {}; + return Parse._request({ + route: 'events', + className: name, + method: 'POST', + data: { dimensions: dimensions } + })._thenRunCallbacks(options); + } + }); +}(this)); + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * @class Parse.Config is a local representation of configuration data that + * can be set from the Parse dashboard. + */ + Parse.Config = function() { + this.attributes = {}; + this._escapedAttributes = {}; + }; + + /** + * Retrieves the most recently-fetched configuration object, either from + * memory or from local storage if necessary. + * + * @return {Parse.Config} The most recently-fetched Parse.Config if it + * exists, else an empty Parse.Config. + */ + Parse.Config.current = function() { + if (Parse.Config._currentConfig) { + return Parse.Config._currentConfig; + } + + var config = new Parse.Config(); + + if (Parse.Storage.async) { + return config; + } + + var configData = Parse.Storage.getItem(Parse._getParsePath( + Parse.Config._CURRENT_CONFIG_KEY)); + + if (configData) { + config._finishFetch(JSON.parse(configData)); + Parse.Config._currentConfig = config; + } + return config; + }; + + /** + * Gets a new configuration object from the server. + * @param {Object} options A Backbone-style options object. + * Valid options are:
    + *
  • success: Function to call when the get completes successfully. + *
  • error: Function to call when the get fails. + *
+ * @return {Parse.Promise} A promise that is resolved with a newly-created + * configuration object when the get completes. + */ + Parse.Config.get = function(options) { + options = options || {}; + + var request = Parse._request({ + route: "config", + method: "GET", + }); + + return request.then(function(response) { + if (!response || !response.params) { + var errorObject = new Parse.Error( + Parse.Error.INVALID_JSON, + "Config JSON response invalid."); + return Parse.Promise.error(errorObject); + } + + var config = new Parse.Config(); + config._finishFetch(response); + Parse.Config._currentConfig = config; + return config; + })._thenRunCallbacks(options); + }; + + Parse.Config.prototype = { + + /** + * Gets the HTML-escaped value of an attribute. + */ + escape: function(attr) { + var html = this._escapedAttributes[attr]; + if (html) { + return html; + } + var val = this.attributes[attr]; + var escaped; + if (Parse._isNullOrUndefined(val)) { + escaped = ''; + } else { + escaped = _.escape(val.toString()); + } + this._escapedAttributes[attr] = escaped; + return escaped; + }, + + /** + * Gets the value of an attribute. + * @param {String} attr The name of an attribute. + */ + get: function(attr) { + return this.attributes[attr]; + }, + + _finishFetch: function(serverData) { + this.attributes = Parse._decode(null, _.clone(serverData.params)); + if (!Parse.Storage.async) { + // We only provide local caching of config with synchronous Storage + Parse.Storage.setItem( + Parse._getParsePath(Parse.Config._CURRENT_CONFIG_KEY), + JSON.stringify(serverData)); + } + } + }; + + Parse.Config._currentConfig = null; + + Parse.Config._CURRENT_CONFIG_KEY = "currentConfig"; + +}(this)); + + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * Constructs a new Parse.Error object with the given code and message. + * @param {Number} code An error code constant from Parse.Error. + * @param {String} message A detailed description of the error. + * @class + * + *

Class used for all objects passed to error callbacks.

+ */ + Parse.Error = function(code, message) { + this.code = code; + this.message = message; + }; + + _.extend(Parse.Error, /** @lends Parse.Error */ { + /** + * Error code indicating some error other than those enumerated here. + * @constant + */ + OTHER_CAUSE: -1, + + /** + * Error code indicating that something has gone wrong with the server. + * If you get this error code, it is Parse's fault. Contact us at + * https://parse.com/help + * @constant + */ + INTERNAL_SERVER_ERROR: 1, + + /** + * Error code indicating the connection to the Parse servers failed. + * @constant + */ + CONNECTION_FAILED: 100, + + /** + * Error code indicating the specified object doesn't exist. + * @constant + */ + OBJECT_NOT_FOUND: 101, + + /** + * Error code indicating you tried to query with a datatype that doesn't + * support it, like exact matching an array or object. + * @constant + */ + INVALID_QUERY: 102, + + /** + * Error code indicating a missing or invalid classname. Classnames are + * case-sensitive. They must start with a letter, and a-zA-Z0-9_ are the + * only valid characters. + * @constant + */ + INVALID_CLASS_NAME: 103, + + /** + * Error code indicating an unspecified object id. + * @constant + */ + MISSING_OBJECT_ID: 104, + + /** + * Error code indicating an invalid key name. Keys are case-sensitive. They + * must start with a letter, and a-zA-Z0-9_ are the only valid characters. + * @constant + */ + INVALID_KEY_NAME: 105, + + /** + * Error code indicating a malformed pointer. You should not see this unless + * you have been mucking about changing internal Parse code. + * @constant + */ + INVALID_POINTER: 106, + + /** + * Error code indicating that badly formed JSON was received upstream. This + * either indicates you have done something unusual with modifying how + * things encode to JSON, or the network is failing badly. + * @constant + */ + INVALID_JSON: 107, + + /** + * Error code indicating that the feature you tried to access is only + * available internally for testing purposes. + * @constant + */ + COMMAND_UNAVAILABLE: 108, + + /** + * You must call Parse.initialize before using the Parse library. + * @constant + */ + NOT_INITIALIZED: 109, + + /** + * Error code indicating that a field was set to an inconsistent type. + * @constant + */ + INCORRECT_TYPE: 111, + + /** + * Error code indicating an invalid channel name. A channel name is either + * an empty string (the broadcast channel) or contains only a-zA-Z0-9_ + * characters and starts with a letter. + * @constant + */ + INVALID_CHANNEL_NAME: 112, + + /** + * Error code indicating that push is misconfigured. + * @constant + */ + PUSH_MISCONFIGURED: 115, + + /** + * Error code indicating that the object is too large. + * @constant + */ + OBJECT_TOO_LARGE: 116, + + /** + * Error code indicating that the operation isn't allowed for clients. + * @constant + */ + OPERATION_FORBIDDEN: 119, + + /** + * Error code indicating the result was not found in the cache. + * @constant + */ + CACHE_MISS: 120, + + /** + * Error code indicating that an invalid key was used in a nested + * JSONObject. + * @constant + */ + INVALID_NESTED_KEY: 121, + + /** + * Error code indicating that an invalid filename was used for ParseFile. + * A valid file name contains only a-zA-Z0-9_. characters and is between 1 + * and 128 characters. + * @constant + */ + INVALID_FILE_NAME: 122, + + /** + * Error code indicating an invalid ACL was provided. + * @constant + */ + INVALID_ACL: 123, + + /** + * Error code indicating that the request timed out on the server. Typically + * this indicates that the request is too expensive to run. + * @constant + */ + TIMEOUT: 124, + + /** + * Error code indicating that the email address was invalid. + * @constant + */ + INVALID_EMAIL_ADDRESS: 125, + + /** + * Error code indicating a missing content type. + * @constant + */ + MISSING_CONTENT_TYPE: 126, + + /** + * Error code indicating a missing content length. + * @constant + */ + MISSING_CONTENT_LENGTH: 127, + + /** + * Error code indicating an invalid content length. + * @constant + */ + INVALID_CONTENT_LENGTH: 128, + + /** + * Error code indicating a file that was too large. + * @constant + */ + FILE_TOO_LARGE: 129, + + /** + * Error code indicating an error saving a file. + * @constant + */ + FILE_SAVE_ERROR: 130, + + /** + * Error code indicating that a unique field was given a value that is + * already taken. + * @constant + */ + DUPLICATE_VALUE: 137, + + /** + * Error code indicating that a role's name is invalid. + * @constant + */ + INVALID_ROLE_NAME: 139, + + /** + * Error code indicating that an application quota was exceeded. Upgrade to + * resolve. + * @constant + */ + EXCEEDED_QUOTA: 140, + + /** + * Error code indicating that a Cloud Code script failed. + * @constant + */ + SCRIPT_FAILED: 141, + + /** + * Error code indicating that a Cloud Code validation failed. + * @constant + */ + VALIDATION_ERROR: 142, + + /** + * Error code indicating that invalid image data was provided. + * @constant + */ + INVALID_IMAGE_DATA: 150, + + /** + * Error code indicating an unsaved file. + * @constant + */ + UNSAVED_FILE_ERROR: 151, + + /** + * Error code indicating an invalid push time. + */ + INVALID_PUSH_TIME_ERROR: 152, + + /** + * Error code indicating an error deleting a file. + * @constant + */ + FILE_DELETE_ERROR: 153, + + /** + * Error code indicating that the application has exceeded its request + * limit. + * @constant + */ + REQUEST_LIMIT_EXCEEDED: 155, + + /** + * Error code indicating an invalid event name. + */ + INVALID_EVENT_NAME: 160, + + /** + * Error code indicating that the username is missing or empty. + * @constant + */ + USERNAME_MISSING: 200, + + /** + * Error code indicating that the password is missing or empty. + * @constant + */ + PASSWORD_MISSING: 201, + + /** + * Error code indicating that the username has already been taken. + * @constant + */ + USERNAME_TAKEN: 202, + + /** + * Error code indicating that the email has already been taken. + * @constant + */ + EMAIL_TAKEN: 203, + + /** + * Error code indicating that the email is missing, but must be specified. + * @constant + */ + EMAIL_MISSING: 204, + + /** + * Error code indicating that a user with the specified email was not found. + * @constant + */ + EMAIL_NOT_FOUND: 205, + + /** + * Error code indicating that a user object without a valid session could + * not be altered. + * @constant + */ + SESSION_MISSING: 206, + + /** + * Error code indicating that a user can only be created through signup. + * @constant + */ + MUST_CREATE_USER_THROUGH_SIGNUP: 207, + + /** + * Error code indicating that an an account being linked is already linked + * to another user. + * @constant + */ + ACCOUNT_ALREADY_LINKED: 208, + + /** + * Error code indicating that the current session token is invalid. + * @constant + */ + INVALID_SESSION_TOKEN: 209, + + /** + * Error code indicating that a user cannot be linked to an account because + * that account's id could not be found. + * @constant + */ + LINKED_ID_MISSING: 250, + + /** + * Error code indicating that a user with a linked (e.g. Facebook) account + * has an invalid session. + * @constant + */ + INVALID_LINKED_SESSION: 251, + + /** + * Error code indicating that a service being linked (e.g. Facebook or + * Twitter) is unsupported. + * @constant + */ + UNSUPPORTED_SERVICE: 252, + + /** + * Error code indicating that there were multiple errors. Aggregate errors + * have an "errors" property, which is an array of error objects with more + * detail about each error that occurred. + * @constant + */ + AGGREGATE_ERROR: 600, + + /** + * Error code indicating the client was unable to read an input file. + * @constant + */ + FILE_READ_ERROR: 601, + + /** + * Error code indicating a real error code is unavailable because + * we had to use an XDomainRequest object to allow CORS requests in + * Internet Explorer, which strips the body from HTTP responses that have + * a non-2XX status code. + * @constant + */ + X_DOMAIN_REQUEST: 602 + }); + +}(this)); + +/*global _: false */ +(function() { + var root = this; + var Parse = (root.Parse || (root.Parse = {})); + var eventSplitter = /\s+/; + var slice = Array.prototype.slice; + + /** + * @class + * + *

Parse.Events is a fork of Backbone's Events module, provided for your + * convenience.

+ * + *

A module that can be mixed in to any object in order to provide + * it with custom events. You may bind callback functions to an event + * with `on`, or remove these functions with `off`. + * Triggering an event fires all callbacks in the order that `on` was + * called. + * + *

+   *     var object = {};
+   *     _.extend(object, Parse.Events);
+   *     object.on('expand', function(){ alert('expanded'); });
+   *     object.trigger('expand');

+ * + *

For more information, see the + * Backbone + * documentation.

+ */ + Parse.Events = { + /** + * Bind one or more space separated events, `events`, to a `callback` + * function. Passing `"all"` will bind the callback to all events fired. + */ + on: function(events, callback, context) { + + var calls, event, node, tail, list; + if (!callback) { + return this; + } + events = events.split(eventSplitter); + calls = this._callbacks || (this._callbacks = {}); + + // Create an immutable callback list, allowing traversal during + // modification. The tail is an empty object that will always be used + // as the next node. + event = events.shift(); + while (event) { + list = calls[event]; + node = list ? list.tail : {}; + node.next = tail = {}; + node.context = context; + node.callback = callback; + calls[event] = {tail: tail, next: list ? list.next : node}; + event = events.shift(); + } + + return this; + }, + + /** + * Remove one or many callbacks. If `context` is null, removes all callbacks + * with that function. If `callback` is null, removes all callbacks for the + * event. If `events` is null, removes all bound callbacks for all events. + */ + off: function(events, callback, context) { + var event, calls, node, tail, cb, ctx; + + // No events, or removing *all* events. + if (!(calls = this._callbacks)) { + return; + } + if (!(events || callback || context)) { + delete this._callbacks; + return this; + } + + // Loop through the listed events and contexts, splicing them out of the + // linked list of callbacks if appropriate. + events = events ? events.split(eventSplitter) : Object.keys(calls); + event = events.shift(); + while (event) { + node = calls[event]; + delete calls[event]; + if (!node || !(callback || context)) { + event = events.shift(); + continue; + } + // Create a new list, omitting the indicated callbacks. + tail = node.tail; + node = node.next; + while (node !== tail) { + cb = node.callback; + ctx = node.context; + if ((callback && cb !== callback) || (context && ctx !== context)) { + this.on(event, cb, ctx); + } + node = node.next; + } + event = events.shift(); + } + + return this; + }, + + /** + * Trigger one or many events, firing all bound callbacks. Callbacks are + * passed the same arguments as `trigger` is, apart from the event name + * (unless you're listening on `"all"`, which will cause your callback to + * receive the true name of the event as the first argument). + */ + trigger: function(events) { + var event, node, calls, tail, args, all, rest; + if (!(calls = this._callbacks)) { + return this; + } + all = calls.all; + events = events.split(eventSplitter); + rest = slice.call(arguments, 1); + + // For each event, walk through the linked list of callbacks twice, + // first to trigger the event, then to trigger any `"all"` callbacks. + event = events.shift(); + while (event) { + node = calls[event]; + if (node) { + tail = node.tail; + while ((node = node.next) !== tail) { + node.callback.apply(node.context || this, rest); + } + } + node = all; + if (node) { + tail = node.tail; + args = [event].concat(rest); + while ((node = node.next) !== tail) { + node.callback.apply(node.context || this, args); + } + } + event = events.shift(); + } + + return this; + } + }; + + /** + * @function + */ + Parse.Events.bind = Parse.Events.on; + + /** + * @function + */ + Parse.Events.unbind = Parse.Events.off; +}.call(this)); + + +/*global navigator: false */ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * Creates a new GeoPoint with any of the following forms:
+ *
+   *   new GeoPoint(otherGeoPoint)
+   *   new GeoPoint(30, 30)
+   *   new GeoPoint([30, 30])
+   *   new GeoPoint({latitude: 30, longitude: 30})
+   *   new GeoPoint()  // defaults to (0, 0)
+   *   
+ * @class + * + *

Represents a latitude / longitude point that may be associated + * with a key in a ParseObject or used as a reference point for geo queries. + * This allows proximity-based queries on the key.

+ * + *

Only one key in a class may contain a GeoPoint.

+ * + *

Example:

+   *   var point = new Parse.GeoPoint(30.0, -20.0);
+   *   var object = new Parse.Object("PlaceObject");
+   *   object.set("location", point);
+   *   object.save();

+ */ + Parse.GeoPoint = function(arg1, arg2) { + if (_.isArray(arg1)) { + Parse.GeoPoint._validate(arg1[0], arg1[1]); + this.latitude = arg1[0]; + this.longitude = arg1[1]; + } else if (_.isObject(arg1)) { + Parse.GeoPoint._validate(arg1.latitude, arg1.longitude); + this.latitude = arg1.latitude; + this.longitude = arg1.longitude; + } else if (_.isNumber(arg1) && _.isNumber(arg2)) { + Parse.GeoPoint._validate(arg1, arg2); + this.latitude = arg1; + this.longitude = arg2; + } else { + this.latitude = 0; + this.longitude = 0; + } + + // Add properties so that anyone using Webkit or Mozilla will get an error + // if they try to set values that are out of bounds. + var self = this; + if (this.__defineGetter__ && this.__defineSetter__) { + // Use _latitude and _longitude to actually store the values, and add + // getters and setters for latitude and longitude. + this._latitude = this.latitude; + this._longitude = this.longitude; + this.__defineGetter__("latitude", function() { + return self._latitude; + }); + this.__defineGetter__("longitude", function() { + return self._longitude; + }); + this.__defineSetter__("latitude", function(val) { + Parse.GeoPoint._validate(val, self.longitude); + self._latitude = val; + }); + this.__defineSetter__("longitude", function(val) { + Parse.GeoPoint._validate(self.latitude, val); + self._longitude = val; + }); + } + }; + + /** + * @lends Parse.GeoPoint.prototype + * @property {float} latitude North-south portion of the coordinate, in range + * [-90, 90]. Throws an exception if set out of range in a modern browser. + * @property {float} longitude East-west portion of the coordinate, in range + * [-180, 180]. Throws if set out of range in a modern browser. + */ + + /** + * Throws an exception if the given lat-long is out of bounds. + */ + Parse.GeoPoint._validate = function(latitude, longitude) { + if (latitude < -90.0) { + throw "Parse.GeoPoint latitude " + latitude + " < -90.0."; + } + if (latitude > 90.0) { + throw "Parse.GeoPoint latitude " + latitude + " > 90.0."; + } + if (longitude < -180.0) { + throw "Parse.GeoPoint longitude " + longitude + " < -180.0."; + } + if (longitude > 180.0) { + throw "Parse.GeoPoint longitude " + longitude + " > 180.0."; + } + }; + + /** + * Creates a GeoPoint with the user's current location, if available. + * Calls options.success with a new GeoPoint instance or calls options.error. + * @param {Object} options An object with success and error callbacks. + */ + Parse.GeoPoint.current = function(options) { + var promise = new Parse.Promise(); + navigator.geolocation.getCurrentPosition(function(location) { + promise.resolve(new Parse.GeoPoint({ + latitude: location.coords.latitude, + longitude: location.coords.longitude + })); + + }, function(error) { + promise.reject(error); + }); + + return promise._thenRunCallbacks(options); + }; + + Parse.GeoPoint.prototype = { + /** + * Returns a JSON representation of the GeoPoint, suitable for Parse. + * @return {Object} + */ + toJSON: function() { + Parse.GeoPoint._validate(this.latitude, this.longitude); + return { + "__type": "GeoPoint", + latitude: this.latitude, + longitude: this.longitude + }; + }, + + /** + * Returns the distance from this GeoPoint to another in radians. + * @param {Parse.GeoPoint} point the other Parse.GeoPoint. + * @return {Number} + */ + radiansTo: function(point) { + var d2r = Math.PI / 180.0; + var lat1rad = this.latitude * d2r; + var long1rad = this.longitude * d2r; + var lat2rad = point.latitude * d2r; + var long2rad = point.longitude * d2r; + var deltaLat = lat1rad - lat2rad; + var deltaLong = long1rad - long2rad; + var sinDeltaLatDiv2 = Math.sin(deltaLat / 2); + var sinDeltaLongDiv2 = Math.sin(deltaLong / 2); + // Square of half the straight line chord distance between both points. + var a = ((sinDeltaLatDiv2 * sinDeltaLatDiv2) + + (Math.cos(lat1rad) * Math.cos(lat2rad) * + sinDeltaLongDiv2 * sinDeltaLongDiv2)); + a = Math.min(1.0, a); + return 2 * Math.asin(Math.sqrt(a)); + }, + + /** + * Returns the distance from this GeoPoint to another in kilometers. + * @param {Parse.GeoPoint} point the other Parse.GeoPoint. + * @return {Number} + */ + kilometersTo: function(point) { + return this.radiansTo(point) * 6371.0; + }, + + /** + * Returns the distance from this GeoPoint to another in miles. + * @param {Parse.GeoPoint} point the other Parse.GeoPoint. + * @return {Number} + */ + milesTo: function(point) { + return this.radiansTo(point) * 3958.8; + } + }; +}(this)); + +/*global navigator: false */ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + var PUBLIC_KEY = "*"; + + /** + * Creates a new ACL. + * If no argument is given, the ACL has no permissions for anyone. + * If the argument is a Parse.User, the ACL will have read and write + * permission for only that user. + * If the argument is any other JSON object, that object will be interpretted + * as a serialized ACL created with toJSON(). + * @see Parse.Object#setACL + * @class + * + *

An ACL, or Access Control List can be added to any + * Parse.Object to restrict access to only a subset of users + * of your application.

+ */ + Parse.ACL = function(arg1) { + var self = this; + self.permissionsById = {}; + if (_.isObject(arg1)) { + if (arg1 instanceof Parse.User) { + self.setReadAccess(arg1, true); + self.setWriteAccess(arg1, true); + } else { + if (_.isFunction(arg1)) { + throw "Parse.ACL() called with a function. Did you forget ()?"; + } + Parse._objectEach(arg1, function(accessList, userId) { + if (!_.isString(userId)) { + throw "Tried to create an ACL with an invalid userId."; + } + self.permissionsById[userId] = {}; + Parse._objectEach(accessList, function(allowed, permission) { + if (permission !== "read" && permission !== "write") { + throw "Tried to create an ACL with an invalid permission type."; + } + if (!_.isBoolean(allowed)) { + throw "Tried to create an ACL with an invalid permission value."; + } + self.permissionsById[userId][permission] = allowed; + }); + }); + } + } + }; + + /** + * Returns a JSON-encoded version of the ACL. + * @return {Object} + */ + Parse.ACL.prototype.toJSON = function() { + return _.clone(this.permissionsById); + }; + + Parse.ACL.prototype._setAccess = function(accessType, userId, allowed) { + if (userId instanceof Parse.User) { + userId = userId.id; + } else if (userId instanceof Parse.Role) { + userId = "role:" + userId.getName(); + } + if (!_.isString(userId)) { + throw "userId must be a string."; + } + if (!_.isBoolean(allowed)) { + throw "allowed must be either true or false."; + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + if (!allowed) { + // The user already doesn't have this permission, so no action needed. + return; + } else { + permissions = {}; + this.permissionsById[userId] = permissions; + } + } + + if (allowed) { + this.permissionsById[userId][accessType] = true; + } else { + delete permissions[accessType]; + if (_.isEmpty(permissions)) { + delete permissions[userId]; + } + } + }; + + Parse.ACL.prototype._getAccess = function(accessType, userId) { + if (userId instanceof Parse.User) { + userId = userId.id; + } else if (userId instanceof Parse.Role) { + userId = "role:" + userId.getName(); + } + var permissions = this.permissionsById[userId]; + if (!permissions) { + return false; + } + return permissions[accessType] ? true : false; + }; + + /** + * Set whether the given user is allowed to read this object. + * @param userId An instance of Parse.User or its objectId. + * @param {Boolean} allowed Whether that user should have read access. + */ + Parse.ACL.prototype.setReadAccess = function(userId, allowed) { + this._setAccess("read", userId, allowed); + }; + + /** + * Get whether the given user id is *explicitly* allowed to read this object. + * Even if this returns false, the user may still be able to access it if + * getPublicReadAccess returns true or a role that the user belongs to has + * write access. + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + Parse.ACL.prototype.getReadAccess = function(userId) { + return this._getAccess("read", userId); + }; + + /** + * Set whether the given user id is allowed to write this object. + * @param userId An instance of Parse.User or its objectId, or a Parse.Role.. + * @param {Boolean} allowed Whether that user should have write access. + */ + Parse.ACL.prototype.setWriteAccess = function(userId, allowed) { + this._setAccess("write", userId, allowed); + }; + + /** + * Get whether the given user id is *explicitly* allowed to write this object. + * Even if this returns false, the user may still be able to write it if + * getPublicWriteAccess returns true or a role that the user belongs to has + * write access. + * @param userId An instance of Parse.User or its objectId, or a Parse.Role. + * @return {Boolean} + */ + Parse.ACL.prototype.getWriteAccess = function(userId) { + return this._getAccess("write", userId); + }; + + /** + * Set whether the public is allowed to read this object. + * @param {Boolean} allowed + */ + Parse.ACL.prototype.setPublicReadAccess = function(allowed) { + this.setReadAccess(PUBLIC_KEY, allowed); + }; + + /** + * Get whether the public is allowed to read this object. + * @return {Boolean} + */ + Parse.ACL.prototype.getPublicReadAccess = function() { + return this.getReadAccess(PUBLIC_KEY); + }; + + /** + * Set whether the public is allowed to write this object. + * @param {Boolean} allowed + */ + Parse.ACL.prototype.setPublicWriteAccess = function(allowed) { + this.setWriteAccess(PUBLIC_KEY, allowed); + }; + + /** + * Get whether the public is allowed to write this object. + * @return {Boolean} + */ + Parse.ACL.prototype.getPublicWriteAccess = function() { + return this.getWriteAccess(PUBLIC_KEY); + }; + + /** + * Get whether users belonging to the given role are allowed + * to read this object. Even if this returns false, the role may + * still be able to write it if a parent role has read access. + * + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has read access. false otherwise. + * @throws {String} If role is neither a Parse.Role nor a String. + */ + Parse.ACL.prototype.getRoleReadAccess = function(role) { + if (role instanceof Parse.Role) { + // Normalize to the String name + role = role.getName(); + } + if (_.isString(role)) { + return this.getReadAccess("role:" + role); + } + throw "role must be a Parse.Role or a String"; + }; + + /** + * Get whether users belonging to the given role are allowed + * to write this object. Even if this returns false, the role may + * still be able to write it if a parent role has write access. + * + * @param role The name of the role, or a Parse.Role object. + * @return {Boolean} true if the role has write access. false otherwise. + * @throws {String} If role is neither a Parse.Role nor a String. + */ + Parse.ACL.prototype.getRoleWriteAccess = function(role) { + if (role instanceof Parse.Role) { + // Normalize to the String name + role = role.getName(); + } + if (_.isString(role)) { + return this.getWriteAccess("role:" + role); + } + throw "role must be a Parse.Role or a String"; + }; + + /** + * Set whether users belonging to the given role are allowed + * to read this object. + * + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can read this object. + * @throws {String} If role is neither a Parse.Role nor a String. + */ + Parse.ACL.prototype.setRoleReadAccess = function(role, allowed) { + if (role instanceof Parse.Role) { + // Normalize to the String name + role = role.getName(); + } + if (_.isString(role)) { + this.setReadAccess("role:" + role, allowed); + return; + } + throw "role must be a Parse.Role or a String"; + }; + + /** + * Set whether users belonging to the given role are allowed + * to write this object. + * + * @param role The name of the role, or a Parse.Role object. + * @param {Boolean} allowed Whether the given role can write this object. + * @throws {String} If role is neither a Parse.Role nor a String. + */ + Parse.ACL.prototype.setRoleWriteAccess = function(role, allowed) { + if (role instanceof Parse.Role) { + // Normalize to the String name + role = role.getName(); + } + if (_.isString(role)) { + this.setWriteAccess("role:" + role, allowed); + return; + } + throw "role must be a Parse.Role or a String"; + }; + +}(this)); + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * @class + * A Parse.Op is an atomic operation that can be applied to a field in a + * Parse.Object. For example, calling object.set("foo", "bar") + * is an example of a Parse.Op.Set. Calling object.unset("foo") + * is a Parse.Op.Unset. These operations are stored in a Parse.Object and + * sent to the server as part of object.save() operations. + * Instances of Parse.Op should be immutable. + * + * You should not create subclasses of Parse.Op or instantiate Parse.Op + * directly. + */ + Parse.Op = function() { + this._initialize.apply(this, arguments); + }; + + Parse.Op.prototype = { + _initialize: function() {} + }; + + _.extend(Parse.Op, { + /** + * To create a new Op, call Parse.Op._extend(); + */ + _extend: Parse._extend, + + // A map of __op string to decoder function. + _opDecoderMap: {}, + + /** + * Registers a function to convert a json object with an __op field into an + * instance of a subclass of Parse.Op. + */ + _registerDecoder: function(opName, decoder) { + Parse.Op._opDecoderMap[opName] = decoder; + }, + + /** + * Converts a json object into an instance of a subclass of Parse.Op. + */ + _decode: function(json) { + var decoder = Parse.Op._opDecoderMap[json.__op]; + if (decoder) { + return decoder(json); + } else { + return undefined; + } + } + }); + + /* + * Add a handler for Batch ops. + */ + Parse.Op._registerDecoder("Batch", function(json) { + var op = null; + Parse._arrayEach(json.ops, function(nextOp) { + nextOp = Parse.Op._decode(nextOp); + op = nextOp._mergeWithPrevious(op); + }); + return op; + }); + + /** + * @class + * A Set operation indicates that either the field was changed using + * Parse.Object.set, or it is a mutable container that was detected as being + * changed. + */ + Parse.Op.Set = Parse.Op._extend(/** @lends Parse.Op.Set.prototype */ { + _initialize: function(value) { + this._value = value; + }, + + /** + * Returns the new value of this field after the set. + */ + value: function() { + return this._value; + }, + + /** + * Returns a JSON version of the operation suitable for sending to Parse. + * @return {Object} + */ + toJSON: function() { + return Parse._encode(this.value()); + }, + + _mergeWithPrevious: function(previous) { + return this; + }, + + _estimate: function(oldValue) { + return this.value(); + } + }); + + /** + * A sentinel value that is returned by Parse.Op.Unset._estimate to + * indicate the field should be deleted. Basically, if you find _UNSET as a + * value in your object, you should remove that key. + */ + Parse.Op._UNSET = {}; + + /** + * @class + * An Unset operation indicates that this field has been deleted from the + * object. + */ + Parse.Op.Unset = Parse.Op._extend(/** @lends Parse.Op.Unset.prototype */ { + /** + * Returns a JSON version of the operation suitable for sending to Parse. + * @return {Object} + */ + toJSON: function() { + return { __op: "Delete" }; + }, + + _mergeWithPrevious: function(previous) { + return this; + }, + + _estimate: function(oldValue) { + return Parse.Op._UNSET; + } + }); + + Parse.Op._registerDecoder("Delete", function(json) { + return new Parse.Op.Unset(); + }); + + /** + * @class + * An Increment is an atomic operation where the numeric value for the field + * will be increased by a given amount. + */ + Parse.Op.Increment = Parse.Op._extend( + /** @lends Parse.Op.Increment.prototype */ { + + _initialize: function(amount) { + this._amount = amount; + }, + + /** + * Returns the amount to increment by. + * @return {Number} the amount to increment by. + */ + amount: function() { + return this._amount; + }, + + /** + * Returns a JSON version of the operation suitable for sending to Parse. + * @return {Object} + */ + toJSON: function() { + return { __op: "Increment", amount: this._amount }; + }, + + _mergeWithPrevious: function(previous) { + if (!previous) { + return this; + } else if (previous instanceof Parse.Op.Unset) { + return new Parse.Op.Set(this.amount()); + } else if (previous instanceof Parse.Op.Set) { + return new Parse.Op.Set(previous.value() + this.amount()); + } else if (previous instanceof Parse.Op.Increment) { + return new Parse.Op.Increment(this.amount() + previous.amount()); + } else { + throw "Op is invalid after previous op."; + } + }, + + _estimate: function(oldValue) { + if (!oldValue) { + return this.amount(); + } + return oldValue + this.amount(); + } + }); + + Parse.Op._registerDecoder("Increment", function(json) { + return new Parse.Op.Increment(json.amount); + }); + + /** + * @class + * Add is an atomic operation where the given objects will be appended to the + * array that is stored in this field. + */ + Parse.Op.Add = Parse.Op._extend(/** @lends Parse.Op.Add.prototype */ { + _initialize: function(objects) { + this._objects = objects; + }, + + /** + * Returns the objects to be added to the array. + * @return {Array} The objects to be added to the array. + */ + objects: function() { + return this._objects; + }, + + /** + * Returns a JSON version of the operation suitable for sending to Parse. + * @return {Object} + */ + toJSON: function() { + return { __op: "Add", objects: Parse._encode(this.objects()) }; + }, + + _mergeWithPrevious: function(previous) { + if (!previous) { + return this; + } else if (previous instanceof Parse.Op.Unset) { + return new Parse.Op.Set(this.objects()); + } else if (previous instanceof Parse.Op.Set) { + return new Parse.Op.Set(this._estimate(previous.value())); + } else if (previous instanceof Parse.Op.Add) { + return new Parse.Op.Add(previous.objects().concat(this.objects())); + } else { + throw "Op is invalid after previous op."; + } + }, + + _estimate: function(oldValue) { + if (!oldValue) { + return _.clone(this.objects()); + } else { + return oldValue.concat(this.objects()); + } + } + }); + + Parse.Op._registerDecoder("Add", function(json) { + return new Parse.Op.Add(Parse._decode(undefined, json.objects)); + }); + + /** + * @class + * AddUnique is an atomic operation where the given items will be appended to + * the array that is stored in this field only if they were not already + * present in the array. + */ + Parse.Op.AddUnique = Parse.Op._extend( + /** @lends Parse.Op.AddUnique.prototype */ { + + _initialize: function(objects) { + this._objects = _.uniq(objects); + }, + + /** + * Returns the objects to be added to the array. + * @return {Array} The objects to be added to the array. + */ + objects: function() { + return this._objects; + }, + + /** + * Returns a JSON version of the operation suitable for sending to Parse. + * @return {Object} + */ + toJSON: function() { + return { __op: "AddUnique", objects: Parse._encode(this.objects()) }; + }, + + _mergeWithPrevious: function(previous) { + if (!previous) { + return this; + } else if (previous instanceof Parse.Op.Unset) { + return new Parse.Op.Set(this.objects()); + } else if (previous instanceof Parse.Op.Set) { + return new Parse.Op.Set(this._estimate(previous.value())); + } else if (previous instanceof Parse.Op.AddUnique) { + return new Parse.Op.AddUnique(this._estimate(previous.objects())); + } else { + throw "Op is invalid after previous op."; + } + }, + + _estimate: function(oldValue) { + if (!oldValue) { + return _.clone(this.objects()); + } else { + // We can't just take the _.uniq(_.union(...)) of oldValue and + // this.objects, because the uniqueness may not apply to oldValue + // (especially if the oldValue was set via .set()) + var newValue = _.clone(oldValue); + Parse._arrayEach(this.objects(), function(obj) { + if (obj instanceof Parse.Object && obj.id) { + var matchingObj = _.find(newValue, function(anObj) { + return (anObj instanceof Parse.Object) && (anObj.id === obj.id); + }); + if (!matchingObj) { + newValue.push(obj); + } else { + var index = _.indexOf(newValue, matchingObj); + newValue[index] = obj; + } + } else if (!_.contains(newValue, obj)) { + newValue.push(obj); + } + }); + return newValue; + } + } + }); + + Parse.Op._registerDecoder("AddUnique", function(json) { + return new Parse.Op.AddUnique(Parse._decode(undefined, json.objects)); + }); + + /** + * @class + * Remove is an atomic operation where the given objects will be removed from + * the array that is stored in this field. + */ + Parse.Op.Remove = Parse.Op._extend(/** @lends Parse.Op.Remove.prototype */ { + _initialize: function(objects) { + this._objects = _.uniq(objects); + }, + + /** + * Returns the objects to be removed from the array. + * @return {Array} The objects to be removed from the array. + */ + objects: function() { + return this._objects; + }, + + /** + * Returns a JSON version of the operation suitable for sending to Parse. + * @return {Object} + */ + toJSON: function() { + return { __op: "Remove", objects: Parse._encode(this.objects()) }; + }, + + _mergeWithPrevious: function(previous) { + if (!previous) { + return this; + } else if (previous instanceof Parse.Op.Unset) { + return previous; + } else if (previous instanceof Parse.Op.Set) { + return new Parse.Op.Set(this._estimate(previous.value())); + } else if (previous instanceof Parse.Op.Remove) { + return new Parse.Op.Remove(_.union(previous.objects(), this.objects())); + } else { + throw "Op is invalid after previous op."; + } + }, + + _estimate: function(oldValue) { + if (!oldValue) { + return []; + } else { + var newValue = _.difference(oldValue, this.objects()); + // If there are saved Parse Objects being removed, also remove them. + Parse._arrayEach(this.objects(), function(obj) { + if (obj instanceof Parse.Object && obj.id) { + newValue = _.reject(newValue, function(other) { + return (other instanceof Parse.Object) && (other.id === obj.id); + }); + } + }); + return newValue; + } + } + }); + + Parse.Op._registerDecoder("Remove", function(json) { + return new Parse.Op.Remove(Parse._decode(undefined, json.objects)); + }); + + /** + * @class + * A Relation operation indicates that the field is an instance of + * Parse.Relation, and objects are being added to, or removed from, that + * relation. + */ + Parse.Op.Relation = Parse.Op._extend( + /** @lends Parse.Op.Relation.prototype */ { + + _initialize: function(adds, removes) { + this._targetClassName = null; + + var self = this; + + var pointerToId = function(object) { + if (object instanceof Parse.Object) { + if (!object.id) { + throw "You can't add an unsaved Parse.Object to a relation."; + } + if (!self._targetClassName) { + self._targetClassName = object.className; + } + if (self._targetClassName !== object.className) { + throw "Tried to create a Parse.Relation with 2 different types: " + + self._targetClassName + " and " + object.className + "."; + } + return object.id; + } + return object; + }; + + this.relationsToAdd = _.uniq(_.map(adds, pointerToId)); + this.relationsToRemove = _.uniq(_.map(removes, pointerToId)); + }, + + /** + * Returns an array of unfetched Parse.Object that are being added to the + * relation. + * @return {Array} + */ + added: function() { + var self = this; + return _.map(this.relationsToAdd, function(objectId) { + var object = Parse.Object._create(self._targetClassName); + object.id = objectId; + return object; + }); + }, + + /** + * Returns an array of unfetched Parse.Object that are being removed from + * the relation. + * @return {Array} + */ + removed: function() { + var self = this; + return _.map(this.relationsToRemove, function(objectId) { + var object = Parse.Object._create(self._targetClassName); + object.id = objectId; + return object; + }); + }, + + /** + * Returns a JSON version of the operation suitable for sending to Parse. + * @return {Object} + */ + toJSON: function() { + var adds = null; + var removes = null; + var self = this; + var idToPointer = function(id) { + return { __type: 'Pointer', + className: self._targetClassName, + objectId: id }; + }; + var pointers = null; + if (this.relationsToAdd.length > 0) { + pointers = _.map(this.relationsToAdd, idToPointer); + adds = { "__op": "AddRelation", "objects": pointers }; + } + + if (this.relationsToRemove.length > 0) { + pointers = _.map(this.relationsToRemove, idToPointer); + removes = { "__op": "RemoveRelation", "objects": pointers }; + } + + if (adds && removes) { + return { "__op": "Batch", "ops": [adds, removes]}; + } + + return adds || removes || {}; + }, + + _mergeWithPrevious: function(previous) { + if (!previous) { + return this; + } else if (previous instanceof Parse.Op.Unset) { + throw "You can't modify a relation after deleting it."; + } else if (previous instanceof Parse.Op.Relation) { + if (previous._targetClassName && + previous._targetClassName !== this._targetClassName) { + throw "Related object must be of class " + previous._targetClassName + + ", but " + this._targetClassName + " was passed in."; + } + var newAdd = _.union(_.difference(previous.relationsToAdd, + this.relationsToRemove), + this.relationsToAdd); + var newRemove = _.union(_.difference(previous.relationsToRemove, + this.relationsToAdd), + this.relationsToRemove); + + var newRelation = new Parse.Op.Relation(newAdd, newRemove); + newRelation._targetClassName = this._targetClassName; + return newRelation; + } else { + throw "Op is invalid after previous op."; + } + }, + + _estimate: function(oldValue, object, key) { + if (!oldValue) { + var relation = new Parse.Relation(object, key); + relation.targetClassName = this._targetClassName; + } else if (oldValue instanceof Parse.Relation) { + if (this._targetClassName) { + if (oldValue.targetClassName) { + if (oldValue.targetClassName !== this._targetClassName) { + throw "Related object must be a " + oldValue.targetClassName + + ", but a " + this._targetClassName + " was passed in."; + } + } else { + oldValue.targetClassName = this._targetClassName; + } + } + return oldValue; + } else { + throw "Op is invalid after previous op."; + } + } + }); + + Parse.Op._registerDecoder("AddRelation", function(json) { + return new Parse.Op.Relation(Parse._decode(undefined, json.objects), []); + }); + Parse.Op._registerDecoder("RemoveRelation", function(json) { + return new Parse.Op.Relation([], Parse._decode(undefined, json.objects)); + }); + +}(this)); + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * Creates a new Relation for the given parent object and key. This + * constructor should rarely be used directly, but rather created by + * Parse.Object.relation. + * @param {Parse.Object} parent The parent of this relation. + * @param {String} key The key for this relation on the parent. + * @see Parse.Object#relation + * @class + * + *

+ * A class that is used to access all of the children of a many-to-many + * relationship. Each instance of Parse.Relation is associated with a + * particular parent object and key. + *

+ */ + Parse.Relation = function(parent, key) { + this.parent = parent; + this.key = key; + this.targetClassName = null; + }; + + Parse.Relation.prototype = { + /** + * Makes sure that this relation has the right parent and key. + */ + _ensureParentAndKey: function(parent, key) { + this.parent = this.parent || parent; + this.key = this.key || key; + if (this.parent !== parent) { + throw "Internal Error. Relation retrieved from two different Objects."; + } + if (this.key !== key) { + throw "Internal Error. Relation retrieved from two different keys."; + } + }, + + /** + * Adds a Parse.Object or an array of Parse.Objects to the relation. + * @param {} objects The item or items to add. + */ + add: function(objects) { + if (!_.isArray(objects)) { + objects = [objects]; + } + + var change = new Parse.Op.Relation(objects, []); + this.parent.set(this.key, change); + this.targetClassName = change._targetClassName; + }, + + /** + * Removes a Parse.Object or an array of Parse.Objects from this relation. + * @param {} objects The item or items to remove. + */ + remove: function(objects) { + if (!_.isArray(objects)) { + objects = [objects]; + } + + var change = new Parse.Op.Relation([], objects); + this.parent.set(this.key, change); + this.targetClassName = change._targetClassName; + }, + + /** + * Returns a JSON version of the object suitable for saving to disk. + * @return {Object} + */ + toJSON: function() { + return { "__type": "Relation", "className": this.targetClassName }; + }, + + /** + * Returns a Parse.Query that is limited to objects in this + * relation. + * @return {Parse.Query} + */ + query: function() { + var targetClass; + var query; + if (!this.targetClassName) { + targetClass = Parse.Object._getSubclass(this.parent.className); + query = new Parse.Query(targetClass); + query._extraOptions.redirectClassNameForKey = this.key; + } else { + targetClass = Parse.Object._getSubclass(this.targetClassName); + query = new Parse.Query(targetClass); + } + query._addCondition("$relatedTo", "object", this.parent._toPointer()); + query._addCondition("$relatedTo", "key", this.key); + + return query; + } + }; +}(this)); + +/*global window: false, process: false */ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * A Promise is returned by async methods as a hook to provide callbacks to be + * called when the async task is fulfilled. + * + *

Typical usage would be like:

+   *    query.find().then(function(results) {
+   *      results[0].set("foo", "bar");
+   *      return results[0].saveAsync();
+   *    }).then(function(result) {
+   *      console.log("Updated " + result.id);
+   *    });
+   * 

+ * + * @see Parse.Promise.prototype.then + * @class + */ + Parse.Promise = function() { + this._resolved = false; + this._rejected = false; + this._resolvedCallbacks = []; + this._rejectedCallbacks = []; + }; + + _.extend(Parse.Promise, /** @lends Parse.Promise */ { + + _isPromisesAPlusCompliant: false, + + /** + * Returns true iff the given object fulfils the Promise interface. + * @return {Boolean} + */ + is: function(promise) { + return promise && promise.then && _.isFunction(promise.then); + }, + + /** + * Returns a new promise that is resolved with a given value. + * @return {Parse.Promise} the new promise. + */ + as: function() { + var promise = new Parse.Promise(); + promise.resolve.apply(promise, arguments); + return promise; + }, + + /** + * Returns a new promise that is rejected with a given error. + * @return {Parse.Promise} the new promise. + */ + error: function() { + var promise = new Parse.Promise(); + promise.reject.apply(promise, arguments); + return promise; + }, + + /** + * Returns a new promise that is fulfilled when all of the input promises + * are resolved. If any promise in the list fails, then the returned promise + * will fail with the last error. If they all succeed, then the returned + * promise will succeed, with the results being the results of all the input + * promises. For example:
+     *   var p1 = Parse.Promise.as(1);
+     *   var p2 = Parse.Promise.as(2);
+     *   var p3 = Parse.Promise.as(3);
+     *
+     *   Parse.Promise.when(p1, p2, p3).then(function(r1, r2, r3) {
+     *     console.log(r1);  // prints 1
+     *     console.log(r2);  // prints 2
+     *     console.log(r3);  // prints 3
+     *   });
+ * + * The input promises can also be specified as an array:
+     *   var promises = [p1, p2, p3];
+     *   Parse.Promise.when(promises).then(function(r1, r2, r3) {
+     *     console.log(r1);  // prints 1
+     *     console.log(r2);  // prints 2
+     *     console.log(r3);  // prints 3
+     *   });
+     * 
+ * @param {Array} promises a list of promises to wait for. + * @return {Parse.Promise} the new promise. + */ + when: function(promises) { + // Allow passing in Promises as separate arguments instead of an Array. + var objects; + if (promises && Parse._isNullOrUndefined(promises.length)) { + objects = arguments; + } else { + objects = promises; + } + + var total = objects.length; + var hadError = false; + var results = []; + var errors = []; + results.length = objects.length; + errors.length = objects.length; + + if (total === 0) { + return Parse.Promise.as.apply(this, results); + } + + var promise = new Parse.Promise(); + + var resolveOne = function() { + total = total - 1; + if (total === 0) { + if (hadError) { + promise.reject(errors); + } else { + promise.resolve.apply(promise, results); + } + } + }; + + Parse._arrayEach(objects, function(object, i) { + if (Parse.Promise.is(object)) { + object.then(function(result) { + results[i] = result; + resolveOne(); + }, function(error) { + errors[i] = error; + hadError = true; + resolveOne(); + }); + } else { + results[i] = object; + resolveOne(); + } + }); + + return promise; + }, + + /** + * Runs the given asyncFunction repeatedly, as long as the predicate + * function returns a truthy value. Stops repeating if asyncFunction returns + * a rejected promise. + * @param {Function} predicate should return false when ready to stop. + * @param {Function} asyncFunction should return a Promise. + */ + _continueWhile: function(predicate, asyncFunction) { + if (predicate()) { + return asyncFunction().then(function() { + return Parse.Promise._continueWhile(predicate, asyncFunction); + }); + } + return Parse.Promise.as(); + } + }); + + _.extend(Parse.Promise.prototype, /** @lends Parse.Promise.prototype */ { + + /** + * Marks this promise as fulfilled, firing any callbacks waiting on it. + * @param {Object} result the result to pass to the callbacks. + */ + resolve: function(result) { + if (this._resolved || this._rejected) { + throw "A promise was resolved even though it had already been " + + (this._resolved ? "resolved" : "rejected") + "."; + } + this._resolved = true; + this._result = arguments; + var results = arguments; + Parse._arrayEach(this._resolvedCallbacks, function(resolvedCallback) { + resolvedCallback.apply(this, results); + }); + this._resolvedCallbacks = []; + this._rejectedCallbacks = []; + }, + + /** + * Marks this promise as fulfilled, firing any callbacks waiting on it. + * @param {Object} error the error to pass to the callbacks. + */ + reject: function(error) { + if (this._resolved || this._rejected) { + throw "A promise was rejected even though it had already been " + + (this._resolved ? "resolved" : "rejected") + "."; + } + this._rejected = true; + this._error = error; + Parse._arrayEach(this._rejectedCallbacks, function(rejectedCallback) { + rejectedCallback(error); + }); + this._resolvedCallbacks = []; + this._rejectedCallbacks = []; + }, + + /** + * Adds callbacks to be called when this promise is fulfilled. Returns a new + * Promise that will be fulfilled when the callback is complete. It allows + * chaining. If the callback itself returns a Promise, then the one returned + * by "then" will not be fulfilled until that one returned by the callback + * is fulfilled. + * @param {Function} resolvedCallback Function that is called when this + * Promise is resolved. Once the callback is complete, then the Promise + * returned by "then" will also be fulfilled. + * @param {Function} rejectedCallback Function that is called when this + * Promise is rejected with an error. Once the callback is complete, then + * the promise returned by "then" with be resolved successfully. If + * rejectedCallback is null, or it returns a rejected Promise, then the + * Promise returned by "then" will be rejected with that error. + * @return {Parse.Promise} A new Promise that will be fulfilled after this + * Promise is fulfilled and either callback has completed. If the callback + * returned a Promise, then this Promise will not be fulfilled until that + * one is. + */ + then: function(resolvedCallback, rejectedCallback) { + var promise = new Parse.Promise(); + + var wrappedResolvedCallback = function() { + var result = arguments; + if (resolvedCallback) { + if (Parse.Promise._isPromisesAPlusCompliant) { + try { + result = [resolvedCallback.apply(this, result)]; + } catch (e) { + result = [Parse.Promise.error(e)]; + } + } else { + result = [resolvedCallback.apply(this, result)]; + } + } + if (result.length === 1 && Parse.Promise.is(result[0])) { + result[0].then(function() { + promise.resolve.apply(promise, arguments); + }, function(error) { + promise.reject(error); + }); + } else { + promise.resolve.apply(promise, result); + } + }; + + var wrappedRejectedCallback = function(error) { + var result = []; + if (rejectedCallback) { + if (Parse.Promise._isPromisesAPlusCompliant) { + try { + result = [rejectedCallback(error)]; + } catch (e) { + result = [Parse.Promise.error(e)]; + } + } else { + result = [rejectedCallback(error)]; + } + if (result.length === 1 && Parse.Promise.is(result[0])) { + result[0].then(function() { + promise.resolve.apply(promise, arguments); + }, function(error) { + promise.reject(error); + }); + } else { + if (Parse.Promise._isPromisesAPlusCompliant) { + promise.resolve.apply(promise, result); + } else { + promise.reject(result[0]); + } + } + } else { + promise.reject(error); + } + }; + + var runLater = function(func) { + func.call(); + }; + if (Parse.Promise._isPromisesAPlusCompliant) { + if (typeof(window) !== 'undefined' && window.setTimeout) { + runLater = function(func) { + window.setTimeout(func, 0); + }; + } else if (typeof(process) !== 'undefined' && process.nextTick) { + runLater = function(func) { + process.nextTick(func); + }; + } + } + + var self = this; + if (this._resolved) { + runLater(function() { + wrappedResolvedCallback.apply(self, self._result); + }); + } else if (this._rejected) { + runLater(function() { + wrappedRejectedCallback(self._error); + }); + } else { + this._resolvedCallbacks.push(wrappedResolvedCallback); + this._rejectedCallbacks.push(wrappedRejectedCallback); + } + + return promise; + }, + + /** + * Add handlers to be called when the promise + * is either resolved or rejected + */ + always: function(callback) { + return this.then(callback, callback); + }, + + /** + * Add handlers to be called when the Promise object is resolved + */ + done: function(callback) { + return this.then(callback); + }, + + /** + * Add handlers to be called when the Promise object is rejected + */ + fail: function(callback) { + return this.then(null, callback); + }, + + /** + * Run the given callbacks after this promise is fulfilled. + * @param optionsOrCallback {} A Backbone-style options callback, or a + * callback function. If this is an options object and contains a "model" + * attributes, that will be passed to error callbacks as the first argument. + * @param model {} If truthy, this will be passed as the first result of + * error callbacks. This is for Backbone-compatability. + * @return {Parse.Promise} A promise that will be resolved after the + * callbacks are run, with the same result as this. + */ + _thenRunCallbacks: function(optionsOrCallback, model) { + var options; + if (_.isFunction(optionsOrCallback)) { + var callback = optionsOrCallback; + options = { + success: function(result) { + callback(result, null); + }, + error: function(error) { + callback(null, error); + } + }; + } else { + options = _.clone(optionsOrCallback); + } + options = options || {}; + + return this.then(function(result) { + if (options.success) { + options.success.apply(this, arguments); + } else if (model) { + // When there's no callback, a sync event should be triggered. + model.trigger('sync', model, result, options); + } + return Parse.Promise.as.apply(Parse.Promise, arguments); + }, function(error) { + if (options.error) { + if (!_.isUndefined(model)) { + options.error(model, error); + } else { + options.error(error); + } + } else if (model) { + // When there's no error callback, an error event should be triggered. + model.trigger('error', model, error, options); + } + // By explicitly returning a rejected Promise, this will work with + // either jQuery or Promises/A semantics. + return Parse.Promise.error(error); + }); + }, + + /** + * Adds a callback function that should be called regardless of whether + * this promise failed or succeeded. The callback will be given either the + * array of results for its first argument, or the error as its second, + * depending on whether this Promise was rejected or resolved. Returns a + * new Promise, like "then" would. + * @param {Function} continuation the callback. + */ + _continueWith: function(continuation) { + return this.then(function() { + return continuation(arguments, null); + }, function(error) { + return continuation(null, error); + }); + } + + }); + +}(this)); + +/*jshint bitwise:false *//*global FileReader: true, File: true */ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + var b64Digit = function(number) { + if (number < 26) { + return String.fromCharCode(65 + number); + } + if (number < 52) { + return String.fromCharCode(97 + (number - 26)); + } + if (number < 62) { + return String.fromCharCode(48 + (number - 52)); + } + if (number === 62) { + return "+"; + } + if (number === 63) { + return "/"; + } + throw "Tried to encode large digit " + number + " in base64."; + }; + + var encodeBase64 = function(array) { + var chunks = []; + chunks.length = Math.ceil(array.length / 3); + _.times(chunks.length, function(i) { + var b1 = array[i * 3]; + var b2 = array[i * 3 + 1] || 0; + var b3 = array[i * 3 + 2] || 0; + + var has2 = (i * 3 + 1) < array.length; + var has3 = (i * 3 + 2) < array.length; + + chunks[i] = [ + b64Digit((b1 >> 2) & 0x3F), + b64Digit(((b1 << 4) & 0x30) | ((b2 >> 4) & 0x0F)), + has2 ? b64Digit(((b2 << 2) & 0x3C) | ((b3 >> 6) & 0x03)) : "=", + has3 ? b64Digit(b3 & 0x3F) : "=" + ].join(""); + }); + return chunks.join(""); + }; + + /** + * Reads a File using a FileReader. + * @param file {File} the File to read. + * @param type {String} (optional) the mimetype to override with. + * @return {Parse.Promise} A Promise that will be fulfilled with a + * base64-encoded string of the data and its mime type. + */ + var readAsync = function(file, type) { + var promise = new Parse.Promise(); + + if (typeof(FileReader) === "undefined") { + return Parse.Promise.error(new Parse.Error( + Parse.Error.FILE_READ_ERROR, + "Attempted to use a FileReader on an unsupported browser.")); + } + + var reader = new FileReader(); + reader.onloadend = function() { + if (reader.readyState !== 2) { + promise.reject(new Parse.Error( + Parse.Error.FILE_READ_ERROR, + "Error reading file.")); + return; + } + + var dataURL = reader.result; + var matches = /^data:([^;]*);base64,(.*)$/.exec(dataURL); + if (!matches) { + promise.reject(new Parse.Error( + Parse.Error.FILE_READ_ERROR, + "Unable to interpret data URL: " + dataURL)); + return; + } + + promise.resolve(matches[2], type || matches[1]); + }; + reader.readAsDataURL(file); + return promise; + }; + + /** + * A Parse.File is a local representation of a file that is saved to the Parse + * cloud. + * @class + * @param name {String} The file's name. This will be prefixed by a unique + * value once the file has finished saving. The file name must begin with + * an alphanumeric character, and consist of alphanumeric characters, + * periods, spaces, underscores, or dashes. + * @param data {Array} The data for the file, as either: + * 1. an Array of byte value Numbers, or + * 2. an Object like { base64: "..." } with a base64-encoded String. + * 3. a File object selected with a file upload control. (3) only works + * in Firefox 3.6+, Safari 6.0.2+, Chrome 7+, and IE 10+. + * For example:
+   * var fileUploadControl = $("#profilePhotoFileUpload")[0];
+   * if (fileUploadControl.files.length > 0) {
+   *   var file = fileUploadControl.files[0];
+   *   var name = "photo.jpg";
+   *   var parseFile = new Parse.File(name, file);
+   *   parseFile.save().then(function() {
+   *     // The file has been saved to Parse.
+   *   }, function(error) {
+   *     // The file either could not be read, or could not be saved to Parse.
+   *   });
+   * }
+ * @param type {String} Optional Content-Type header to use for the file. If + * this is omitted, the content type will be inferred from the name's + * extension. + */ + Parse.File = function(name, data, type) { + this._name = name; + + // Guess the content type from the extension if we need to. + var extension = /\.([^.]*)$/.exec(name); + if (extension) { + extension = extension[1].toLowerCase(); + } + var specifiedType = type || ''; + + if (_.isArray(data)) { + this._source = Parse.Promise.as(encodeBase64(data), specifiedType); + } else if (data && data.base64) { + // if it contains data uri, extract based64 and the type out of it. + /*jslint maxlen: 1000*/ + var dataUriRegexp = /^data:([a-zA-Z]*\/[a-zA-Z+.-]*);(charset=[a-zA-Z0-9\-\/\s]*,)?base64,(\S+)/; + /*jslint maxlen: 80*/ + + var matches = dataUriRegexp.exec(data.base64); + if (matches && matches.length > 0) { + // if data URI with charset, there will have 4 matches. + this._source = Parse.Promise.as( + (matches.length === 4 ? matches[3] : matches[2]), matches[1] + ); + } else { + this._source = Parse.Promise.as(data.base64, specifiedType); + } + } else if (typeof(File) !== "undefined" && data instanceof File) { + this._source = readAsync(data, type); + } else if (_.isString(data)) { + throw "Creating a Parse.File from a String is not yet supported."; + } + }; + + Parse.File.prototype = { + + /** + * Gets the name of the file. Before save is called, this is the filename + * given by the user. After save is called, that name gets prefixed with a + * unique identifier. + */ + name: function() { + return this._name; + }, + + /** + * Gets the url of the file. It is only available after you save the file or + * after you get the file from a Parse.Object. + * @return {String} + */ + url: function() { + return this._url; + }, + + /** + * Saves the file to the Parse cloud. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} Promise that is resolved when the save finishes. + */ + save: function(options) { + options= options || {}; + + var self = this; + if (!self._previousSave) { + self._previousSave = self._source.then(function(base64, type) { + var data = { + base64: base64, + _ContentType: type + }; + return Parse._request({ + route: "files", + className: self._name, + method: 'POST', + data: data, + useMasterKey: options.useMasterKey + }); + + }).then(function(response) { + self._name = response.name; + self._url = response.url; + return self; + }); + } + return self._previousSave._thenRunCallbacks(options); + } + }; + +}(this)); + +// Parse.Object is analogous to the Java ParseObject. +// It also implements the same interface as a Backbone model. + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * Creates a new model with defined attributes. A client id (cid) is + * automatically generated and assigned for you. + * + *

You won't normally call this method directly. It is recommended that + * you use a subclass of Parse.Object instead, created by calling + * extend.

+ * + *

However, if you don't want to use a subclass, or aren't sure which + * subclass is appropriate, you can use this form:

+   *     var object = new Parse.Object("ClassName");
+   * 
+ * That is basically equivalent to:
+   *     var MyClass = Parse.Object.extend("ClassName");
+   *     var object = new MyClass();
+   * 

+ * + * @param {Object} attributes The initial set of data to store in the object. + * @param {Object} options A set of Backbone-like options for creating the + * object. The only option currently supported is "collection". + * @see Parse.Object.extend + * + * @class + * + *

The fundamental unit of Parse data, which implements the Backbone Model + * interface.

+ */ + Parse.Object = function(attributes, options) { + // Allow new Parse.Object("ClassName") as a shortcut to _create. + if (_.isString(attributes)) { + return Parse.Object._create.apply(this, arguments); + } + + attributes = attributes || {}; + if (options && options.parse) { + attributes = this.parse(attributes); + } + var defaults = Parse._getValue(this, 'defaults'); + if (defaults) { + attributes = _.extend({}, defaults, attributes); + } + if (options && options.collection) { + this.collection = options.collection; + } + + this._serverData = {}; // The last known data for this object from cloud. + this._opSetQueue = [{}]; // List of sets of changes to the data. + this.attributes = {}; // The best estimate of this's current data. + + this._hashedJSON = {}; // Hash of values of containers at last save. + this._escapedAttributes = {}; + this.cid = _.uniqueId('c'); + this.changed = {}; + this._silent = {}; + this._pending = {}; + if (!this.set(attributes, {silent: true})) { + throw new Error("Can't create an invalid Parse.Object"); + } + this.changed = {}; + this._silent = {}; + this._pending = {}; + this._hasData = true; + this._previousAttributes = _.clone(this.attributes); + this.initialize.apply(this, arguments); + }; + + /** + * The ID of this object, unique within its class. + * @name id + * @type String + * @field + * @memberOf Parse.Object.prototype + */ + + /** + * The first time this object was saved on the server. + * @name createdAt + * @type Date + * @field + * @memberOf Parse.Object.prototype + */ + + /** + * The last time this object was updated on the server. + * @name updatedAt + * @type Date + * @field + * @memberOf Parse.Object.prototype + */ + + /** + * Saves the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
+   *   Parse.Object.saveAll([object1, object2, ...], {
+   *     success: function(list) {
+   *       // All the objects were saved.
+   *     },
+   *     error: function(error) {
+   *       // An error occurred while saving one of the objects.
+   *     },
+   *   });
+   * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {Object} options A Backbone-style callback object. + * Valid options are:
    + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ */ + Parse.Object.saveAll = function(list, options) { + options = options || {}; + return Parse.Object._deepSaveAsync(list, { + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken + })._thenRunCallbacks(options); + }; + + /** + * Destroy the given list of models on the server if it was already persisted. + * Optimistically removes each model from its collection, if it has one. + * If `wait: true` is passed, waits for the server to respond before removal. + * + *

Unlike saveAll, if an error occurs while deleting an individual model, + * this method will continue trying to delete the rest of the models if + * possible, except in the case of a fatal error like a connection error. + * + *

In particular, the Parse.Error object returned in the case of error may + * be one of two types: + * + *

    + *
  • A Parse.Error.AGGREGATE_ERROR. This object's "errors" property is an + * array of other Parse.Error objects. Each error object in this array + * has an "object" property that references the object that could not be + * deleted (for instance, because that object could not be found).
  • + *
  • A non-aggregate Parse.Error. This indicates a serious error that + * caused the delete operation to be aborted partway through (for + * instance, a connection failure in the middle of the delete).
  • + *
+ * + *
+   *   Parse.Object.destroyAll([object1, object2, ...], {
+   *     success: function() {
+   *       // All the objects were deleted.
+   *     },
+   *     error: function(error) {
+   *       // An error occurred while deleting one or more of the objects.
+   *       // If this is an aggregate error, then we can inspect each error
+   *       // object individually to determine the reason why a particular
+   *       // object was not deleted.
+   *       if (error.code == Parse.Error.AGGREGATE_ERROR) {
+   *         for (var i = 0; i < error.errors.length; i++) {
+   *           console.log("Couldn't delete " + error.errors[i].object.id +
+   *             "due to " + error.errors[i].message);
+   *         }
+   *       } else {
+   *         console.log("Delete aborted because of " + error.message);
+   *       }
+   *     },
+   *   });
+   * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {Object} options A Backbone-style callback object. + * Valid options are:
    + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * @return {Parse.Promise} A promise that is fulfilled when the destroyAll + * completes. + */ + Parse.Object.destroyAll = function(list, options) { + options = options || {}; + + var triggerDestroy = function(object) { + object.trigger('destroy', object, object.collection, options); + }; + + var errors = []; + var destroyBatch = function(batch) { + var promise = Parse.Promise.as(); + + if (batch.length > 0) { + promise = promise.then(function() { + return Parse._request({ + route: "batch", + method: "POST", + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken, + data: { + requests: _.map(batch, function(object) { + return { + method: "DELETE", + path: "/1/classes/" + object.className + "/" + object.id + }; + }) + } + }); + }).then(function(responses, status, xhr) { + Parse._arrayEach(batch, function(object, i) { + if (responses[i].success && options.wait) { + triggerDestroy(object); + } else if (responses[i].error) { + var error = new Parse.Error(responses[i].error.code, + responses[i].error.error); + error.object = object; + + errors.push(error); + } + }); + }); + } + + return promise; + }; + + var promise = Parse.Promise.as(); + var batch = []; + Parse._arrayEach(list, function(object, i) { + if (!object.id || !options.wait) { + triggerDestroy(object); + } + + if (object.id) { + batch.push(object); + } + + if (batch.length === 20 || i+1 === list.length) { + var thisBatch = batch; + batch = []; + + promise = promise.then(function() { + return destroyBatch(thisBatch); + }); + } + }); + + return promise.then(function() { + if (errors.length === 0) { + return true; + } else { + var error = new Parse.Error(Parse.Error.AGGREGATE_ERROR, + "Error deleting an object in destroyAll"); + error.errors = errors; + + return Parse.Promise.error(error); + } + })._thenRunCallbacks(options); + }; + + /** + * Fetches the given list of Parse.Object. + * If any error is encountered, stops and calls the error handler. + * + *
+   *   Parse.Object.fetchAll([object1, object2, ...], {
+   *     success: function(list) {
+   *       // All the objects were fetched.
+   *     },
+   *     error: function(error) {
+   *       // An error occurred while fetching one of the objects.
+   *     },
+   *   });
+   * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {Object} options A Backbone-style callback object. + * Valid options are:
    + *
  • success: A Backbone-style success callback. + *
  • error: An Backbone-style error callback. + *
+ */ + Parse.Object.fetchAll = function(list, options) { + return Parse.Object._fetchAll( + list, + true + )._thenRunCallbacks(options); + }; + + /** + * Fetches the given list of Parse.Object if needed. + * If any error is encountered, stops and calls the error handler. + * + *
+   *   Parse.Object.fetchAllIfNeeded([object1, ...], {
+   *     success: function(list) {
+   *       // Objects were fetched and updated.
+   *     },
+   *     error: function(error) {
+   *       // An error occurred while fetching one of the objects.
+   *     },
+   *   });
+   * 
+ * + * @param {Array} list A list of Parse.Object. + * @param {Object} options A Backbone-style callback object. + * Valid options are:
    + *
  • success: A Backbone-style success callback. + *
  • error: An Backbone-style error callback. + *
+ */ + Parse.Object.fetchAllIfNeeded = function(list, options) { + return Parse.Object._fetchAll( + list, + false + )._thenRunCallbacks(options); + }; + + // Attach all inheritable methods to the Parse.Object prototype. + _.extend(Parse.Object.prototype, Parse.Events, + /** @lends Parse.Object.prototype */ { + _existed: false, + + /** + * Initialize is an empty function by default. Override it with your own + * initialization logic. + */ + initialize: function(){}, + + /** + * Returns a JSON version of the object suitable for saving to Parse. + * @return {Object} + */ + toJSON: function() { + var json = this._toFullJSON(); + Parse._arrayEach(["__type", "className"], + function(key) { delete json[key]; }); + return json; + }, + + _toFullJSON: function(seenObjects) { + var json = _.clone(this.attributes); + Parse._objectEach(json, function(val, key) { + json[key] = Parse._encode(val, seenObjects); + }); + Parse._objectEach(this._operations, function(val, key) { + json[key] = val; + }); + + if (_.has(this, "id")) { + json.objectId = this.id; + } + if (_.has(this, "createdAt")) { + if (_.isDate(this.createdAt)) { + json.createdAt = this.createdAt.toJSON(); + } else { + json.createdAt = this.createdAt; + } + } + + if (_.has(this, "updatedAt")) { + if (_.isDate(this.updatedAt)) { + json.updatedAt = this.updatedAt.toJSON(); + } else { + json.updatedAt = this.updatedAt; + } + } + json.__type = "Object"; + json.className = this.className; + return json; + }, + + /** + * Updates _hashedJSON to reflect the current state of this object. + * Adds any changed hash values to the set of pending changes. + */ + _refreshCache: function() { + var self = this; + if (self._refreshingCache) { + return; + } + self._refreshingCache = true; + Parse._objectEach(this.attributes, function(value, key) { + if (value instanceof Parse.Object) { + value._refreshCache(); + } else if (_.isObject(value)) { + var objectArray = false; + if (_.isArray(value)) { + // We don't cache arrays of Parse.Objects + _.each(value, function(arrVal) { + if (arrVal instanceof Parse.Object) { + objectArray = true; + arrVal._refreshCache(); + } + }); + } + if (!objectArray && self._resetCacheForKey(key)) { + self.set(key, new Parse.Op.Set(value), { silent: true }); + } + } + }); + delete self._refreshingCache; + }, + + /** + * Returns true if this object has been modified since its last + * save/refresh. If an attribute is specified, it returns true only if that + * particular attribute has been modified since the last save/refresh. + * @param {String} attr An attribute name (optional). + * @return {Boolean} + */ + dirty: function(attr) { + this._refreshCache(); + + var currentChanges = _.last(this._opSetQueue); + + if (attr) { + return (currentChanges[attr] ? true : false); + } + if (!this.id) { + return true; + } + if (_.keys(currentChanges).length > 0) { + return true; + } + return false; + }, + + /** + * Returns an array of keys that have been modified since last save/refresh + * @return {Array of string} + */ + dirtyKeys: function() { + return _.keys(_.last(this._opSetQueue)); + }, + + /** + * Gets a Pointer referencing this Object. + */ + _toPointer: function() { + if (!this.id) { + throw new Error("Can't serialize an unsaved Parse.Object"); + } + return { __type: "Pointer", + className: this.className, + objectId: this.id }; + }, + + /** + * Gets the value of an attribute. + * @param {String} attr The string name of an attribute. + */ + get: function(attr) { + return this.attributes[attr]; + }, + + /** + * Gets a relation on the given class for the attribute. + * @param String attr The attribute to get the relation for. + */ + relation: function(attr) { + var value = this.get(attr); + if (value) { + if (!(value instanceof Parse.Relation)) { + throw "Called relation() on non-relation field " + attr; + } + value._ensureParentAndKey(this, attr); + return value; + } else { + return new Parse.Relation(this, attr); + } + }, + + /** + * Gets the HTML-escaped value of an attribute. + */ + escape: function(attr) { + var html = this._escapedAttributes[attr]; + if (html) { + return html; + } + var val = this.attributes[attr]; + var escaped; + if (Parse._isNullOrUndefined(val)) { + escaped = ''; + } else { + escaped = _.escape(val.toString()); + } + this._escapedAttributes[attr] = escaped; + return escaped; + }, + + /** + * Returns true if the attribute contains a value that is not + * null or undefined. + * @param {String} attr The string name of the attribute. + * @return {Boolean} + */ + has: function(attr) { + return !Parse._isNullOrUndefined(this.attributes[attr]); + }, + + /** + * Pulls "special" fields like objectId, createdAt, etc. out of attrs + * and puts them on "this" directly. Removes them from attrs. + * @param attrs - A dictionary with the data for this Parse.Object. + */ + _mergeMagicFields: function(attrs) { + // Check for changes of magic fields. + var model = this; + var specialFields = ["id", "objectId", "createdAt", "updatedAt"]; + Parse._arrayEach(specialFields, function(attr) { + if (attrs[attr]) { + if (attr === "objectId") { + model.id = attrs[attr]; + } else if ((attr === "createdAt" || attr === "updatedAt") && + !_.isDate(attrs[attr])) { + model[attr] = Parse._parseDate(attrs[attr]); + } else { + model[attr] = attrs[attr]; + } + delete attrs[attr]; + } + }); + }, + + /** + * Copies the given serverData to "this", refreshes attributes, and + * clears pending changes; + */ + _copyServerData: function(serverData) { + // Copy server data + var tempServerData = {}; + Parse._objectEach(serverData, function(value, key) { + tempServerData[key] = Parse._decode(key, value); + }); + this._serverData = tempServerData; + + // Refresh the attributes. + this._rebuildAllEstimatedData(); + + + // Clear out any changes the user might have made previously. + this._refreshCache(); + this._opSetQueue = [{}]; + + // Refresh the attributes again. + this._rebuildAllEstimatedData(); + }, + + /** + * Merges another object's attributes into this object. + */ + _mergeFromObject: function(other) { + if (!other) { + return; + } + + // This does the inverse of _mergeMagicFields. + this.id = other.id; + this.createdAt = other.createdAt; + this.updatedAt = other.updatedAt; + + this._copyServerData(other._serverData); + + this._hasData = true; + }, + + /** + * Returns the json to be sent to the server. + */ + _startSave: function() { + this._opSetQueue.push({}); + }, + + /** + * Called when a save fails because of an error. Any changes that were part + * of the save need to be merged with changes made after the save. This + * might throw an exception is you do conflicting operations. For example, + * if you do: + * object.set("foo", "bar"); + * object.set("invalid field name", "baz"); + * object.save(); + * object.increment("foo"); + * then this will throw when the save fails and the client tries to merge + * "bar" with the +1. + */ + _cancelSave: function() { + var self = this; + var failedChanges = _.first(this._opSetQueue); + this._opSetQueue = _.rest(this._opSetQueue); + var nextChanges = _.first(this._opSetQueue); + Parse._objectEach(failedChanges, function(op, key) { + var op1 = failedChanges[key]; + var op2 = nextChanges[key]; + if (op1 && op2) { + nextChanges[key] = op2._mergeWithPrevious(op1); + } else if (op1) { + nextChanges[key] = op1; + } + }); + this._saving = this._saving - 1; + }, + + /** + * Called when a save completes successfully. This merges the changes that + * were saved into the known server data, and overrides it with any data + * sent directly from the server. + */ + _finishSave: function(serverData) { + // Grab a copy of any object referenced by this object. These instances + // may have already been fetched, and we don't want to lose their data. + // Note that doing it like this means we will unify separate copies of the + // same object, but that's a risk we have to take. + var fetchedObjects = {}; + Parse._traverse(this.attributes, function(object) { + if (object instanceof Parse.Object && object.id && object._hasData) { + fetchedObjects[object.id] = object; + } + }); + + var savedChanges = _.first(this._opSetQueue); + this._opSetQueue = _.rest(this._opSetQueue); + this._applyOpSet(savedChanges, this._serverData); + this._mergeMagicFields(serverData); + var self = this; + Parse._objectEach(serverData, function(value, key) { + self._serverData[key] = Parse._decode(key, value); + + // Look for any objects that might have become unfetched and fix them + // by replacing their values with the previously observed values. + var fetched = Parse._traverse(self._serverData[key], function(object) { + if (object instanceof Parse.Object && fetchedObjects[object.id]) { + return fetchedObjects[object.id]; + } + }); + if (fetched) { + self._serverData[key] = fetched; + } + }); + this._rebuildAllEstimatedData(); + this._saving = this._saving - 1; + }, + + /** + * Called when a fetch or login is complete to set the known server data to + * the given object. + */ + _finishFetch: function(serverData, hasData) { + + this._opSetQueue = [{}]; + + // Bring in all the new server data. + this._mergeMagicFields(serverData); + this._copyServerData(serverData); + + this._hasData = hasData; + }, + + /** + * Applies the set of Parse.Op in opSet to the object target. + */ + _applyOpSet: function(opSet, target) { + var self = this; + Parse._objectEach(opSet, function(change, key) { + target[key] = change._estimate(target[key], self, key); + if (target[key] === Parse.Op._UNSET) { + delete target[key]; + } + }); + }, + + /** + * Replaces the cached value for key with the current value. + * Returns true if the new value is different than the old value. + */ + _resetCacheForKey: function(key) { + var value = this.attributes[key]; + if (_.isObject(value) && + !(value instanceof Parse.Object) && + !(value instanceof Parse.File)) { + value = value.toJSON ? value.toJSON() : value; + var json = JSON.stringify(value); + if (this._hashedJSON[key] !== json) { + var wasSet = !!this._hashedJSON[key]; + this._hashedJSON[key] = json; + return wasSet; + } + } + return false; + }, + + /** + * Populates attributes[key] by starting with the last known data from the + * server, and applying all of the local changes that have been made to that + * key since then. + */ + _rebuildEstimatedDataForKey: function(key) { + var self = this; + delete this.attributes[key]; + if (this._serverData[key]) { + this.attributes[key] = this._serverData[key]; + } + Parse._arrayEach(this._opSetQueue, function(opSet) { + var op = opSet[key]; + if (op) { + self.attributes[key] = op._estimate(self.attributes[key], self, key); + if (self.attributes[key] === Parse.Op._UNSET) { + delete self.attributes[key]; + } else { + self._resetCacheForKey(key); + } + } + }); + }, + + /** + * Populates attributes by starting with the last known data from the + * server, and applying all of the local changes that have been made since + * then. + */ + _rebuildAllEstimatedData: function() { + var self = this; + + var previousAttributes = _.clone(this.attributes); + + this.attributes = _.clone(this._serverData); + Parse._arrayEach(this._opSetQueue, function(opSet) { + self._applyOpSet(opSet, self.attributes); + Parse._objectEach(opSet, function(op, key) { + self._resetCacheForKey(key); + }); + }); + + // Trigger change events for anything that changed because of the fetch. + Parse._objectEach(previousAttributes, function(oldValue, key) { + if (self.attributes[key] !== oldValue) { + self.trigger('change:' + key, self, self.attributes[key], {}); + } + }); + Parse._objectEach(this.attributes, function(newValue, key) { + if (!_.has(previousAttributes, key)) { + self.trigger('change:' + key, self, newValue, {}); + } + }); + }, + + /** + * Sets a hash of model attributes on the object, firing + * "change" unless you choose to silence it. + * + *

You can call it with an object containing keys and values, or with one + * key and value. For example:

+     *   gameTurn.set({
+     *     player: player1,
+     *     diceRoll: 2
+     *   }, {
+     *     error: function(gameTurnAgain, error) {
+     *       // The set failed validation.
+     *     }
+     *   });
+     *
+     *   game.set("currentPlayer", player2, {
+     *     error: function(gameTurnAgain, error) {
+     *       // The set failed validation.
+     *     }
+     *   });
+     *
+     *   game.set("finished", true);

+ * + * @param {String} key The key to set. + * @param {} value The value to give it. + * @param {Object} options A set of Backbone-like options for the set. + * The only supported options are silent, + * error, and promise. + * @return {Boolean} true if the set succeeded. + * @see Parse.Object#validate + * @see Parse.Error + */ + set: function(key, value, options) { + var attrs, attr; + if (_.isObject(key) || Parse._isNullOrUndefined(key)) { + attrs = key; + Parse._objectEach(attrs, function(v, k) { + attrs[k] = Parse._decode(k, v); + }); + options = value; + } else { + attrs = {}; + attrs[key] = Parse._decode(key, value); + } + + // Extract attributes and options. + options = options || {}; + if (!attrs) { + return this; + } + if (attrs instanceof Parse.Object) { + attrs = attrs.attributes; + } + + var self = this; + Parse._objectEach(attrs, function(unused_value, key) { + if (self.constructor.readOnlyAttributes && + self.constructor.readOnlyAttributes[key]) { + throw new Error('Cannot modify readonly key: ' + key); + } + }); + + // If the unset option is used, every attribute should be a Unset. + if (options.unset) { + Parse._objectEach(attrs, function(unused_value, key) { + attrs[key] = new Parse.Op.Unset(); + }); + } + + // Apply all the attributes to get the estimated values. + var dataToValidate = _.clone(attrs); + Parse._objectEach(dataToValidate, function(value, key) { + if (value instanceof Parse.Op) { + dataToValidate[key] = value._estimate(self.attributes[key], + self, key); + if (dataToValidate[key] === Parse.Op._UNSET) { + delete dataToValidate[key]; + } + } + }); + + // Run validation. + if (!this._validate(attrs, options)) { + return false; + } + + this._mergeMagicFields(attrs); + + options.changes = {}; + var escaped = this._escapedAttributes; + var prev = this._previousAttributes || {}; + + // Update attributes. + Parse._arrayEach(_.keys(attrs), function(attr) { + var val = attrs[attr]; + + // If this is a relation object we need to set the parent correctly, + // since the location where it was parsed does not have access to + // this object. + if (val instanceof Parse.Relation) { + val.parent = self; + } + + if (!(val instanceof Parse.Op)) { + val = new Parse.Op.Set(val); + } + + // See if this change will actually have any effect. + var isRealChange = true; + if (val instanceof Parse.Op.Set && + _.isEqual(self.attributes[attr], val.value)) { + isRealChange = false; + } + + if (isRealChange) { + delete escaped[attr]; + if (options.silent) { + self._silent[attr] = true; + } else { + options.changes[attr] = true; + } + } + + var currentChanges = _.last(self._opSetQueue); + currentChanges[attr] = val._mergeWithPrevious(currentChanges[attr]); + self._rebuildEstimatedDataForKey(attr); + + if (isRealChange) { + self.changed[attr] = self.attributes[attr]; + if (!options.silent) { + self._pending[attr] = true; + } + } else { + delete self.changed[attr]; + delete self._pending[attr]; + } + }); + + if (!options.silent) { + this.change(options); + } + return this; + }, + + /** + * Remove an attribute from the model, firing "change" unless + * you choose to silence it. This is a noop if the attribute doesn't + * exist. + */ + unset: function(attr, options) { + options = options || {}; + options.unset = true; + return this.set(attr, null, options); + }, + + /** + * Atomically increments the value of the given attribute the next time the + * object is saved. If no amount is specified, 1 is used by default. + * + * @param attr {String} The key. + * @param amount {Number} The amount to increment by. + */ + increment: function(attr, amount) { + if (_.isUndefined(amount) || _.isNull(amount)) { + amount = 1; + } + return this.set(attr, new Parse.Op.Increment(amount)); + }, + + /** + * Atomically add an object to the end of the array associated with a given + * key. + * @param attr {String} The key. + * @param item {} The item to add. + */ + add: function(attr, item) { + return this.set(attr, new Parse.Op.Add([item])); + }, + + /** + * Atomically add an object to the array associated with a given key, only + * if it is not already present in the array. The position of the insert is + * not guaranteed. + * + * @param attr {String} The key. + * @param item {} The object to add. + */ + addUnique: function(attr, item) { + return this.set(attr, new Parse.Op.AddUnique([item])); + }, + + /** + * Atomically remove all instances of an object from the array associated + * with a given key. + * + * @param attr {String} The key. + * @param item {} The object to remove. + */ + remove: function(attr, item) { + return this.set(attr, new Parse.Op.Remove([item])); + }, + + /** + * Returns an instance of a subclass of Parse.Op describing what kind of + * modification has been performed on this field since the last time it was + * saved. For example, after calling object.increment("x"), calling + * object.op("x") would return an instance of Parse.Op.Increment. + * + * @param attr {String} The key. + * @returns {Parse.Op} The operation, or undefined if none. + */ + op: function(attr) { + return _.last(this._opSetQueue)[attr]; + }, + + /** + * Clear all attributes on the model, firing "change" unless + * you choose to silence it. + */ + clear: function(options) { + options = options || {}; + options.unset = true; + var keysToClear = _.extend(this.attributes, this._operations); + return this.set(keysToClear, options); + }, + + /** + * Returns a JSON-encoded set of operations to be sent with the next save + * request. + */ + _getSaveJSON: function() { + var json = _.clone(_.first(this._opSetQueue)); + Parse._objectEach(json, function(op, key) { + json[key] = op.toJSON(); + }); + return json; + }, + + /** + * Returns true if this object can be serialized for saving. + */ + _canBeSerialized: function() { + return Parse.Object._canBeSerializedAsValue(this.attributes); + }, + + /** + * Fetch the model from the server. If the server's representation of the + * model differs from its current attributes, they will be overriden, + * triggering a "change" event. + * + * @param {Object} options A Backbone-style callback object. + * Valid options are:
    + *
  • success: A Backbone-style success callback. + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * @return {Parse.Promise} A promise that is fulfilled when the fetch + * completes. + */ + fetch: function(options) { + var self = this; + options = options || {}; + var request = Parse._request({ + method: 'GET', + route: "classes", + className: this.className, + objectId: this.id, + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken + }); + return request.then(function(response, status, xhr) { + self._finishFetch(self.parse(response, status, xhr), true); + return self; + })._thenRunCallbacks(options, this); + }, + + /** + * Set a hash of model attributes, and save the model to the server. + * updatedAt will be updated when the request returns. + * You can either call it as:
+     *   object.save();
+ * or
+     *   object.save(null, options);
+ * or
+     *   object.save(attrs, options);
+ * or
+     *   object.save(key, value, options);
+ * + * For example,
+     *   gameTurn.save({
+     *     player: "Jake Cutter",
+     *     diceRoll: 2
+     *   }, {
+     *     success: function(gameTurnAgain) {
+     *       // The save was successful.
+     *     },
+     *     error: function(gameTurnAgain, error) {
+     *       // The save failed.  Error is an instance of Parse.Error.
+     *     }
+     *   });
+ * or with promises:
+     *   gameTurn.save({
+     *     player: "Jake Cutter",
+     *     diceRoll: 2
+     *   }).then(function(gameTurnAgain) {
+     *     // The save was successful.
+     *   }, function(error) {
+     *     // The save failed.  Error is an instance of Parse.Error.
+     *   });
+ * + * @param {Object} options A Backbone-style callback object. + * Valid options are:
    + *
  • wait: Set to true to wait for the server to confirm a successful + * save before modifying the attributes on the object. + *
  • silent: Set to true to avoid firing the `set` event. + *
  • success: A Backbone-style success callback. + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * @return {Parse.Promise} A promise that is fulfilled when the save + * completes. + * @see Parse.Error + */ + save: function(arg1, arg2, arg3) { + var i, attrs, current, options, saved; + if (_.isObject(arg1) || Parse._isNullOrUndefined(arg1)) { + attrs = arg1; + options = arg2; + } else { + attrs = {}; + attrs[arg1] = arg2; + options = arg3; + } + + // Make save({ success: function() {} }) work. + if (!options && attrs) { + var extra_keys = _.reject(attrs, function(value, key) { + return _.include(["success", "error", "wait"], key); + }); + if (extra_keys.length === 0) { + var all_functions = true; + if (_.has(attrs, "success") && !_.isFunction(attrs.success)) { + all_functions = false; + } + if (_.has(attrs, "error") && !_.isFunction(attrs.error)) { + all_functions = false; + } + if (all_functions) { + // This attrs object looks like it's really an options object, + // and there's no other options object, so let's just use it. + return this.save(null, attrs); + } + } + } + + options = _.clone(options) || {}; + if (options.wait) { + current = _.clone(this.attributes); + } + + var setOptions = _.clone(options) || {}; + if (setOptions.wait) { + setOptions.silent = true; + } + var setError; + setOptions.error = function(model, error) { + setError = error; + }; + if (attrs && !this.set(attrs, setOptions)) { + return Parse.Promise.error(setError)._thenRunCallbacks(options, this); + } + + var model = this; + + // If there is any unsaved child, save it first. + model._refreshCache(); + + + + var unsavedChildren = []; + var unsavedFiles = []; + Parse.Object._findUnsavedChildren(model.attributes, + unsavedChildren, + unsavedFiles); + if (unsavedChildren.length + unsavedFiles.length > 0) { + return Parse.Object._deepSaveAsync(this.attributes, { + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken + }).then(function() { + return model.save(null, options); + }, function(error) { + return Parse.Promise.error(error)._thenRunCallbacks(options, model); + }); + } + + this._startSave(); + this._saving = (this._saving || 0) + 1; + + this._allPreviousSaves = this._allPreviousSaves || Parse.Promise.as(); + this._allPreviousSaves = this._allPreviousSaves._continueWith(function() { + var method = model.id ? 'PUT' : 'POST'; + + var json = model._getSaveJSON(); + + var route = "classes"; + var className = model.className; + if (model.className === "_User" && !model.id) { + // Special-case user sign-up. + route = "users"; + className = null; + } + var request = Parse._request({ + route: route, + className: className, + objectId: model.id, + method: method, + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken, + data: json + }); + + request = request.then(function(resp, status, xhr) { + var serverAttrs = model.parse(resp, status, xhr); + if (options.wait) { + serverAttrs = _.extend(attrs || {}, serverAttrs); + } + model._finishSave(serverAttrs); + if (options.wait) { + model.set(current, setOptions); + } + return model; + + }, function(error) { + model._cancelSave(); + return Parse.Promise.error(error); + + })._thenRunCallbacks(options, model); + + return request; + }); + return this._allPreviousSaves; + }, + + /** + * Destroy this model on the server if it was already persisted. + * Optimistically removes the model from its collection, if it has one. + * If `wait: true` is passed, waits for the server to respond + * before removal. + * + * @param {Object} options A Backbone-style callback object. + * Valid options are:
    + *
  • wait: Set to true to wait for the server to confirm successful + * deletion of the object before triggering the `destroy` event. + *
  • success: A Backbone-style success callback + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * @return {Parse.Promise} A promise that is fulfilled when the destroy + * completes. + */ + destroy: function(options) { + options = options || {}; + var model = this; + + var triggerDestroy = function() { + model.trigger('destroy', model, model.collection, options); + }; + + if (!this.id) { + return triggerDestroy(); + } + + if (!options.wait) { + triggerDestroy(); + } + + var request = Parse._request({ + route: "classes", + className: this.className, + objectId: this.id, + method: 'DELETE', + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken + }); + return request.then(function() { + if (options.wait) { + triggerDestroy(); + } + return model; + })._thenRunCallbacks(options, this); + }, + + /** + * Converts a response into the hash of attributes to be set on the model. + * @ignore + */ + parse: function(resp, status, xhr) { + var output = _.clone(resp); + _(["createdAt", "updatedAt"]).each(function(key) { + if (output[key]) { + output[key] = Parse._parseDate(output[key]); + } + }); + if (!output.updatedAt) { + output.updatedAt = output.createdAt; + } + if (status) { + this._existed = (status !== 201); + } + return output; + }, + + /** + * Creates a new model with identical attributes to this one. + * @return {Parse.Object} + */ + clone: function() { + return new this.constructor(this.attributes); + }, + + /** + * Returns true if this object has never been saved to Parse. + * @return {Boolean} + */ + isNew: function() { + return !this.id; + }, + + /** + * Call this method to manually fire a `"change"` event for this model and + * a `"change:attribute"` event for each changed attribute. + * Calling this will cause all objects observing the model to update. + */ + change: function(options) { + options = options || {}; + var changing = this._changing; + this._changing = true; + + // Silent changes become pending changes. + var self = this; + Parse._objectEach(this._silent, function(attr) { + self._pending[attr] = true; + }); + + // Silent changes are triggered. + var changes = _.extend({}, options.changes, this._silent); + this._silent = {}; + Parse._objectEach(changes, function(unused_value, attr) { + self.trigger('change:' + attr, self, self.get(attr), options); + }); + if (changing) { + return this; + } + + // This is to get around lint not letting us make a function in a loop. + var deleteChanged = function(value, attr) { + if (!self._pending[attr] && !self._silent[attr]) { + delete self.changed[attr]; + } + }; + + // Continue firing `"change"` events while there are pending changes. + while (!_.isEmpty(this._pending)) { + this._pending = {}; + this.trigger('change', this, options); + // Pending and silent changes still remain. + Parse._objectEach(this.changed, deleteChanged); + self._previousAttributes = _.clone(this.attributes); + } + + this._changing = false; + return this; + }, + + /** + * Returns true if this object was created by the Parse server when the + * object might have already been there (e.g. in the case of a Facebook + * login) + */ + existed: function() { + return this._existed; + }, + + /** + * Determine if the model has changed since the last "change" + * event. If you specify an attribute name, determine if that attribute + * has changed. + * @param {String} attr Optional attribute name + * @return {Boolean} + */ + hasChanged: function(attr) { + if (!arguments.length) { + return !_.isEmpty(this.changed); + } + return this.changed && _.has(this.changed, attr); + }, + + /** + * Returns an object containing all the attributes that have changed, or + * false if there are no changed attributes. Useful for determining what + * parts of a view need to be updated and/or what attributes need to be + * persisted to the server. Unset attributes will be set to undefined. + * You can also pass an attributes object to diff against the model, + * determining if there *would be* a change. + */ + changedAttributes: function(diff) { + if (!diff) { + return this.hasChanged() ? _.clone(this.changed) : false; + } + var changed = {}; + var old = this._previousAttributes; + Parse._objectEach(diff, function(diffVal, attr) { + if (!_.isEqual(old[attr], diffVal)) { + changed[attr] = diffVal; + } + }); + return changed; + }, + + /** + * Gets the previous value of an attribute, recorded at the time the last + * "change" event was fired. + * @param {String} attr Name of the attribute to get. + */ + previous: function(attr) { + if (!arguments.length || !this._previousAttributes) { + return null; + } + return this._previousAttributes[attr]; + }, + + /** + * Gets all of the attributes of the model at the time of the previous + * "change" event. + * @return {Object} + */ + previousAttributes: function() { + return _.clone(this._previousAttributes); + }, + + /** + * Checks if the model is currently in a valid state. It's only possible to + * get into an *invalid* state if you're using silent changes. + * @return {Boolean} + */ + isValid: function() { + return !this.validate(this.attributes); + }, + + /** + * You should not call this function directly unless you subclass + * Parse.Object, in which case you can override this method + * to provide additional validation on set and + * save. Your implementation should return + * + * @param {Object} attrs The current data to validate. + * @param {Object} options A Backbone-like options object. + * @return {} False if the data is valid. An error object otherwise. + * @see Parse.Object#set + */ + validate: function(attrs, options) { + if (_.has(attrs, "ACL") && !(attrs.ACL instanceof Parse.ACL)) { + return new Parse.Error(Parse.Error.OTHER_CAUSE, + "ACL must be a Parse.ACL."); + } + var correct = true; + Parse._objectEach(attrs, function(unused_value, key) { + if (!(/^[A-Za-z][0-9A-Za-z_]*$/).test(key)) { + correct = false; + } + }); + if (!correct) { + return new Parse.Error(Parse.Error.INVALID_KEY_NAME); + } + return false; + }, + + /** + * Run validation against a set of incoming attributes, returning `true` + * if all is well. If a specific `error` callback has been passed, + * call that instead of firing the general `"error"` event. + */ + _validate: function(attrs, options) { + if (options.silent || !this.validate) { + return true; + } + attrs = _.extend({}, this.attributes, attrs); + var error = this.validate(attrs, options); + if (!error) { + return true; + } + if (options && options.error) { + options.error(this, error, options); + } else { + this.trigger('error', this, error, options); + } + return false; + }, + + /** + * Returns the ACL for this object. + * @returns {Parse.ACL} An instance of Parse.ACL. + * @see Parse.Object#get + */ + getACL: function() { + return this.get("ACL"); + }, + + /** + * Sets the ACL to be used for this object. + * @param {Parse.ACL} acl An instance of Parse.ACL. + * @param {Object} options Optional Backbone-like options object to be + * passed in to set. + * @return {Boolean} Whether the set passed validation. + * @see Parse.Object#set + */ + setACL: function(acl, options) { + return this.set("ACL", acl, options); + } + + }); + + /** + * Returns the appropriate subclass for making new instances of the given + * className string. + */ + Parse.Object._getSubclass = function(className) { + if (!_.isString(className)) { + throw "Parse.Object._getSubclass requires a string argument."; + } + var ObjectClass = Parse.Object._classMap[className]; + if (!ObjectClass) { + ObjectClass = Parse.Object.extend(className); + Parse.Object._classMap[className] = ObjectClass; + } + return ObjectClass; + }; + + /** + * Creates an instance of a subclass of Parse.Object for the given classname. + */ + Parse.Object._create = function(className, attributes, options) { + var ObjectClass = Parse.Object._getSubclass(className); + return new ObjectClass(attributes, options); + }; + + /** + * Returns a list of object ids given a list of objects. + */ + Parse.Object._toObjectIdArray = function(list, omitObjectsWithData) { + if (list.length === 0) { + return Parse.Promise.as(list); + } + + var error; + var className = list[0].className; + var objectIds = []; + for (var i = 0; i < list.length; i++) { + var object = list[i]; + if (className !== object.className) { + error = new Parse.Error(Parse.Error.INVALID_CLASS_NAME, + "All objects should be of the same class"); + return Parse.Promise.error(error); + } else if (!object.id) { + error = new Parse.Error(Parse.Error.MISSING_OBJECT_ID, + "All objects must have an ID"); + return Parse.Promise.error(error); + } else if (omitObjectsWithData && object._hasData) { + continue; + } + objectIds.push(object.id); + } + + return Parse.Promise.as(objectIds); + }; + + /** + * Updates a list of objects with fetched results. + */ + Parse.Object._updateWithFetchedResults = function(list, fetched, forceFetch) { + var fetchedObjectsById = {}; + Parse._arrayEach(fetched, function(object, i) { + fetchedObjectsById[object.id] = object; + }); + + for (var i = 0; i < list.length; i++) { + var object = list[i]; + var fetchedObject = fetchedObjectsById[object.id]; + if (!fetchedObject && forceFetch) { + var error = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, + "All objects must exist on the server"); + return Parse.Promise.error(error); + } + + object._mergeFromObject(fetchedObject); + } + + return Parse.Promise.as(list); + }; + + /** + * Fetches the objects given in list. The forceFetch option will fetch all + * objects if true and ignore objects with data if false. + */ + Parse.Object._fetchAll = function(list, forceFetch) { + if (list.length === 0) { + return Parse.Promise.as(list); + } + + var omitObjectsWithData = !forceFetch; + return Parse.Object._toObjectIdArray( + list, + omitObjectsWithData + ).then(function(objectIds) { + var className = list[0].className; + var query = new Parse.Query(className); + query.containedIn("objectId", objectIds); + query.limit = objectIds.length; + return query.find(); + }).then(function(results) { + return Parse.Object._updateWithFetchedResults( + list, + results, + forceFetch + ); + }); + }; + + // Set up a map of className to class so that we can create new instances of + // Parse Objects from JSON automatically. + Parse.Object._classMap = {}; + + Parse.Object._extend = Parse._extend; + + /** + * Creates a new subclass of Parse.Object for the given Parse class name. + * + *

Every extension of a Parse class will inherit from the most recent + * previous extension of that class. When a Parse.Object is automatically + * created by parsing JSON, it will use the most recent extension of that + * class.

+ * + *

You should call either:

+   *     var MyClass = Parse.Object.extend("MyClass", {
+   *         Instance methods,
+   *         initialize: function(attrs, options) {
+   *             this.someInstanceProperty = [],
+   *             Other instance properties
+   *         }
+   *     }, {
+   *         Class properties
+   *     });
+ * or, for Backbone compatibility:
+   *     var MyClass = Parse.Object.extend({
+   *         className: "MyClass",
+   *         Instance methods,
+   *         initialize: function(attrs, options) {
+   *             this.someInstanceProperty = [],
+   *             Other instance properties
+   *         }
+   *     }, {
+   *         Class properties
+   *     });

+ * + * @param {String} className The name of the Parse class backing this model. + * @param {Object} protoProps Instance properties to add to instances of the + * class returned from this method. + * @param {Object} classProps Class properties to add the class returned from + * this method. + * @return {Class} A new subclass of Parse.Object. + */ + Parse.Object.extend = function(className, protoProps, classProps) { + // Handle the case with only two args. + if (!_.isString(className)) { + if (className && _.has(className, "className")) { + return Parse.Object.extend(className.className, className, protoProps); + } else { + throw new Error( + "Parse.Object.extend's first argument should be the className."); + } + } + + // If someone tries to subclass "User", coerce it to the right type. + if (className === "User" && Parse.User._performUserRewrite) { + className = "_User"; + } + protoProps = protoProps || {}; + protoProps.className = className; + + var NewClassObject = null; + if (_.has(Parse.Object._classMap, className)) { + var OldClassObject = Parse.Object._classMap[className]; + // This new subclass has been told to extend both from "this" and from + // OldClassObject. This is multiple inheritance, which isn't supported. + // For now, let's just pick one. + NewClassObject = OldClassObject._extend(protoProps, classProps); + } else { + NewClassObject = this._extend(protoProps, classProps); + } + // Extending a subclass should reuse the classname automatically. + NewClassObject.extend = function(arg0) { + if (_.isString(arg0) || (arg0 && _.has(arg0, "className"))) { + return Parse.Object.extend.apply(NewClassObject, arguments); + } + var newArguments = [className].concat(Parse._.toArray(arguments)); + return Parse.Object.extend.apply(NewClassObject, newArguments); + }; + + /** + * Creates a reference to a subclass of Parse.Object with the given id. This + * does not exist on Parse.Object, only on subclasses. + * + *

A shortcut for:

+     *  var Foo = Parse.Object.extend("Foo");
+     *  var pointerToFoo = new Foo();
+     *  pointerToFoo.id = "myObjectId";
+     * 
+ * + * @name createWithoutData + * @param {String} id The ID of the object to create a reference to. + * @return {Parse.Object} A Parse.Object reference. + * @function + * @memberOf Parse.Object + */ + NewClassObject.createWithoutData = function(id) { + var obj = new NewClassObject(); + obj.id = id; + return obj; + }; + + Parse.Object._classMap[className] = NewClassObject; + return NewClassObject; + }; + + Parse.Object._findUnsavedChildren = function(object, children, files) { + Parse._traverse(object, function(object) { + if (object instanceof Parse.Object) { + object._refreshCache(); + if (object.dirty()) { + children.push(object); + } + return; + } + + if (object instanceof Parse.File) { + if (!object.url()) { + files.push(object); + } + return; + } + }); + }; + + Parse.Object._canBeSerializedAsValue = function(object) { + + if (object instanceof Parse.Object) { + return !!object.id; + } + if (object instanceof Parse.File) { + // Don't recurse indefinitely into files. + return true; + } + + var canBeSerializedAsValue = true; + + if (_.isArray(object)) { + Parse._arrayEach(object, function(child) { + if (!Parse.Object._canBeSerializedAsValue(child)) { + canBeSerializedAsValue = false; + } + }); + } else if (_.isObject(object)) { + Parse._objectEach(object, function(child) { + if (!Parse.Object._canBeSerializedAsValue(child)) { + canBeSerializedAsValue = false; + } + }); + } + return canBeSerializedAsValue; + }; + + /** + * @param {Object} object The root object. + * @param {Object} options: The only valid option is useMasterKey. + */ + Parse.Object._deepSaveAsync = function(object, options) { + var unsavedChildren = []; + var unsavedFiles = []; + Parse.Object._findUnsavedChildren(object, unsavedChildren, unsavedFiles); + + var promise = Parse.Promise.as(); + _.each(unsavedFiles, function(file) { + promise = promise.then(function() { + return file.save(options); + }); + }); + + var objects = _.uniq(unsavedChildren); + var remaining = _.uniq(objects); + + return promise.then(function() { + return Parse.Promise._continueWhile(function() { + return remaining.length > 0; + }, function() { + + // Gather up all the objects that can be saved in this batch. + var batch = []; + var newRemaining = []; + Parse._arrayEach(remaining, function(object) { + // Limit batches to 20 objects. + if (batch.length > 20) { + newRemaining.push(object); + return; + } + + if (object._canBeSerialized()) { + batch.push(object); + } else { + newRemaining.push(object); + } + }); + remaining = newRemaining; + + // If we can't save any objects, there must be a circular reference. + if (batch.length === 0) { + return Parse.Promise.error( + new Parse.Error(Parse.Error.OTHER_CAUSE, + "Tried to save a batch with a cycle.")); + } + + // Reserve a spot in every object's save queue. + var readyToStart = Parse.Promise.when(_.map(batch, function(object) { + return object._allPreviousSaves || Parse.Promise.as(); + })); + var batchFinished = new Parse.Promise(); + Parse._arrayEach(batch, function(object) { + object._allPreviousSaves = batchFinished; + }); + + // Save a single batch, whether previous saves succeeded or failed. + return readyToStart._continueWith(function() { + return Parse._request({ + route: "batch", + method: "POST", + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken, + data: { + requests: _.map(batch, function(object) { + var json = object._getSaveJSON(); + var method = "POST"; + + var path = "/1/classes/" + object.className; + if (object.id) { + path = path + "/" + object.id; + method = "PUT"; + } + + object._startSave(); + + return { + method: method, + path: path, + body: json + }; + }) + } + }).then(function(response, status, xhr) { + var error; + Parse._arrayEach(batch, function(object, i) { + if (response[i].success) { + object._finishSave( + object.parse(response[i].success, status, xhr)); + } else { + error = error || response[i].error; + object._cancelSave(); + } + }); + if (error) { + return Parse.Promise.error( + new Parse.Error(error.code, error.error)); + } + + }).then(function(results) { + batchFinished.resolve(results); + return results; + }, function(error) { + batchFinished.reject(error); + return Parse.Promise.error(error); + }); + }); + }); + }).then(function() { + return object; + }); + }; + +}(this)); + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * Represents a Role on the Parse server. Roles represent groupings of + * Users for the purposes of granting permissions (e.g. specifying an ACL + * for an Object). Roles are specified by their sets of child users and + * child roles, all of which are granted any permissions that the parent + * role has. + * + *

Roles must have a name (which cannot be changed after creation of the + * role), and must specify an ACL.

+ * @class + * A Parse.Role is a local representation of a role persisted to the Parse + * cloud. + */ + Parse.Role = Parse.Object.extend("_Role", /** @lends Parse.Role.prototype */ { + // Instance Methods + + /** + * Constructs a new ParseRole with the given name and ACL. + * + * @param {String} name The name of the Role to create. + * @param {Parse.ACL} acl The ACL for this role. Roles must have an ACL. + */ + constructor: function(name, acl) { + if (_.isString(name) && (acl instanceof Parse.ACL)) { + Parse.Object.prototype.constructor.call(this, null, null); + this.setName(name); + this.setACL(acl); + } else { + Parse.Object.prototype.constructor.call(this, name, acl); + } + }, + + /** + * Gets the name of the role. You can alternatively call role.get("name") + * + * @return {String} the name of the role. + */ + getName: function() { + return this.get("name"); + }, + + /** + * Sets the name for a role. This value must be set before the role has + * been saved to the server, and cannot be set once the role has been + * saved. + * + *

+ * A role's name can only contain alphanumeric characters, _, -, and + * spaces. + *

+ * + *

This is equivalent to calling role.set("name", name)

+ * + * @param {String} name The name of the role. + * @param {Object} options Standard options object with success and error + * callbacks. + */ + setName: function(name, options) { + return this.set("name", name, options); + }, + + /** + * Gets the Parse.Relation for the Parse.Users that are direct + * children of this role. These users are granted any privileges that this + * role has been granted (e.g. read or write access through ACLs). You can + * add or remove users from the role through this relation. + * + *

This is equivalent to calling role.relation("users")

+ * + * @return {Parse.Relation} the relation for the users belonging to this + * role. + */ + getUsers: function() { + return this.relation("users"); + }, + + /** + * Gets the Parse.Relation for the Parse.Roles that are direct + * children of this role. These roles' users are granted any privileges that + * this role has been granted (e.g. read or write access through ACLs). You + * can add or remove child roles from this role through this relation. + * + *

This is equivalent to calling role.relation("roles")

+ * + * @return {Parse.Relation} the relation for the roles belonging to this + * role. + */ + getRoles: function() { + return this.relation("roles"); + }, + + /** + * @ignore + */ + validate: function(attrs, options) { + if ("name" in attrs && attrs.name !== this.getName()) { + var newName = attrs.name; + if (this.id && this.id !== attrs.objectId) { + // Check to see if the objectId being set matches this.id. + // This happens during a fetch -- the id is set before calling fetch. + // Let the name be set in this case. + return new Parse.Error(Parse.Error.OTHER_CAUSE, + "A role's name can only be set before it has been saved."); + } + if (!_.isString(newName)) { + return new Parse.Error(Parse.Error.OTHER_CAUSE, + "A role's name must be a String."); + } + if (!(/^[0-9a-zA-Z\-_ ]+$/).test(newName)) { + return new Parse.Error(Parse.Error.OTHER_CAUSE, + "A role's name can only contain alphanumeric characters, _," + + " -, and spaces."); + } + } + if (Parse.Object.prototype.validate) { + return Parse.Object.prototype.validate.call(this, attrs, options); + } + return false; + } + }); +}(this)); + + +/*global _: false */ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * Creates a new instance with the given models and options. Typically, you + * will not call this method directly, but will instead make a subclass using + * Parse.Collection.extend. + * + * @param {Array} models An array of instances of Parse.Object. + * + * @param {Object} options An optional object with Backbone-style options. + * Valid options are:
    + *
  • model: The Parse.Object subclass that this collection contains. + *
  • query: An instance of Parse.Query to use when fetching items. + *
  • comparator: A string property name or function to sort by. + *
+ * + * @see Parse.Collection.extend + * + * @class + * + *

Provides a standard collection class for our sets of models, ordered + * or unordered. For more information, see the + * Backbone + * documentation.

+ */ + Parse.Collection = function(models, options) { + options = options || {}; + if (options.comparator) { + this.comparator = options.comparator; + } + if (options.model) { + this.model = options.model; + } + if (options.query) { + this.query = options.query; + } + this._reset(); + this.initialize.apply(this, arguments); + if (models) { + this.reset(models, {silent: true, parse: options.parse}); + } + }; + + // Define the Collection's inheritable methods. + _.extend(Parse.Collection.prototype, Parse.Events, + /** @lends Parse.Collection.prototype */ { + + // The default model for a collection is just a Parse.Object. + // This should be overridden in most cases. + + model: Parse.Object, + + /** + * Initialize is an empty function by default. Override it with your own + * initialization logic. + */ + initialize: function(){}, + + /** + * The JSON representation of a Collection is an array of the + * models' attributes. + */ + toJSON: function() { + return this.map(function(model){ return model.toJSON(); }); + }, + + /** + * Add a model, or list of models to the set. Pass **silent** to avoid + * firing the `add` event for every new model. + * + * @param {Array} models An array of instances of Parse.Object. + * + * @param {Object} options An optional object with Backbone-style options. + * Valid options are:
    + *
  • at: The index at which to add the models. + *
  • silent: Set to true to avoid firing the `add` event for every new + * model. + *
+ */ + add: function(models, options) { + var i, index, length, model, cid, id, cids = {}, ids = {}; + options = options || {}; + models = _.isArray(models) ? models.slice() : [models]; + + // Begin by turning bare objects into model references, and preventing + // invalid models or duplicate models from being added. + for (i = 0, length = models.length; i < length; i++) { + models[i] = this._prepareModel(models[i], options); + model = models[i]; + if (!model) { + throw new Error("Can't add an invalid model to a collection"); + } + cid = model.cid; + if (cids[cid] || this._byCid[cid]) { + throw new Error("Duplicate cid: can't add the same model " + + "to a collection twice"); + } + id = model.id; + if (!Parse._isNullOrUndefined(id) && (ids[id] || this._byId[id])) { + throw new Error("Duplicate id: can't add the same model " + + "to a collection twice"); + } + ids[id] = model; + cids[cid] = model; + } + + // Listen to added models' events, and index models for lookup by + // `id` and by `cid`. + for (i = 0; i < length; i++) { + (model = models[i]).on('all', this._onModelEvent, this); + this._byCid[model.cid] = model; + if (model.id) { + this._byId[model.id] = model; + } + } + + // Insert models into the collection, re-sorting if needed, and triggering + // `add` events unless silenced. + this.length += length; + index = Parse._isNullOrUndefined(options.at) ? + this.models.length : options.at; + this.models.splice.apply(this.models, [index, 0].concat(models)); + if (this.comparator) { + this.sort({silent: true}); + } + if (options.silent) { + return this; + } + for (i = 0, length = this.models.length; i < length; i++) { + model = this.models[i]; + if (cids[model.cid]) { + options.index = i; + model.trigger('add', model, this, options); + } + } + return this; + }, + + /** + * Remove a model, or a list of models from the set. Pass silent to avoid + * firing the remove event for every model removed. + * + * @param {Array} models The model or list of models to remove from the + * collection. + * @param {Object} options An optional object with Backbone-style options. + * Valid options are:
    + *
  • silent: Set to true to avoid firing the `remove` event. + *
+ */ + remove: function(models, options) { + var i, l, index, model; + options = options || {}; + models = _.isArray(models) ? models.slice() : [models]; + for (i = 0, l = models.length; i < l; i++) { + model = this.getByCid(models[i]) || this.get(models[i]); + if (!model) { + continue; + } + delete this._byId[model.id]; + delete this._byCid[model.cid]; + index = this.indexOf(model); + this.models.splice(index, 1); + this.length--; + if (!options.silent) { + options.index = index; + model.trigger('remove', model, this, options); + } + this._removeReference(model); + } + return this; + }, + + /** + * Gets a model from the set by id. + * @param {String} id The Parse objectId identifying the Parse.Object to + * fetch from this collection. + */ + get: function(id) { + return id && this._byId[id.id || id]; + }, + + /** + * Gets a model from the set by client id. + * @param {} cid The Backbone collection id identifying the Parse.Object to + * fetch from this collection. + */ + getByCid: function(cid) { + return cid && this._byCid[cid.cid || cid]; + }, + + /** + * Gets the model at the given index. + * + * @param {Number} index The index of the model to return. + */ + at: function(index) { + return this.models[index]; + }, + + /** + * Forces the collection to re-sort itself. You don't need to call this + * under normal circumstances, as the set will maintain sort order as each + * item is added. + * @param {Object} options An optional object with Backbone-style options. + * Valid options are:
    + *
  • silent: Set to true to avoid firing the `reset` event. + *
+ */ + sort: function(options) { + options = options || {}; + if (!this.comparator) { + throw new Error('Cannot sort a set without a comparator'); + } + var boundComparator = _.bind(this.comparator, this); + if (this.comparator.length === 1) { + this.models = this.sortBy(boundComparator); + } else { + this.models.sort(boundComparator); + } + if (!options.silent) { + this.trigger('reset', this, options); + } + return this; + }, + + /** + * Plucks an attribute from each model in the collection. + * @param {String} attr The attribute to return from each model in the + * collection. + */ + pluck: function(attr) { + return _.map(this.models, function(model){ return model.get(attr); }); + }, + + /** + * When you have more items than you want to add or remove individually, + * you can reset the entire set with a new list of models, without firing + * any `add` or `remove` events. Fires `reset` when finished. + * + * @param {Array} models The model or list of models to remove from the + * collection. + * @param {Object} options An optional object with Backbone-style options. + * Valid options are:
    + *
  • silent: Set to true to avoid firing the `reset` event. + *
+ */ + reset: function(models, options) { + var self = this; + models = models || []; + options = options || {}; + Parse._arrayEach(this.models, function(model) { + self._removeReference(model); + }); + this._reset(); + this.add(models, {silent: true, parse: options.parse}); + if (!options.silent) { + this.trigger('reset', this, options); + } + return this; + }, + + /** + * Fetches the default set of models for this collection, resetting the + * collection when they arrive. If `add: true` is passed, appends the + * models to the collection instead of resetting. + * + * @param {Object} options An optional object with Backbone-style options. + * Valid options are:
    + *
  • silent: Set to true to avoid firing `add` or `reset` events for + * models fetched by this fetch. + *
  • success: A Backbone-style success callback. + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, uses the Master Key for + * this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ */ + fetch: function(options) { + options = _.clone(options) || {}; + if (options.parse === undefined) { + options.parse = true; + } + var collection = this; + var query = this.query || new Parse.Query(this.model); + return query.find({ + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken + }).then(function(results) { + if (options.add) { + collection.add(results, options); + } else { + collection.reset(results, options); + } + return collection; + })._thenRunCallbacks(options, this); + }, + + /** + * Creates a new instance of a model in this collection. Add the model to + * the collection immediately, unless `wait: true` is passed, in which case + * we wait for the server to agree. + * + * @param {Parse.Object} model The new model to create and add to the + * collection. + * @param {Object} options An optional object with Backbone-style options. + * Valid options are:
    + *
  • wait: Set to true to wait for the server to confirm creation of the + * model before adding it to the collection. + *
  • silent: Set to true to avoid firing an `add` event. + *
  • success: A Backbone-style success callback. + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, uses the Master Key for + * this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ */ + create: function(model, options) { + var coll = this; + options = options ? _.clone(options) : {}; + model = this._prepareModel(model, options); + if (!model) { + return false; + } + if (!options.wait) { + coll.add(model, options); + } + var success = options.success; + options.success = function(nextModel, resp, xhr) { + if (options.wait) { + coll.add(nextModel, options); + } + if (success) { + success(nextModel, resp); + } else { + nextModel.trigger('sync', model, resp, options); + } + }; + model.save(null, options); + return model; + }, + + /** + * Converts a response into a list of models to be added to the collection. + * The default implementation is just to pass it through. + * @ignore + */ + parse: function(resp, xhr) { + return resp; + }, + + /** + * Proxy to _'s chain. Can't be proxied the same way the rest of the + * underscore methods are proxied because it relies on the underscore + * constructor. + */ + chain: function() { + return _(this.models).chain(); + }, + + /** + * Reset all internal state. Called when the collection is reset. + */ + _reset: function(options) { + this.length = 0; + this.models = []; + this._byId = {}; + this._byCid = {}; + }, + + /** + * Prepare a model or hash of attributes to be added to this collection. + */ + _prepareModel: function(model, options) { + if (!(model instanceof Parse.Object)) { + var attrs = model; + options.collection = this; + model = new this.model(attrs, options); + if (!model._validate(model.attributes, options)) { + model = false; + } + } else if (!model.collection) { + model.collection = this; + } + return model; + }, + + /** + * Internal method to remove a model's ties to a collection. + */ + _removeReference: function(model) { + if (this === model.collection) { + delete model.collection; + } + model.off('all', this._onModelEvent, this); + }, + + /** + * Internal method called every time a model in the set fires an event. + * Sets need to update their indexes when models change ids. All other + * events simply proxy through. "add" and "remove" events that originate + * in other collections are ignored. + */ + _onModelEvent: function(ev, model, collection, options) { + if ((ev === 'add' || ev === 'remove') && collection !== this) { + return; + } + if (ev === 'destroy') { + this.remove(model, options); + } + if (model && ev === 'change:objectId') { + delete this._byId[model.previous("objectId")]; + this._byId[model.id] = model; + } + this.trigger.apply(this, arguments); + } + + }); + + // Underscore methods that we want to implement on the Collection. + var methods = ['forEach', 'each', 'map', 'reduce', 'reduceRight', 'find', + 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', + 'include', 'contains', 'invoke', 'max', 'min', 'sortBy', 'sortedIndex', + 'toArray', 'size', 'first', 'initial', 'rest', 'last', 'without', 'indexOf', + 'shuffle', 'lastIndexOf', 'isEmpty', 'groupBy']; + + // Mix in each Underscore method as a proxy to `Collection#models`. + Parse._arrayEach(methods, function(method) { + Parse.Collection.prototype[method] = function() { + return _[method].apply(_, [this.models].concat(_.toArray(arguments))); + }; + }); + + /** + * Creates a new subclass of Parse.Collection. For example,
+   *   var MyCollection = Parse.Collection.extend({
+   *     // Instance properties
+   *
+   *     model: MyClass,
+   *     query: MyQuery,
+   *
+   *     getFirst: function() {
+   *       return this.at(0);
+   *     }
+   *   }, {
+   *     // Class properties
+   *
+   *     makeOne: function() {
+   *       return new MyCollection();
+   *     }
+   *   });
+   *
+   *   var collection = new MyCollection();
+   * 
+ * + * @function + * @param {Object} instanceProps Instance properties for the collection. + * @param {Object} classProps Class properies for the collection. + * @return {Class} A new subclass of Parse.Collection. + */ + Parse.Collection.extend = Parse._extend; + +}(this)); + +/*global _: false, document: false */ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * Creating a Parse.View creates its initial element outside of the DOM, + * if an existing element is not provided... + * @class + * + *

A fork of Backbone.View, provided for your convenience. If you use this + * class, you must also include jQuery, or another library that provides a + * jQuery-compatible $ function. For more information, see the + * Backbone + * documentation.

+ *

Available in the client SDK only.

+ */ + Parse.View = function(options) { + this.cid = _.uniqueId('view'); + this._configure(options || {}); + this._ensureElement(); + this.initialize.apply(this, arguments); + this.delegateEvents(); + }; + + // Cached regex to split keys for `delegate`. + var eventSplitter = /^(\S+)\s*(.*)$/; + + // List of view options to be merged as properties. + + var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', + 'className', 'tagName']; + + // Set up all inheritable **Parse.View** properties and methods. + _.extend(Parse.View.prototype, Parse.Events, + /** @lends Parse.View.prototype */ { + + // The default `tagName` of a View's element is `"div"`. + tagName: 'div', + + /** + * jQuery delegate for element lookup, scoped to DOM elements within the + * current view. This should be prefered to global lookups where possible. + */ + $: function(selector) { + return this.$el.find(selector); + }, + + /** + * Initialize is an empty function by default. Override it with your own + * initialization logic. + */ + initialize: function(){}, + + /** + * The core function that your view should override, in order + * to populate its element (`this.el`), with the appropriate HTML. The + * convention is for **render** to always return `this`. + */ + render: function() { + return this; + }, + + /** + * Remove this view from the DOM. Note that the view isn't present in the + * DOM by default, so calling this method may be a no-op. + */ + remove: function() { + this.$el.remove(); + return this; + }, + + /** + * For small amounts of DOM Elements, where a full-blown template isn't + * needed, use **make** to manufacture elements, one at a time. + *
+     *     var el = this.make('li', {'class': 'row'},
+     *                        this.model.escape('title'));
+ */ + make: function(tagName, attributes, content) { + var el = document.createElement(tagName); + if (attributes) { + Parse.$(el).attr(attributes); + } + if (content) { + Parse.$(el).html(content); + } + return el; + }, + + /** + * Changes the view's element (`this.el` property), including event + * re-delegation. + */ + setElement: function(element, delegate) { + this.$el = Parse.$(element); + this.el = this.$el[0]; + if (delegate !== false) { + this.delegateEvents(); + } + return this; + }, + + /** + * Set callbacks. this.events is a hash of + *
+     * *{"event selector": "callback"}*
+     *
+     *     {
+     *       'mousedown .title':  'edit',
+     *       'click .button':     'save'
+     *       'click .open':       function(e) { ... }
+     *     }
+     * 
+ * pairs. Callbacks will be bound to the view, with `this` set properly. + * Uses event delegation for efficiency. + * Omitting the selector binds the event to `this.el`. + * This only works for delegate-able events: not `focus`, `blur`, and + * not `change`, `submit`, and `reset` in Internet Explorer. + */ + delegateEvents: function(events) { + events = events || Parse._getValue(this, 'events'); + if (!events) { + return; + } + this.undelegateEvents(); + var self = this; + Parse._objectEach(events, function(method, key) { + if (!_.isFunction(method)) { + method = self[events[key]]; + } + if (!method) { + throw new Error('Event "' + events[key] + '" does not exist'); + } + var match = key.match(eventSplitter); + var eventName = match[1], selector = match[2]; + method = _.bind(method, self); + eventName += '.delegateEvents' + self.cid; + if (selector === '') { + self.$el.bind(eventName, method); + } else { + self.$el.delegate(selector, eventName, method); + } + }); + }, + + /** + * Clears all callbacks previously bound to the view with `delegateEvents`. + * You usually don't need to use this, but may wish to if you have multiple + * Backbone views attached to the same DOM element. + */ + undelegateEvents: function() { + this.$el.unbind('.delegateEvents' + this.cid); + }, + + /** + * Performs the initial configuration of a View with a set of options. + * Keys with special meaning *(model, collection, id, className)*, are + * attached directly to the view. + */ + _configure: function(options) { + if (this.options) { + options = _.extend({}, this.options, options); + } + var self = this; + _.each(viewOptions, function(attr) { + if (options[attr]) { + self[attr] = options[attr]; + } + }); + this.options = options; + }, + + /** + * Ensure that the View has a DOM element to render into. + * If `this.el` is a string, pass it through `$()`, take the first + * matching element, and re-assign it to `el`. Otherwise, create + * an element from the `id`, `className` and `tagName` properties. + */ + _ensureElement: function() { + if (!this.el) { + var attrs = Parse._getValue(this, 'attributes') || {}; + if (this.id) { + attrs.id = this.id; + } + if (this.className) { + attrs['class'] = this.className; + } + this.setElement(this.make(this.tagName, attrs), false); + } else { + this.setElement(this.el, false); + } + } + + }); + + /** + * @function + * @param {Object} instanceProps Instance properties for the view. + * @param {Object} classProps Class properies for the view. + * @return {Class} A new subclass of Parse.View. + */ + Parse.View.extend = Parse._extend; + +}(this)); + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * @class + * + *

A Parse.User object is a local representation of a user persisted to the + * Parse cloud. This class is a subclass of a Parse.Object, and retains the + * same functionality of a Parse.Object, but also extends it with various + * user specific methods, like authentication, signing up, and validation of + * uniqueness.

+ */ + Parse.User = Parse.Object.extend("_User", /** @lends Parse.User.prototype */ { + // Instance Variables + _isCurrentUser: false, + + + // Instance Methods + + /** + * Merges another object's attributes into this object. + */ + _mergeFromObject: function(other) { + if (other.getSessionToken()) { + this._sessionToken = other.getSessionToken(); + } + Parse.User.__super__._mergeFromObject.call(this, other); + }, + + /** + * Internal method to handle special fields in a _User response. + */ + _mergeMagicFields: function(attrs) { + if (attrs.sessionToken) { + this._sessionToken = attrs.sessionToken; + delete attrs.sessionToken; + } + Parse.User.__super__._mergeMagicFields.call(this, attrs); + }, + + /** + * Removes null values from authData (which exist temporarily for + * unlinking) + */ + _cleanupAuthData: function() { + if (!this.isCurrent()) { + return; + } + var authData = this.get('authData'); + if (!authData) { + return; + } + Parse._objectEach(this.get('authData'), function(value, key) { + if (!authData[key]) { + delete authData[key]; + } + }); + }, + + /** + * Synchronizes authData for all providers. + */ + _synchronizeAllAuthData: function() { + var authData = this.get('authData'); + if (!authData) { + return; + } + + var self = this; + Parse._objectEach(this.get('authData'), function(value, key) { + self._synchronizeAuthData(key); + }); + }, + + /** + * Synchronizes auth data for a provider (e.g. puts the access token in the + * right place to be used by the Facebook SDK). + */ + _synchronizeAuthData: function(provider) { + if (!this.isCurrent()) { + return; + } + var authType; + if (_.isString(provider)) { + authType = provider; + provider = Parse.User._authProviders[authType]; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData'); + if (!authData || !provider) { + return; + } + var success = provider.restoreAuthentication(authData[authType]); + if (!success) { + this._unlinkFrom(provider); + } + }, + + _handleSaveResult: function(makeCurrent) { + // Clean up and synchronize the authData object, removing any unset values + if (makeCurrent) { + this._isCurrentUser = true; + } + this._cleanupAuthData(); + this._synchronizeAllAuthData(); + // Don't keep the password around. + delete this._serverData.password; + this._rebuildEstimatedDataForKey("password"); + this._refreshCache(); + if (makeCurrent || this.isCurrent()) { + Parse.User._saveCurrentUser(this); + } + }, + + /** + * Unlike in the Android/iOS SDKs, logInWith is unnecessary, since you can + * call linkWith on the user (even if it doesn't exist yet on the server). + */ + _linkWith: function(provider, options) { + var authType; + if (_.isString(provider)) { + authType = provider; + provider = Parse.User._authProviders[provider]; + } else { + authType = provider.getAuthType(); + } + if (_.has(options, 'authData')) { + var authData = this.get('authData') || {}; + authData[authType] = options.authData; + this.set('authData', authData); + + // Overridden so that the user can be made the current user. + var newOptions = _.clone(options) || {}; + newOptions.success = function(model) { + model._handleSaveResult(true); + if (options.success) { + options.success.apply(this, arguments); + } + }; + return this.save({'authData': authData}, newOptions); + } else { + var self = this; + var promise = new Parse.Promise(); + provider.authenticate({ + success: function(provider, result) { + self._linkWith(provider, { + authData: result, + success: options.success, + error: options.error + }).then(function() { + promise.resolve(self); + }); + }, + error: function(provider, error) { + if (options.error) { + options.error(self, error); + } + promise.reject(error); + } + }); + return promise; + } + }, + + /** + * Unlinks a user from a service. + */ + _unlinkFrom: function(provider, options) { + var authType; + if (_.isString(provider)) { + authType = provider; + provider = Parse.User._authProviders[provider]; + } else { + authType = provider.getAuthType(); + } + var newOptions = _.clone(options); + var self = this; + newOptions.authData = null; + newOptions.success = function(model) { + self._synchronizeAuthData(provider); + if (options.success) { + options.success.apply(this, arguments); + } + }; + return this._linkWith(provider, newOptions); + }, + + /** + * Checks whether a user is linked to a service. + */ + _isLinked: function(provider) { + var authType; + if (_.isString(provider)) { + authType = provider; + } else { + authType = provider.getAuthType(); + } + var authData = this.get('authData') || {}; + return !!authData[authType]; + }, + + /** + * Deauthenticates all providers. + */ + _logOutWithAll: function() { + var authData = this.get('authData'); + if (!authData) { + return; + } + var self = this; + Parse._objectEach(this.get('authData'), function(value, key) { + self._logOutWith(key); + }); + }, + + /** + * Deauthenticates a single provider (e.g. removing access tokens from the + * Facebook SDK). + */ + _logOutWith: function(provider) { + if (!this.isCurrent()) { + return; + } + if (_.isString(provider)) { + provider = Parse.User._authProviders[provider]; + } + if (provider && provider.deauthenticate) { + provider.deauthenticate(); + } + }, + + /** + * Signs up a new user. You should call this instead of save for + * new Parse.Users. This will create a new Parse.User on the server, and + * also persist the session on disk so that you can access the user using + * current. + * + *

A username and password must be set before calling signUp.

+ * + *

Calls options.success or options.error on completion.

+ * + * @param {Object} attrs Extra fields to set on the new user, or null. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled when the signup + * finishes. + * @see Parse.User.signUp + */ + signUp: function(attrs, options) { + var error; + options = options || {}; + + var username = (attrs && attrs.username) || this.get("username"); + if (!username || (username === "")) { + error = new Parse.Error( + Parse.Error.OTHER_CAUSE, + "Cannot sign up user with an empty name."); + if (options && options.error) { + options.error(this, error); + } + return Parse.Promise.error(error); + } + + var password = (attrs && attrs.password) || this.get("password"); + if (!password || (password === "")) { + error = new Parse.Error( + Parse.Error.OTHER_CAUSE, + "Cannot sign up user with an empty password."); + if (options && options.error) { + options.error(this, error); + } + return Parse.Promise.error(error); + } + + // Overridden so that the user can be made the current user. + var newOptions = _.clone(options); + newOptions.success = function(model) { + model._handleSaveResult(Parse.User._canUseCurrentUser()); + if (options.success) { + options.success.apply(this, arguments); + } + }; + return this.save(attrs, newOptions); + }, + + /** + * Logs in a Parse.User. On success, this saves the session to localStorage, + * so you can retrieve the currently logged in user using + * current. + * + *

A username and password must be set before calling logIn.

+ * + *

Calls options.success or options.error on completion.

+ * + * @param {Object} options A Backbone-style options object. + * @see Parse.User.logIn + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login is complete. + */ + logIn: function(options) { + if (!Parse.User._canUseCurrentUser()) { + throw new Error( + 'It is not possible to log in on a server environment.' + ); + } + var model = this; + options = options || {}; + var request = Parse._request({ + route: "login", + method: "GET", + useMasterKey: options.useMasterKey, + data: this.toJSON() + }); + return request.then(function(resp, status, xhr) { + var serverAttrs = model.parse(resp, status, xhr); + model._finishFetch(serverAttrs); + model._handleSaveResult(true); + return model; + })._thenRunCallbacks(options, this); + }, + + /** + * @see Parse.Object#save + */ + save: function(arg1, arg2, arg3) { + var i, attrs, current, options, saved; + if (_.isObject(arg1) || _.isNull(arg1) || _.isUndefined(arg1)) { + attrs = arg1; + options = arg2; + } else { + attrs = {}; + attrs[arg1] = arg2; + options = arg3; + } + options = options || {}; + + var newOptions = _.clone(options); + newOptions.success = function(model) { + model._handleSaveResult(false); + if (options.success) { + options.success.apply(this, arguments); + } + }; + return Parse.Object.prototype.save.call(this, attrs, newOptions); + }, + + /** + * @see Parse.Object#fetch + */ + fetch: function(options) { + var newOptions = options ? _.clone(options) : {}; + newOptions.success = function(model) { + model._handleSaveResult(false); + if (options && options.success) { + options.success.apply(this, arguments); + } + }; + return Parse.Object.prototype.fetch.call(this, newOptions); + }, + + /** + * Returns true if current would return this user. + * @see Parse.User#current + */ + isCurrent: function() { + return this._isCurrentUser; + }, + + /** + * Returns get("username"). + * @return {String} + * @see Parse.Object#get + */ + getUsername: function() { + return this.get("username"); + }, + + /** + * Calls set("username", username, options) and returns the result. + * @param {String} username + * @param {Object} options A Backbone-style options object. + * @return {Boolean} + * @see Parse.Object.set + */ + setUsername: function(username, options) { + return this.set("username", username, options); + }, + + /** + * Calls set("password", password, options) and returns the result. + * @param {String} password + * @param {Object} options A Backbone-style options object. + * @return {Boolean} + * @see Parse.Object.set + */ + setPassword: function(password, options) { + return this.set("password", password, options); + }, + + /** + * Returns get("email"). + * @return {String} + * @see Parse.Object#get + */ + getEmail: function() { + return this.get("email"); + }, + + /** + * Calls set("email", email, options) and returns the result. + * @param {String} email + * @param {Object} options A Backbone-style options object. + * @return {Boolean} + * @see Parse.Object.set + */ + setEmail: function(email, options) { + return this.set("email", email, options); + }, + + /** + * Checks whether this user is the current user and has been authenticated. + * @return (Boolean) whether this user is the current user and is logged in. + */ + authenticated: function() { + return !!this._sessionToken && + (Parse.User.current() && Parse.User.current().id === this.id); + }, + + /** + * Returns the session token for this user, if the user has been logged in, + * or if it is the result of a query with the master key. Otherwise, returns + * undefined. + * @return {String} the session token, or undefined + */ + getSessionToken: function() { + return this._sessionToken; + }, + + /** + * Request a revocable session token to replace the older style of token. + * @param {Object} options A Backbone-style options object. + * + * @return {Parse.Promise} A promise that is resolved when the replacement + * token has been fetched. + */ + _upgradeToRevocableSession: function(options) { + options = options || {}; + if (!Parse.User.current()) { + return Parse.Promise.as()._thenRunCallbacks(options); + } + var currentSession = Parse.User.current().getSessionToken(); + if (Parse.Session._isRevocable(currentSession)) { + return Parse.Promise.as()._thenRunCallbacks(options); + } + return Parse._request({ + route: 'upgradeToRevocableSession', + method: 'POST', + useMasterKey: options.useMasterKey, + sessionToken: currentSession + }).then(function(result) { + var session = new Parse.Session(); + session._finishFetch(result); + var currentUser = Parse.User.current(); + currentUser._sessionToken = session.getSessionToken(); + Parse.User._saveCurrentUser(currentUser); + })._thenRunCallbacks(options); + }, + + }, /** @lends Parse.User */ { + // Class Variables + + // The currently logged-in user. + _currentUser: null, + + // Whether currentUser is known to match the serialized version on disk. + // This is useful for saving a localstorage check if you try to load + // _currentUser frequently while there is none stored. + _currentUserMatchesDisk: false, + + // The localStorage key suffix that the current user is stored under. + _CURRENT_USER_KEY: "currentUser", + + // The mapping of auth provider names to actual providers + _authProviders: {}, + + // Whether to rewrite className User to _User + _performUserRewrite: true, + + // Whether to send a Revocable Session header + _isRevocableSessionEnabled: false, + + // Whether to enable a memory-unsafe current user in node.js + _enableUnsafeCurrentUser: false, + + + // Class Methods + + /** + * Signs up a new user with a username (or email) and password. + * This will create a new Parse.User on the server, and also persist the + * session in localStorage so that you can access the user using + * {@link #current}. + * + *

Calls options.success or options.error on completion.

+ * + * @param {String} username The username (or email) to sign up with. + * @param {String} password The password to sign up with. + * @param {Object} attrs Extra fields to set on the new user. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the signup completes. + * @see Parse.User#signUp + */ + signUp: function(username, password, attrs, options) { + attrs = attrs || {}; + attrs.username = username; + attrs.password = password; + var user = Parse.Object._create("_User"); + return user.signUp(attrs, options); + }, + + /** + * Logs in a user with a username (or email) and password. On success, this + * saves the session to disk, so you can retrieve the currently logged in + * user using current. + * + *

Calls options.success or options.error on completion.

+ * + * @param {String} username The username (or email) to log in with. + * @param {String} password The password to log in with. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + * @see Parse.User#logIn + */ + logIn: function(username, password, options) { + var user = Parse.Object._create("_User"); + user._finishFetch({ username: username, password: password }); + return user.logIn(options); + }, + + /** + * Logs in a user with a session token. On success, this saves the session + * to disk, so you can retrieve the currently logged in user using + * current. + * + *

Calls options.success or options.error on completion.

+ * + * @param {String} sessionToken The sessionToken to log in with. + * @param {Object} options A Backbone-style options object. + * @return {Parse.Promise} A promise that is fulfilled with the user when + * the login completes. + */ + become: function(sessionToken, options) { + if (!Parse.User._canUseCurrentUser()) { + throw new Error( + 'It is not secure to become a user on a node.js server environment.' + ); + } + options = options || {}; + + var user = Parse.Object._create("_User"); + return Parse._request({ + route: "users", + className: "me", + method: "GET", + useMasterKey: options.useMasterKey, + sessionToken: sessionToken + }).then(function(resp, status, xhr) { + var serverAttrs = user.parse(resp, status, xhr); + user._finishFetch(serverAttrs); + user._handleSaveResult(true); + return user; + + })._thenRunCallbacks(options, user); + }, + + /** + * Logs out the currently logged in user session. This will remove the + * session from disk, log out of linked services, and future calls to + * current will return null. + * @return {Parse.Promise} A promise that is resolved when the session is + * destroyed on the server. + */ + logOut: function() { + if (!Parse.User._canUseCurrentUser()) { + throw new Error( + 'There is no current user user on a node.js server environment.' + ); + } + return Parse.User._currentAsync().then(function(currentUser) { + var promise = Parse.Storage.removeItemAsync( + Parse._getParsePath(Parse.User._CURRENT_USER_KEY)); + + if (currentUser !== null) { + var currentSession = currentUser.getSessionToken(); + if (Parse.Session._isRevocable(currentSession)) { + promise.then(function() { + return Parse._request({ + route: 'logout', + method: 'POST', + sessionToken: currentSession + }); + }); + } + currentUser._logOutWithAll(); + currentUser._isCurrentUser = false; + } + Parse.User._currentUserMatchesDisk = true; + Parse.User._currentUser = null; + + return promise; + }); + }, + + /** + * Requests a password reset email to be sent to the specified email address + * associated with the user account. This email allows the user to securely + * reset their password on the Parse site. + * + *

Calls options.success or options.error on completion.

+ * + * @param {String} email The email address associated with the user that + * forgot their password. + * @param {Object} options A Backbone-style options object. + */ + requestPasswordReset: function(email, options) { + options = options || {}; + var request = Parse._request({ + route: "requestPasswordReset", + method: "POST", + useMasterKey: options.useMasterKey, + data: { email: email } + }); + return request._thenRunCallbacks(options); + }, + + /** + * Retrieves the currently logged in ParseUser with a valid session, + * either from memory or localStorage, if necessary. + * @return {Parse.Object} The currently logged in Parse.User. + */ + current: function() { + if (!Parse.User._canUseCurrentUser()) { + throw new Error( + 'There is no current user user on a node.js server environment.' + ); + } + if (Parse.Storage.async) { + // We can't return the current user synchronously + Parse.User._currentAsync(); + return Parse.User._currentUser; + } + + if (Parse.User._currentUser) { + return Parse.User._currentUser; + } + + if (Parse.User._currentUserMatchesDisk) { + + return Parse.User._currentUser; + } + + // Load the user from local storage. + Parse.User._currentUserMatchesDisk = true; + + var userData = Parse.Storage.getItem(Parse._getParsePath( + Parse.User._CURRENT_USER_KEY)); + if (!userData) { + + return null; + } + Parse.User._currentUser = Parse.Object._create("_User"); + Parse.User._currentUser._isCurrentUser = true; + + var json = JSON.parse(userData); + Parse.User._currentUser.id = json._id; + delete json._id; + Parse.User._currentUser._sessionToken = json._sessionToken; + delete json._sessionToken; + Parse.User._currentUser._finishFetch(json); + + Parse.User._currentUser._synchronizeAllAuthData(); + Parse.User._currentUser._refreshCache(); + Parse.User._currentUser._opSetQueue = [{}]; + return Parse.User._currentUser; + }, + + /** + * Retrieves the currently logged in ParseUser from asynchronous Storage. + * @return {Parse.Promise} A Promise that is resolved with the currently + * logged in Parse User + */ + _currentAsync: function() { + if (Parse.User._currentUser) { + return Parse.Promise.as(Parse.User._currentUser); + } + + if (Parse.User._currentUserMatchesDisk) { + return Parse.Promise.as(Parse.User._currentUser); + } + + // Load the user from Storage + return Parse.Storage.getItemAsync(Parse._getParsePath( + Parse.User._CURRENT_USER_KEY)).then(function(userData) { + if (!userData) { + return null; + } + Parse.User._currentUser = Parse.Object._create("_User"); + Parse.User._currentUser._isCurrentUser = true; + + var json = JSON.parse(userData); + Parse.User._currentUser.id = json._id; + delete json._id; + Parse.User._currentUser._sessionToken = json._sessionToken; + delete json._sessionToken; + Parse.User._currentUser._finishFetch(json); + + Parse.User._currentUser._synchronizeAllAuthData(); + Parse.User._currentUser._refreshCache(); + Parse.User._currentUser._opSetQueue = [{}]; + return Parse.User._currentUser; + }); + }, + + /** + * Allow someone to define a custom User class without className + * being rewritten to _User. The default behavior is to rewrite + * User to _User for legacy reasons. This allows developers to + * override that behavior. + * + * @param {Boolean} isAllowed Whether or not to allow custom User class + */ + allowCustomUserClass: function(isAllowed) { + this._performUserRewrite = !isAllowed; + }, + + /** + * Allow a legacy application to start using revocable sessions. If the + * current session token is not revocable, a request will be made for a new, + * revocable session. + * It is not necessary to call this method from cloud code unless you are + * handling user signup or login from the server side. In a cloud code call, + * this function will not attempt to upgrade the current token. + * @param {Object} options A Backbone-style options object. + * + * @return {Parse.Promise} A promise that is resolved when the process has + * completed. If a replacement session token is requested, the promise + * will be resolved after a new token has been fetched. + */ + enableRevocableSession: function(options) { + options = options || {}; + Parse.User._isRevocableSessionEnabled = true; + if (Parse.User._canUseCurrentUser() && Parse.User.current()) { + return Parse.User.current()._upgradeToRevocableSession(options); + } + return Parse.Promise.as()._thenRunCallbacks(options); + }, + + /** + * + */ + enableUnsafeCurrentUser: function() { + Parse.User._enableUnsafeCurrentUser = true; + }, + + _canUseCurrentUser: function() { + return !Parse._isNode || Parse.User._enableUnsafeCurrentUser; + }, + + /** + * Persists a user as currentUser to localStorage, and into the singleton. + */ + _saveCurrentUser: function(user) { + if (Parse.User._currentUser !== null && + Parse.User._currentUser !== user) { + Parse.User.logOut(); + } + user._isCurrentUser = true; + Parse.User._currentUser = user; + Parse.User._currentUserMatchesDisk = true; + + var json = user.toJSON(); + json._id = user.id; + json._sessionToken = user._sessionToken; + if (Parse.Storage.async) { + Parse.Storage.setItemAsync( + Parse._getParsePath(Parse.User._CURRENT_USER_KEY), + JSON.stringify(json)); + } else { + Parse.Storage.setItem( + Parse._getParsePath(Parse.User._CURRENT_USER_KEY), + JSON.stringify(json)); + } + }, + + _registerAuthenticationProvider: function(provider) { + Parse.User._authProviders[provider.getAuthType()] = provider; + // Synchronize the current user with the auth provider. + if (Parse.User.current()) { + Parse.User.current()._synchronizeAuthData(provider.getAuthType()); + } + }, + + _logInWith: function(provider, options) { + var user = Parse.Object._create("_User"); + return user._linkWith(provider, options); + } + + }); +}(this)); + + +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + + /** + * @class + * + *

A Parse.Session object is a local representation of a revocable session. + * This class is a subclass of a Parse.Object, and retains the same + * functionality of a Parse.Object.

+ */ + Parse.Session = Parse.Object.extend('_Session', + /** @lends Parse.Session.prototype */ + { + /** + * Returns the session token string. + * @return {String} + */ + getSessionToken: function() { + return this._sessionToken; + }, + + /** + * Internal method to handle special fields in a _Session response. + */ + _mergeMagicFields: function(attrs) { + if (attrs.sessionToken) { + this._sessionToken = attrs.sessionToken; + delete attrs.sessionToken; + } + Parse.Session.__super__._mergeMagicFields.call(this, attrs); + }, + }, /** @lends Parse.Session */ { + + // Throw an error when modifying these read-only fields + readOnlyAttributes: { + createdWith: true, + expiresAt: true, + installationId: true, + restricted: true, + sessionToken: true, + user: true + }, + + /** + * Retrieves the Session object for the currently logged in session. + * @return {Parse.Promise} A promise that is resolved with the Parse.Session + * object after it has been fetched. + */ + current: function(options) { + options = options || {}; + + var session = Parse.Object._create('_Session'); + var currentToken = Parse.User.current().getSessionToken(); + return Parse._request({ + route: 'sessions', + className: 'me', + method: 'GET', + useMasterKey: options.useMasterKey, + sessionToken: currentToken + }).then(function(resp, status, xhr) { + var serverAttrs = session.parse(resp, status, xhr); + session._finishFetch(serverAttrs); + return session; + })._thenRunCallbacks(options, session); + }, + + /** + * Determines whether a session token is revocable. + * @return {Boolean} + */ + _isRevocable: function(token) { + return token.indexOf('r:') > -1; + }, + + /** + * Determines whether the current session token is revocable. + * This method is useful for migrating Express.js or Node.js web apps to + * use revocable sessions. If you are migrating an app that uses the Parse + * SDK in the browser only, please use Parse.User.enableRevocableSession() + * instead, so that sessions can be automatically upgraded. + * @return {Boolean} + */ + isCurrentSessionRevocable: function() { + if (Parse.User.current() !== null) { + return Parse.Session._isRevocable( + Parse.User.current().getSessionToken() + ); + } + } + }); +})(this); + +// Parse.Query is a way to create a list of Parse.Objects. +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * Creates a new parse Parse.Query for the given Parse.Object subclass. + * @param objectClass - + * An instance of a subclass of Parse.Object, or a Parse className string. + * @class + * + *

Parse.Query defines a query that is used to fetch Parse.Objects. The + * most common use case is finding all objects that match a query through the + * find method. For example, this sample code fetches all objects + * of class MyClass. It calls a different function depending on + * whether the fetch succeeded or not. + * + *

+   * var query = new Parse.Query(MyClass);
+   * query.find({
+   *   success: function(results) {
+   *     // results is an array of Parse.Object.
+   *   },
+   *
+   *   error: function(error) {
+   *     // error is an instance of Parse.Error.
+   *   }
+   * });

+ * + *

A Parse.Query can also be used to retrieve a single object whose id is + * known, through the get method. For example, this sample code fetches an + * object of class MyClass and id myId. It calls a + * different function depending on whether the fetch succeeded or not. + * + *

+   * var query = new Parse.Query(MyClass);
+   * query.get(myId, {
+   *   success: function(object) {
+   *     // object is an instance of Parse.Object.
+   *   },
+   *
+   *   error: function(object, error) {
+   *     // error is an instance of Parse.Error.
+   *   }
+   * });

+ * + *

A Parse.Query can also be used to count the number of objects that match + * the query without retrieving all of those objects. For example, this + * sample code counts the number of objects of the class MyClass + *

+   * var query = new Parse.Query(MyClass);
+   * query.count({
+   *   success: function(number) {
+   *     // There are number instances of MyClass.
+   *   },
+   *
+   *   error: function(error) {
+   *     // error is an instance of Parse.Error.
+   *   }
+   * });

+ */ + Parse.Query = function(objectClass) { + if (_.isString(objectClass)) { + objectClass = Parse.Object._getSubclass(objectClass); + } + + this.objectClass = objectClass; + + this.className = objectClass.prototype.className; + + this._where = {}; + this._include = []; + this._limit = -1; // negative limit means, do not send a limit + this._skip = 0; + this._extraOptions = {}; + }; + + /** + * Constructs a Parse.Query that is the OR of the passed in queries. For + * example: + *
var compoundQuery = Parse.Query.or(query1, query2, query3);
+ * + * will create a compoundQuery that is an or of the query1, query2, and + * query3. + * @param {...Parse.Query} var_args The list of queries to OR. + * @return {Parse.Query} The query that is the OR of the passed in queries. + */ + Parse.Query.or = function() { + var queries = _.toArray(arguments); + var className = null; + Parse._arrayEach(queries, function(q) { + if (_.isNull(className)) { + className = q.className; + } + + if (className !== q.className) { + throw "All queries must be for the same class"; + } + }); + var query = new Parse.Query(className); + query._orQuery(queries); + return query; + }; + + Parse.Query.prototype = { + /** + * Constructs a Parse.Object whose id is already known by fetching data from + * the server. Either options.success or options.error is called when the + * find completes. + * + * @param {String} objectId The id of the object to be fetched. + * @param {Object} options A Backbone-style options object. + * Valid options are:
    + *
  • success: A Backbone-style success callback + *
  • error: An Backbone-style error callback. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ */ + get: function(objectId, options) { + var self = this; + self.equalTo('objectId', objectId); + + var firstOptions = {}; + if (options && _.has(options, 'useMasterKey')) { + firstOptions = { useMasterKey: options.useMasterKey }; + } + if (options && _.has(options, 'sessionToken')) { + firstOptions.sessionToken = options.sessionToken; + } + + return self.first(firstOptions).then(function(response) { + if (response) { + return response; + } + + var errorObject = new Parse.Error(Parse.Error.OBJECT_NOT_FOUND, + "Object not found."); + return Parse.Promise.error(errorObject); + + })._thenRunCallbacks(options, null); + }, + + /** + * Returns a JSON representation of this query. + * @return {Object} The JSON representation of the query. + */ + toJSON: function() { + var params = { + where: this._where + }; + + if (this._include.length > 0) { + params.include = this._include.join(","); + } + if (this._select) { + params.keys = this._select.join(","); + } + if (this._limit >= 0) { + params.limit = this._limit; + } + if (this._skip > 0) { + params.skip = this._skip; + } + if (this._order !== undefined) { + params.order = this._order.join(","); + } + + Parse._objectEach(this._extraOptions, function(v, k) { + params[k] = v; + }); + + return params; + }, + + /** + * Retrieves a list of ParseObjects that satisfy this query. + * Either options.success or options.error is called when the find + * completes. + * + * @param {Object} options A Backbone-style options object. Valid options + * are:
    + *
  • success: Function to call when the find completes successfully. + *
  • error: Function to call when the find fails. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * + * @return {Parse.Promise} A promise that is resolved with the results when + * the query completes. + */ + find: function(options) { + var self = this; + options = options || {}; + + var request = Parse._request({ + route: "classes", + className: this.className, + method: "GET", + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken, + data: this.toJSON() + }); + + return request.then(function(response) { + return _.map(response.results, function(json) { + var obj; + if (response.className) { + obj = new Parse.Object(response.className); + } else { + obj = new self.objectClass(); + } + obj._finishFetch(json, true); + return obj; + }); + })._thenRunCallbacks(options); + }, + + /** + * Counts the number of objects that match this query. + * Either options.success or options.error is called when the count + * completes. + * + * @param {Object} options A Backbone-style options object. Valid options + * are:
    + *
  • success: Function to call when the count completes successfully. + *
  • error: Function to call when the find fails. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * + * @return {Parse.Promise} A promise that is resolved with the count when + * the query completes. + */ + count: function(options) { + var self = this; + options = options || {}; + + var params = this.toJSON(); + params.limit = 0; + params.count = 1; + var request = Parse._request({ + route: "classes", + className: self.className, + method: "GET", + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken, + data: params + }); + + return request.then(function(response) { + return response.count; + })._thenRunCallbacks(options); + }, + + /** + * Retrieves at most one Parse.Object that satisfies this query. + * + * Either options.success or options.error is called when it completes. + * success is passed the object if there is one. otherwise, undefined. + * + * @param {Object} options A Backbone-style options object. Valid options + * are:
    + *
  • success: Function to call when the find completes successfully. + *
  • error: Function to call when the find fails. + *
  • useMasterKey: In Cloud Code and Node only, causes the Master Key to + * be used for this request. + *
  • sessionToken: A valid session token, used for making a request on + * behalf of a specific user. + *
+ * + * @return {Parse.Promise} A promise that is resolved with the object when + * the query completes. + */ + first: function(options) { + var self = this; + options = options || {}; + + var params = this.toJSON(); + params.limit = 1; + var request = Parse._request({ + route: "classes", + className: this.className, + method: "GET", + useMasterKey: options.useMasterKey, + sessionToken: options.sessionToken, + data: params + }); + + return request.then(function(response) { + return _.map(response.results, function(json) { + var obj; + if (response.className) { + obj = new Parse.Object(response.className); + } else { + obj = new self.objectClass(); + } + obj._finishFetch(json, true); + return obj; + })[0]; + })._thenRunCallbacks(options); + }, + + /** + * Returns a new instance of Parse.Collection backed by this query. + * @param {Array} items An array of instances of Parse.Object + * with which to start this Collection. + * @param {Object} options An optional object with Backbone-style options. + * Valid options are:
    + *
  • model: The Parse.Object subclass that this collection contains. + *
  • query: An instance of Parse.Query to use when fetching items. + *
  • comparator: A string property name or function to sort by. + *
+ * @return {Parse.Collection} + */ + collection: function(items, options) { + options = options || {}; + return new Parse.Collection(items, _.extend(options, { + model: this.objectClass, + query: this + })); + }, + + /** + * Sets the number of results to skip before returning any results. + * This is useful for pagination. + * Default is to skip zero results. + * @param {Number} n the number of results to skip. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + skip: function(n) { + this._skip = n; + return this; + }, + + /** + * Sets the limit of the number of results to return. The default limit is + * 100, with a maximum of 1000 results being returned at a time. + * @param {Number} n the number of results to limit to. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + limit: function(n) { + this._limit = n; + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * be equal to the provided value. + * @param {String} key The key to check. + * @param value The value that the Parse.Object must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + equalTo: function(key, value) { + if (_.isUndefined(value)) { + return this.doesNotExist(key); + } + + this._where[key] = Parse._encode(value); + return this; + }, + + /** + * Helper for condition queries + */ + _addCondition: function(key, condition, value) { + // Check if we already have a condition + if (!this._where[key]) { + this._where[key] = {}; + } + this._where[key][condition] = Parse._encode(value); + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * be not equal to the provided value. + * @param {String} key The key to check. + * @param value The value that must not be equalled. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + notEqualTo: function(key, value) { + this._addCondition(key, "$ne", value); + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * be less than the provided value. + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + lessThan: function(key, value) { + this._addCondition(key, "$lt", value); + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * be greater than the provided value. + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + greaterThan: function(key, value) { + this._addCondition(key, "$gt", value); + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * be less than or equal to the provided value. + * @param {String} key The key to check. + * @param value The value that provides an upper bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + lessThanOrEqualTo: function(key, value) { + this._addCondition(key, "$lte", value); + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * be greater than or equal to the provided value. + * @param {String} key The key to check. + * @param value The value that provides an lower bound. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + greaterThanOrEqualTo: function(key, value) { + this._addCondition(key, "$gte", value); + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * be contained in the provided list of values. + * @param {String} key The key to check. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + containedIn: function(key, values) { + this._addCondition(key, "$in", values); + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * not be contained in the provided list of values. + * @param {String} key The key to check. + * @param {Array} values The values that will not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + notContainedIn: function(key, values) { + this._addCondition(key, "$nin", values); + return this; + }, + + /** + * Add a constraint to the query that requires a particular key's value to + * contain each one of the provided list of values. + * @param {String} key The key to check. This key's value must be an array. + * @param {Array} values The values that will match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + containsAll: function(key, values) { + this._addCondition(key, "$all", values); + return this; + }, + + + /** + * Add a constraint for finding objects that contain the given key. + * @param {String} key The key that should exist. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + exists: function(key) { + this._addCondition(key, "$exists", true); + return this; + }, + + /** + * Add a constraint for finding objects that do not contain a given key. + * @param {String} key The key that should not exist + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + doesNotExist: function(key) { + this._addCondition(key, "$exists", false); + return this; + }, + + /** + * Add a regular expression constraint for finding string values that match + * the provided regular expression. + * This may be slow for large datasets. + * @param {String} key The key that the string to match is stored in. + * @param {RegExp} regex The regular expression pattern to match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + matches: function(key, regex, modifiers) { + this._addCondition(key, "$regex", regex); + if (!modifiers) { modifiers = ""; } + // Javascript regex options support mig as inline options but store them + // as properties of the object. We support mi & should migrate them to + // modifiers + if (regex.ignoreCase) { modifiers += 'i'; } + if (regex.multiline) { modifiers += 'm'; } + + if (modifiers && modifiers.length) { + this._addCondition(key, "$options", modifiers); + } + return this; + }, + + /** + * Add a constraint that requires that a key's value matches a Parse.Query + * constraint. + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + matchesQuery: function(key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + this._addCondition(key, "$inQuery", queryJSON); + return this; + }, + + /** + * Add a constraint that requires that a key's value not matches a + * Parse.Query constraint. + * @param {String} key The key that the contains the object to match the + * query. + * @param {Parse.Query} query The query that should not match. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + doesNotMatchQuery: function(key, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + this._addCondition(key, "$notInQuery", queryJSON); + return this; + }, + + + /** + * Add a constraint that requires that a key's value matches a value in + * an object returned by a different Parse.Query. + * @param {String} key The key that contains the value that is being + * matched. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + matchesKeyInQuery: function(key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + this._addCondition(key, "$select", + { key: queryKey, query: queryJSON }); + return this; + }, + + /** + * Add a constraint that requires that a key's value not match a value in + * an object returned by a different Parse.Query. + * @param {String} key The key that contains the value that is being + * excluded. + * @param {String} queryKey The key in the objects returned by the query to + * match against. + * @param {Parse.Query} query The query to run. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + doesNotMatchKeyInQuery: function(key, queryKey, query) { + var queryJSON = query.toJSON(); + queryJSON.className = query.className; + this._addCondition(key, "$dontSelect", + { key: queryKey, query: queryJSON }); + return this; + }, + + /** + * Add constraint that at least one of the passed in queries matches. + * @param {Array} queries + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + _orQuery: function(queries) { + var queryJSON = _.map(queries, function(q) { + return q.toJSON().where; + }); + + this._where.$or = queryJSON; + return this; + }, + + /** + * Converts a string into a regex that matches it. + * Surrounding with \Q .. \E does this, we just need to escape \E's in + * the text separately. + */ + _quote: function(s) { + return "\\Q" + s.replace("\\E", "\\E\\\\E\\Q") + "\\E"; + }, + + /** + * Add a constraint for finding string values that contain a provided + * string. This may be slow for large datasets. + * @param {String} key The key that the string to match is stored in. + * @param {String} substring The substring that the value must contain. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + contains: function(key, value) { + this._addCondition(key, "$regex", this._quote(value)); + return this; + }, + + /** + * Add a constraint for finding string values that start with a provided + * string. This query will use the backend index, so it will be fast even + * for large datasets. + * @param {String} key The key that the string to match is stored in. + * @param {String} prefix The substring that the value must start with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + startsWith: function(key, value) { + this._addCondition(key, "$regex", "^" + this._quote(value)); + return this; + }, + + /** + * Add a constraint for finding string values that end with a provided + * string. This will be slow for large datasets. + * @param {String} key The key that the string to match is stored in. + * @param {String} suffix The substring that the value must end with. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + endsWith: function(key, value) { + this._addCondition(key, "$regex", this._quote(value) + "$"); + return this; + }, + + /** + * Sorts the results in ascending order by the given key. + * + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + ascending: function() { + this._order = []; + return this.addAscending.apply(this, arguments); + }, + + /** + * Sorts the results in ascending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + addAscending: function(key) { + var self = this; + if (!this._order) { + this._order = []; + } + Parse._arrayEach(arguments, function(key) { + if (Array.isArray(key)) { + key = key.join(); + } + self._order = self._order.concat(key.replace(/\s/g, "").split(",")); + }); + return this; + }, + + /** + * Sorts the results in descending order by the given key. + * + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + descending: function(key) { + this._order = []; + return this.addDescending.apply(this, arguments); + }, + + /** + * Sorts the results in descending order by the given key, + * but can also add secondary sort descriptors without overwriting _order. + * + * @param {(String|String[]|...String} key The key to order by, which is a + * string of comma separated values, or an Array of keys, or multiple keys. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + addDescending: function(key) { + var self = this; + if (!this._order) { + this._order = []; + } + Parse._arrayEach(arguments, function(key) { + if (Array.isArray(key)) { + key = key.join(); + } + self._order = self._order.concat( + _.map(key.replace(/\s/g, "").split(","), + function(k) { return "-" + k; })); + }); + return this; + }, + + /** + * Add a proximity based constraint for finding objects with key point + * values near the point given. + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + near: function(key, point) { + if (!(point instanceof Parse.GeoPoint)) { + // Try to cast it to a GeoPoint, so that near("loc", [20,30]) works. + point = new Parse.GeoPoint(point); + } + this._addCondition(key, "$nearSphere", point); + return this; + }, + + /** + * Add a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in radians) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + withinRadians: function(key, point, distance) { + this.near(key, point); + this._addCondition(key, "$maxDistance", distance); + return this; + }, + + /** + * Add a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 3958.8 miles. + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in miles) of results to + * return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + withinMiles: function(key, point, distance) { + return this.withinRadians(key, point, distance / 3958.8); + }, + + /** + * Add a proximity based constraint for finding objects with key point + * values near the point given and within the maximum distance given. + * Radius of earth used is 6371.0 kilometers. + * @param {String} key The key that the Parse.GeoPoint is stored in. + * @param {Parse.GeoPoint} point The reference Parse.GeoPoint that is used. + * @param {Number} maxDistance Maximum distance (in kilometers) of results + * to return. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + withinKilometers: function(key, point, distance) { + return this.withinRadians(key, point, distance / 6371.0); + }, + + /** + * Add a constraint to the query that requires a particular key's + * coordinates be contained within a given rectangular geographic bounding + * box. + * @param {String} key The key to be constrained. + * @param {Parse.GeoPoint} southwest + * The lower-left inclusive corner of the box. + * @param {Parse.GeoPoint} northeast + * The upper-right inclusive corner of the box. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + withinGeoBox: function(key, southwest, northeast) { + if (!(southwest instanceof Parse.GeoPoint)) { + southwest = new Parse.GeoPoint(southwest); + } + if (!(northeast instanceof Parse.GeoPoint)) { + northeast = new Parse.GeoPoint(northeast); + } + this._addCondition(key, '$within', { '$box': [southwest, northeast] }); + return this; + }, + + /** + * Include nested Parse.Objects for the provided key. You can use dot + * notation to specify which fields in the included object are also fetched. + * @param {String} key The name of the key to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + include: function() { + var self = this; + Parse._arrayEach(arguments, function(key) { + if (_.isArray(key)) { + self._include = self._include.concat(key); + } else { + self._include.push(key); + } + }); + return this; + }, + + /** + * Restrict the fields of the returned Parse.Objects to include only the + * provided keys. If this is called multiple times, then all of the keys + * specified in each of the calls will be included. + * @param {Array} keys The names of the keys to include. + * @return {Parse.Query} Returns the query, so you can chain this call. + */ + select: function() { + var self = this; + this._select = this._select || []; + Parse._arrayEach(arguments, function(key) { + if (_.isArray(key)) { + self._select = self._select.concat(key); + } else { + self._select.push(key); + } + }); + return this; + }, + + /** + * Iterates over each result of a query, calling a callback for each one. If + * the callback returns a promise, the iteration will not continue until + * that promise has been fulfilled. If the callback returns a rejected + * promise, then iteration will stop with that error. The items are + * processed in an unspecified order. The query may not have any sort order, + * and may not use limit or skip. + * @param {Function} callback Callback that will be called with each result + * of the query. + * @param {Object} options An optional Backbone-like options object with + * success and error callbacks that will be invoked once the iteration + * has finished. + * @return {Parse.Promise} A promise that will be fulfilled once the + * iteration has completed. + */ + each: function(callback, options) { + options = options || {}; + + if (this._order || this._skip || (this._limit >= 0)) { + var error = + "Cannot iterate on a query with sort, skip, or limit."; + return Parse.Promise.error(error)._thenRunCallbacks(options); + } + + var promise = new Parse.Promise(); + + var query = new Parse.Query(this.objectClass); + // We can override the batch size from the options. + // This is undocumented, but useful for testing. + query._limit = options.batchSize || 100; + query._where = _.clone(this._where); + query._include = _.clone(this._include); + if (this._select) { + query._select = _.clone(this._select); + } + + query.ascending('objectId'); + + var findOptions = {}; + if (_.has(options, "useMasterKey")) { + findOptions.useMasterKey = options.useMasterKey; + } + if (_.has(options, 'sessionToken')) { + findOptions.sessionToken = options.sessionToken; + } + + var finished = false; + return Parse.Promise._continueWhile(function() { + return !finished; + + }, function() { + return query.find(findOptions).then(function(results) { + var callbacksDone = Parse.Promise.as(); + Parse._.each(results, function(result) { + callbacksDone = callbacksDone.then(function() { + return callback(result); + }); + }); + + return callbacksDone.then(function() { + if (results.length >= query._limit) { + query.greaterThan("objectId", results[results.length - 1].id); + } else { + finished = true; + } + }); + }); + })._thenRunCallbacks(options); + } + }; + +}(this)); + +/*global FB: false , console: false*/ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + var PUBLIC_KEY = "*"; + + var initialized = false; + var requestedPermissions; + var initOptions; + var provider = { + authenticate: function(options) { + var self = this; + FB.login(function(response) { + if (response.authResponse) { + if (options.success) { + options.success(self, { + id: response.authResponse.userID, + access_token: response.authResponse.accessToken, + expiration_date: new Date(response.authResponse.expiresIn * 1000 + + (new Date()).getTime()).toJSON() + }); + } + } else { + if (options.error) { + options.error(self, response); + } + } + }, { + scope: requestedPermissions + }); + }, + restoreAuthentication: function(authData) { + if (authData) { + var authResponse = { + userID: authData.id, + accessToken: authData.access_token, + expiresIn: (Parse._parseDate(authData.expiration_date).getTime() - + (new Date()).getTime()) / 1000 + }; + var newOptions = _.clone(initOptions); + newOptions.authResponse = authResponse; + + // Suppress checks for login status from the browser. + newOptions.status = false; + + // If the user doesn't match the one known by the FB SDK, log out. + // Most of the time, the users will match -- it's only in cases where + // the FB SDK knows of a different user than the one being restored + // from a Parse User that logged in with username/password. + var existingResponse = FB.getAuthResponse(); + if (existingResponse && + existingResponse.userID !== authResponse.userID) { + FB.logout(); + } + + FB.init(newOptions); + } + return true; + }, + getAuthType: function() { + return "facebook"; + }, + deauthenticate: function() { + this.restoreAuthentication(null); + } + }; + + /** + * Provides a set of utilities for using Parse with Facebook. + * @namespace + * Provides a set of utilities for using Parse with Facebook. + */ + Parse.FacebookUtils = { + /** + * Initializes Parse Facebook integration. Call this function after you + * have loaded the Facebook Javascript SDK with the same parameters + * as you would pass to + * + * FB.init(). Parse.FacebookUtils will invoke FB.init() for you + * with these arguments. + * + * @param {Object} options Facebook options argument as described here: + * + * FB.init(). The status flag will be coerced to 'false' because it + * interferes with Parse Facebook integration. Call FB.getLoginStatus() + * explicitly if this behavior is required by your application. + */ + init: function(options) { + if (typeof(FB) === 'undefined') { + throw "The Facebook JavaScript SDK must be loaded before calling init."; + } + initOptions = _.clone(options) || {}; + if (initOptions.status && typeof(console) !== "undefined") { + var warn = console.warn || console.log || function() {}; + warn.call(console, "The 'status' flag passed into" + + " FB.init, when set to true, can interfere with Parse Facebook" + + " integration, so it has been suppressed. Please call" + + " FB.getLoginStatus() explicitly if you require this behavior."); + } + initOptions.status = false; + FB.init(initOptions); + Parse.User._registerAuthenticationProvider(provider); + initialized = true; + }, + + /** + * Gets whether the user has their account linked to Facebook. + * + * @param {Parse.User} user User to check for a facebook link. + * The user must be logged in on this device. + * @return {Boolean} true if the user has their account + * linked to Facebook. + */ + isLinked: function(user) { + return user._isLinked("facebook"); + }, + + /** + * Logs in a user using Facebook. This method delegates to the Facebook + * SDK to authenticate the user, and then automatically logs in (or + * creates, in the case where it is a new user) a Parse.User. + * + * @param {String, Object} permissions The permissions required for Facebook + * log in. This is a comma-separated string of permissions. + * Alternatively, supply a Facebook authData object as described in our + * REST API docs if you want to handle getting facebook auth tokens + * yourself. + * @param {Object} options Standard options object with success and error + * callbacks. + */ + logIn: function(permissions, options) { + if (!permissions || _.isString(permissions)) { + if (!initialized) { + throw "You must initialize FacebookUtils before calling logIn."; + } + requestedPermissions = permissions; + return Parse.User._logInWith("facebook", options); + } else { + var newOptions = _.clone(options) || {}; + newOptions.authData = permissions; + return Parse.User._logInWith("facebook", newOptions); + } + }, + + /** + * Links Facebook to an existing PFUser. This method delegates to the + * Facebook SDK to authenticate the user, and then automatically links + * the account to the Parse.User. + * + * @param {Parse.User} user User to link to Facebook. This must be the + * current user. + * @param {String, Object} permissions The permissions required for Facebook + * log in. This is a comma-separated string of permissions. + * Alternatively, supply a Facebook authData object as described in our + * REST API docs if you want to handle getting facebook auth tokens + * yourself. + * @param {Object} options Standard options object with success and error + * callbacks. + */ + link: function(user, permissions, options) { + if (!permissions || _.isString(permissions)) { + if (!initialized) { + throw "You must initialize FacebookUtils before calling link."; + } + requestedPermissions = permissions; + return user._linkWith("facebook", options); + } else { + var newOptions = _.clone(options) || {}; + newOptions.authData = permissions; + return user._linkWith("facebook", newOptions); + } + }, + + /** + * Unlinks the Parse.User from a Facebook account. + * + * @param {Parse.User} user User to unlink from Facebook. This must be the + * current user. + * @param {Object} options Standard options object with success and error + * callbacks. + */ + unlink: function(user, options) { + if (!initialized) { + throw "You must initialize FacebookUtils before calling unlink."; + } + return user._unlinkFrom("facebook", options); + } + }; + +}(this)); + +/*global _: false, document: false, window: false, navigator: false */ +(function(root) { + root.Parse = root.Parse || {}; + var Parse = root.Parse; + var _ = Parse._; + + /** + * History serves as a global router (per frame) to handle hashchange + * events or pushState, match the appropriate route, and trigger + * callbacks. You shouldn't ever have to create one of these yourself + * — you should use the reference to Parse.history + * that will be created for you automatically if you make use of + * Routers with routes. + * @class + * + *

A fork of Backbone.History, provided for your convenience. If you + * use this class, you must also include jQuery, or another library + * that provides a jQuery-compatible $ function. For more information, + * see the + * Backbone documentation.

+ *

Available in the client SDK only.

+ */ + Parse.History = function() { + this.handlers = []; + _.bindAll(this, 'checkUrl'); + }; + + // Cached regex for cleaning leading hashes and slashes . + var routeStripper = /^[#\/]/; + + // Cached regex for detecting MSIE. + var isExplorer = /msie [\w.]+/; + + // Has the history handling already been started? + Parse.History.started = false; + + // Set up all inheritable **Parse.History** properties and methods. + _.extend(Parse.History.prototype, Parse.Events, + /** @lends Parse.History.prototype */ { + + // The default interval to poll for hash changes, if necessary, is + // twenty times a second. + interval: 50, + + // Gets the true hash value. Cannot use location.hash directly due to bug + // in Firefox where location.hash will always be decoded. + getHash: function(windowOverride) { + var loc = windowOverride ? windowOverride.location : window.location; + var match = loc.href.match(/#(.*)$/); + return match ? match[1] : ''; + }, + + // Get the cross-browser normalized URL fragment, either from the URL, + // the hash, or the override. + getFragment: function(fragment, forcePushState) { + if (Parse._isNullOrUndefined(fragment)) { + if (this._hasPushState || forcePushState) { + fragment = window.location.pathname; + var search = window.location.search; + if (search) { + fragment += search; + } + } else { + fragment = this.getHash(); + } + } + if (!fragment.indexOf(this.options.root)) { + fragment = fragment.substr(this.options.root.length); + } + return fragment.replace(routeStripper, ''); + }, + + /** + * Start the hash change handling, returning `true` if the current + * URL matches an existing route, and `false` otherwise. + */ + start: function(options) { + if (Parse.History.started) { + throw new Error("Parse.history has already been started"); + } + Parse.History.started = true; + + // Figure out the initial configuration. Do we need an iframe? + // Is pushState desired ... is it available? + this.options = _.extend({}, {root: '/'}, this.options, options); + this._wantsHashChange = this.options.hashChange !== false; + this._wantsPushState = !!this.options.pushState; + this._hasPushState = !!(this.options.pushState && + window.history && + window.history.pushState); + var fragment = this.getFragment(); + var docMode = document.documentMode; + var oldIE = (isExplorer.exec(navigator.userAgent.toLowerCase()) && + (!docMode || docMode <= 7)); + + if (oldIE) { + this.iframe = Parse.$('