diff --git a/bower.json b/bower.json index ce14364..422810f 100644 --- a/bower.json +++ b/bower.json @@ -1,7 +1,7 @@ { "name": "firebase-util", "description": "A set of experimental power tools for Firebase.", - "version": "0.0.0", + "version": "0.2.6", "authors": [ "Firebase (https://firebase.google.com/)" ], diff --git a/dist/firebase-util-normalize.min.js b/dist/firebase-util-normalize.min.js new file mode 100644 index 0000000..051f077 --- /dev/null +++ b/dist/firebase-util-normalize.min.js @@ -0,0 +1,10 @@ +/*! + * Firebase-util: A set of experimental power tools for Firebase. + * + * Version: 0.2.6 + * URL: https://github.com/firebase/firebase-util + * Date: 2016-11-21T15:08:33.470Z + * License: MIT http://firebase.mit-license.org/ + */ +require=function t(e,r,n){function i(a,o){if(!r[a]){if(!e[a]){var h="function"==typeof require&&require;if(!o&&h)return h(a,!0);if(s)return s(a,!0);var u=new Error("Cannot find module '"+a+"'");throw u.code="MODULE_NOT_FOUND",u}var c=r[a]={exports:{}};e[a][0].call(c.exports,function(t){var r=e[a][1][t];return i(r?r:t)},c,c.exports,t,e,r,n)}return r[a].exports}for(var s="function"==typeof require&&require,a=0;a=0}function a(t,e){if(t instanceof s)return u.pick(t,["path","id","key","alias","pathName","url"]);"string"==typeof t&&(t={key:t});var r=t.key.split("."),n=e.getPath(r[0]);return{pathName:r[0],id:r[1],key:t.key,alias:t.alias||r[1],path:n,url:n?n.url()+"/"+r[1]:null}}function o(t,e,r){if(null!==r){if(e.indexOf(".")>0){for(var n,i=e.split(".").reverse();i.length>1&&(n=i.pop());)t=t[n]=u.has(t,n)?t[n]:{};e=i.pop()}t[e]=r}}function h(t,e){var r=e;if(!u.isObject(t))return u.undef;var n=t[e];if(e.indexOf(".")>0){var i=e.split(".").reverse();for(n=t;i.length;)r=i.pop(),n=u.isObject(n)&&n.hasOwnProperty(r)?n[r]:u.undef}return n}var u=t("../../common"),c=t("./PathManager");n.prototype={add:function(t){var e=new s(a(t,this.pathMgr));if(this.fields.hasOwnProperty(e.alias))throw new Error("Duplicate field alias "+e.alias+"("+e.key+")");if(null===e.path)throw new Error("Invalid path specified for field "+e.key+"; it was not in the paths provided, which are : "+this.pathMgr.getPathNames().join(","));var r=e.path.getDependency();if(null!==r&&null===i(this.fields,r))throw new Error("Dynamic paths must reference a field declared in the map. Please add "+n.key(r.path,r.field)+" to the select() criteria before using it in a dynamic field");this.fields[e.alias]=e,this.length++},forEach:function(t,e){return u.find(this.fields,t,e)!==u.undef},getField:function(t){return this.fields[t]||null},getPath:function(t){return this.getPathManager().getPath(t)},getPathManager:function(){return this.pathMgr},pathFor:function(t){var e=this.getField(t);return e?e.path:this.pathMgr.first()},fieldsFor:function(t){return u.filter(u.toArray(this.fields),function(e){return e.pathName===t})},aliasFor:function(t){var e=u.find(this.fields,function(e){return e.url===t},this);return e?e.alias:null},extractData:function(t,e){var r={},n=this.pathMgr.getPathName(t.ref().toString());if(null===n&&null!==t.ref().parent()){var i=this.pathMgr.getPathFor(t.ref().parent().toString());i&&i.hasDependency()&&(n=i.name())}var s=e?"exportVal":"val";return u.each(this.fieldsFor(n),function(e){switch(e.id){case"$key":o(r,e.alias,t.key());break;case"$value":o(r,e.alias,t[s]());break;default:t.hasChild(e.id)&&o(r,e.alias,t.child(e.id)[s]())}}),r},snapFor:function(t,e){var r,n=this.pathFor(e);if(!n)return null;var s=n.getDependency();if(null!==s){var a=i(this.fields,s),o=this.snapFor(t,a.alias);o&&(r="$key"===s.field?n.child(o.key()).url():"$value"===s.field?n.child(o.val()).url():n.child(o.child(s.field).val()).url())}else r=n.url();return r?u.find(t,function(t){return t.ref().toString()===r})||null:null},denest:function(t){var e={};return u.each(this.getPathManager().getPaths(),function(t){e[t.name()]={path:t,data:{}}}),this.forEach(function(r){var n=h(t,r.alias);if(n!==u.undef)switch(r.id){case"$value":e[r.pathName].data=n;break;case"$key":break;default:e[r.pathName].data[r.id]=n}}),e},idFor:function(t){var e=this.getField(t);return e?e.id:t}},n.key=function(t,e){return"string"!=typeof t&&(t=t.name()),t+"."+e},n.fieldMap=function(t,e){var r,i=t.getField(e);i?(r=i.path,"$value"!==i.id&&(r=r.child(i.id))):r=t.pathFor(e).child(e);var s=new c([r]),a=new n(s);return a.add({key:n.key(r,"$value"),alias:e}),a},n.recordMap=function(t,e){var r=t.getPathManager(),i=u.map(r.getPaths(),function(t){return t.normChild(e)}),s=new n(new c(i));return t.forEach(function(t){s.add({key:t.key,alias:t.alias})}),s},e.exports=n},{"../../common":19,"./PathManager":10}],5:[function(t,e,r){"use strict";function n(){this.criteria=[],s.each(arguments,this.add,this)}function i(t){this.match=t}var s=t("../../common");n.prototype={add:function(t){this.criteria.push(new i(t))},test:function(t,e,r){return s.contains(this.criteria,function(n){return!n.test(t,e,r)})===!1}},i.prototype.test=function(t,e,r){return this.match(t,e,r)===!0},e.exports=n},{"../../common":19}],6:[function(t,e,r){"use strict";function n(t){i(arguments),this.pathMgr=new u(h.toArray(arguments)),this.map=new l(this.pathMgr),this.filters=new c,this.finalized=!1}function i(t){function e(t){return h.isArray(t)&&(t=t[0]),!h.isFirebaseRef(t)}if(t.length<1)throw new Error("Must provide at least one path definition");if(h.contains(t,e))throw new Error("Each argument to the NormalizedCollection constructor must be a valid Firebase reference or an Array containing a Firebase ref as the first argument")}function s(t,e){if(t.finalized)throw new Error("Cannot call "+e+"() after ref() has been invoked")}function a(t){var e=[],r=[],n="";return h.each(t.pathMgr.getPaths(),function(t){var r=t.getDependency();e.push(h.printf('\t"%s%s"%s',t.url(),t.id()===t.name()?"":" as "+t.name(),r?"-> "+r.path+"."+r.field:""))}),t.map.forEach(function(t){r.push(h.printf('"%s%s"',t.key,t.alias===t.id?"":" as "+t.alias)),r.length%5===0&&r.push("\n")}),t.filters.criteria.length>0&&(n=h.printf("<%s filters applied>",t.filters.criteria.length)),h.printf("NormalizedCollection(\n%s\n).select(%s)%s.ref()",e.join("\n"),r.join(", "),n)}function o(t){var e;if(e="string"==typeof t?t:h.has(t,"key")?t.key:h.undef,"string"!=typeof e||e.indexOf(".")<=0)throw new Error('Each field passed to NormalizedCollection.select() must either be a string in the format "pathAlias.fieldId", or an object in the format {key: "pathAlias.fieldId", alias: "any_name_for_field"}, but I received '+JSON.stringify(t))}var h=t("../../common"),u=t("./PathManager"),c=t("./Filter"),l=t("./FieldMap"),f=t("./NormalizedRef"),d=t("./RecordSet");n.prototype={select:function(t){s(this,"select");var e=h.args("NormalizedCollection.select",arguments,1);return h.each(e.restAsList(0,["string","object"]),function(t){o(t),this.map.add(t)},this),this},filter:function(t){s(this,"filter");var e=h.args("NormalizedCollection.filter",arguments,1,1);return this.filters.add(e.nextReq("function")),this},ref:function(){if(!this.map.length)throw new Error("Must call select() with at least one field before creating a ref");this.finalized=!0,h.log.isInfoEnabled()&&h.log.info("NormalizedRef created using %s",a(this));var t=new d(this.map,this.filters);return new f(t)}},e.exports=n},{"../../common":19,"./FieldMap":4,"./Filter":5,"./NormalizedRef":7,"./PathManager":10,"./RecordSet":14}],7:[function(t,e,r){"use strict";function n(t,e){this._super(this,t),this._parent=e||null,this._key=t.getName(),this._toString=t.getUrl()}function i(t){return function(){var e=o.toArray(arguments);o.each(this.$getPaths(),function(r){var n=r.ref();n[t].apply(n,e)})}}function s(t){return function(){var e=o.toArray(arguments),r=this.$getMaster();return r[t].apply(r,e)}}function a(t){return function(){throw new Error(t+" is not supported for NormalizedCollection references. Try calling it on the original reference used to create the NormalizedCollection instead.")}}var o=t("../../common"),h=t("./Query");o.inherits(n,h,{child:function(t){for(var e=t.split("/").reverse(),r=this,i=this;e.length;){var s=e.pop();i=new n(i.$getRecord().makeChild(s),r),r=i}return i},parent:function(){return this._parent},root:function(){for(var t=this;null!==t.parent();)t=t.parent();return t},name:function(){return console.warn("The name() function has been deprecated. Use key() instead."),this.key()},key:function(){return this._key},toString:function(){return this._toString},set:function(t,e,r){this.$getRecord().saveData(t,{callback:e,context:r,isUpdate:!1})},update:function(t,e,r){this.$getRecord().saveData(t,{callback:e,context:r,isUpdate:!0})},remove:function(t,e){this.$getRecord().saveData(null,{callback:t,context:e,isUpdate:!1})},push:function(t,e,r){var n=this.$getMaster().push().key(),i=this.child(n);return arguments.length&&i.set.apply(i,arguments),i},setWithPriority:function(t,e,r,n){this.$getRecord().saveData(t,{callback:r,context:n,isUpdate:!1,priority:e})},setPriority:function(t,e,r){this.$getMaster().setPriority(t,e,r)},auth:s("auth"),unauth:s("unauth"),authWithCustomToken:s("authWithCustomToken"),authAnonymously:s("authAnonymously"),authWithPassword:s("authWithPassword"),authWithOAuthPopup:s("authWithOAuthPopup"),authWithOAuthRedirect:s("authWithOAuthRedirect"),authWithOAuthToken:s("authWithOAuthToken"),getAuth:s("getAuth"),onAuth:s("onAuth"),offAuth:s("offAuth"),createUser:s("createUser"),changePassword:s("changePassword"),removeUser:s("removeUser"),resetPassword:s("resetPassword"),changeEmail:s("changeEmail"),goOffline:i("goOffline"),goOnline:i("goOnline"),transaction:a("transaction"),onDisconnect:a("onDisconnect")}),e.exports=n},{"../../common":19,"./Query":11}],8:[function(t,e,r){"use strict";function n(t,e){if(this._ref=t,this._rec=t.$getRecord(),!i.isArray(e))throw new Error("Must provide an array of snapshots to merge");this._pri=this._rec.getPriority(e),this._snaps=e}var i=t("../../common");n.prototype={val:function(){return this._snaps.length?this._rec.mergeData(this._snaps,!1):null},child:function(t){var e,r=t.split("/").reverse(),i=r.pop();for(e=new n(this._ref.child(i),this._rec.getChildSnaps(this._snaps,i));r.length;)e=e.child(r.pop());return e},forEach:function(t,e){return this._rec.forEachKey(this._snaps,function(r,n){return"$value"!==r&&"$key"!==r&&t.call(e,this.child(n))},this)},hasChild:function(t){for(var e=t.split("/").reverse(),r=e.length>0,n=this._ref,i=this;r&&e.length;){var s=e.pop();r=n.$getRecord().hasChild(i._snaps,s),r&&e.length&&(n=n.child(s),i=i.child(s))}return r},hasChildren:function(){return this._rec.forEachKey(this._snaps,function(t){return"$key"!==t&&"$value"!==t})},name:function(){return console.warn("name() has been deprecated. Use key() instead."),this.key()},key:function(){return this._rec.getName()},numChildren:function(){var t=0;return this._rec.forEachKey(this._snaps,function(e){"$key"!==e&&"$value"!==e&&t++}),t},ref:function(){return this._ref.ref()},getPriority:function(){return this._pri},exportVal:function(){return this._rec.mergeData(this._snaps,!0)},exists:function(){return null!==this.val()}},e.exports=n},{"../../common":19}],9:[function(t,e,r){"use strict";function n(t,e){var r=i(t);this._ref=r.ref,this._alias=r.alias,this._dep=r.dep,this._parent=e||null}function i(t){var e,r,n=null;return a.isArray(t)?(e=t[0],r=t[1],n=t[2]):e=a.isFunction(t.ref)?t.ref():t,{ref:e,alias:r||e.key(),dep:s(n)}}function s(t){if(a.isObject(t))return t;if(t){var e=t.split(".");return{path:e[0],field:e[1]}}return null}var a=t("../../common");n.prototype={ref:function(){return this._ref},reff:function(){return this.ref().ref()},child:function(t){return new n(this.reff().child(t),this)},normChild:function(t){var e=this.getDependency();return null!==e?new n([this.reff(),this.name(),e.path+"."+e.field],this):new n([this.reff().child(t),this.name()],this)},hasDependency:function(){return null!==this._dep},getDependency:function(){return this._dep},url:function(){return this.reff().toString()},name:function(){return this._alias},id:function(){return this.reff().key()},parent:function(){return this._parent},clone:function(){return new n([this._ref,this._alias,this._dep],this._parent)}},e.exports=n},{"../../common":19}],10:[function(t,e,r){"use strict";function n(t){this.paths=[],this.pathsByUrl={},this.deps={},this.pathNames=[],a.each(t,this.add,this)}function i(t,e){return a.map(t,function(t){return e[t].path+"."+e[t].field}).join(" >> ")}var s=t("./Path"),a=t("../../common");n.prototype={add:function(t){var e=t instanceof s?t.clone():new s(t);if(!this.paths.length&&e.hasDependency())throw new Error("The master path (i.e. the first) may not declare a dependency. Perhaps you have put the wrong path first in the list?");if(a.has(this.pathsByUrl,e.url()))throw new Error("Duplicate path: "+e.url());if(a.contains(this.pathNames,e.name()))throw new Error("Duplicate path name. The .key() value for each path must be unique, or you can give each a path an alias by using [firebaseRef, alias] in the constructor. The aliases must also be unique.");this._map(e),this.paths.push(e),this.pathsByUrl[e.url()]=e.name(),this.pathNames.push(e.name())},count:function(){return this.paths.length},first:function(){return this.paths[0]},getPath:function(t){return a.find(this.paths,function(e){return e.name()===t})||null},getPathFor:function(t){var e=this.getPathName(t);return e?this.getPath(e):null},getPaths:function(){return this.paths.slice()},getPathName:function(t){return this.pathsByUrl[t]||null},getPathNames:function(){return this.pathNames.slice()},getUrls:function(){return a.keys(this.pathsByUrl)},getDependencyGraph:function(){return a.extend(!0,this.deps)},_map:function(t){var e=this.first(),r=t.getDependency();!r&&e&&(r={path:e.name(),field:"$key"}),r&&(this.deps[t.name()]=r,this._assertNotCircularDep(t.name()))},_assertNotCircularDep:function(t){for(var e=[t],r=this.deps[t];a.isDefined(r);){var n=r.path;if(a.contains(e,n))throw e.push(n),new Error("Circular dependencies in paths: "+i(e,this.deps));e.push(n),r=a.val(this.deps,n)}}},e.exports=n},{"../../common":19,"./Path":9}],11:[function(t,e,r){"use strict";function n(t,e){var r=this;r._ref=t,r._rec=e,e&&e.setRef(r)}var i=t("../../common"),s=t("./Transmogrifier");n.prototype={on:function(t,e,r,n){function s(t){"function"==typeof r&&null!==t&&r.call(n,t)}return 3===arguments.length&&i.isObject(r)&&(n=r,r=i.undef),this.$getRecord().watch(t,e,s,n),e},once:function(t,e,r,n){function s(r){o.off(t,s),e.call(n,r)}function a(t){"function"==typeof r&&null!==t&&r.call(n,t)}var o=this;return 3===arguments.length&&i.isObject(r)&&(n=r,r=i.undef),this.on(t,s,a)},off:function(t,e,r){this.$getRecord().unwatch(t,e,r)},orderByChild:function(){return this.$replicate("orderByChild",i.toArray(arguments))},orderByKey:function(){return this.$replicate("orderByKey",i.toArray(arguments))},orderByValue:function(){return this.$replicate("orderByValue",i.toArray(arguments))},orderByPriority:function(){return this.$replicate("orderByPriority",i.toArray(arguments))},limitToFirst:function(){return this.$replicate("limitToFirst",i.toArray(arguments))},limitToLast:function(){return this.$replicate("limitToLast",i.toArray(arguments))},limit:function(){return this.$replicate("limit",i.toArray(arguments))},startAt:function(){return this.$replicate("startAt",i.toArray(arguments))},endAt:function(){return this.$replicate("endAt",i.toArray(arguments))},equalTo:function(){return this.$replicate("equalTo",i.toArray(arguments))},ref:function(){return this._ref},$getRecord:function(){return this._rec},$getMaster:function(){return this._rec.getPathManager().first().ref()},$getPaths:function(){return this._rec.getPathManager().getPaths()},$replicate:function(t,e){var r=this.$getRecord(),i=this.$getMaster();return i=i[t].apply(i,e),new n(this._ref,s.replicate(r,i))}},i.registerFirebaseWrapper(n),e.exports=n},{"../../common":19,"./Transmogrifier":17}],12:[function(t,e,r){"use strict";function n(t){var e=t.getPathManager().first().id(),r=f.mergeToString(t.getPathManager().getUrls());this._super(t,e,r),this._eventManagers={},f.log.debug("Record created",this.getName(),this.getUrl())}function i(t){this.rec=t,this.pm=t.getPathManager(),this.running=!1,this._init()}function s(t,e){this.event=t,this.rec=e,this.map=e.getFieldMap(),this.pm=e.getPathManager(),this.subs=[],this.dyno=null}function a(t,e,r,n){var i=t.getDependency(),s=e.getPath(i.path),a=s.ref();if("$key"===i.field)throw new Error("Dynamic paths do not support $key (you should probably just join on this path)");"$value"!==i.field&&(a=a.child(i.field));var o,h=a.on("value",function(e){o&&o.key()!==e.val()&&(f.log.debug("Record.Dyno: stopped monitoring %s",o.toString()),o.off(r,n),n(null)),null!==e.val()&&(o=t.ref().child(e.val()),o.on(r,n),f.log("Record.Dyno: monitoring %s",o.toString()))});this.dispose=function(){a.off("value",h),o&&o.off(r,n)}}function o(t,e,r){f.each(t.fieldsFor(e.name()),function(t){switch(t.id){case"$key":break;case"$value":f.has(r,".value")||(r[".value"]=null);break;default:f.has(r,t.id)||(r[t.id]=null)}})}var h=t("./FieldMap"),u=t("./RecordField"),c=t("./AbstractRecord"),l=t("./SnapshotFactory"),f=t("../../common");f.inherits(n,c,{makeChild:function(t){var e=h.fieldMap(this.getFieldMap(),t);return new u(e)},hasChild:function(t,e){var r=this.getFieldMap().getField(e);if(!r)return!1;var n=this.getFieldMap().snapFor(t,e);return null!==n&&n.hasChild(e)},getChildSnaps:function(t,e){var r,n=this.getFieldMap().snapFor(t,e),i=this.getFieldMap().getField(e);if(i)switch(i.id){case"$key":throw new Error("Cannot get child snapshot from key (not a real child element)");case"$value":r=n;break;default:r=n.child(i.id)}else r=n.child(e);return[r]},forEachKey:function(t,e,r){function n(t,e){switch(e){case"$key":return!0;case"$value":return t&&null!==t.val();default:return t&&t.hasChild(e)}}var i=this.getFieldMap();return i.forEach(function(s){var a=i.snapFor(t,s.alias);return!!n(a,s.id)&&e.call(r,s.id,s.alias)===!0})},mergeData:function(t,e){var r=null,n=this.getFieldMap();return t.length>0&&null!==t[0].val()&&(r=f.extend.apply(null,f.map(t,function(t){return n.extractData(t,e)})),e&&null!==r&&null!==t[0].getPriority()&&(f.isObject(r)||(r={".value":r}),r[".priority"]=t[0].getPriority())),r},getPriority:function(t){return t[0].getPriority()},getClass:function(){return n},saveData:function(t,e){var r=f.queue(),n=this.getFieldMap(),i=this.getPathManager().getPaths();if(e.isUpdate&&!f.isObject(t))throw new Error("First argument to update() command must be an object");if(null===t)f.each(i,function(t){t.hasDependency()||t.reff().remove(r.getHandler())});else if(f.isObject(t)){var s=n.denest(t);f.each(s,function(t){var a=t.path,h=t.data,u=this._writeRef(s,a);null!==u?f.isEmpty(h)&&e.isUpdate||(f.isObject(h)||(h={".value":h}),e.isUpdate||o(n,a,h),f.isDefined(e.priority)&&(h[".priority"]=e.priority),f.has(h,".value")?u.set(h,r.getHandler()):u.update(h,r.getHandler())):f.log.info("No dynamic key found for master",i[0].ref().toString(),"with dynamic path",a.ref().toString())},this)}else{if(1!==i.length)throw new Error("Cannot set multiple paths to a non-object value. Since this is a NormalizedCollection, the data will be split between the paths. But I can't split a primitive value");f.isDefined(e.priority)?i[0].ref().setWithPriority(t,e.priority,r.getHandler()):i[0].ref().set(t,r.getHandler())}r.handler(e.callback||f.noop,e.context)},getName:function(){return this._name},getUrl:function(){return this._url},_start:function(t){f.has(this._eventManagers,t)||(f.log.debug("Record._start: event=%s, url=%s",t,this.getUrl()),this._eventManagers[t]="value"===t?new i(this):new s(t,this)),this._eventManagers[t].start()},_stop:function(t){f.has(this._eventManagers,t)&&(f.log.debug("Record._stop: event=%s, url=%s",t,this.getUrl()),this._eventManagers[t].stop())},_writeRef:function(t,e){var r=e.reff(),n=e.getDependency();if(null!==n){var i=this.getPathManager().getPath(n.path),s=this._depKey(t,i,n.field);r=null===s?null:r.child(s)}return r},_depKey:function(t,e,r){var n,i=t[e.name()].data;switch(r){case"$key":n=e.id();break;case"$value":n=f.has(i,".value")?i[".value"]:f.isEmpty(i)?null:i;break;default:n=f.has(i,r)?i[r]:null}var s=typeof n;if(null!==n&&"string"!==s)throw new Error("Dynamic key values must be a string. Type was "+s+" for "+e.ref().toString()+"->"+r);return n}}),i.prototype={start:function(){this.running||(this.running=!0,f.each(this.pm.getPathNames(),this._startPath,this))},stop:function(){this.running&&(this.running=!1,f.each(this.subs,function(t){t()}),this._init())},update:function(t,e){this.snaps[t]=e,this._checkLoadState(),f.log("Record.ValueEventManager.update: url=%s, loadCompleted=%s",e.ref().toString(),this.loadCompleted),this.loadCompleted&&this.rec.trigger(new l("value",this.rec.getName(),f.toArray(this.snaps)))},_startPath:function(t){var e=this,r=e.pm.getPath(t),n=f.bind(e.update,e,t);if(r.hasDependency()){var i=new a(r,this.rec.getFieldMap(),"value",n);this.subs.push(i.dispose)}else r.ref().on("value",n),e.subs.push(function(){r.ref().off("value",n)})},_checkLoadState:function(){if(!this.loadCompleted){var t=this.snaps,e=this.pm.getPathNames();this.loadCompleted=!f.contains(e,function(e){return!t.hasOwnProperty(e)})}},_init:function(){this.loadCompleted=!1,this.snaps={},this.subs=[]}},s.prototype={start:function(){f.each(this.pm.getPathNames(),function(t){var e=this.event,r=this.pm.getPath(t),n=f.bind(this.update,this);r.hasDependency()?(this.dyno=new a(r,this.map,e,n),this.subs.push(this.dyno.dispose)):(r.ref().on(e,n),this.subs.push(function(){r.ref().off(e,n)}))},this)},stop:function(){f.each(this.subs,function(t){t()}),this.subs=[]},update:function(t,e){null!==t&&null!==this.map.aliasFor(t.ref().toString())&&(f.log("Record.ChildEventManager.update: event=%s, key=%s/%s",this.event,t.ref().parent().key(),t.key()),this.rec.trigger(new l(this.event,t.key(),t,e)))}},e.exports=n},{"../../common":19,"./AbstractRecord":3,"./FieldMap":4,"./RecordField":13,"./SnapshotFactory":16}],13:[function(t,e,r){"use strict";function n(t){if(this.handlers={},this.path=t.getPathManager().first(),this._super(t,this.path.name(),this.path.url()),1!==t.getPathManager().count())throw new Error("RecordField must have exactly one path, but we got "+t.getPathManager().count());if(1!==t.length)throw new Error("RecordField must have exactly one field, but we found "+t.length);u.log.debug("RecordField created",this.getName(),this.getUrl())}function i(t){return t.callback?function(){t.callback.apply(t.context,arguments)}:u.noop}var s=t("./PathManager"),a=t("./FieldMap"),o=t("./AbstractRecord"),h=t("./SnapshotFactory"),u=t("../../common");u.inherits(n,o,{makeChild:function(t){var e=new s([this.path.child(t)]),r=new a(e);return r.add({key:a.key(e.first(),"$value"),alias:t}),new n(r)},hasChild:function(t,e){return t[0].hasChild(e)},getChildSnaps:function(t,e){return[t[0].child(e)]},mergeData:function(t,e){return e?t[0].exportVal():t[0].val()},getPriority:function(t){return t[0].getPriority()},forEachKey:function(t,e,r){var n=t[0];return n.forEach(function(t){e.call(r,t.key(),t.key())})},saveData:function(t,e){var r=this.path.ref();if(e.isUpdate){if(!u.isObject(t))throw new Error("When using update(), the data must be an object.");u.has(e,"priority")&&(t[".priority"]=e.priority),r.update(t,i(e))}else u.has(e,"priority")?r.setWithPriority(t,e.priority,i(e)):r.set(t,i(e))},getClass:function(){return n},_start:function(t){var e=this;this.handlers[t]=function(r,n){e.trigger(new h(t,r.key(),r,n))},this.path.ref().on(t,this.handlers[t],this._cancel,this)},_stop:function(t){this.handlers.hasOwnProperty(t)&&this.path.ref().off(t,this.handlers[t],this)}}),e.exports=n},{"../../common":19,"./AbstractRecord":3,"./FieldMap":4,"./PathManager":10,"./SnapshotFactory":16}],14:[function(t,e,r){"use strict";function n(t,e){var r=a.mergeToString(t.getPathManager().getPathNames()),n=a.mergeToString(t.getPathManager().getUrls());this._super(t,r,n),this.filters=e,this.monitor=new h(this)}var i=t("./Record"),s=t("./AbstractRecord"),a=t("../../common"),o=t("./FieldMap"),h=t("./RecordSetEventManager");a.inherits(n,s,{makeChild:function(t){var e=o.recordMap(this.getFieldMap(),t);return new i(e)},hasChild:function(t,e){return a.contains(t,function(t){return t.key()===e})},getChildSnaps:function(t,e){return a.filter(t,function(t){return t.key()===e})},mergeData:function(t,e){var r=this,n=null;return t.length&&null!==t[0].val()&&(n={},a.each(t,function(t){null!==t.val()&&r.filters.test(t.val(),t.key(),t.getPriority())&&(n[t.key()]=e?t.exportVal():t.val())})),n},getPriority:function(){return null},forEachKey:function(t,e,r){t.forEach(function(t){return e.call(r,t.key(),t.key())})},getClass:function(){return n},saveData:function(t,e){var r=a.queue();if(null===t)a.each(this.getPathManager().getPaths(),function(t){t.ref().remove(r.getHandler())});else{if(!a.isObject(t))throw new Error("Calls to set() or update() on a NormalizedCollection must pass either null or an object value. There is no way to split a primitive value between the paths");a.each(t,function(t,n){if(".value"===n||".priority"===n)throw new Error("Cannot use .priority or .value on the root path of a NormalizedCollection. You probably meant to sort the records anyway (i.e. one level lower).");this.child(n).saveData(t,{isUpdate:e.isUpdate,callback:r.getHandler()})},this),e.priority&&this.getPathManager().first().ref().setPriority(e.priority,r.getHandler())}r.handler(e.callback||a.noop,e.context)},_getChildKey:function(t,e,r){var n=r,i=this.getPathManager().getPathFor(t.ref().toString());if(i.hasDependency()){var s=i.getDependency(),o=this.getFieldMap().getPath(s.path);if(!o)throw new Error("Invalid dependency path. "+t.ref.toString()+" depends on "+s.path+", but that alias does not exist in the paths provided.");var h=a.find(e,function(t){return t.ref().toString()===o.url()});h?(h=h.child(r),"$value"!==s.field&&(h=h.child(s.field)),n=h.val()):n=null}return n},_start:function(){this.monitor.start()},_stop:function(t,e){0===e&&this.monitor.stop()}}),e.exports=n},{"../../common":19,"./AbstractRecord":3,"./FieldMap":4,"./Record":12,"./RecordSetEventManager":15}],15:[function(t,e,r){"use strict";function n(t){var e=t.getPathManager();this.masterRef=e.first().ref(),this.url=this.masterRef.toString(),this.recList=new i(t,this.url),this.running=!1}function i(t,e){this.obs=t,this.url=e,this._reset()}var s=t("./SnapshotFactory"),a=t("../../common");n.prototype={start:function(){return this.running||(a.log("RecordSetEventManager: Loading normalized records from master list %s",this.url),this.running=!0,this.masterRef.on("child_added",this._add,this),this.masterRef.on("child_removed",this._remove,this),this.masterRef.on("child_moved",this._move,this),this.masterRef.once("value",this.recList.masterPathLoaded,this.recList)),this},stop:function(){return this.running&&(a.log("RecordSetEventManager: Stopped monitoring master list %s",this.url),this.running=!1,this.masterRef.off("child_added",this._add,this),this.masterRef.off("child_removed",this._remove,this),this.masterRef.off("child_moved",this._move,this),this.recList.unloaded()),this},_add:function(t,e){this.recList.add(t.key(),e)},_remove:function(t){this.recList.remove(t.key())},_move:function(t,e){this.recList.move(t.key(),e)}},i.prototype={add:function(t,e){a.log.debug("RecordList.add: key=%s, prevChild=%s",t,e);var r=this.obs.child(t),n=a.bind(this._valueUpdated,this,t);this.loading[t]={rec:r,prev:e,fn:n,unwatch:function(){r.unwatch("value",n)}},this.loadComplete||this.initialKeysLeft.push(t),r.watch("value",n)},remove:function(t){a.log.debug("RecordList.remove: key=%s",t);var e=this._dropRecord(t);null!==e&&this._notify("child_removed",t,e)},move:function(t,e){if(a.has(this.recs,t)){var r=a.indexOf(this.recIds,t);this.recIds.splice(r,1),this._putAfter(t,e),this._notify("child_moved",t)}},masterPathLoaded:function(){a.log.debug("RecordList: Initial data has been loaded from master list at %s",this.url),this.masterLoaded=!0,this._checkLoadState()&&this._notifyValue()},unloaded:function(){this._reset()},findKey:function(t){return a.indexOf(this.recIds,t)},_reset:function(){a.each(this.recs,function(t,e){this.remove(e)},this),a.each(this.filtered,function(t,e){this._dropRecord(e)},this),a.each(this.loading,function(t,e){this._dropRecord(e)},this),this.recs={},this.recIds=[],this.snaps={},this.loading={},this.filtered={},this.loadComplete=!1,this.initialKeysLeft=[],this.masterLoaded=!1},_valueUpdated:function(t,e){var r;this.snaps[t]=e,a.has(this.loading,t)?(r=this.loading[t],delete this.loading[t],this._processAdd(e,r)):a.has(this.recs,t)?(r=this.recs[t],this._processChange(e,r)):a.has(this.filtered,t)?null!==e.val()&&this.obs.filters.test(e.val(),t,e.getPriority())&&(r=this.filtered[t],delete this.filtered[t],a.log("RecordList: Unfiltered key %s",t),this._processAdd(e,r)):a.log("RecordList: Orphan key %s ignored. Probably a concurrent edit.",t)},_processAdd:function(t,e){var r=t.key();this.obs.filters.test(t.val(),r,t.getPriority())?(this.recs[r]=e,this._putAfter(r,e.prev),this._notify("child_added",r)):(a.log("RecordList: Filtered key %s",r),this.filtered[r]=e),this._checkLoadState(r)&&this._notifyValue()},_processChange:function(t,e){if(null!==t.val()){var r=t.key();this.obs.filters.test(t.val(),r,t.getPriority())?this._notify("child_changed",r):(delete this.recs[r],this.filtered[r]=e,this._notify("child_removed",r,this.snaps[r]))}},_notify:function(t,e,r){var n;"child_added"!==t&&"child_moved"!==t||(n=this._getPrevChild(e)),a.log("RecordList._notify: event=%s, key=%s, prev=%s",t,e,n);var i=new s(t,e,r||this.snaps[e],n);this.obs.trigger(i),this._notifyValue()},_notifyValue:function(){if(this.loadComplete){a.log.debug("RecordList._notifyValue: snap_keys=%s",a.keys(this.snaps));var t=new s("value",null,a.toArray(this.snaps));this.obs.trigger(t)}},_getPrevChild:function(t){if(!this.recIds.length)return null;var e=this.findKey(t);return e===-1?this.recIds[this.recIds.length-1]:0===e?null:this.recIds[e-1]},_posFor:function(t){var e,r;return null===t?e=0:(r=this.findKey(t),e=r===-1?this.recIds.length:r+1),e},_putAfter:function(t,e){var r=this._posFor(e);this.recIds.splice(r,0,t)},_dropRecord:function(t){var e=null;return this.recs[t]&&(e=this.snaps[t],this.recs[t].unwatch()),this.loading[t]&&this.loading[t].unwatch(),a.has(this.filtered,t)&&this.filtered[t].unwatch(),delete this.loading[t],delete this.snaps[t],delete this.filtered[t],delete this.recs[t],a.remove(this.recIds,t),e},_checkLoadState:function(t){return!(this.loadComplete||(t&&a.remove(this.initialKeysLeft,t),this.initialKeysLeft.length||!this.masterLoaded))&&(this.loadComplete=!0, +!0)}},e.exports=n},{"../../common":19,"./SnapshotFactory":16}],16:[function(t,e,r){"use strict";function n(t,e,r,n){this.event=t,this.key=e,this.snaps=s(r),this.prevChild=n,i(this)}function i(t){switch(t.event){case"value":break;case"child_added":case"child_moved":if("string"!=typeof t.key||!t.key)throw new Error("Invalid trigger key "+t.key);if(t.prevChild===a.undef)throw new Error("Triggers must provide a valid prevChild value for child_added and child_moved events");break;case"child_removed":case"child_changed":if("string"!=typeof t.key||!t.key)throw new Error("Invalid trigger key "+t.key);break;default:throw new Error("Invalid trigger event type: "+t.event)}}function s(t){return t instanceof o?t._snaps.slice():a.isArray(t)?t.slice():[t]}var a=t("../../common"),o=t("./NormalizedSnapshot.js");n.prototype.create=function(t){var e;return e="value"===this.event?new o(t,this.snaps):new o(t.ref().child(this.key),this.snaps)},n.prototype.toString=function(){return a.printf("SnapshotFactory(event=%s, key=%s, numberOfSnapshots=%s, prevChild=%s",this.event,this.key,this.snaps.length,this.prevChild===a.undef?"undefined":this.prevChild)},e.exports=n},{"../../common":19,"./NormalizedSnapshot.js":8}],17:[function(t,e,r){"use strict";var n=t("../../common"),i=t("./PathManager"),s=t("./Path"),a=t("./FieldMap"),o=t("./RecordSet");e.exports={replicate:function(t,e){var r=t.getPathManager().getPaths().slice(0),h=r[0];r[0]=new s([e,h.name(),h.getDependency()]);var u=new i(n.map(r,function(t){return t.clone()})),c=new a(u);t.getFieldMap().forEach(c.add,c);var l,f=t.getClass();return l=f===o?new f(c,t.filters):new f(c)}}},{"../../common":19,"./FieldMap":4,"./Path":9,"./PathManager":10,"./RecordSet":14}],18:[function(t,e,r){var n=t("./index.js");r.log=n.log,r.logLevel=n.logLevel,r.escapeEmail=n.escapeEmail},{"./index.js":19}],19:[function(t,e,r){var n=t("./libs/util.js"),i=t("./libs/logger.js");n.extend(r,n,{args:t("./libs/args.js"),log:i,logLevel:i.logLevel,Observable:t("./libs/Observable.js"),Observer:t("./libs/Observer.js"),queue:t("./libs/queue.js")})},{"./libs/Observable.js":20,"./libs/Observer.js":21,"./libs/args.js":22,"./libs/logger.js":23,"./libs/queue.js":24,"./libs/util.js":25}],20:[function(t,e,r){"use strict";function n(t,e){e||(e={}),this._observableProps=o(t,e),this.resetObservers()}function i(t,e){h.each(e,function(e){var r=h.indexOf(t,e);r>=0&&t.splice(r,1)})}function s(t,e){var r=[];return h.each(e,function(e){h.has(t.observers,e)?t.observers[e].length&&(r=r.concat(t.observers[e])):c.warn("Observable.hasObservers: invalid event type %s",e)}),r}function a(t,e,r){h.has(e.oneTimeResults,t)&&r.notify.apply(r,e.oneTimeResults[t])}function o(t,e){return h.extend({onAdd:h.noop,onRemove:h.noop,onEvent:h.noop,oneTimeEvents:[]},e,{eventsMonitored:t,observers:{},oneTimeResults:{}})}var h=t("./util.js"),u=t("./args.js"),c=t("./logger.js"),l=t("./Observer.js");n.prototype={observe:function(t,e,r,n){var i,s=u("observe",arguments,2,4);return t=s.nextFromReq(this._observableProps.eventsMonitored),t&&(e=s.nextReq("function"),r=s.next("function"),n=s.next("object"),i=new l(this,t,e,n,r),this._observableProps.observers[t].push(i),this._observableProps.onAdd(t,i),this.isOneTimeEvent(t)&&a(t,this._observableProps,i)),i},hasObservers:function(t){return this.getObservers(t).length>0},stopObserving:function(t,e,r){var n=u("stopObserving",arguments);t=n.next(["array","string"],this._observableProps.eventsMonitored),e=n.next(["function"]),r=n.next(["object"]),h.each(t,function(t){var n=[],s=this.getObservers(t);h.each(s,function(i){i.matches(t,e,r)&&(i.notifyCancelled(null),n.push(i))},this),i(this._observableProps.observers[t],n),n.length&&this._observableProps.onRemove(t,n)},this)},abortObservers:function(t){var e=[];if(this.hasObservers()){var r=this.getObservers().slice();h.each(r,function(r){r.notifyCancelled(t),e.push(r)},this),this.resetObservers(),e.length&&this._observableProps.onRemove(this.event,e)}},getObservers:function(t){return t=u("getObservers",arguments).listFrom(this._observableProps.eventsMonitored,!0),s(this._observableProps,t)},triggerEvent:function(t){var e=u("triggerEvent",arguments),r=e.listFromWarn(this._observableProps.eventsMonitored,!0),n=e.restAsList();r&&h.each(r,function(e){if(this.isOneTimeEvent(t)){if(h.isArray(this._observableProps.oneTimeResults,t))return void c.warn("One time event was triggered twice, should by definition be triggered once",t);this._observableProps.oneTimeResults[t]=n}var r=this.getObservers(e),i=0;h.each(r,function(t){t.notify.apply(t,n.slice(0)),i++}),this._observableProps.onEvent.apply(null,[e,i].concat(n.slice(0)))},this)},resetObservers:function(){h.each(this._observableProps.eventsMonitored,function(t){this._observableProps.observers[t]=[]},this)},isOneTimeEvent:function(t){return h.contains(this._observableProps.oneTimeEvents,t)},observeOnce:function(t,e,r,n){var i,s=u("observeOnce",arguments,2,4);return t=s.nextFromWarn(this._observableProps.eventsMonitored),t&&(e=s.nextReq("function"),r=s.next("function"),n=s.next("object"),i=new l(this,t,e,n,r,(!0)),this._observableProps.observers[t].push(i),this._observableProps.onAdd(t,i),this.isOneTimeEvent(t)&&a(t,this._observableProps,i)),i}},e.exports=n},{"./Observer.js":21,"./args.js":22,"./logger.js":23,"./util.js":25}],21:[function(t,e,r){"use strict";function n(t,e,r,n,i,s){if("function"!=typeof r)throw new Error("Must provide a valid notifyFn");this.observable=t,this.fn=r,this.event=e,this.cancelFn=i||function(){},this.context=n,this.oneTimeEvent=!!s}var i=t("./util.js");n.prototype={notify:function(){var t=i.toArray(arguments);this.fn.apply(this.context,t),this.oneTimeEvent&&this.observable.stopObserving(this.event,this.fn,this.context)},matches:function(t,e,r){return i.isArray(t)?i.contains(t,function(t){return this.matches(t,e,r)},this):!(t&&t!==this.event||e&&e!==this&&e!==this.fn||r&&r!==this.context)},notifyCancelled:function(t){this.cancelFn.call(this.context,t||null)}},e.exports=n},{"./util.js":25}],22:[function(t,e,r){"use strict";function n(t,e,r,i){if("string"!=typeof t||!h.isObject(e))throw new Error("Args requires at least 2 args: fnName, arguments[, minArgs, maxArgs]");if(!(this instanceof n))return new n(t,e,r,i);this.fnName=t,this.argList=h.toArray(e),this.origArgs=h.toArray(e);var s=this.length=this.argList.length;if(this.pos=-1,h.isUndefined(r)&&(r=0),h.isUndefined(i)&&(i=this.argList.length),si){var a=i>r?h.printf("%d to %d",r,i):r;throw Error(h.printf("%s must be called with %s arguments, but received %d",t,a,s))}}function i(t,e){return e===!0||(h.isArray(e)||(e=[e]),h.contains(e,function(e){switch(e){case"array":return h.isArray(t);case"string":return"string"==typeof t;case"number":return isFinite(parseInt(t,10));case"int":case"integer":return isFinite(parseFloat(t));case"object":return h.isObject(t);case"function":return"function"==typeof t;case"bool":case"boolean":return"boolean"==typeof t;case"boolean-like":return!h.isObject(t);default:throw new Error("Args received an invalid data type: "+e)}}))}function s(t,e,r,n){if(n=h.printf("%s: invalid argument at pos %d, %s (received %s)",e,r,n),t===!0)throw new Error(n);if(!h.has(u,t))throw new Error("The `required` value passed to Args methods must either be true or a method name from logger");u[t](n)}function a(t,e,r){u.warn("%s: invalid choice %s, must be one of [%s]",t,e,r)}function o(t,e){if(e===!0)return t;var r=h.isArray(e)?e[0]:e;switch(r){case"array":return h.isArray(t)?t:[t];case"string":return t+"";case"number":return parseFloat(t);case"int":case"integer":return parseInt(t,10);case"bool":case"boolean":case"boolean-like":return!!t;case"function":case"object":return t;default:throw new Error("Args received an invalid data type: "+r)}}var h=t("./util.js"),u=t("./logger.js");n.prototype={orig:function(){return this.origArgs.slice(0)},restAsList:function(t,e){var r=this.argList.slice(0);if(t||e)for(var n=0,i=r.length;n=i+1;if(o){var h=l.bind(console["debug"===r?"log":r],console);f[r]=function(){var t=l.toArray(arguments);if(t.length>1&&"string"==typeof t[0]){var r=t[0].match(/(%s|%d|%j)/g);if(r){var n=[l.printf.apply(l,t)];t=t.length>r.length+1?n.concat(t.slice(r.length+1)):n}}e&&a(e,t)||h.apply("undefined"==typeof console?c:console,t)}}else f[r]=s;f["is"+n(r)+"Enabled"]=function(){return o}});var r=function(t){return function(){f.logLevel(t)}}(u);return u=t,r},f.logLevel(i()),e.exports=f},{"./util.js":25}],24:[function(t,e,r){"use strict";function n(t){this.needs=0,this.met=0,this.queued=[],this.errors=[],this.criteria=[],this.processing=!1,i.each(t,this.addCriteria,this)}var i=t("./util.js");n.prototype={addCriteria:function(t,e){if(this.processing)throw new Error("Cannot call addCriteria() after invoking done(), fail(), or handler() methods");return this.criteria.push(e?[t,e]:t),this},getHandler:function(){var t,e;return this.addCriteria(function(r){e!==i.undef?r(e):t=r}),function(r){t?t(r):e=r}},ready:function(){return this.needs===this.met},done:function(t,e){return t&&this._runOrStore(function(){this.hasErrors()||t.call(e)}),this},fail:function(t,e){return this._runOrStore(function(){this.hasErrors()&&t.apply(e,this.getErrors())}),this},handler:function(t,e){return this._runOrStore(function(){t.apply(e,this.getErrors())}),this},chain:function(t){return this.addCriteria(t.handler,t),this},when:function(t){this._runOrStore(function(){this.hasErrors()?t.reject.apply(t,this.getErrors()):t.resolve()})},addError:function(t){this.errors.push(t)},hasErrors:function(){return this.errors.length},getErrors:function(){return this.errors.slice(0)},_process:function(){this.processing=!0,this.needs=this.criteria.length,i.each(this.criteria,this._evaluateCriteria,this)},_evaluateCriteria:function(t){var e=null;i.isArray(t)&&(e=t[1],t=t[0]);try{t.call(e,i.bind(this._criteriaMet,this))}catch(r){this.addError(r)}},_criteriaMet:function(t){t&&this.addError(t),this.met++,this.ready()&&i.each(this.queued,this._run,this)},_runOrStore:function(t){this.processing||this._process(),this.ready()?this._run(t):this.queued.push(t)},_run:function(t){t.call(this)}},e.exports=function(t,e){var r=new n(t);return e&&r.done(e),r}},{"./util.js":25}],25:[function(t,e,r){(function(e){"use strict";function n(t,e){switch(e){case"%d":return parseInt(t,10);case"%j":return t=c.isObject(t)?JSON.stringify(t):t+"",t.length>500&&(t=t.substr(0,500)+".../*truncated*/...}"),t;case"%s":return t+"";default:return t}}function i(t){return c.isObject(t)&&t+""=="[object Arguments]"}function s(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),r=this,n=function(){},i=function(){return r.apply(this instanceof n&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return n.prototype=this.prototype,i.prototype=new n,i}function a(t,e){var r,n;for(r=0,n=this.length;r>>0;if(0===s)return-1;if(r=0,arguments.length>1&&(r=Number(arguments[1]),r!==r?r=0:0!==r&&r!==1/0&&r!==-(1/0)&&(r=(r>0||-1)*Math.floor(Math.abs(r)))),r>=s)return-1;for(n=r>=0?r:Math.max(s-Math.abs(r),0);n0?r.slice(e):r},c.extend=function(t,e){var r=c.toArray(arguments),n="boolean"==typeof r[0]&&r.shift(),i=r.shift();return c.each(r,function(t){c.isObject(t)&&c.each(t,function(t,e){i[e]=n&&c.isObject(i[e])?c.extend(!0,i[e],t):t})}),i},c.bind=function(t,e){var r=Array.prototype.slice.call(arguments,1);return(t.bind||s).apply(t,r)},c.isEmpty=function(t){return t===u||null===t||c.isArrayLike(t)&&0===t.length||c.isObject(t)&&!c.contains(t,function(t){return!0})},c.keys=function(t){var e=[];return c.each(t,function(t,r){e.push(r)}),e},c.map=function(t,e,r){var n=[];return c.each(t,function(i,s){var a=e.call(r,i,s,t);a!==u&&n.push(a)}),n},c.mapObject=function(t,e,r){var n={};return c.each(t,function(i,s){var a=e.call(r,i,s,t);a!==u&&(n[s]=a)}),n},c.find=function(t,e,r){if(c.isArrayLike(t)){for(var n=0,i=t.length;n-1;e=function(t){return function(e){return e===t}}(e)}return c.find(t,e,r)!==u},c.each=function(t,e,r){if(c.isArrayLike(t))(t.forEach||a).call(t,e,r);else if(c.isObject(t)){var n;for(n in t)t.hasOwnProperty(n)&&e.call(r,t[n],n,t)}},c.indexOf=function(t,e){return(t.indexOf||h).call(t,e)},c.remove=function(t,e){var r=!1;if(c.isArray(t)){var n=c.indexOf(t,e);n>-1&&(t.splice(n,1),r=!0)}else if(c.isObject(t)){var i;for(i in t)if(t.hasOwnProperty(i)&&e===t[i]){r=!0,delete t[i];break}}return r},c.defer=function(t,e){var r=c.toArray(arguments);setTimeout(c.bind.apply(null,r),0)},c.call=function(t,e){var r=c.toArray(arguments,2),n=[];return c.each(t,function(t){return"function"!=typeof t||e?void(c.isObject(t)&&"function"==typeof t[e]&&n.push(t[e].apply(t,r))):n.push(t.apply(null,r))}),n},c.isEqual=function(t,e,r){if(t===e)return!0;if(typeof t!=typeof e)return!1;if(c.isObject(t)&&c.isObject(e)){var n=c.isArrayLike(t),i=c.isArrayLike(e);if(n||i)return n&&i&&t.length===e.length&&!c.contains(t,function(t,r){return!c.isEqual(t,e[r])});var s=r?c.keys(t):c.keys(t).sort(),a=r?c.keys(e):c.keys(e).sort();return c.isEqual(s,a)&&!c.contains(t,function(t,r){return!c.isEqual(t,e[r])})}return!1},c.bindAll=function(t,e){return c.each(e,function(r,n){"function"==typeof r&&(e[n]=c.bind(r,t))}),e},c.printf=function(){var t=c.toArray(arguments),e=t.shift(),r=e.match(/(%s|%d|%j)/g);return r&&t.length&&c.find(r,function(r){return e=e.replace(r,n(t.shift(),r)),0===t.length}),e},c.mergeToString=function(t){return 0===t.length?null:1===t.length?t[0]:"["+t.join("][")+"]"},c.construct=function(t,e){function r(){return t.apply(this,e)}return r.prototype=t.prototype,new r},c.noop=function(){};var f=[];c.isFirebaseRef=function(t){if(!c.isObject(t))return!1;var e=Object.getPrototypeOf(t);return!(!e||e.constructor!==c.Firebase.prototype.constructor)||("function"==typeof t.ref&&"function"==typeof t.ref().transaction||c.isFirebaseRefWrapper(t))},c.isFirebaseRefWrapper=function(t){return c.contains(f,function(e){return t instanceof e})},c.isQueryRef=function(t){return c.isFirebaseRef(t)&&"function"!=typeof t.transaction},c.registerFirebaseWrapper=function(t){f.push(t)},c._mockFirebaseRef=function(t){c.Firebase=t},c.escapeEmail=function(t){return(t||"").replace(".",",")},c.assertValidEvent=function(t){if(!c.contains(l,t))throw new Error("Event must be one of "+l+", but received "+t)},c.inherits=function(t,e){var r=[t.prototype].concat(c.toArray(arguments).slice(2));return t.prototype=new e,t.prototype.constructor=e,c.each(r,function(e){c.each(e,function(e,r){t.prototype[r]=e})}),t.prototype._super=function(){e.apply(this,arguments)},t},c.deepCopy=function(t){if(!c.isObject(t))return t;var e=c.isArrayLike(t)?[]:{};return c.each(t,function(t,r){e[r]=c.deepCopy(t)}),e},c.pick=function(t,e){if(!c.isObject(t))return{};var r=c.isArrayLike(t)?[]:{};return c.each(e,function(e){r[e]=t[e]}),r},c.eachByPath=function(t,e,r,n){var i={};c.each(e,function(e,r){var n=t.pathFor(r),s=t.getField(r),a=s?s.id:r;c.has(i,n.name())||(i[n.name()]={path:n,data:{}}),i[n.name()].data[a]=e}),c.each(i,function(t){r.call(n,t.path,t.data)})}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{firebase:"firebase"}],"firebase-util":[function(t,e,r){"use strict";var n=t("./common/index.js");n.extend(r,t("./common/exports.js"),t("./NormalizedCollection/exports.js")),"undefined"!=typeof window&&(window.hasOwnProperty("Firebase")?window.Firebase.util=n:console.warn("Firebase not found on the global window instance. Cannot add Firebase.util namespace."))},{"./NormalizedCollection/exports.js":2,"./common/exports.js":18,"./common/index.js":19}]},{},[1]); \ No newline at end of file diff --git a/dist/firebase-util-paginate.min.js b/dist/firebase-util-paginate.min.js new file mode 100644 index 0000000..4b660b7 --- /dev/null +++ b/dist/firebase-util-paginate.min.js @@ -0,0 +1,10 @@ +/*! + * Firebase-util: A set of experimental power tools for Firebase. + * + * Version: 0.2.6 + * URL: https://github.com/firebase/firebase-util + * Date: 2016-11-21T15:08:32.518Z + * License: MIT http://firebase.mit-license.org/ + */ +require=function t(e,n,r){function i(o,a){if(!n[o]){if(!e[o]){var u="function"==typeof require&&require;if(!a&&u)return u(o,!0);if(s)return s(o,!0);var h=new Error("Cannot find module '"+o+"'");throw h.code="MODULE_NOT_FOUND",h}var c=n[o]={exports:{}};e[o][0].call(c.exports,function(t){var n=e[o][1][t];return i(n?n:t)},c,c.exports,t,e,n,r)}return n[o].exports}for(var s="function"==typeof require&&require,o=0;o2&&!s.isObject(n))throw new Error("Optional third argument to Firebase.util.Scroll must be an object of key/value pairs");var a=new u(t);return a.scroll=new o(a,e,r(n,"windowSize","Scroll"),i),a},n.Paginate=function(t,e,n){if(!s.isFirebaseRef(t)||s.isQueryRef(t))throw new Error("First argument to Firebase.util.Paginate must be a valid Firebase ref. It cannot be a Query (e.g. you have called orderByChild()).");if("string"!=typeof e)throw new Error("Second argument to Firebase.util.Paginate must be a valid string");if(arguments.length>2&&!s.isObject(n))throw new Error("Optional third argument to Firebase.util.Paginate must be an object of key/value pairs");var i=new u(t);return i.page=new a(i,e,r(n,"pageSize","Paginate")),i}},{"../common":9,"./libs/Paginate.js":5,"./libs/ReadOnlyRef.js":6,"./libs/Scroll.js":7}],3:[function(t,e,n){"use strict";function r(t,e,n,r){i.log.debug("Cache: caching %s using field=%s maxRecordsLoaded=%d",t.toString(),e,n),this.offset=new s({field:e,max:n,ref:t.ref()}),this.outRef=t,this.inRef=null,this.queryRef=null,this.countRef=null,this.keys={},this.start=0,this.count=-1,this.endCount=-1,this.nextListeners=[],this.offset.observe(this._keyChange,this),this.desc=r}var i=t("../../common"),s=t("./Offset");r.prototype.moveTo=function(t,e){i.log.debug("Cache.moveTo: startOffset=%d, numberOfRecords=%d",t,e);var n=this.start,r=this.count;this.start=t,this.count=e,this.endCount=this.start+this.count,n!==this.start?this.offset.goTo(t,e):r!==this.count&&this._refChange()},r.prototype.hasNext=function(){return this.count===-1||this.endCount>this.start+this.count},r.prototype.hasPrev=function(){return this.start>0},r.prototype.observeHasNext=function(t,e){var n=this.nextListeners,r=[t,e];return n.push(r),function(){i.remove(n,r)}},r.prototype.destroy=function(){this._unsubscribe(),this.offset.destroy(),this.offset=null,this.start=0,this.count=-1,this.inRef=null,this.outRef=null,this.queryRef=null,this.countRef=null,this.keys=null,this.nextListeners=null},r.prototype._keyChange=function(t,e,n){this.inRef=n,i.log.debug("Cache._keyChange: %s %s %s",t,e,n.toString()),this._refChange()},r.prototype._unsubscribe=function(){this.queryRef&&(this.queryRef.off("child_added",this._add,this),this.queryRef.off("child_removed",this._remove,this),this.queryRef.off("child_moved",this._move,this),this.queryRef.off("child_changed",this._change,this),this.queryRef.off("value",this._value,this),this.queryRef.off("value",this._removeOrphans,this),this.queryRef=null),this.countRef&&(this.countRef.off("value",this._count,this),this.countRef=null)},r.prototype._refChange=function(){this._unsubscribe(),this.inRef&&this.count>-1&&(this.countRef=this.desc?this.inRef.limitToLast(this.count+1):this.inRef.limitToFirst(this.count+1),this.countRef.on("value",this._count,this),this.queryRef=this.desc?this.inRef.limitToLast(this.count):this.inRef.limitToFirst(this.count),this.queryRef.on("child_added",this._add,this),this.queryRef.on("child_removed",this._remove,this),this.queryRef.on("child_moved",this._move,this),this.queryRef.on("child_changed",this._change,this),this.queryRef.on("value",this._value,this),this.queryRef.once("value",this._removeOrphans,this))},r.prototype._add=function(t,e){var n=t.key();i.has(this.keys,n)?i.isEqual(this.keys[n],t.val())||this._change(t):(this.keys[n]=t,this.outRef.$trigger("child_added",t,e))},r.prototype._remove=function(t){var e=t.key();i.has(this.keys,e)&&(this.outRef.$trigger("child_removed",t),delete this.keys[e])},r.prototype._move=function(t,e){var n=t.key();i.has(this.keys,n)&&(this.keys[n]=t,this.outRef.$trigger("child_moved",t,e))},r.prototype._change=function(t){this.keys[t.key()]=t,this.outRef.$trigger("child_changed",t)},r.prototype._value=function(t){this.outRef.$trigger("value",t)},r.prototype._count=function(t){this.endCount=this.start+t.numChildren();var e=this.hasNext();i.each(this.nextListeners,function(t){t[0].call(t[1],e)})},r.prototype._removeOrphans=function(t){i.each(this.keys,function(e,n){t.hasChild(n)||(this.outRef.$trigger("child_removed",e),delete this.keys[n])},this)},e.exports=r},{"../../common":9,"./Offset":4}],4:[function(t,e,n){"use strict";function r(t){this.keys=[],this.field=t.field,this.ref=o(t.ref,t.field),this.max=t.max,this.listeners=[],this.curr=0,this.sub=null,this.isSubscribing=!1,this.lastNotifyValue=h.undef,this._debouncedRecache=a(function(){h.log.debug("Offset._debouncedRecache: recaching keys for offset %d",this.curr),this.keys=[],this._grow(this._listen)},this,100,1e3)}function i(t,e){var n;switch(e){case"$key":n=t.key();break;case"$priority":n=t.getPriority();break;case"$value":n=t.val();break;default:var r=t.val();if(!h.isObject(r))throw new Error("A value of type "+typeof r+'Was found. But we are attempting to order by child field "'+e+"\". Pagination requires all records to be objects or it can't determine an appropriate offset value.");n=r[e]}return{val:n,key:t.key()}}function s(t,e){return e===!1?null:null===e?t:t.startAt(e.val,e.key)}function o(t,e){return"$key"===e?t.orderByKey():"$priority"===e?t.orderByPriority():"$value"===e?t.orderByValue():t.orderByChild(e)}function a(t,e,n,r){function i(){if(u&&(u(),u=null),a&&Date.now()-a>r)c||(c=!0,setTimeout(s,0));else{a||(a=Date.now());var t=setTimeout(s,n);u=function(){clearTimeout(t)}}}function s(){u=null,a=null,c=!1,t.apply(e,h)}function o(){h=Array.prototype.slice.call(arguments,0),i()}var a,u,h,c;if("number"==typeof e&&(r=n,n=e,e=null),"number"!=typeof n)throw new Error("Must provide a valid integer for wait. Try 0 for a default");if("function"!=typeof t)throw new Error("Must provide a valid function to debounce");return r||(r=10*n||100),o.running=function(){return a>0},o}function u(t){var e=t.length;return e?t[e-1]:null}var h=t("../../common");r.prototype.goTo=function(t){t!==this.curr&&(h.log("Offset.goTo: offset changed from %d to %d",this.curr,t),this.curr=t,this.lastNotifyValue=h.undef,this._listen())},r.prototype.observe=function(t,e){this.listeners.push([t,e]);var n=this.getKey(),r=s(this.ref,n);t.call(e,n&&n.val,n&&n.key,r)},r.prototype.getKey=function(t){return arguments.length||(t=this.curr),0===t?null:this.keys.length>t&&this.keys[t]},r.prototype.destroy=function(){this._unsubscribe(),this.curr=0,this.keys=[],this.lastNotifyValue=h.undef,this.isSubscribing=!1},r.prototype._notify=function(){var t=this.getKey();if(!h.isEqual(this.lastNotifyValue,t)){h.log("Offset._notify: key at offset %d is %s",this.curr,t&&t.key),this.lastNotifyValue=t;var e=s(this.ref,t);h.each(this.listeners,function(n){n[0].call(n[1],t&&t.val,t&&t.key,e)})}},r.prototype._recache=function(){this.isSubscribing||this._debouncedRecache()};var c=0;r.prototype._grow=function(t){var e=this,n=e.keys.length;if(e.curr>=n){var r=e.getKey(),s=u(e.keys),o=Math.min(e.curr+(s?2:1)-n,e.max),a=null!==s?e.ref.startAt(s.val,s.key):e.ref;a.limitToFirst(o).once("value",function(n){var a=null!==s;if(n.forEach(function(t){return a?void(a=!1):void e.keys.push(i(t,e.field))}),c++>1e4)throw new Error("Tried to fetch more than 10,000 pages to determine the correct offset. Giving up now. Sorry.");e.curr>=e.keys.length&&n.numChildren()===o?setTimeout(h.bind(e._grow,e,t),0):(c=0,h.log.debug("Offset._grow: Cached %d keys",e.keys.length),t.call(e,!h.isEqual(e.getKey(),r)))})}else t.call(e,!1)},r.prototype._startOffset=function(){return Math.max(0,this.curr-this.max,this.curr-10)},r.prototype._queryRef=function(){var t=this._startOffset(),e=this.ref;if(t>0){var n=this.getKey(t);e=e.startAt(n.val,n.key)}return e.limitToLast(Math.max(this.curr-t,1))},r.prototype._moved=function(t){t.key()===this.getKey()&&this._recache()},r.prototype._unsubscribe=function(){this.sub&&(this.sub.off("child_added",this._recache,this),this.sub.off("child_moved",this._moved,this),this.sub.off("child_removed",this._recache,this),this.sub.off("value",this._doneSubscribing,this),this.sub=null)},r.prototype._subscribe=function(){this._unsubscribe(),this.sub=this._queryRef(),this.isSubscribing=!0,this.sub.on("child_added",this._recache,this),this.sub.on("child_moved",this._moved,this),this.sub.on("child_removed",this._recache,this),this.sub.once("value",this._doneSubscribing,this)},r.prototype._doneSubscribing=function(){this.isSubscribing=!1,this._notify()},r.prototype._monitorEmptyOffset=function(){function t(i){var s=i.numChildren();s>(null===r?0:1)&&(h.log.debug("Offset._monitorEmptyOffset: A value exists now."),n.off("value",t),e._grow())}var e=this,n=e.ref,r=null;this.keys.length&&(r=u(this.keys),n=n.startAt(r.val,r.key)),h.log.debug("Offset._monitorEmptyOffset: No value exists at offset %d, currently %d keys at this path. Watching for a new value.",this.curr,this.keys.length),n.limitToFirst(2).on("value",t)},r.prototype._listen=function(){this._unsubscribe(),this.curr>=this.keys.length?this._grow(function(){this.keys.length>=this.curr?this._subscribe():(this._monitorEmptyOffset(),this._notify())}):this._subscribe()},e.exports=r},{"../../common":9}],5:[function(t,e,n){"use strict";function r(t,e,n){this.currPage=0,this.field=e,this.ref=t,this.pageSize=n.pageSize,this.max=n.maxCacheSize,this.subs=[],this.pageChangeListeners=[],this.pageCountListeners=[],this.cache=new a(t,e,n.maxCacheSize),this.pageCount=-1,this.couldHaveMore=!1,this.cache.observeHasNext(this._countPages,this)}function i(t,e){return 0===t?0:Math.ceil(t/e)}function s(t,e){var n={};if(n.bindFunction=function(t,e){return function(){return t.apply(e,[e])}},n.stateChange=function(t){4==n.request.readyState&&n.callbackFunction(n.request.responseText)},n.getRequest=function(){return window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):!!window.XMLHttpRequest&&new XMLHttpRequest},n.postBody=arguments[2]||"",n.callbackFunction=e,n.url=t,n.request=n.getRequest(),n.request){var r=n.request;r.onreadystatechange=n.bindFunction(n.stateChange,n),""!==n.postBody?(r.open("POST",t,!0),r.setRequestHeader("X-Requested-With","XMLHttpRequest"),r.setRequestHeader("Content-type","application/x-www-form-urlencoded"),r.setRequestHeader("Connection","close")):r.open("GET",t,!0),r.send(n.postBody)}return n}var o=t("../../common"),a=t("./Cache");r.prototype.next=function(){return this.hasNext()&&(this.currPage++,o.log.debug("Paginate.next: current page is %d",this.currPage),this._pageChange()),this},r.prototype.prev=function(){return this.hasPrev()&&(this.currPage--,o.log.debug("Paginate.prev: current page is %d",this.currPage),this._pageChange()),this},r.prototype.setPage=function(t){t>0&&t<=this.pageCount?(this.currPage=t,o.log.debug("Paginate.setPage: current page is %d",this.currPage),this._pageChange()):o.log.warn("Paginate.setPage: invalid page number %d",t)},r.prototype.hasNext=function(){return this.cache.hasNext()},r.prototype.hasPrev=function(){return this.currPage>1},r.prototype.onPageChange=function(t,e){var n=this.pageChangeListeners,r=[t,e];return n.push(r),t.call(e,this.currPage),function(){o.remove(n,r)}},r.prototype.onPageCount=function(t,e){var n=this.pageCountListeners,r=[t,e];return n.push(r),this.pageCount>-1?t.call(e,this.pageCount,this.couldHaveMore):this._countPages(),function(){o.remove(n,r)}},r.prototype.getCountByDowloadingAllKeys=function(t,e){var n=this;n.downloadingEverything=!0;var r=n.ref.ref().toString();r.match(/\/$/)||(r+="/"),r+=".json?shallow=true",s(r,function(r){var s=0;try{s=o.keys(JSON.parse(r)).length}catch(a){o.log.warn(a)}o.log.debug("Paginate.getCountByDownloadingAllKeys: found %d keys",s),n.downloadingEverything=!1,n.pageCount=i(s,n.pageSize),n.couldHaveMore=!1,n._notifyPageCount(),t&&t.call(e,s)})},r.prototype.destroy=function(){this.cache.destroy(),this.cache=null,this.ref=null,o.each(this.subs,function(t){t()}),this.subs=[],this.pageCountListeners.length=0,this.pageChangeListeners.length=0},r.prototype._countPages=function(){var t=this,e=t.currPage;if(!this.downloadingEverything)if(t.pageCount===-1){var n=t.max,r=t.pageSize,i=this.ref.ref().limitToFirst(n);i.once("value",function(e){t.pageCount===-1&&(t.couldHaveMore=e.numChildren()===n,t.pageCount=Math.ceil(e.numChildren()/r),t._notifyPageCount(),t._countPages())})}else e>=t.pageCount&&(t.pageCount=e,t.couldHaveMore=t.cache.hasNext(),t._notifyPageCount())},r.prototype._pageChange=function(){var t=this.currPage,e=(t-1)*this.pageSize;this.cache.moveTo(e,this.pageSize),this._countPages(),o.each(this.pageChangeListeners,function(e){e[0].call(e[1],t)})},r.prototype._notifyPageCount=function(){var t=this.pageCount,e=this.couldHaveMore;o.each(this.pageCountListeners,function(n){n[0].call(n[1],t,e)})},e.exports=r},{"../../common":9,"./Cache":3}],6:[function(t,e,n){"use strict";function r(t){this._ref=t,this._obs=new a.Observable(["value","child_added","child_removed","child_moved","child_changed"])}function i(t){return function(){var e=a.toArray(arguments),n=this.ref();return n[t].apply(n,e)}}function s(t){return function(){throw new Error(t+" is not supported. This is a read-only reference. You can modify child records after calling .child(), or work with the original by using .ref().")}}function o(t){return function(){throw new Error(t+" is not supported for Paginate and Scroll references. Try calling it on the original reference used to create the instance instead.")}}var a=t("../../common");r.prototype={on:function(t,e,n,r){this._obs.observe(t,e,n,r)},once:function(t,e,n,r){function i(n){s.off(t,i,s),e.call(r,n)}var s=this;this.on(t,i,n,this)},off:function(t,e,n){this._obs.stopObserving(t,e,n)},ref:function(){return this._ref},child:i("child"),parent:i("parent"),root:i("root"),name:i("name"),key:i("key"),toString:i("toString"),auth:i("auth"),unauth:i("unauth"),authWithCustomToken:i("authWithCustomToken"),authAnonymously:i("authAnonymously"),authWithPassword:i("authWithPassword"),authWithOAuthPopup:i("authWithOAuthPopup"),authWithOAuthRedirect:i("authWithOAuthRedirect"),authWithOAuthToken:i("authWithOAuthToken"),getAuth:i("getAuth"),onAuth:i("onAuth"),offAuth:i("offAuth"),createUser:i("createUser"),changePassword:i("changePassword"),removeUser:i("removeUser"),resetPassword:i("resetPassword"),changeEmail:i("changeEmail"),goOffline:i("goOffline"),goOnline:i("goOnline"),set:s("set"),update:s("update"),remove:s("remove"),push:s("push"),setWithPriority:s("setWithPriority"),setPriority:s("setPriority"),transaction:s("transaction"),limit:o("limit"),onDisconnect:o("onDisconnect"),orderByChild:o("orderByChild"),orderByKey:o("orderByKey"),orderByPriority:o("orderByPriority"),limitToFirst:o("limitToFirst"),limitToLast:o("limitToLast"),startAt:o("startAt"),endAt:o("endAt"),equalTo:o("equalTo"),$trigger:function(){this._obs.triggerEvent.apply(this._obs,a.toArray(arguments))}},e.exports=r},{"../../common":9}],7:[function(t,e,n){"use strict";function r(t,e,n,r){this.max=n.windowSize,this.start=0,this.end=0,this.cache=new i(t,e,n.maxCacheSize,r)}var i=t("./Cache");r.prototype.next=function(t){this.hasNext()&&(this.end=this.end+t,this.start=Math.max(0,this.end-this.max,this.start),this.cache.moveTo(this.start,this.end-this.start))},r.prototype.prev=function(t){this.hasPrev()&&(this.start=Math.max(0,this.start-t),this.end=Math.min(this.start+this.max,this.end),this.cache.moveTo(this.start,this.end-this.start))},r.prototype.hasNext=function(){return this.cache.hasNext()},r.prototype.hasPrev=function(){return this.start>0},r.prototype.observeHasNext=function(t,e){return this.cache.observeHasNext(t,e)},r.prototype.destroy=function(){this.cache.destroy(),this.ref=null,this.cache=null},e.exports=r},{"./Cache":3}],8:[function(t,e,n){var r=t("./index.js");n.log=r.log,n.logLevel=r.logLevel,n.escapeEmail=r.escapeEmail},{"./index.js":9}],9:[function(t,e,n){var r=t("./libs/util.js"),i=t("./libs/logger.js");r.extend(n,r,{args:t("./libs/args.js"),log:i,logLevel:i.logLevel,Observable:t("./libs/Observable.js"),Observer:t("./libs/Observer.js"),queue:t("./libs/queue.js")})},{"./libs/Observable.js":10,"./libs/Observer.js":11,"./libs/args.js":12,"./libs/logger.js":13,"./libs/queue.js":14,"./libs/util.js":15}],10:[function(t,e,n){"use strict";function r(t,e){e||(e={}),this._observableProps=a(t,e),this.resetObservers()}function i(t,e){u.each(e,function(e){var n=u.indexOf(t,e);n>=0&&t.splice(n,1)})}function s(t,e){var n=[];return u.each(e,function(e){u.has(t.observers,e)?t.observers[e].length&&(n=n.concat(t.observers[e])):c.warn("Observable.hasObservers: invalid event type %s",e)}),n}function o(t,e,n){u.has(e.oneTimeResults,t)&&n.notify.apply(n,e.oneTimeResults[t])}function a(t,e){return u.extend({onAdd:u.noop,onRemove:u.noop,onEvent:u.noop,oneTimeEvents:[]},e,{eventsMonitored:t,observers:{},oneTimeResults:{}})}var u=t("./util.js"),h=t("./args.js"),c=t("./logger.js"),f=t("./Observer.js");r.prototype={observe:function(t,e,n,r){var i,s=h("observe",arguments,2,4);return t=s.nextFromReq(this._observableProps.eventsMonitored),t&&(e=s.nextReq("function"),n=s.next("function"),r=s.next("object"),i=new f(this,t,e,r,n),this._observableProps.observers[t].push(i),this._observableProps.onAdd(t,i),this.isOneTimeEvent(t)&&o(t,this._observableProps,i)),i},hasObservers:function(t){return this.getObservers(t).length>0},stopObserving:function(t,e,n){var r=h("stopObserving",arguments);t=r.next(["array","string"],this._observableProps.eventsMonitored),e=r.next(["function"]),n=r.next(["object"]),u.each(t,function(t){var r=[],s=this.getObservers(t);u.each(s,function(i){i.matches(t,e,n)&&(i.notifyCancelled(null),r.push(i))},this),i(this._observableProps.observers[t],r),r.length&&this._observableProps.onRemove(t,r)},this)},abortObservers:function(t){var e=[];if(this.hasObservers()){var n=this.getObservers().slice();u.each(n,function(n){n.notifyCancelled(t),e.push(n)},this),this.resetObservers(),e.length&&this._observableProps.onRemove(this.event,e)}},getObservers:function(t){return t=h("getObservers",arguments).listFrom(this._observableProps.eventsMonitored,!0),s(this._observableProps,t)},triggerEvent:function(t){var e=h("triggerEvent",arguments),n=e.listFromWarn(this._observableProps.eventsMonitored,!0),r=e.restAsList();n&&u.each(n,function(e){if(this.isOneTimeEvent(t)){if(u.isArray(this._observableProps.oneTimeResults,t))return void c.warn("One time event was triggered twice, should by definition be triggered once",t);this._observableProps.oneTimeResults[t]=r}var n=this.getObservers(e),i=0;u.each(n,function(t){t.notify.apply(t,r.slice(0)),i++}),this._observableProps.onEvent.apply(null,[e,i].concat(r.slice(0)))},this)},resetObservers:function(){u.each(this._observableProps.eventsMonitored,function(t){this._observableProps.observers[t]=[]},this)},isOneTimeEvent:function(t){return u.contains(this._observableProps.oneTimeEvents,t)},observeOnce:function(t,e,n,r){var i,s=h("observeOnce",arguments,2,4);return t=s.nextFromWarn(this._observableProps.eventsMonitored),t&&(e=s.nextReq("function"),n=s.next("function"),r=s.next("object"),i=new f(this,t,e,r,n,(!0)),this._observableProps.observers[t].push(i),this._observableProps.onAdd(t,i),this.isOneTimeEvent(t)&&o(t,this._observableProps,i)),i}},e.exports=r},{"./Observer.js":11,"./args.js":12,"./logger.js":13,"./util.js":15}],11:[function(t,e,n){"use strict";function r(t,e,n,r,i,s){if("function"!=typeof n)throw new Error("Must provide a valid notifyFn");this.observable=t,this.fn=n,this.event=e,this.cancelFn=i||function(){},this.context=r,this.oneTimeEvent=!!s}var i=t("./util.js");r.prototype={notify:function(){var t=i.toArray(arguments);this.fn.apply(this.context,t),this.oneTimeEvent&&this.observable.stopObserving(this.event,this.fn,this.context)},matches:function(t,e,n){return i.isArray(t)?i.contains(t,function(t){return this.matches(t,e,n)},this):!(t&&t!==this.event||e&&e!==this&&e!==this.fn||n&&n!==this.context)},notifyCancelled:function(t){this.cancelFn.call(this.context,t||null)}},e.exports=r},{"./util.js":15}],12:[function(t,e,n){"use strict";function r(t,e,n,i){if("string"!=typeof t||!u.isObject(e))throw new Error("Args requires at least 2 args: fnName, arguments[, minArgs, maxArgs]");if(!(this instanceof r))return new r(t,e,n,i);this.fnName=t,this.argList=u.toArray(e),this.origArgs=u.toArray(e);var s=this.length=this.argList.length;if(this.pos=-1,u.isUndefined(n)&&(n=0),u.isUndefined(i)&&(i=this.argList.length),si){var o=i>n?u.printf("%d to %d",n,i):n;throw Error(u.printf("%s must be called with %s arguments, but received %d",t,o,s))}}function i(t,e){return e===!0||(u.isArray(e)||(e=[e]),u.contains(e,function(e){switch(e){case"array":return u.isArray(t);case"string":return"string"==typeof t;case"number":return isFinite(parseInt(t,10));case"int":case"integer":return isFinite(parseFloat(t));case"object":return u.isObject(t);case"function":return"function"==typeof t;case"bool":case"boolean":return"boolean"==typeof t;case"boolean-like":return!u.isObject(t);default:throw new Error("Args received an invalid data type: "+e)}}))}function s(t,e,n,r){if(r=u.printf("%s: invalid argument at pos %d, %s (received %s)",e,n,r),t===!0)throw new Error(r);if(!u.has(h,t))throw new Error("The `required` value passed to Args methods must either be true or a method name from logger");h[t](r)}function o(t,e,n){h.warn("%s: invalid choice %s, must be one of [%s]",t,e,n)}function a(t,e){if(e===!0)return t;var n=u.isArray(e)?e[0]:e;switch(n){case"array":return u.isArray(t)?t:[t];case"string":return t+"";case"number":return parseFloat(t);case"int":case"integer":return parseInt(t,10);case"bool":case"boolean":case"boolean-like":return!!t;case"function":case"object":return t;default:throw new Error("Args received an invalid data type: "+n)}}var u=t("./util.js"),h=t("./logger.js");r.prototype={orig:function(){return this.origArgs.slice(0)},restAsList:function(t,e){var n=this.argList.slice(0);if(t||e)for(var r=0,i=n.length;r=i+1;if(a){var u=f.bind(console["debug"===n?"log":n],console);l[n]=function(){var t=f.toArray(arguments);if(t.length>1&&"string"==typeof t[0]){var n=t[0].match(/(%s|%d|%j)/g);if(n){var r=[f.printf.apply(f,t)];t=t.length>n.length+1?r.concat(t.slice(n.length+1)):r}}e&&o(e,t)||u.apply("undefined"==typeof console?c:console,t)}}else l[n]=s;l["is"+r(n)+"Enabled"]=function(){return a}});var n=function(t){return function(){l.logLevel(t)}}(h);return h=t,n},l.logLevel(i()),e.exports=l},{"./util.js":15}],14:[function(t,e,n){"use strict";function r(t){this.needs=0,this.met=0,this.queued=[],this.errors=[],this.criteria=[],this.processing=!1,i.each(t,this.addCriteria,this)}var i=t("./util.js");r.prototype={addCriteria:function(t,e){if(this.processing)throw new Error("Cannot call addCriteria() after invoking done(), fail(), or handler() methods");return this.criteria.push(e?[t,e]:t),this},getHandler:function(){var t,e;return this.addCriteria(function(n){e!==i.undef?n(e):t=n}),function(n){t?t(n):e=n}},ready:function(){return this.needs===this.met},done:function(t,e){return t&&this._runOrStore(function(){this.hasErrors()||t.call(e)}),this},fail:function(t,e){return this._runOrStore(function(){this.hasErrors()&&t.apply(e,this.getErrors())}),this},handler:function(t,e){return this._runOrStore(function(){t.apply(e,this.getErrors())}),this},chain:function(t){return this.addCriteria(t.handler,t),this},when:function(t){this._runOrStore(function(){this.hasErrors()?t.reject.apply(t,this.getErrors()):t.resolve()})},addError:function(t){this.errors.push(t)},hasErrors:function(){return this.errors.length},getErrors:function(){return this.errors.slice(0)},_process:function(){this.processing=!0,this.needs=this.criteria.length,i.each(this.criteria,this._evaluateCriteria,this)},_evaluateCriteria:function(t){var e=null;i.isArray(t)&&(e=t[1],t=t[0]);try{t.call(e,i.bind(this._criteriaMet,this))}catch(n){this.addError(n)}},_criteriaMet:function(t){t&&this.addError(t),this.met++,this.ready()&&i.each(this.queued,this._run,this)},_runOrStore:function(t){this.processing||this._process(),this.ready()?this._run(t):this.queued.push(t)},_run:function(t){t.call(this)}},e.exports=function(t,e){var n=new r(t);return e&&n.done(e),n}},{"./util.js":15}],15:[function(t,e,n){(function(e){"use strict";function r(t,e){switch(e){case"%d":return parseInt(t,10);case"%j":return t=c.isObject(t)?JSON.stringify(t):t+"",t.length>500&&(t=t.substr(0,500)+".../*truncated*/...}"),t;case"%s":return t+"";default:return t}}function i(t){return c.isObject(t)&&t+""=="[object Arguments]"}function s(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),n=this,r=function(){},i=function(){return n.apply(this instanceof r&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return r.prototype=this.prototype,i.prototype=new r,i}function o(t,e){var n,r;for(n=0,r=this.length;n>>0;if(0===s)return-1;if(n=0,arguments.length>1&&(n=Number(arguments[1]),n!==n?n=0:0!==n&&n!==1/0&&n!==-(1/0)&&(n=(n>0||-1)*Math.floor(Math.abs(n)))),n>=s)return-1;for(r=n>=0?n:Math.max(s-Math.abs(n),0);r0?n.slice(e):n},c.extend=function(t,e){var n=c.toArray(arguments),r="boolean"==typeof n[0]&&n.shift(),i=n.shift();return c.each(n,function(t){c.isObject(t)&&c.each(t,function(t,e){i[e]=r&&c.isObject(i[e])?c.extend(!0,i[e],t):t})}),i},c.bind=function(t,e){var n=Array.prototype.slice.call(arguments,1);return(t.bind||s).apply(t,n)},c.isEmpty=function(t){return t===h||null===t||c.isArrayLike(t)&&0===t.length||c.isObject(t)&&!c.contains(t,function(t){return!0})},c.keys=function(t){var e=[];return c.each(t,function(t,n){e.push(n)}),e},c.map=function(t,e,n){var r=[];return c.each(t,function(i,s){var o=e.call(n,i,s,t);o!==h&&r.push(o)}),r},c.mapObject=function(t,e,n){var r={};return c.each(t,function(i,s){var o=e.call(n,i,s,t);o!==h&&(r[s]=o)}),r},c.find=function(t,e,n){if(c.isArrayLike(t)){for(var r=0,i=t.length;r-1;e=function(t){return function(e){return e===t}}(e)}return c.find(t,e,n)!==h},c.each=function(t,e,n){if(c.isArrayLike(t))(t.forEach||o).call(t,e,n);else if(c.isObject(t)){var r;for(r in t)t.hasOwnProperty(r)&&e.call(n,t[r],r,t)}},c.indexOf=function(t,e){return(t.indexOf||u).call(t,e)},c.remove=function(t,e){var n=!1;if(c.isArray(t)){var r=c.indexOf(t,e);r>-1&&(t.splice(r,1),n=!0)}else if(c.isObject(t)){var i;for(i in t)if(t.hasOwnProperty(i)&&e===t[i]){n=!0,delete t[i];break}}return n},c.defer=function(t,e){var n=c.toArray(arguments);setTimeout(c.bind.apply(null,n),0)},c.call=function(t,e){var n=c.toArray(arguments,2),r=[];return c.each(t,function(t){return"function"!=typeof t||e?void(c.isObject(t)&&"function"==typeof t[e]&&r.push(t[e].apply(t,n))):r.push(t.apply(null,n))}),r},c.isEqual=function(t,e,n){if(t===e)return!0;if(typeof t!=typeof e)return!1;if(c.isObject(t)&&c.isObject(e)){var r=c.isArrayLike(t),i=c.isArrayLike(e);if(r||i)return r&&i&&t.length===e.length&&!c.contains(t,function(t,n){ +return!c.isEqual(t,e[n])});var s=n?c.keys(t):c.keys(t).sort(),o=n?c.keys(e):c.keys(e).sort();return c.isEqual(s,o)&&!c.contains(t,function(t,n){return!c.isEqual(t,e[n])})}return!1},c.bindAll=function(t,e){return c.each(e,function(n,r){"function"==typeof n&&(e[r]=c.bind(n,t))}),e},c.printf=function(){var t=c.toArray(arguments),e=t.shift(),n=e.match(/(%s|%d|%j)/g);return n&&t.length&&c.find(n,function(n){return e=e.replace(n,r(t.shift(),n)),0===t.length}),e},c.mergeToString=function(t){return 0===t.length?null:1===t.length?t[0]:"["+t.join("][")+"]"},c.construct=function(t,e){function n(){return t.apply(this,e)}return n.prototype=t.prototype,new n},c.noop=function(){};var l=[];c.isFirebaseRef=function(t){if(!c.isObject(t))return!1;var e=Object.getPrototypeOf(t);return!(!e||e.constructor!==c.Firebase.prototype.constructor)||("function"==typeof t.ref&&"function"==typeof t.ref().transaction||c.isFirebaseRefWrapper(t))},c.isFirebaseRefWrapper=function(t){return c.contains(l,function(e){return t instanceof e})},c.isQueryRef=function(t){return c.isFirebaseRef(t)&&"function"!=typeof t.transaction},c.registerFirebaseWrapper=function(t){l.push(t)},c._mockFirebaseRef=function(t){c.Firebase=t},c.escapeEmail=function(t){return(t||"").replace(".",",")},c.assertValidEvent=function(t){if(!c.contains(f,t))throw new Error("Event must be one of "+f+", but received "+t)},c.inherits=function(t,e){var n=[t.prototype].concat(c.toArray(arguments).slice(2));return t.prototype=new e,t.prototype.constructor=e,c.each(n,function(e){c.each(e,function(e,n){t.prototype[n]=e})}),t.prototype._super=function(){e.apply(this,arguments)},t},c.deepCopy=function(t){if(!c.isObject(t))return t;var e=c.isArrayLike(t)?[]:{};return c.each(t,function(t,n){e[n]=c.deepCopy(t)}),e},c.pick=function(t,e){if(!c.isObject(t))return{};var n=c.isArrayLike(t)?[]:{};return c.each(e,function(e){n[e]=t[e]}),n},c.eachByPath=function(t,e,n,r){var i={};c.each(e,function(e,n){var r=t.pathFor(n),s=t.getField(n),o=s?s.id:n;c.has(i,r.name())||(i[r.name()]={path:r,data:{}}),i[r.name()].data[o]=e}),c.each(i,function(t){n.call(r,t.path,t.data)})}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{firebase:"firebase"}],"firebase-util":[function(t,e,n){"use strict";var r=t("./common/index.js");r.extend(n,t("./common/exports.js"),t("./Paginate/exports.js")),"undefined"!=typeof window&&(window.hasOwnProperty("Firebase")?window.Firebase.util=r:console.warn("Firebase not found on the global window instance. Cannot add Firebase.util namespace."))},{"./Paginate/exports.js":2,"./common/exports.js":8,"./common/index.js":9}]},{},[1]); \ No newline at end of file diff --git a/dist/firebase-util.js b/dist/firebase-util.js new file mode 100644 index 0000000..7d5bf4b --- /dev/null +++ b/dist/firebase-util.js @@ -0,0 +1,4799 @@ +/*! + * Firebase-util: A set of experimental power tools for Firebase. + * + * Version: 0.2.6 + * URL: https://github.com/firebase/firebase-util + * Date: 2016-11-21T15:10:56.452Z + * License: MIT http://firebase.mit-license.org/ + */ + +require=(function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&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}var i=typeof require=="function"&&require;for(var o=0;odata which + * is flattened for each key. + * + * @param {object} data + * @returns {object} an array containing [ [Path, Object]... ] where each object is a key/value store + */ + denest: function(data) { + var out = {}; + util.each(this.getPathManager().getPaths(), function(p) { + out[p.name()] = { path: p, data: {} }; + }); + + this.forEach(function(field) { + var val = getOut(data, field.alias); + if( val !== util.undef ) { + switch(field.id) { + case '$value': + out[field.pathName].data = val; + break; + case '$key': + // do nothing + break; + default: + out[field.pathName].data[field.id] = val; + } + } + }); + return out; + }, + + idFor: function(fieldName) { + var f = this.getField(fieldName); + if( !f ) { return fieldName; } + return f.id; + } +}; + +FieldMap.key = function(path, field) { + if( typeof path !== 'string' ) { + path = path.name(); + } + return path + '.' + field; +}; + +FieldMap.fieldMap = function(map, fieldName) { + var childPath; + var field = map.getField(fieldName); + if( field ) { + childPath = field.path; + if( field.id !== '$value' ) { + childPath = childPath.child(field.id); + } + } + else { + childPath = map.pathFor(fieldName).child(fieldName); + } + var pm = new PathManager([childPath]); + var fm = new FieldMap(pm); + fm.add({key: FieldMap.key(childPath, '$value'), alias: fieldName}); + return fm; +}; + +/** + * Fetch a list of paths suitable for use in a Record. + * + * @param {FieldMap} map to be copied + * @param {string} recordId the push id for the record + * @returns {FieldMap} a copy of the field map with paths ajusted down to the child node + */ +FieldMap.recordMap = function(map, recordId) { + var mgr = map.getPathManager(); + var paths = util.map(mgr.getPaths(), function(p) { + return p.normChild(recordId); + }); + var childMap = new FieldMap(new PathManager(paths)); + map.forEach(function(field) { + childMap.add({key: field.key, alias: field.alias}); + }); + return childMap; +}; + +function getDepField(fields, dep) { + return util.find(fields, function(field) { + return field.pathName === dep.path && field.id === dep.field; + }) || null; +} + +function Field(props) { + // these properties are considered public and accessed directly by other classes + this.path = props.path; + this.id = props.id; + this.key = props.key; + this.alias = props.alias; + this.url = props.url; + this.pathName = props.pathName; + this.isNested = props.alias.indexOf('.') >= 0; +} + +function parseProps(propsRaw, pathMgr) { + if( propsRaw instanceof Field ) { + return util.pick(propsRaw, ['path', 'id', 'key', 'alias', 'pathName', 'url']); + } + else if( typeof(propsRaw) === 'string' ) { + propsRaw = { key: propsRaw }; + } + var parts = propsRaw.key.split('.'); + var path = pathMgr.getPath(parts[0]); + return { + pathName: parts[0], + id: parts[1], + key: propsRaw.key, + alias: propsRaw.alias || parts[1], + path: path, + //todo-dynamic-keys this isn't correct for dynamic fields :( + //todo-dynamic-keys and probably not for $value and $key either + url: path? path.url() + '/' + parts[1] : null + }; +} + +function putIn(data, alias, val) { + if( val === null ) { return; } + if( alias.indexOf('.') > 0 ) { + var parts = alias.split('.').reverse(), p; + while(parts.length > 1 && (p = parts.pop())) { + data = data[p] = util.has(data, p)? data[p] : {}; + } + alias = parts.pop(); + } + data[alias] = val; +} + +function getOut(data, alias) { + var key = alias; + if( !util.isObject(data) ) { return util.undef; } + var val = data[alias]; + if( alias.indexOf('.') > 0 ) { + var parts = alias.split('.').reverse(); + val = data; + while(parts.length) { + key = parts.pop(); + val = util.isObject(val) && val.hasOwnProperty(key)? val[key] : util.undef; + } + } + return val; +} + +module.exports = FieldMap; +},{"../../common":25,"./PathManager":10}],5:[function(require,module,exports){ +'use strict'; + +var util = require('../../common'); + +function Filter() { + this.criteria = []; + util.each(arguments, this.add, this); +} + +Filter.prototype = { + add: function(fn) { + this.criteria.push( + new Condition(fn) + ); + }, + test: function(recordData, key, priority) { + return util.contains(this.criteria, function(cond) { + return !cond.test(recordData, key, priority); + }) === false; + } +}; + +function Condition(fn) { + this.match = fn; +} + +Condition.prototype.test = function(data, key, priority) { + return this.match(data, key, priority) === true; +}; + +module.exports = Filter; +},{"../../common":25}],6:[function(require,module,exports){ +'use strict'; + +var util = require('../../common'); +var PathManager = require('./PathManager'); +var Filter = require('./Filter'); +var FieldMap = require('./FieldMap'); +var NormalizedRef = require('./NormalizedRef'); +var RecordSet = require('./RecordSet'); + +/** + * @param {...object} path + * @constructor + */ +function NormalizedCollection(path) { //jshint unused:vars + assertPaths(arguments); + this.pathMgr = new PathManager(util.toArray(arguments)); + this.map = new FieldMap(this.pathMgr); + this.filters = new Filter(); + this.finalized = false; +} + +NormalizedCollection.prototype = { + select: function(fieldName) { //jshint unused:vars + assertNotFinalized(this, 'select'); + var args = util.args('NormalizedCollection.select', arguments, 1); + util.each(args.restAsList(0, ['string', 'object']), function(f) { + assertValidField(f); + this.map.add(f); + }, this); + return this; + }, + + filter: function(matchFn) { //jshint unused:vars + assertNotFinalized(this, 'filter'); + var args = util.args('NormalizedCollection.filter', arguments, 1, 1); + this.filters.add( + args.nextReq('function') + ); + return this; + }, + + ref: function() { + if( !this.map.length ) { + throw new Error('Must call select() with at least one field' + + ' before creating a ref'); + } + this.finalized = true; + if( util.log.isInfoEnabled() ) { + util.log.info('NormalizedRef created using %s', buildDebugString(this)); + } + var recordSet = new RecordSet(this.map, this.filters); + return new NormalizedRef(recordSet); + } +}; + +function assertPaths(args) { + if( args.length < 1 ) { + throw new Error('Must provide at least one path definition'); + } + function notValidRef(p) { + if( util.isArray(p) ) { + p = p[0]; + } + return !util.isFirebaseRef(p); + } + if( util.contains(args, notValidRef) ) { + throw new Error('Each argument to the NormalizedCollection constructor must be a ' + + 'valid Firebase reference or an Array containing a Firebase ref as the first argument'); + } +} + +function assertNotFinalized(self, m) { + if( self.finalized ) { + throw new Error('Cannot call ' + m + '() after ref() has been invoked'); + } +} + +function buildDebugString(nc) { + var paths = []; + var selects = []; + var filter = ''; + + util.each(nc.pathMgr.getPaths(), function(p) { + var dep = p.getDependency(); + paths.push( + util.printf('\t"%s%s"%s', + p.url(), + p.id() === p.name()? '' : ' as ' + p.name(), + dep? '-> ' + dep.path + '.' + dep.field : '' + ) + ); + }); + + nc.map.forEach(function(f) { + selects.push(util.printf('"%s%s"', f.key, f.alias === f.id? '' : ' as ' + f.alias)); + if( selects.length % 5 === 0 ) { + selects.push('\n'); + } + }); + + if(nc.filters.criteria.length > 0) { + filter = util.printf('<%s filters applied>', nc.filters.criteria.length); + } + + return util.printf('NormalizedCollection(\n%s\n).select(%s)%s.ref()', paths.join('\n'), selects.join(', '), filter); +} + +function assertValidField(f) { + var k; + if( typeof f === 'string' ) { + k = f; + } + else { + k = util.has(f, 'key')? f.key : util.undef; + } + if( typeof k !== 'string' || k.indexOf('.') <= 0 ) { + throw new Error('Each field passed to NormalizedCollection.select() must either be a string ' + + 'in the format "pathAlias.fieldId", or an object in the format ' + + '{key: "pathAlias.fieldId", alias: "any_name_for_field"}, but I received ' + JSON.stringify(f)); + } +} + +module.exports = NormalizedCollection; +},{"../../common":25,"./FieldMap":4,"./Filter":5,"./NormalizedRef":7,"./PathManager":10,"./RecordSet":14}],7:[function(require,module,exports){ +'use strict'; + +var util = require('../../common'); +var Query = require('./Query'); + +function NormalizedRef(record, parent) { + this._super(this, record); + this._parent = parent||null; + this._key = record.getName(); + this._toString = record.getUrl(); +} + +util.inherits(NormalizedRef, Query, { + 'child': function(fieldName) { + var parts = fieldName.split('/').reverse(); // pop is faster than shift + var parent = this; + var ref = this; + while(parts.length) { + var key = parts.pop(); + ref = new NormalizedRef(ref.$getRecord().makeChild(key), parent); + parent = ref; + } + return ref; + }, + + 'parent': function() { + return this._parent; + }, + + 'root': function() { + var p = this; + while(p.parent() !== null) { + p = p.parent(); + } + return p; + }, + + /** @deprecated */ + 'name': function() { + console.warn('The name() function has been deprecated. Use key() instead.'); + return this.key(); + }, + + 'key': function() { + return this._key; + }, + + 'toString': function() { + return this._toString; + }, + + //todo have set, update, push, remove attempt to revert any partial commits + //todo by running a transaction and, if the value is still the "new" partial + //todo value, then revert it to the old complete value + + 'set': function(data, callback, context) { + this.$getRecord().saveData(data, {callback: callback, context: context, isUpdate: false}); + }, + + 'update': function(data, callback, context) { + this.$getRecord().saveData(data, {callback: callback, context: context, isUpdate: true}); + }, + + 'remove': function(callback, context) { + this.$getRecord().saveData(null, {callback: callback, context: context, isUpdate: false}); + }, + + 'push': function(data, callback, context) { // jshint unused:false + var uid = this.$getMaster().push().key(); + var child = this.child(uid); + if( arguments.length ) { + child.set.apply(child, arguments); + } + return child; + }, + + 'setWithPriority': function(data, priority, callback, context) { + this.$getRecord().saveData(data, { + callback: callback, context: context, isUpdate: false, priority: priority + }); + }, + + 'setPriority': function(priority, callback, context) { + this.$getMaster().setPriority(priority, callback, context); + }, + + /**************************** + * WRAPPER FUNCTIONS + ****************************/ + 'auth': wrapMaster('auth'), + 'unauth': wrapMaster('unauth'), + 'authWithCustomToken': wrapMaster('authWithCustomToken'), + 'authAnonymously': wrapMaster('authAnonymously'), + 'authWithPassword': wrapMaster('authWithPassword'), + 'authWithOAuthPopup': wrapMaster('authWithOAuthPopup'), + 'authWithOAuthRedirect': wrapMaster('authWithOAuthRedirect'), + 'authWithOAuthToken': wrapMaster('authWithOAuthToken'), + 'getAuth': wrapMaster('getAuth'), + 'onAuth': wrapMaster('onAuth'), + 'offAuth': wrapMaster('offAuth'), + 'createUser': wrapMaster('createUser'), + 'changePassword': wrapMaster('changePassword'), + 'removeUser': wrapMaster('removeUser'), + 'resetPassword': wrapMaster('resetPassword'), + 'changeEmail': wrapMaster('changeEmail'), + + 'goOffline': wrapAll('goOffline'), + 'goOnline': wrapAll('goOnline'), + + /**************************** + * UNSUPPORTED FUNCTIONS + ***************************/ + 'transaction': notSupported('transaction'), //todo use field map to pick fields and apply to each + 'onDisconnect': notSupported('onDisconnect') //todo use field map to pick fields and apply to each +}); + +function wrapAll(method) { + return function() { + var args = util.toArray(arguments); + util.each(this.$getPaths(), function(p) { + var ref = p.ref(); + ref[method].apply(ref, args); + }); + }; +} + +function wrapMaster(method) { + return function() { + var args = util.toArray(arguments); + var ref = this.$getMaster(); + return ref[method].apply(ref, args); + }; +} + +function notSupported(method) { + return function() { + throw new Error(method + ' is not supported for NormalizedCollection references. ' + + 'Try calling it on the original reference used to create the NormalizedCollection instead.'); + }; +} + +module.exports = NormalizedRef; +},{"../../common":25,"./Query":11}],8:[function(require,module,exports){ +'use strict'; + +var util = require('../../common'); + +function NormalizedSnapshot(ref, snaps) { + this._ref = ref; + this._rec = ref.$getRecord(); + if( !util.isArray(snaps) ) { + throw new Error('Must provide an array of snapshots to merge'); + } + this._pri = this._rec.getPriority(snaps); + this._snaps = snaps; +} + +NormalizedSnapshot.prototype = { + val: function() { + if( !this._snaps.length ) { + return null; + } + return this._rec.mergeData(this._snaps, false); + }, + + child: function(key) { + var snap; + // keys may contain / to separate nested child paths + // so make a list of child keys (we reverse it once + // as this is faster than unshift() on each iteration) + var childParts = key.split('/').reverse(); + // grab the first key and get the child snapshot + var firstChildName = childParts.pop(); + snap = new NormalizedSnapshot( + this._ref.child(firstChildName), + this._rec.getChildSnaps(this._snaps, firstChildName) + ); + // iterate any nested keys and keep calling child on them + while(childParts.length) { + snap = snap.child(childParts.pop()); + } + return snap; + }, + + forEach: function(cb, context) { + return this._rec.forEachKey(this._snaps, function(childId, childAlias) { + if( childId === '$value' || childId === '$key' ) { return false; } + return cb.call(context, this.child(childAlias)); + }, this); + }, + + hasChild: function(key) { + //todo optimize and/or memoize? + var parts = key.split('/').reverse(); + var res = parts.length > 0; + var nextRef = this._ref; + var nsnap = this; + while(res && parts.length) { + var nextKey = parts.pop(); + res = nextRef.$getRecord().hasChild(nsnap._snaps, nextKey); + if( res && parts.length ) { + nextRef = nextRef.child(nextKey); + nsnap = nsnap.child(nextKey); + } + } + return res; + }, + + /** + * Returns true if this snapshot has any child data. Does not return true for $key or $value + * fields. + * + * @returns {boolean} + */ + hasChildren: function() { + // if there are any keys to iterate, and that key is not $key or $value + // then we have children + return this._rec.forEachKey(this._snaps, function(id) { + return id !== '$key' && id !== '$value'; + }); + }, + + /** @deprecated */ + name: function() { + console.warn('name() has been deprecated. Use key() instead.'); + return this.key(); + }, + + key: function() { + return this._rec.getName(); + }, + + numChildren: function() { + //todo-bug does not account for nested aliases (they will change the count here) + var ct = 0; + this._rec.forEachKey(this._snaps, function(id) { + if( id !== '$key' && id !== '$value' ) { ct++; } + }); + return ct; + }, + + ref: function() { return this._ref.ref(); }, + + getPriority: function() { return this._pri; }, + + exportVal: function() { + return this._rec.mergeData(this._snaps, true); + }, + + exists: function() { + return this.val() !== null; + } +}; + +module.exports = NormalizedSnapshot; +},{"../../common":25}],9:[function(require,module,exports){ +'use strict'; + +var util = require('../../common'); + +function Path(pathProps, parent) { + var props = parseProps(pathProps); + this._ref = props.ref; + this._alias = props.alias; + this._dep = props.dep; + this._parent = parent || null; +} + +Path.prototype = { + ref: function() { return this._ref; }, + reff: function() { return this.ref().ref(); }, + child: function(key) { + return new Path(this.reff().child(key), this); + }, + normChild: function(key) { + var dep = this.getDependency(); + if( dep !== null ) { + return new Path([this.reff(), this.name(), dep.path+'.'+dep.field], this); + } + else { + return new Path([this.reff().child(key), this.name()], this); + } + }, + hasDependency: function() { + return this._dep !== null; + }, + getDependency: function() { + return this._dep; + }, + url: function() { return this.reff().toString(); }, + name: function() { return this._alias; }, + id: function() { return this.reff().key(); }, + parent: function() { return this._parent; }, + clone: function() { + return new Path([this._ref, this._alias, this._dep], this._parent); + } +}; + +function parseProps(props) { + var ref, alias, dep = null; + if( util.isArray(props) ) { + ref = props[0]; + alias = props[1]; + dep = props[2]; + } + else if( util.isFunction(props.ref) ) { + ref = props.ref(); + } + else { + ref = props; + } + return { + ref: ref, alias: alias||ref.key(), dep: parseDep(dep) + }; +} + +function parseDep(dep) { + if(util.isObject(dep) ) { + return dep; + } + else if( dep ) { + var parts = dep.split('.'); + return { path: parts[0], field: parts[1] }; + } + return null; +} + +module.exports = Path; +},{"../../common":25}],10:[function(require,module,exports){ +'use strict'; + +var Path = require('./Path'); +var util = require('../../common'); + +function PathManager(paths) { + this.paths = []; + this.pathsByUrl = {}; + this.deps = {}; + this.pathNames = []; + util.each(paths, this.add, this); +} + +PathManager.prototype = { + add: function(pathProps) { + var path = pathProps instanceof Path? pathProps.clone() : new Path(pathProps); + if( !this.paths.length && path.hasDependency() ) { + throw new Error('The master path (i.e. the first) may not declare a dependency.' + + ' Perhaps you have put the wrong path first in the list?'); + } + if( util.has(this.pathsByUrl, path.url()) ) { + throw new Error('Duplicate path: ' + path.url()); + } + if( util.contains(this.pathNames, path.name()) ) { + throw new Error('Duplicate path name. The .key() value for each path must be unique, or you ' + + 'can give each a path an alias by using [firebaseRef, alias] in the constructor. The aliases ' + + 'must also be unique.'); + } + this._map(path); + this.paths.push(path); + this.pathsByUrl[path.url()] = path.name(); + this.pathNames.push(path.name()); + }, + + count: function() { + return this.paths.length; + }, + + first: function() { + return this.paths[0]; + }, + + getPath: function(pathName) { + return util.find(this.paths, function(p) { + return p.name() === pathName; + })||null; + }, + + getPathFor: function(url) { + var n = this.getPathName(url); + return n? this.getPath(n) : null; + }, + + getPaths: function() { + return this.paths.slice(); + }, + + getPathName: function(url) { + return this.pathsByUrl[url] || null; + }, + + getPathNames: function() { + return this.pathNames.slice(); + }, + + getUrls: function() { + return util.keys(this.pathsByUrl); + }, + + //todo remove? + getDependencyGraph: function() { + return util.extend(true, this.deps); + }, + + _map: function(path) { + var first = this.first(); + var dep = path.getDependency(); + if( !dep && first ) { + dep = { path: first.name(), field: '$key' }; + } + if( dep ) { + this.deps[path.name()] = dep; + this._assertNotCircularDep(path.name()); + } + }, + + _assertNotCircularDep: function(pathName) { + var map = [pathName], dep = this.deps[pathName]; + while(util.isDefined(dep)) { + var p = dep.path; + if(util.contains(map, p)) { + map.push(p); // adds it into the error message chain + throw new Error('Circular dependencies in paths: ' + depChain(map, this.deps)); + } + map.push(p); + dep = util.val(this.deps, p); + } + } +}; + +function depChain(map, deps) { + return util.map(map, function(p) { + return deps[p].path + '.' + deps[p].field; + }).join(' >> '); +} + +module.exports = PathManager; +},{"../../common":25,"./Path":9}],11:[function(require,module,exports){ +'use strict'; + +var util = require('../../common'); +var Transmogrifier = require('./Transmogrifier'); + +function Query(ref, record) { + var self = this; + self._ref = ref; + self._rec = record; + // necessary because util.inherit() can only call classes with an empty constructor + // so we can't depend on the params existing for that call + if( record ) { record.setRef(self); } //todo don't like this here, is awkward coupling +} + +Query.prototype = { + 'on': function(event, callback, cancel, context) { + if( arguments.length === 3 && util.isObject(cancel) ) { + context = cancel; + cancel = util.undef; + } + + function cancelHandler(err) { + if( typeof(cancel) === 'function' && err !== null ) { + cancel.call(context, err); + } + } + + this.$getRecord().watch(event, callback, cancelHandler, context); + return callback; + }, + + 'once': function(event, callback, cancel, context) { + var self = this; + if( arguments.length === 3 && util.isObject(cancel) ) { + context = cancel; + cancel = util.undef; + } + function successHandler(snap) { + self.off(event, successHandler); + callback.call(context, snap); + } + + function cancelHandler(err) { + if( typeof(cancel) === 'function' && err !== null ) { + cancel.call(context, err); + } + } + + return this.on(event, successHandler, cancelHandler); + }, + + 'off': function(event, callback, context) { + this.$getRecord().unwatch(event, callback, context); + }, + + /************************************ + * Wrapped functions + ************************************/ + + 'orderByChild': function() { + return this.$replicate('orderByChild', util.toArray(arguments)); + }, + + 'orderByKey': function() { + return this.$replicate('orderByKey', util.toArray(arguments)); + }, + + 'orderByValue': function() { + return this.$replicate('orderByValue', util.toArray(arguments)); + }, + + 'orderByPriority': function() { + return this.$replicate('orderByPriority', util.toArray(arguments)); + }, + + 'limitToFirst': function() { + return this.$replicate('limitToFirst', util.toArray(arguments)); + }, + + 'limitToLast': function() { + return this.$replicate('limitToLast', util.toArray(arguments)); + }, + + /** @deprecated */ + 'limit': function() { + return this.$replicate('limit', util.toArray(arguments)); + }, + + 'startAt': function() { + return this.$replicate('startAt', util.toArray(arguments)); + }, + + 'endAt': function() { + return this.$replicate('endAt', util.toArray(arguments)); + }, + + 'equalTo': function() { + return this.$replicate('equalTo', util.toArray(arguments)); + }, + + 'ref': function() { return this._ref; }, + + /**************************** + * PACKAGE FUNCTIONS (not API) + ***************************/ + + /** @returns {Record} */ + '$getRecord': function() { return this._rec; }, + + /** @return {Firebase} */ + '$getMaster': function() { return this._rec.getPathManager().first().ref(); }, + + /** @return {Array} */ + '$getPaths': function() { return this._rec.getPathManager().getPaths(); }, + + /** + * @param {String} method + * @param {Array|arguments} args + * @returns {Query} + */ + '$replicate': function(method, args) { + var rec = this.$getRecord(); + var ref = this.$getMaster(); + ref = ref[method].apply(ref, args); + return new Query(this._ref, Transmogrifier.replicate(rec, ref)); + } +}; + +util.registerFirebaseWrapper(Query); +module.exports = Query; +},{"../../common":25,"./Transmogrifier":17}],12:[function(require,module,exports){ +'use strict'; + +var FieldMap = require('./FieldMap'); +var RecordField = require('./RecordField'); +var AbstractRecord = require('./AbstractRecord'); +var SnapshotFactory = require('./SnapshotFactory'); +var util = require('../../common'); + +function Record(fieldMap) { + var name = fieldMap.getPathManager().first().id(); + var url = util.mergeToString(fieldMap.getPathManager().getUrls()); + this._super(fieldMap, name, url); + this._eventManagers = {}; + util.log.debug('Record created', this.getName(), this.getUrl()); +} + +util.inherits(Record, AbstractRecord, { + makeChild: function(key) { + var fm = FieldMap.fieldMap(this.getFieldMap(), key); + return new RecordField(fm); + }, + + hasChild: function(snaps, key) { + var field = this.getFieldMap().getField(key); + if( !field ) { return false; } + var snap = this.getFieldMap().snapFor(snaps, key); + return snap !== null && snap.hasChild(key); + }, + + getChildSnaps: function(snaps, fieldName) { + var child; + var snap = this.getFieldMap().snapFor(snaps, fieldName); + var field = this.getFieldMap().getField(fieldName); + if( !field ) { + child = snap.child(fieldName); + } + else { + switch(field.id) { + case '$key': + throw new Error('Cannot get child snapshot from key (not a real child element)'); + case '$value': + child = snap; + break; + default: + child = snap.child(field.id); + } + } + return [child]; + }, + + /** + * Given a list of snapshots to iterate, returns the valid keys + * which exist in both the snapshots and the field map, in the + * order they should be iterated. + * + * Calls iterator with a {string|number} key for the next field to + * iterate only. + * + * If iterator returns true, this method should abort and return true, + * otherwise it should return false (same as Snapshot.forEach). + * + * @param {Array} snaps + * @param {function} iterator + * @param {object} [context] + * @return {boolean} true if aborted + * @abstract + */ + forEachKey: function(snaps, iterator, context) { + function shouldIterate(snap, fieldId) { + switch(fieldId) { + case '$key': + return true; + case '$value': + return snap && snap.val() !== null; + default: + return snap && snap.hasChild(fieldId); + } + } + var map = this.getFieldMap(); + return map.forEach(function(field) { + var snap = map.snapFor(snaps, field.alias); + if( shouldIterate(snap, field.id) ) { + return iterator.call(context, field.id, field.alias) === true; + } + return false; + }); + }, + + /** + * Merge the data by iterating the snapshots in reverse order + * so that keys from later paths do not overwrite keys from earlier paths + * + * @param {Array} snaps list of snapshots to be merged + * @param {boolean} isExport true if exportVal() was called + * @returns {Object} + */ + mergeData: function(snaps, isExport) { + var data = null, map = this.getFieldMap(); + + // if the master path is null, the record does not exist + // so we do not add any data + if( snaps.length > 0 && snaps[0].val() !== null ) { + data = util.extend.apply(null, util.map(snaps, function(ss) { + return map.extractData(ss, isExport); + })); + + if( isExport && data !== null && snaps[0].getPriority() !== null ) { + if( !util.isObject(data) ) { + data = {'.value': data}; + } + data['.priority'] = snaps[0].getPriority(); + } + } + + return data; + }, + + getPriority: function(snaps) { + return snaps[0].getPriority(); + }, + + getClass: function() { return Record; }, + + saveData: function(data, props) { + var q = util.queue(); + var map = this.getFieldMap(); + var paths = this.getPathManager().getPaths(); + if( props.isUpdate && !util.isObject(data) ) { + throw new Error('First argument to update() command must be an object'); + } + if( data === null ) { + util.each(paths, function(p) { + if( !p.hasDependency() ) { + p.reff().remove(q.getHandler()); + } + }); + } + else if(util.isObject(data)) { + var denestedData = map.denest(data); + util.each(denestedData, function(parts) { + var path = parts.path; + var dataForPath = parts.data; + var ref = this._writeRef(denestedData, path); + if( ref !== null ) { + if( !util.isEmpty(dataForPath) || !props.isUpdate ) { + if( !util.isObject(dataForPath) ) { + dataForPath = {'.value': dataForPath}; + } + if( !props.isUpdate ) { + addEmptyFields(map, path, dataForPath); + } + if( util.isDefined(props.priority) ) { + dataForPath['.priority'] = props.priority; + } + if( util.has(dataForPath, '.value') ) { + ref.set(dataForPath, q.getHandler()); + } + else { + ref.update(dataForPath, q.getHandler()); + } + } + } + else { + util.log.info('No dynamic key found for master', paths[0].ref().toString(), 'with dynamic path', path.ref().toString()); + } + }, this); + } + else if( paths.length === 1 ) { + if( util.isDefined(props.priority) ) { + paths[0].ref().setWithPriority(data, props.priority, q.getHandler()); + } + else { + paths[0].ref().set(data, q.getHandler()); + } + } + else { + throw new Error('Cannot set multiple paths to a non-object value. ' + + 'Since this is a NormalizedCollection, the data will be split between the paths. ' + + 'But I can\'t split a primitive value'); + } + q.handler(props.callback||util.noop, props.context); + }, + + getName: function() { + return this._name; + }, + + getUrl: function() { + return this._url; + }, + + _start: function(event) { + if( !util.has(this._eventManagers, event) ) { + util.log.debug('Record._start: event=%s, url=%s', event, this.getUrl()); + this._eventManagers[event] = event === 'value'? + new ValueEventManager(this) : new ChildEventManager(event, this); + } + this._eventManagers[event].start(); + }, + + _stop: function(event) { + if (util.has(this._eventManagers, event)) { + util.log.debug('Record._stop: event=%s, url=%s', event, this.getUrl()); + this._eventManagers[event].stop(); + } + }, + + _writeRef: function(denestedData, path) { + var ref = path.reff(); + var dep = path.getDependency(); + if( dep !== null ) { + var depPath = this.getPathManager().getPath(dep.path); + var key = this._depKey(denestedData, depPath, dep.field); + ref = key === null? null : ref.child(key); + } + return ref; + }, + + _depKey: function(denestedData, path, fieldId) { + var key; + var dat = denestedData[path.name()].data; + switch(fieldId) { + case '$key': + key = path.id(); + break; + case '$value': + key = util.has(dat, '.value')? dat['.value'] : util.isEmpty(dat)? null : dat; + break; + default: + key = util.has(dat, fieldId)? dat[fieldId] : null; + } + var type = typeof key; + if( key !== null && type !== 'string' ) { + throw new Error( + 'Dynamic key values must be a string. Type was ' + + type + ' for ' + path.ref().toString() + '->' + fieldId + ); + } + return key; + } +}); + +function ValueEventManager(rec) { + this.rec = rec; + this.pm = rec.getPathManager(); + this.running = false; + this._init(); +} + +ValueEventManager.prototype = { + start: function() { + if( !this.running ) { + this.running = true; + util.each(this.pm.getPathNames(), this._startPath, this); + } + }, + + stop: function() { + if( this.running ) { + this.running = false; + util.each(this.subs, function(fn) { + fn(); + }); + this._init(); + } + }, + + update: function(pathName, snap) { + this.snaps[pathName] = snap; + this._checkLoadState(); + util.log('Record.ValueEventManager.update: url=%s, loadCompleted=%s', snap.ref().toString(), this.loadCompleted); + if( this.loadCompleted ) { + this.rec.trigger(new SnapshotFactory('value', this.rec.getName(), util.toArray(this.snaps))); + } + }, + + _startPath: function(pathName) { + var self = this; + var path = self.pm.getPath(pathName); + var fn = util.bind(self.update, self, pathName); + if( path.hasDependency() ) { + var dyno = new Dyno(path, this.rec.getFieldMap(), 'value', fn); + this.subs.push(dyno.dispose); + } + else { + path.ref().on('value', fn); + self.subs.push(function() { + path.ref().off('value', fn); + }); + } + }, + + _checkLoadState: function() { + if( this.loadCompleted ) { return; } + var snaps = this.snaps; + var pathNames = this.pm.getPathNames(); + this.loadCompleted = !util.contains(pathNames, function(p) { + return !snaps.hasOwnProperty(p); + }); + }, + + _init: function() { + this.loadCompleted = false; + this.snaps = {}; + this.subs = []; + } +}; + +function ChildEventManager(event, rec) { + this.event = event; + this.rec = rec; + this.map = rec.getFieldMap(); + this.pm = rec.getPathManager(); + this.subs = []; + this.dyno = null; +} + +ChildEventManager.prototype = { + start: function() { + util.each(this.pm.getPathNames(), function(pathName) { + var event = this.event; + var path = this.pm.getPath(pathName); + var fn = util.bind(this.update, this); + if( path.hasDependency() ) { + this.dyno = new Dyno(path, this.map, event, fn); + this.subs.push(this.dyno.dispose); + } + else { + path.ref().on(event, fn); + this.subs.push(function() { + path.ref().off(event, fn); + }); + } + }, this); + }, + + stop: function() { + util.each(this.subs, function(fn) { + fn(); + }); + this.subs = []; + }, + + update: function(snap, prev) { + if( snap !== null && this.map.aliasFor(snap.ref().toString()) !== null ) { + util.log('Record.ChildEventManager.update: event=%s, key=%s/%s', this.event, snap.ref().parent().key(), snap.key()); + this.rec.trigger(new SnapshotFactory(this.event, snap.key(), snap, prev)); + } + } +}; + +/** + * Process a path which depends on the value of another field. We have + * to monitor the field it depends on for value events and update + * the ref that we listen on whenever the id is modified. + * + * @param {Path} path + * @param {FieldMap } fieldMap + * @param {string} event + * @param {function} updateFn + * @constructor + */ +function Dyno(path, fieldMap, event, updateFn) { + var dep = path.getDependency(); + var depPath = fieldMap.getPath(dep.path); + var depRef = depPath.ref(); + if( dep.field === '$key' ) { + throw new Error('Dynamic paths do not support $key (you should probably just join on this path)'); + } + if( dep.field !== '$value' ) { + depRef = depRef.child(dep.field); + } + var ref; + + // establish our listener at the field which contains the id of our ref + var depFn = depRef.on('value', function(snap) { + if( ref && ref.key() !== snap.val() ) { + util.log.debug('Record.Dyno: stopped monitoring %s', ref.toString()); + // any time the id changes, remove the old listener + ref.off(event, updateFn); + updateFn(null); + } + if( snap.val() !== null ) { + // establish our listener at the correct dynamic id for values + ref = path.ref().child(snap.val()); + ref.on(event, updateFn); + util.log('Record.Dyno: monitoring %s', ref.toString()); //debug + } + }); + + // create a dispose method that can turn off all our event listeners + // when _stop is called + this.dispose = function() { + depRef.off('value', depFn); + if( ref ) { + ref.off(event, updateFn); + } + }; +} + +function addEmptyFields(map, path, dataToSave) { + util.each(map.fieldsFor(path.name()), function(f) { + switch(f.id) { + case '$key': + // ignore key + break; + case '$value': + if( !util.has(dataToSave, '.value') ) { + dataToSave['.value'] = null; + } + break; + default: + if( !util.has(dataToSave, f.id) ) { + dataToSave[f.id] = null; + } + } + }); +} + +module.exports = Record; +},{"../../common":25,"./AbstractRecord":3,"./FieldMap":4,"./RecordField":13,"./SnapshotFactory":16}],13:[function(require,module,exports){ +'use strict'; + +var PathManager = require('./PathManager'); +var FieldMap = require('./FieldMap'); +var AbstractRecord = require('./AbstractRecord'); +var SnapshotFactory = require('./SnapshotFactory'); +var util = require('../../common'); + +function RecordField(fieldMap) { + this.handlers = {}; + this.path = fieldMap.getPathManager().first(); + this._super(fieldMap, this.path.name(), this.path.url()); + if( fieldMap.getPathManager().count() !== 1 ) { + throw new Error('RecordField must have exactly one path, but we got '+ fieldMap.getPathManager().count()); + } + if( fieldMap.length !== 1 ) { + throw new Error('RecordField must have exactly one field, but we found '+ fieldMap.length); + } + util.log.debug('RecordField created', this.getName(), this.getUrl()); +} + +util.inherits(RecordField, AbstractRecord, { + makeChild: function(key) { + var pm = new PathManager([this.path.child(key)]); + var fm = new FieldMap(pm); + fm.add({key: FieldMap.key(pm.first(), '$value'), alias: key}); + return new RecordField(fm); + }, + + hasChild: function(snaps, key) { + return snaps[0].hasChild(key); + }, + + getChildSnaps: function(snaps, fieldName) { + // there is exactly one snap and there are no aliases to deal with + return [snaps[0].child(fieldName)]; + }, + + /** + * There is nothing to merge at this level because there is only one + * path and no field map + * + * @param {Array} snaps list of snapshots to be merged + * @param {boolean} isExport true if exportVal() was called + * @returns {Object} + */ + mergeData: function(snaps, isExport) { + return isExport? snaps[0].exportVal() : snaps[0].val(); + }, + + getPriority: function(snaps) { + return snaps[0].getPriority(); + }, + + /** + * Iterates all keys of snapshot. + * + * Calls iterator with a {string|number} key for the next field to + * iterate only. + * + * If iterator returns true, this method should abort and return true, + * otherwise it should return false (same as Snapshot.forEach). + * + * @param {Array} snaps + * @param {function} iterator + * @param {object} [context] + * @return {boolean} true if aborted + * @abstract + */ + forEachKey: function(snaps, iterator, context) { + var firstSnap = snaps[0]; + return firstSnap.forEach(function(ss) { + iterator.call(context, ss.key(), ss.key()); + }); + }, + + saveData: function(data, opts) { + var ref = this.path.ref(); + if( opts.isUpdate ) { + if( !util.isObject(data) ) { + throw new Error('When using update(), the data must be an object.'); + } + if( util.has(opts, 'priority') ) { + data['.priority'] = opts.priority; + } + ref.update(data, wrapCallback(opts)); + } + else if( util.has(opts, 'priority') ) { + ref.setWithPriority(data, opts.priority, wrapCallback(opts)); + } + else { + ref.set(data, wrapCallback(opts)); + } + }, + + getClass: function() { return RecordField; }, + + _start: function(event) { + var self = this; + this.handlers[event] = function(snap, prev) { + self.trigger(new SnapshotFactory(event, snap.key(), snap, prev)); + }; + this.path.ref().on(event, this.handlers[event], this._cancel, this); + }, + + _stop: function(event) { + if( this.handlers.hasOwnProperty(event) ) { + this.path.ref().off(event, this.handlers[event], this); + } + } +}); + +function wrapCallback(opts) { + if( opts.callback ) { + return function() { + opts.callback.apply(opts.context, arguments); + }; + } + else { + return util.noop; + } +} + +module.exports = RecordField; +},{"../../common":25,"./AbstractRecord":3,"./FieldMap":4,"./PathManager":10,"./SnapshotFactory":16}],14:[function(require,module,exports){ +'use strict'; + +var Record = require('./Record'); +var AbstractRecord = require('./AbstractRecord'); +var util = require('../../common'); +var FieldMap = require('./FieldMap'); +var RecordSetEventManager = require('./RecordSetEventManager'); + +/** + * A "Record" (see AbstractRecord) represents a merged set of data used by NormalizedRef. + * It is used by NormalizedRef to create snapshots and monitor Firebase for data changes. + * + * A RecordSet represents the root level of NormalizedCollection's output. It is a list of + * collections (from multiple paths in Firebase) to be joined together. + * + * This is, for the purposes of a NormalizedCollections, the root of the data. Calls to parent() + * from here should return null, just like they would from the root of a Firebase. This is because + * the parent of a normalized collection is ambiguous, so there is no higher level of data. + * + * @param fieldMap this is the field map to be applied to each Record created when calling child() + * @param whereClause this filters the output data and events + * @constructor + */ +function RecordSet(fieldMap, whereClause) { + var name = util.mergeToString(fieldMap.getPathManager().getPathNames()); + var url = util.mergeToString(fieldMap.getPathManager().getUrls()); + + // AbstractRecord makes this observable and abstracts some common impl details + // between RecordSet, Record, and RecordField + this._super(fieldMap, name, url); + + // Used to filter the merged data and determine which merged Records should trigger events and + // which ones should be ignored + this.filters = whereClause; + + // the RecordSetEventManager handles Firebase events and calls event handlers on + // this RecordSet appropriately. See RecordSetEventManager for more details + this.monitor = new RecordSetEventManager(this); +} + +util.inherits(RecordSet, AbstractRecord, { + makeChild: function(key) { + var fm = FieldMap.recordMap(this.getFieldMap(), key); + return new Record(fm); + }, + + /** + * Override AbstractRecord's hasChild since we are dealing + * with record ids at this level. We use the master list to determine + * if records exist, so it's a pretty straightforward hasChild + * on the first snapshot. + * + * @param {Array} snaps a list of array snapshots to test for child + * @param {string} key + */ + hasChild: function(snaps, key) { + return util.contains(snaps, function(s) { + return s.key() === key; + }); + }, + + getChildSnaps: function(snapsArray, recordId) { + return util.filter(snapsArray, function(s) { + return s.key() === recordId; + }); + }, + + /** + * Since the snapshots attached to this level are records, there isn't much + * to do for a merge. Just put them all together. + * + * @param {Array} snaps list of snapshots to be merged + * @param {boolean} isExport true if exportVal() was called + * @returns {Object} + */ + mergeData: function(snaps, isExport) { + var self = this, out = null; + // if the master path is empty, there is no data to be merged + if( snaps.length && snaps[0].val() !== null ) { + out = {}; + util.each(snaps, function(snap) { + if( snap.val() !== null && self.filters.test(snap.val(), snap.key(), snap.getPriority()) ) { + out[snap.key()] = isExport? snap.exportVal() : snap.val(); + } + }); + } + return out; + }, + + getPriority: function() { + return null; + }, + + /** + * Given a list of snapshots to iterate, returns the valid keys + * which exist in both the snapshots and the field map, in the + * order they should be iterated. + * + * Calls iterator with a {string|number} key for the next field to + * iterate only. + * + * If iterator returns true, this method should abort and return true, + * otherwise it should return false (same as Snapshot.forEach). + * + * @param {Array} snaps + * @param {function} iterator + * @param {object} [context] + * @return {boolean} true if aborted + * @abstract + */ + forEachKey: function(snaps, iterator, context) { + snaps.forEach(function(snap) { + return iterator.call(context, snap.key(), snap.key()); + }); + }, + + getClass: function() { return RecordSet; }, + + /** + * Saving a record set is done by grabbing each child record and calling save against that. + * This is the easiest approach since we must distribute fields to each appropriate path. + * It might be more efficient to bulk these into a single write op, and perhaps we should + * explore that if this proves to be slow. + * + * @param data + * @param {Object} opts + */ + saveData: function(data, opts) { + var q = util.queue(); + if( data === null ) { + util.each(this.getPathManager().getPaths(), function(path) { + path.ref().remove(q.getHandler()); + }); + } + else if( !util.isObject(data) ) { + throw new Error('Calls to set() or update() on a NormalizedCollection must pass either ' + + 'null or an object value. There is no way to split a primitive value between the paths'); + } + else { + util.each(data, function(v, k) { + if( k === '.value' || k === '.priority' ) { + throw new Error('Cannot use .priority or .value on the root path of a NormalizedCollection. ' + + 'You probably meant to sort the records anyway (i.e. one level lower).'); + } + this.child(k).saveData(v, {isUpdate: opts.isUpdate, callback: q.getHandler()}); + }, this); + if( opts.priority ) { + this.getPathManager().first().ref().setPriority(opts.priority, q.getHandler()); + } + } + q.handler(opts.callback||util.noop, opts.context); + }, + + /** + * Return the correct child key for a snapshot by determining if its corresponding path + * has dependencies. If so, we look up the id and return that child, otherwise, we just + * return the child for the recordId. + * + * If a dependency exists, but the required field is null or invalid, then we just return + * null in place of the snapshot. + * + * @private + */ + _getChildKey: function(snap, snapsArray, recordId) { + var key = recordId; + var path = this.getPathManager().getPathFor(snap.ref().toString()); + // resolve any dependencies to determine the child key's value + if( path.hasDependency() ) { + var dep = path.getDependency(); + var depPath = this.getFieldMap().getPath(dep.path); + if( !depPath ) { + throw new Error('Invalid dependency path. ' + snap.ref.toString() + + ' depends on ' + dep.path + + ', but that alias does not exist in the paths provided.'); + } + var depSnap = util.find(snapsArray, function(snap) { + return snap.ref().toString() === depPath.url(); + }); + if( depSnap ) { + depSnap = depSnap.child(recordId); + if( dep.field !== '$value' ) { + depSnap = depSnap.child(dep.field); + } + key = depSnap.val(); + } + else { + key = null; + } + } + return key; + }, + + _start: function() { + this.monitor.start(); + }, + + _stop: function(event, count) { + if( count === 0 ) { this.monitor.stop(); } + } +}); + +module.exports = RecordSet; +},{"../../common":25,"./AbstractRecord":3,"./FieldMap":4,"./Record":12,"./RecordSetEventManager":15}],15:[function(require,module,exports){ +'use strict'; + +var SnapshotFactory = require('./SnapshotFactory'); +var util = require('../../common'); + +/** + * Monitors the references attached to a RecordSet and maintains a cache of + * current snapshots (inside RecordList below). Any time there is an update, this calls + * RecordSet.trigger() to notify event listeners. + * + * @param parentRec + * @constructor + */ +function RecordSetEventManager(parentRec) { + var pm = parentRec.getPathManager(); + this.masterRef = pm.first().ref(); + this.url = this.masterRef.toString(); + this.recList = new RecordList(parentRec, this.url); + this.running = false; +} + +RecordSetEventManager.prototype = { + start: function() { + if( !this.running ) { + util.log('RecordSetEventManager: Loading normalized records from master list %s', this.url); + this.running = true; + this.masterRef.on('child_added', this._add, this); + this.masterRef.on('child_removed', this._remove, this); + this.masterRef.on('child_moved', this._move, this); + // make sure all existing keys are loaded into memory before we let recList trigger value events + this.masterRef.once('value', this.recList.masterPathLoaded, this.recList); + } + return this; + }, + + stop: function() { + if( this.running ) { + util.log('RecordSetEventManager: Stopped monitoring master list %s', this.url); + this.running = false; + this.masterRef.off('child_added', this._add, this); + this.masterRef.off('child_removed', this._remove, this); + this.masterRef.off('child_moved', this._move, this); + this.recList.unloaded(); + } + return this; + }, + + _add: function(snap, prevChild) { + this.recList.add(snap.key(), prevChild); + }, + + _remove: function(snap) { + this.recList.remove(snap.key()); + }, + + _move: function(snap, prevChild) { + this.recList.move(snap.key(), prevChild); + } +}; + +function RecordList(observable, url) { + this.obs = observable; + this.url = url; + this._reset(); +} + +RecordList.prototype = { + add: function(key, prevChild) { + util.log.debug('RecordList.add: key=%s, prevChild=%s', key, prevChild); + var rec = this.obs.child(key); + var fn = util.bind(this._valueUpdated, this, key); + this.loading[key] = {rec: rec, prev: prevChild, fn: fn, unwatch: function() { rec.unwatch('value', fn); }}; + if( !this.loadComplete ) { + this.initialKeysLeft.push(key); + } + rec.watch('value', fn); + }, + + remove: function(key) { + util.log.debug('RecordList.remove: key=%s', key); + var oldSnap = this._dropRecord(key); + if( oldSnap !== null ) { + this._notify('child_removed', key, oldSnap); + } + }, + + move: function(key, prevChild) { + if(util.has(this.recs, key)) { + var currPos = util.indexOf(this.recIds, key); + this.recIds.splice(currPos, 1); + this._putAfter(key, prevChild); + this._notify('child_moved', key); + } + }, + + masterPathLoaded: function() { + util.log.debug('RecordList: Initial data has been loaded from master list at %s', this.url); + this.masterLoaded = true; + if( this._checkLoadState() ) { + this._notifyValue(); + } + }, + + unloaded: function() { + this._reset(); + }, + + findKey: function(key) { + return util.indexOf(this.recIds, key); + }, + + _reset: function() { + util.each(this.recs, function(rec, key) { + this.remove(key); + }, this); + util.each(this.filtered, function(rec, key) { + this._dropRecord(key); + }, this); + util.each(this.loading, function(rec, key) { + this._dropRecord(key); + }, this); + this.recs = {}; + this.recIds = []; + this.snaps = {}; + this.loading = {}; + this.filtered = {}; + this.loadComplete = false; + this.initialKeysLeft = []; + this.masterLoaded = false; + }, + + _valueUpdated: function(key, snap) { + var rec; + this.snaps[key] = snap; + if(util.has(this.loading, key)) { + // record has finished loading and merging paths + rec = this.loading[key]; + delete this.loading[key]; + this._processAdd(snap, rec); + } + else if(util.has(this.recs, key)) { + rec = this.recs[key]; + this._processChange(snap, rec); + } + else if(util.has(this.filtered, key)) { + if( snap.val() !== null && this.obs.filters.test(snap.val(), key, snap.getPriority()) ) { + // the record data has changed and it is no longer part of the filtered + // content, so treat it as a newly added record + rec = this.filtered[key]; + delete this.filtered[key]; + util.log('RecordList: Unfiltered key %s', key); + this._processAdd(snap, rec); + } + } + else { + util.log('RecordList: Orphan key %s ignored. Probably a concurrent edit.', key); + } + }, + + _processAdd: function(snap, rec) { + var key = snap.key(); + if( this.obs.filters.test(snap.val(), key, snap.getPriority()) ) { + this.recs[key] = rec; + this._putAfter(key, rec.prev); + this._notify('child_added', key); + } + else { + util.log('RecordList: Filtered key %s', key); + this.filtered[key] = rec; + } + if( this._checkLoadState(key) ) { + this._notifyValue(); + } + }, + + _processChange: function(snap, rec) { + // null records are valid at the record level and can trigger value events, but at + // the Set level, they mean the record is in the process of being deleted so we + // ignore the value event here + if( snap.val() !== null ) { + var key = snap.key(); + if( this.obs.filters.test(snap.val(), key, snap.getPriority()) ) { + // a changed record that has not been filtered + this._notify('child_changed', key); + } + else { + // record changes caused it to become filtered, so treat it as a removed rec + // however, we'll continue to watch it for changes so it can be unfiltered later + delete this.recs[key]; + this.filtered[key] = rec; + this._notify('child_removed', key, this.snaps[key]); + } + } + }, + + _notify: function(event, key, oldSnap) { + var prev; + if( event === 'child_added' || event === 'child_moved' ) { + prev = this._getPrevChild(key); + } + util.log('RecordList._notify: event=%s, key=%s, prev=%s', event, key, prev); + var factory = new SnapshotFactory(event, key, oldSnap||this.snaps[key], prev); + this.obs.trigger(factory); + this._notifyValue(); + }, + + _notifyValue: function() { + if( this.loadComplete ) { + util.log.debug('RecordList._notifyValue: snap_keys=%s', util.keys(this.snaps)); + var factory = new SnapshotFactory('value', null, util.toArray(this.snaps)); + this.obs.trigger(factory); + } + }, + + _getPrevChild: function(key) { + if( !this.recIds.length ) { return null; } + var pos = this.findKey(key); + if( pos === -1 ) { + return this.recIds[this.recIds.length-1]; + } + else if( pos === 0 ) { + return null; + } + else { + return this.recIds[pos-1]; + } + }, + + _posFor: function(prevKey) { + var pos, x; + if( prevKey === null ) { + pos = 0; + } + else { + x = this.findKey(prevKey); + pos = x === -1? this.recIds.length : x+1; + } + return pos; + }, + + _putAfter: function(key, prevChild) { + var newPos = this._posFor(prevChild); + this.recIds.splice(newPos, 0, key); + }, + + _dropRecord: function(key) { + var res = null; + if(this.recs[key]) { + res = this.snaps[key]; + this.recs[key].unwatch(); + } + if(this.loading[key]) { + this.loading[key].unwatch(); + } + if(util.has(this.filtered, key)) { + this.filtered[key].unwatch(); + } + delete this.loading[key]; + delete this.snaps[key]; + delete this.filtered[key]; + delete this.recs[key]; + util.remove(this.recIds, key); + return res; + }, + + /** + * Because the initial once('value') will probably trigger before all the child paths + * are retrieved (remember that we are monitoring multiple paths per child), we need + * to wait for them to load in before triggering our first value event. + * @private + */ + _checkLoadState: function(key) { + if( !this.loadComplete ) { + if( key ) { + util.remove(this.initialKeysLeft, key); + } + if( !this.initialKeysLeft.length && this.masterLoaded ) { + this.loadComplete = true; + return true; + } + } + return false; + } +}; + +module.exports = RecordSetEventManager; +},{"../../common":25,"./SnapshotFactory":16}],16:[function(require,module,exports){ + +'use strict'; + +var util = require('../../common'); +var NormalizedSnapshot = require('./NormalizedSnapshot.js'); + +function SnapshotFactory(event, key, snaps, prevChild) { + this.event = event; + this.key = key; + this.snaps = unwrapSnapshots(snaps); + this.prevChild = prevChild; + assertValidTrigger(this); +} + +SnapshotFactory.prototype.create = function(ref) { + var snapshot; + if( this.event === 'value' ) { + snapshot = new NormalizedSnapshot(ref, this.snaps); + } + else { + snapshot = new NormalizedSnapshot(ref.ref().child(this.key), this.snaps); + } + return snapshot; +}; + +SnapshotFactory.prototype.toString = function() { + return util.printf( + 'SnapshotFactory(event=%s, key=%s, numberOfSnapshots=%s, prevChild=%s', + this.event, this.key, this.snaps.length, this.prevChild === util.undef? 'undefined' : this.prevChild + ); +}; + +function assertValidTrigger(trigger) { + switch(trigger.event) { + case 'value': + break; + case 'child_added': + case 'child_moved': + if( typeof trigger.key !== 'string' || !trigger.key ) { + throw new Error('Invalid trigger key ' + trigger.key); + } + if( trigger.prevChild === util.undef ) { + throw new Error('Triggers must provide a valid prevChild value for child_added and child_moved events'); + } + break; + case 'child_removed': + case 'child_changed': + if( typeof trigger.key !== 'string' || !trigger.key ) { + throw new Error('Invalid trigger key ' + trigger.key); + } + break; + default: + throw new Error('Invalid trigger event type: ' + trigger.event); + } +} + +function unwrapSnapshots(snaps) { + if( snaps instanceof NormalizedSnapshot ) { + return snaps._snaps.slice(); + } + if( !util.isArray(snaps) ) { + return [snaps]; + } + return snaps.slice(); +} + +module.exports = SnapshotFactory; +},{"../../common":25,"./NormalizedSnapshot.js":8}],17:[function(require,module,exports){ +'use strict'; + +var util = require('../../common'); +var PathManager = require('./PathManager'); +var Path = require('./Path'); +var FieldMap = require('./FieldMap'); +var RecordSet = require('./RecordSet'); + +module.exports = { + replicate: function(record, newMasterRef) { + // create a new set of paths + var paths = record.getPathManager().getPaths().slice(0); + var firstPath = paths[0]; + paths[0] = new Path([newMasterRef, firstPath.name(), firstPath.getDependency()]); + var mgr = new PathManager(util.map(paths, function(p) { return p.clone(); })); + + // create a new field map from the updated paths + var fieldMap = new FieldMap(mgr); + record.getFieldMap().forEach(fieldMap.add, fieldMap); + + // recreate the AbstractRecord instance + var Clazz = record.getClass(); + var rec; + if( Clazz === RecordSet ) { + rec = new Clazz(fieldMap, record.filters); + } + else { + rec = new Clazz(fieldMap); + } + + // done! + return rec; + } +}; +},{"../../common":25,"./FieldMap":4,"./Path":9,"./PathManager":10,"./RecordSet":14}],18:[function(require,module,exports){ +'use strict'; +var util = require('../common'); +var Scroll = require('./libs/Scroll.js'); +var Paginate = require('./libs/Paginate.js'); +var ReadOnlyRef = require('./libs/ReadOnlyRef.js'); + +var DEFAULTS = { + field: null, + pageSize: 10, + windowSize: 250 +}; + +exports.Scroll = function(baseRef, sortField, opts, desc) { + if( !util.isFirebaseRef(baseRef) || util.isQueryRef(baseRef) ) { + throw new Error('First argument to Firebase.util.Scroll must be a valid Firebase ref. It cannot be a Query (e.g. you have called orderByChild()).'); + } + + if( typeof sortField !== 'string' ) { + throw new Error('Second argument to Firebase.util.Scroll must be a valid string'); + } + if( arguments.length > 2 && !util.isObject(opts) ) { + throw new Error('Optional third argument to Firebase.util.Scroll must be an object of key/value pairs'); + } + var ref = new ReadOnlyRef(baseRef); + ref.scroll = new Scroll(ref, sortField, calcOpts(opts, 'windowSize', 'Scroll'), desc); + return ref; +}; + +exports.Paginate = function(baseRef, sortField, opts) { + if( !util.isFirebaseRef(baseRef) || util.isQueryRef(baseRef) ) { + throw new Error('First argument to Firebase.util.Paginate must be a valid Firebase ref. It cannot be a Query (e.g. you have called orderByChild()).'); + } + if( typeof sortField !== 'string' ) { + throw new Error('Second argument to Firebase.util.Paginate must be a valid string'); + } + if( arguments.length > 2 && !util.isObject(opts) ) { + throw new Error('Optional third argument to Firebase.util.Paginate must be an object of key/value pairs'); + } + var ref = new ReadOnlyRef(baseRef); + ref.page = new Paginate(ref, sortField, calcOpts(opts, 'pageSize', 'Paginate')); + return ref; +}; + +function calcOpts(opts, maxFromKey, method) { + var res = util.extend({}, DEFAULTS, opts); + if( !res.maxCacheSize ) { + res.maxCacheSize = res[maxFromKey] * 3; + } + assertNumber(res, maxFromKey, method); + assertNumber(res, 'maxCacheSize', method); + return res; +} + +function assertNumber(obj, key, method) { + if( typeof obj[key] !== 'number' ) { + throw new Error('Argument ' + key + ' passed into opts for ' + method + 'must be a number' ); + } +} +},{"../common":25,"./libs/Paginate.js":21,"./libs/ReadOnlyRef.js":22,"./libs/Scroll.js":23}],19:[function(require,module,exports){ +'use strict'; +var util = require('../../common'); +var Offset = require('./Offset'); + +function Cache(outRef, sortField, maxRecordsLoaded, desc) { + util.log.debug('Cache: caching %s using field=%s maxRecordsLoaded=%d', outRef.toString(), sortField, maxRecordsLoaded); + this.offset = new Offset({field: sortField, max: maxRecordsLoaded, ref: outRef.ref()}); + this.outRef = outRef; + this.inRef = null; + this.queryRef = null; + this.countRef = null; + this.keys = {}; + this.start = 0; + this.count = -1; + this.endCount = -1; + this.nextListeners = []; + this.offset.observe(this._keyChange, this); + this.desc = desc; +} + +Cache.prototype.moveTo = function(startOffset, numberOfRecords) { + util.log.debug('Cache.moveTo: startOffset=%d, numberOfRecords=%d', startOffset, numberOfRecords); + var s = this.start, e = this.count; + this.start = startOffset; + this.count = numberOfRecords; + this.endCount = this.start + this.count; + if( s !== this.start ) { + this.offset.goTo(startOffset, numberOfRecords); + } + else if( e !== this.count ) { + this._refChange(); + } +}; + +Cache.prototype.hasNext = function() { + return this.count === -1 || this.endCount > this.start + this.count; +}; + +Cache.prototype.hasPrev = function() { + return this.start > 0; +}; + +Cache.prototype.observeHasNext = function(callback, context) { + var list = this.nextListeners; + var parts = [callback, context]; + list.push(parts); + return function() { + util.remove(list, parts); + }; +}; + +Cache.prototype.destroy = function() { + this._unsubscribe(); + this.offset.destroy(); + this.offset = null; + this.start = 0; + this.count = -1; + this.inRef = null; + this.outRef = null; + this.queryRef = null; + this.countRef = null; + this.keys = null; + this.nextListeners = null; +}; + +Cache.prototype._keyChange = function(val, key, ref) { + this.inRef = ref; + util.log.debug('Cache._keyChange: %s %s %s', val, key, ref.toString()); + this._refChange(); +}; + +Cache.prototype._unsubscribe = function() { + if( this.queryRef ) { + this.queryRef.off('child_added', this._add, this); + this.queryRef.off('child_removed', this._remove, this); + this.queryRef.off('child_moved', this._move, this); + this.queryRef.off('child_changed', this._change, this); + this.queryRef.off('value', this._value, this); + this.queryRef.off('value', this._removeOrphans, this); + this.queryRef = null; + } + if( this.countRef ) { + this.countRef.off('value', this._count, this); + this.countRef = null; + } +}; + +Cache.prototype._refChange = function() { + this._unsubscribe(); + if( this.inRef && this.count > -1 ) { + this.countRef = this.desc ? this.inRef.limitToLast(this.count+1) : this.inRef.limitToFirst(this.count+1); + this.countRef.on('value', this._count, this); + //todo we should queue all the events until the once('value') is completed + //todo so that we can trigger removed before added + this.queryRef = this.desc ? this.inRef.limitToLast(this.count) : this.inRef.limitToFirst(this.count); + this.queryRef.on('child_added', this._add, this); + this.queryRef.on('child_removed', this._remove, this); + this.queryRef.on('child_moved', this._move, this); + this.queryRef.on('child_changed', this._change, this); + this.queryRef.on('value', this._value, this); + this.queryRef.once('value', this._removeOrphans, this); + } +}; + +Cache.prototype._add = function(snap, prevChild) { + var key = snap.key(); + if( !util.has(this.keys, key) ) { + this.keys[key] = snap; + this.outRef.$trigger('child_added', snap, prevChild); + } + else if( !util.isEqual(this.keys[key], snap.val()) ) { + this._change(snap); + } +}; + +Cache.prototype._remove = function(snap) { + var key = snap.key(); + if( util.has(this.keys, key) ) { + this.outRef.$trigger('child_removed', snap); + delete this.keys[key]; + } +}; + +Cache.prototype._move = function(snap, prevChild) { + var key = snap.key(); + if( util.has(this.keys, key) ) { + this.keys[key] = snap; + this.outRef.$trigger('child_moved', snap, prevChild); + } +}; + +Cache.prototype._change = function(snap) { + this.keys[snap.key()] = snap; + this.outRef.$trigger('child_changed', snap); +}; + +Cache.prototype._value = function(snap) { + this.outRef.$trigger('value', snap); +}; + +Cache.prototype._count = function(snap) { + this.endCount = this.start + snap.numChildren(); + var hasNext = this.hasNext(); + util.each(this.nextListeners, function(parts) { + parts[0].call(parts[1], hasNext); + }); +}; + +Cache.prototype._removeOrphans = function(valueSnap) { + util.each(this.keys, function(cachedSnap, key) { + if( !valueSnap.hasChild(key) ) { + this.outRef.$trigger('child_removed', cachedSnap); + delete this.keys[key]; + } + }, this); +}; + +module.exports = Cache; +},{"../../common":25,"./Offset":20}],20:[function(require,module,exports){ +'use strict'; +var util = require('../../common'); + +function Offset(opts) { + this.keys = []; + this.field = opts.field; + this.ref = baseRef(opts.ref, opts.field); + this.max = opts.max; + this.listeners = []; + this.curr = 0; + this.sub = null; + this.isSubscribing = false; + this.lastNotifyValue = util.undef; + this._debouncedRecache = debounce(function() { + util.log.debug('Offset._debouncedRecache: recaching keys for offset %d', this.curr); + this.keys = []; + this._grow(this._listen); + }, this, 100, 1000); +} + +Offset.prototype.goTo = function(newOffset) { + if( newOffset !== this.curr ) { + util.log('Offset.goTo: offset changed from %d to %d', this.curr, newOffset); + this.curr = newOffset; + this.lastNotifyValue = util.undef; + this._listen(); + } +}; + +Offset.prototype.observe = function(callback, context) { + this.listeners.push([callback, context]); + var key = this.getKey(); + var ref = offsetRef(this.ref, key); + callback.call(context, key && key.val, key && key.key, ref); +}; + +Offset.prototype.getKey = function(offset) { + if( !arguments.length ) { offset = this.curr; } + if( offset === 0 ) { return null; } + return this.keys.length > offset && this.keys[offset]; +}; + +Offset.prototype.destroy = function() { + this._unsubscribe(); + this.curr = 0; + this.keys = []; + this.lastNotifyValue = util.undef; + this.isSubscribing = false; +}; + +Offset.prototype._notify = function() { + var key = this.getKey(); + if( !util.isEqual(this.lastNotifyValue, key) ) { + util.log('Offset._notify: key at offset %d is %s', this.curr, key && key.key); + this.lastNotifyValue = key; + var ref = offsetRef(this.ref, key); + util.each(this.listeners, function(parts) { + parts[0].call(parts[1], key && key.val, key && key.key, ref); + }); + } +}; + +Offset.prototype._recache = function() { + if( !this.isSubscribing ) { + this._debouncedRecache(); + } +}; + +var killCount = 0; +Offset.prototype._grow = function(callback) { + var self = this; + var len = self.keys.length; + if( self.curr >= len ) { + var oldKey = self.getKey(); + var startAt = lastKey(self.keys); + var limit = Math.min(self.curr + (startAt? 2 : 1) - len, self.max); + var ref = startAt !== null? self.ref.startAt(startAt.val, startAt.key) : self.ref; + ref.limitToFirst(limit).once('value', function(snap) { + var skipFirst = startAt !== null; + snap.forEach(function(ss) { + if( skipFirst ) { + skipFirst = false; + return; + } + self.keys.push(extractKey(ss, self.field)); + }); + if( killCount++ > 10000 ) { + throw new Error('Tried to fetch more than 10,000 pages to determine the correct offset. Giving up now. Sorry.'); + } + if( self.curr >= self.keys.length && snap.numChildren() === limit ) { + // prevents recursive scoping + setTimeout(util.bind(self._grow, self, callback), 0); + } + else { + killCount = 0; + util.log.debug('Offset._grow: Cached %d keys', self.keys.length); + callback.call(self, !util.isEqual(self.getKey(), oldKey)); + } + }); + } + else { + callback.call(self, false); + } +}; + +Offset.prototype._startOffset = function() { + return Math.max(0, this.curr - this.max, this.curr - 10); +}; + +Offset.prototype._queryRef = function() { + var start = this._startOffset(); + var ref = this.ref; + if( start > 0 ) { + var key = this.getKey(start); + ref = ref.startAt(key.val, key.key); + } + return ref.limitToLast(Math.max(this.curr - start, 1)); +}; + +Offset.prototype._moved = function(snap) { + if( snap.key() === this.getKey() ) { + this._recache(); + } +}; + +Offset.prototype._unsubscribe = function() { + if( this.sub ) { + this.sub.off('child_added', this._recache, this); + this.sub.off('child_moved', this._moved, this); + this.sub.off('child_removed', this._recache, this); + this.sub.off('value', this._doneSubscribing, this); + this.sub = null; + } +}; + +Offset.prototype._subscribe = function() { + this._unsubscribe(); + this.sub = this._queryRef(); + this.isSubscribing = true; + this.sub.on('child_added', this._recache, this); + this.sub.on('child_moved', this._moved, this); + this.sub.on('child_removed', this._recache, this); + this.sub.once('value', this._doneSubscribing, this); +}; + +Offset.prototype._doneSubscribing = function() { + this.isSubscribing = false; + this._notify(); +}; + +Offset.prototype._monitorEmptyOffset = function() { + var self = this; + var ref = self.ref; + var key = null; + function fn(snap) { + var count = snap.numChildren(); + if( count > (key === null? 0 : 1) ) { + util.log.debug('Offset._monitorEmptyOffset: A value exists now.'); + ref.off('value', fn); + self._grow(); + } + } + if( this.keys.length ) { + key = lastKey(this.keys); + ref = ref.startAt(key.val, key.key); + } + util.log.debug('Offset._monitorEmptyOffset: No value exists at offset %d, currently %d keys at this path. Watching for a new value.', this.curr, this.keys.length); + ref.limitToFirst(2).on('value', fn); +}; + +Offset.prototype._listen = function() { + this._unsubscribe(); + if( this.curr >= this.keys.length ) { + this._grow(function(/*changed*/) { + if( this.keys.length >= this.curr ) { + this._subscribe(); + } + else { + this._monitorEmptyOffset(); + this._notify(); + } + }); + } + else { + this._subscribe(); + } +}; + +function extractKey(snap, field) { + var v; + switch(field) { + case '$key': + v = snap.key(); + break; + case '$priority': + v = snap.getPriority(); + break; + case '$value': + v = snap.val(); + break; + default: + var obj = snap.val(); + if( !util.isObject(obj) ) { + throw new Error('A value of type ' + typeof obj + 'Was found. ' + + 'But we are attempting to order by child field "' + field + '". ' + + 'Pagination requires all records to be objects or it can\'t determine an ' + + 'appropriate offset value.'); + } + else { + v = obj[field]; + } + } + return {val: v, key: snap.key()}; +} + +function offsetRef(baseRef, startKey) { + if( startKey === false ) { + return null; + } + else if( startKey === null ) { + return baseRef; + } + else { + return baseRef.startAt(startKey.val, startKey.key); + } +} + +function baseRef(ref, field) { + if( field === '$key' ) { + return ref.orderByKey(); + } + else if( field === '$priority' ) { + return ref.orderByPriority(); + } + else if( field === '$value' ) { + return ref.orderByValue(); + } + else { + return ref.orderByChild(field); + } +} + +/** + * A rudimentary debounce method + * @param {function} fn the function to debounce + * @param {object} [ctx] the `this` context to set in fn + * @param {int} wait number of milliseconds to pause before sending out after each invocation + * @param {int} [maxWait] max milliseconds to wait before sending out, defaults to wait * 10 or 100 + */ +function debounce(fn, ctx, wait, maxWait) { + var start, cancelTimer, args, runScheduledForNextTick; + if( typeof(ctx) === 'number' ) { + maxWait = wait; + wait = ctx; + ctx = null; + } + + if( typeof wait !== 'number' ) { + throw new Error('Must provide a valid integer for wait. Try 0 for a default'); + } + if( typeof(fn) !== 'function' ) { + throw new Error('Must provide a valid function to debounce'); + } + if( !maxWait ) { maxWait = wait*10 || 100; } + + // clears the current wait timer and creates a new one + // however, if maxWait is exceeded, calls runNow() on the next tick. + function resetTimer() { + if( cancelTimer ) { + cancelTimer(); + cancelTimer = null; + } + if( start && Date.now() - start > maxWait ) { + if(!runScheduledForNextTick){ + runScheduledForNextTick = true; + setTimeout(runNow, 0); + } + } + else { + if( !start ) { start = Date.now(); } + var to = setTimeout(runNow, wait); + cancelTimer = function() { clearTimeout(to); }; + } + } + + // Clears the queue and invokes the debounced function with the most recent arguments + function runNow() { + cancelTimer = null; + start = null; + runScheduledForNextTick = false; + fn.apply(ctx, args); + } + + function debounced() { + args = Array.prototype.slice.call(arguments, 0); + resetTimer(); + } + debounced.running = function() { + return start > 0; + }; + + return debounced; +} + +function lastKey(list) { + var len = list.length; + return len? list[len-1] : null; +} + +module.exports = Offset; +},{"../../common":25}],21:[function(require,module,exports){ +'use strict'; +var util = require('../../common'); +var Cache = require('./Cache'); + +/** + * @param {Firebase} ref + * @param {String} field + * @param {object} [opts] + * @constructor + */ +function Paginate(ref, field, opts) { + this.currPage = 0; + this.field = field; + this.ref = ref; + this.pageSize = opts.pageSize; + this.max = opts.maxCacheSize; + this.subs = []; + this.pageChangeListeners = []; + this.pageCountListeners = []; + this.cache = new Cache(ref, field, opts.maxCacheSize); + this.pageCount = -1; + this.couldHaveMore = false; + this.cache.observeHasNext(this._countPages, this); +} + +/** + * Unload current records and load the next page into the PaginatedRef + * + * @return {Paginate} returns `this` + */ +Paginate.prototype.next = function() { + if( this.hasNext() ) { + this.currPage++; + util.log.debug('Paginate.next: current page is %d', this.currPage); + this._pageChange(); + } + return this; +}; + +/** + * Unload current records and load the previous page into the PaginatedRef. + * + * @return {Paginate} returns `this` + */ +Paginate.prototype.prev = function() { + if( this.hasPrev() ) { + this.currPage--; + util.log.debug('Paginate.prev: current page is %d', this.currPage); + this._pageChange(); + } + return this; +}; + +/** + * Skip to a specific page. The pageNumber must be less than pageCount. + * + * @param {int} pageNumber + * @return {Paginate} returns `this` + */ +Paginate.prototype.setPage = function(pageNumber) { + if( pageNumber > 0 && pageNumber <= this.pageCount ) { + this.currPage = pageNumber; + util.log.debug('Paginate.setPage: current page is %d', this.currPage); + this._pageChange(); + } + else { + util.log.warn('Paginate.setPage: invalid page number %d', pageNumber); + } +}; + +/** + * @return {boolean} true if there are more records after the currently loaded page + */ +Paginate.prototype.hasNext = function() { + return this.cache.hasNext(); +}; + +/** + * @return {boolean} true if there are more records before the currently loaded page + */ +Paginate.prototype.hasPrev = function() { + return this.currPage > 1; +}; + +/** + * Invoked whenever the page count changes. This may not be accurate if number of pages + * exceeds the maxCacheSize. + * + * The callback is delivered two arguments. The first is the {int} count, and the second + * is a {boolean}couldHaveMore which is true whenever we have run into maxCacheSize (i.e. there + * could be more) + * + * @param {Function} callback + * @param {Object} [context] + * @return {Function} a dispose function that cancels the listener + */ +Paginate.prototype.onPageChange = function(callback, context) { + var listeners = this.pageChangeListeners; + var parts = [callback, context]; + listeners.push(parts); + callback.call(context, this.currPage); + return function() { + util.remove(listeners, parts); + }; +}; + +/** + * Invoked whenever the local page count is changed. This may not include + * all records that exist on the remote server, as it is limited by maxCacheSize + */ +Paginate.prototype.onPageCount = function(callback, context) { + var listeners = this.pageCountListeners; + var parts = [callback, context]; + listeners.push(parts); + if( this.pageCount > -1 ) { + callback.call(context, this.pageCount, this.couldHaveMore); + } + else { + this._countPages(); + } + return function() { + util.remove(listeners, parts); + }; +}; + +/** + * Asynchronously fetch the total page count. This maxes a REST API + * call using shallow=true. All the keys must be able to fit in memory at the same time. + * + * @param {Function} [callback] + * @param {Object} [context] + */ +Paginate.prototype.getCountByDowloadingAllKeys = function(callback, context) { + var self = this; + self.downloadingEverything = true; + var url = self.ref.ref().toString(); + if( !url.match(/\/$/) ) { url += '/'; } + url += '.json?shallow=true'; + microAjax(url, function(data) { + var count = 0; + try { + count = util.keys(JSON.parse(data)).length; + } + catch(e) { + util.log.warn(e); + } + util.log.debug('Paginate.getCountByDownloadingAllKeys: found %d keys', count); + self.downloadingEverything = false; + self.pageCount = countPages(count, self.pageSize); + self.couldHaveMore = false; + self._notifyPageCount(); + if( callback ) { callback.call(context, count); } + }); +}; + +/** + * Deletes locally cached data and cancels all listeners. Unloads + * records and triggers child_removed events. + */ +Paginate.prototype.destroy = function() { + this.cache.destroy(); + this.cache = null; + this.ref = null; + util.each(this.subs, function(fn) { fn(); }); + this.subs = []; + this.pageCountListeners.length = 0; + this.pageChangeListeners.length = 0; +}; + +Paginate.prototype._countPages = function() { + var self = this; + var currPage = self.currPage; + if( !this.downloadingEverything ) { + if( self.pageCount === -1 ) { + var max = self.max; + var pageSize = self.pageSize; + var ref = this.ref.ref().limitToFirst(max); + ref.once('value', function(snap) { + if( self.pageCount === -1 ) { // double-null check pattern (may have changed during async op) + self.couldHaveMore = snap.numChildren() === max; + self.pageCount = Math.ceil(snap.numChildren() / pageSize); + self._notifyPageCount(); + self._countPages(); + } + }); + } + else if( currPage >= self.pageCount ) { + self.pageCount = currPage; + self.couldHaveMore = self.cache.hasNext(); + self._notifyPageCount(); + } + } +}; + +Paginate.prototype._pageChange = function() { + var currPage = this.currPage; + var start = (currPage -1) * this.pageSize; + this.cache.moveTo(start, this.pageSize); + this._countPages(); + util.each(this.pageChangeListeners, function(parts) { + parts[0].call(parts[1], currPage); + }); +}; + +Paginate.prototype._notifyPageCount = function() { + var pageCount = this.pageCount; + var couldHaveMore = this.couldHaveMore; + util.each(this.pageCountListeners, function(parts) { + parts[0].call(parts[1], pageCount, couldHaveMore); + }); +}; + +function countPages(recordCount, pageSize) { + if( recordCount === 0 ) { + return 0; + } + else { + return Math.ceil(recordCount / pageSize); + } +} + +// https://code.google.com/p/microajax/ +// new BSD license: http://opensource.org/licenses/BSD-3-Clause +function microAjax(url,callbackFunction){var o={};o.bindFunction=function(caller,object){return function(){return caller.apply(object,[object]);};};o.stateChange=function(object){if(o.request.readyState==4) o.callbackFunction(o.request.responseText);};o.getRequest=function(){if(window.ActiveXObject) return new ActiveXObject('Microsoft.XMLHTTP');else if(window.XMLHttpRequest) return new XMLHttpRequest();return false;};o.postBody=(arguments[2]||"");o.callbackFunction=callbackFunction;o.url=url;o.request=o.getRequest();if(o.request){var req=o.request;req.onreadystatechange=o.bindFunction(o.stateChange,o);if(o.postBody!==""){req.open("POST",url,true);req.setRequestHeader('X-Requested-With','XMLHttpRequest');req.setRequestHeader('Content-type','application/x-www-form-urlencoded');req.setRequestHeader('Connection','close');}else{req.open("GET",url,true);} req.send(o.postBody);} return o;} // jshint ignore:line + +module.exports = Paginate; + +},{"../../common":25,"./Cache":19}],22:[function(require,module,exports){ +'use strict'; +var util = require('../../common'); + +function ReadOnlyRef(ref) { + this._ref = ref; + this._obs = new util.Observable( + ['value', 'child_added', 'child_removed', 'child_moved', 'child_changed'] + ); +} + + +ReadOnlyRef.prototype = { + 'on': function(event, callback, cancel, context) { + this._obs.observe(event, callback, cancel, context); + }, + + 'once': function(event, callback, cancel, context) { + var self = this; + function fn(snap) { + /*jshint validthis:true */ + self.off(event, fn, self); + callback.call(context, snap); + } + this.on(event, fn, cancel, this); + }, + + 'off': function(event, callback, context) { + this._obs.stopObserving(event, callback, context); + }, + + /**************************** + * WRAPPER FUNCTIONS + ****************************/ + 'ref': function() { + return this._ref; + }, + 'child': wrapMaster('child'), + 'parent': wrapMaster('parent'), + 'root': wrapMaster('root'), + 'name': wrapMaster('name'), + 'key': wrapMaster('key'), + 'toString': wrapMaster('toString'), + + 'auth': wrapMaster('auth'), + 'unauth': wrapMaster('unauth'), + 'authWithCustomToken': wrapMaster('authWithCustomToken'), + 'authAnonymously': wrapMaster('authAnonymously'), + 'authWithPassword': wrapMaster('authWithPassword'), + 'authWithOAuthPopup': wrapMaster('authWithOAuthPopup'), + 'authWithOAuthRedirect': wrapMaster('authWithOAuthRedirect'), + 'authWithOAuthToken': wrapMaster('authWithOAuthToken'), + 'getAuth': wrapMaster('getAuth'), + 'onAuth': wrapMaster('onAuth'), + 'offAuth': wrapMaster('offAuth'), + 'createUser': wrapMaster('createUser'), + 'changePassword': wrapMaster('changePassword'), + 'removeUser': wrapMaster('removeUser'), + 'resetPassword': wrapMaster('resetPassword'), + 'changeEmail': wrapMaster('changeEmail'), + + 'goOffline': wrapMaster('goOffline'), + 'goOnline': wrapMaster('goOnline'), + + /**************************** + * UNSUPPORTED FUNCTIONS + ***************************/ + 'set': isReadOnly('set'), + 'update': isReadOnly('update'), + 'remove': isReadOnly('remove'), + 'push': isReadOnly('push'), + 'setWithPriority': isReadOnly('setWithPriority'), + 'setPriority': isReadOnly('setPriority'), + 'transaction': isReadOnly('transaction'), + + /** @deprecated */ + 'limit': notSupported('limit'), + + 'onDisconnect': notSupported('onDisconnect'), + 'orderByChild': notSupported('orderByChild'), + 'orderByKey': notSupported('orderByKey'), + 'orderByPriority': notSupported('orderByPriority'), + 'limitToFirst': notSupported('limitToFirst'), + 'limitToLast': notSupported('limitToLast'), + 'startAt': notSupported('startAt'), + 'endAt': notSupported('endAt'), + 'equalTo': notSupported('equalTo'), + + /** INTERNAL METHODS */ + $trigger: function() { + this._obs.triggerEvent.apply(this._obs, util.toArray(arguments)); + } +}; + +function wrapMaster(method) { + return function() { + var args = util.toArray(arguments); + var ref = this.ref(); + return ref[method].apply(ref, args); + }; +} + +function isReadOnly(method) { + return function() { + throw new Error(method + ' is not supported. This is a read-only reference. You can ' + + 'modify child records after calling .child(), or work with the original by using .ref().'); + }; +} + +function notSupported(method) { + return function() { + throw new Error(method + ' is not supported for Paginate and Scroll references. ' + + 'Try calling it on the original reference used to create the instance instead.'); + }; +} + +module.exports = ReadOnlyRef; +},{"../../common":25}],23:[function(require,module,exports){ +'use strict'; +var Cache = require('./Cache'); + +/** + * @param {ReadOnlyRef} readOnlyRef + * @param {String} field + * @param {Object} [opts] + * @constructor + */ +function Scroll(readOnlyRef, field, opts, desc) { + this.max = opts.windowSize; + this.start = 0; + this.end = 0; + this.cache = new Cache(readOnlyRef, field, opts.maxCacheSize, desc); +} + +/** + * Load the next numberToAppend records and trigger child_added events + * for them. If the total number of records exceeds maxRecords, then + * child_removed events will be triggered for the first items in the list. + * + * @param {int} numberToAppend + */ +Scroll.prototype.next = function(numberToAppend) { + if( this.hasNext() ) { + this.end = this.end + numberToAppend; + this.start = Math.max(0, this.end - this.max, this.start); + this.cache.moveTo(this.start, this.end - this.start); + } +}; + +/** + * Load the previous numberToAppend records and trigger child_added events + * for them. If the total number of records exceeds maxRecords, then + * child_removed events will be triggered for the last items in the list. + * + * @param {int} numberToPrepend + */ +Scroll.prototype.prev = function(numberToPrepend) { + if( this.hasPrev() ) { + this.start = Math.max(0, this.start - numberToPrepend); + this.end = Math.min(this.start + this.max, this.end); + this.cache.moveTo(this.start, this.end-this.start); + } +}; + +/** + * @return {boolean} true if there are more records after the currently loaded page + */ +Scroll.prototype.hasNext = function() { + return this.cache.hasNext(); +}; + +/** + * @return {boolean} true if there are more records before the currently loaded page + */ +Scroll.prototype.hasPrev = function() { + return this.start > 0; +}; + +Scroll.prototype.observeHasNext = function(callback, context) { + return this.cache.observeHasNext(callback, context); +}; + +/** + * Deletes locally cached data and cancels all listeners. Unloads + * records and triggers child_removed events. + */ +Scroll.prototype.destroy = function() { + this.cache.destroy(); + this.ref = null; + this.cache = null; +}; + +module.exports = Scroll; +},{"./Cache":19}],24:[function(require,module,exports){ +/** + * This file loads all the public methods from + * the common/ library. To fetch all methods + * for internal use, just do require('./src/common'), + * which loads index.js and includes the private methods. + */ + +var util = require('./index.js'); +exports.log = util.log; +exports.logLevel = util.logLevel; +exports.escapeEmail = util.escapeEmail; +},{"./index.js":25}],25:[function(require,module,exports){ +/** + * This file loads the entire common/ package for INTERNAL USE. + * The public methods are specified by exports.js + */ + +var util = require('./libs/util.js'); +var log = require('./libs/logger.js'); + +util.extend( + exports, + util, + { + args: require('./libs/args.js'), + log: log, + logLevel: log.logLevel, + Observable: require('./libs/Observable.js'), + Observer: require('./libs/Observer.js'), + queue: require('./libs/queue.js') + } +); +},{"./libs/Observable.js":26,"./libs/Observer.js":27,"./libs/args.js":28,"./libs/logger.js":29,"./libs/queue.js":30,"./libs/util.js":31}],26:[function(require,module,exports){ +'use strict'; + +var util = require('./util.js'); +var getArgs = require('./args.js'); +var log = require('./logger.js'); +var Observer = require('./Observer.js'); + +/** + * A simple observer model for watching events. + * @param eventsMonitored + * @param [opts] can contain callbacks for onAdd, onRemove, and onEvent, as well as a list of oneTimeEvents + * @constructor + */ +function Observable(eventsMonitored, opts) { + if( !opts ) { opts = {}; } + this._observableProps = parseProps(eventsMonitored, opts); + this.resetObservers(); +} +Observable.prototype = { + /** + * @param {String} event + * @param {Function|util.Observer} callback + * @param {Function} [cancelFn] + * @param {Object} [scope] + */ + observe: function(event, callback, cancelFn, scope) { + var args = getArgs('observe', arguments, 2, 4), obs; + event = args.nextFromReq(this._observableProps.eventsMonitored); + if( event ) { + callback = args.nextReq('function'); + cancelFn = args.next('function'); + scope = args.next('object'); + obs = new Observer(this, event, callback, scope, cancelFn); + this._observableProps.observers[event].push(obs); + this._observableProps.onAdd(event, obs); + if( this.isOneTimeEvent(event) ) { + checkOneTimeEvents(event, this._observableProps, obs); + } + } + return obs; + }, + + /** + * @param {String|Array} [event] + * @returns {boolean} + */ + hasObservers: function(event) { + return this.getObservers(event).length > 0; + }, + + /** + * @param {String|Array} events + * @param {Function|Observer} callback + * @param {Object} [scope] + */ + stopObserving: function(events, callback, scope) { + var args = getArgs('stopObserving', arguments); + events = args.next(['array', 'string'], this._observableProps.eventsMonitored); + callback = args.next(['function']); + scope = args.next(['object']); + util.each(events, function(event) { + var removes = []; + var observers = this.getObservers(event); + util.each(observers, function(obs) { + if( obs.matches(event, callback, scope) ) { + obs.notifyCancelled(null); + removes.push(obs); + } + }, this); + removeAll(this._observableProps.observers[event], removes); + if( removes.length ) { + this._observableProps.onRemove(event, removes); + } + }, this); + }, + + /** + * Turn off all observers and call cancel callbacks with an error + * @param {String} error + * @returns {*} + */ + abortObservers: function(error) { + var removes = []; + if( this.hasObservers() ) { + var observers = this.getObservers().slice(); + util.each(observers, function(obs) { + obs.notifyCancelled(error); + removes.push(obs); + }, this); + this.resetObservers(); + if( removes.length ) { + this._observableProps.onRemove(this.event, removes); + } + } + }, + + /** + * @param {String|Array} [events] + * @returns {*} + */ + getObservers: function(events) { + events = getArgs('getObservers', arguments).listFrom(this._observableProps.eventsMonitored, true); + return getObserversFor(this._observableProps, events); + }, + + triggerEvent: function(event) { + var args = getArgs('triggerEvent', arguments); + var events = args.listFromWarn(this._observableProps.eventsMonitored, true); + var passThruArgs = args.restAsList(); + if( events ) { + util.each(events, function(e) { + if( this.isOneTimeEvent(event) ) { + if( util.isArray(this._observableProps.oneTimeResults, event) ) { + log.warn('One time event was triggered twice, should by definition be triggered once', event); + return; + } + this._observableProps.oneTimeResults[event] = passThruArgs; + } + var observers = this.getObservers(e), ct = 0; + util.each(observers, function(obs) { + obs.notify.apply(obs, passThruArgs.slice(0)); + ct++; + }); + this._observableProps.onEvent.apply(null, [e, ct].concat(passThruArgs.slice(0))); + }, this); + } + }, + + resetObservers: function() { + util.each(this._observableProps.eventsMonitored, function(key) { + this._observableProps.observers[key] = []; + }, this); + }, + + isOneTimeEvent: function(event) { + return util.contains(this._observableProps.oneTimeEvents, event); + }, + + observeOnce: function(event, callback, cancelFn, scope) { + var args = getArgs('observeOnce', arguments, 2, 4), obs; + event = args.nextFromWarn(this._observableProps.eventsMonitored); + if( event ) { + callback = args.nextReq('function'); + cancelFn = args.next('function'); + scope = args.next('object'); + obs = new Observer(this, event, callback, scope, cancelFn, true); + this._observableProps.observers[event].push(obs); + this._observableProps.onAdd(event, obs); + if( this.isOneTimeEvent(event) ) { + checkOneTimeEvents(event, this._observableProps, obs); + } + } + return obs; + } +}; + +function removeAll(list, items) { + util.each(items, function(x) { + var i = util.indexOf(list, x); + if( i >= 0 ) { + list.splice(i, 1); + } + }); +} + +function getObserversFor(props, events) { + var out = []; + util.each(events, function(event) { + if( !util.has(props.observers, event) ) { + log.warn('Observable.hasObservers: invalid event type %s', event); + } + else { + if( props.observers[event].length ) { + out = out.concat(props.observers[event]); + } + } + }); + return out; +} + +function checkOneTimeEvents(event, props, obs) { + if( util.has(props.oneTimeResults, event) ) { + obs.notify.apply(obs, props.oneTimeResults[event]); + } +} + +function parseProps(eventsMonitored, opts) { + return util.extend( + { onAdd: util.noop, onRemove: util.noop, onEvent: util.noop, oneTimeEvents: [] }, + opts, + { eventsMonitored: eventsMonitored, observers: {}, oneTimeResults: {} } + ); +} + +module.exports = Observable; +},{"./Observer.js":27,"./args.js":28,"./logger.js":29,"./util.js":31}],27:[function(require,module,exports){ +'use strict'; +var util = require('./util.js'); + +/** Observer + *************************************************** + * @private + * @constructor + */ +function Observer(observable, event, notifyFn, context, cancelFn, oneTimeEvent) { + if( typeof(notifyFn) !== 'function' ) { + throw new Error('Must provide a valid notifyFn'); + } + this.observable = observable; + this.fn = notifyFn; + this.event = event; + this.cancelFn = cancelFn||function() {}; + this.context = context; + this.oneTimeEvent = !!oneTimeEvent; +} + +Observer.prototype = { + notify: function() { + var args = util.toArray(arguments); + this.fn.apply(this.context, args); + if( this.oneTimeEvent ) { + this.observable.stopObserving(this.event, this.fn, this.context); + } + }, + + matches: function(event, fn, context) { + if( util.isArray(event) ) { + return util.contains(event, function(e) { + return this.matches(e, fn, context); + }, this); + } + return (!event || event === this.event) && + (!fn || fn === this || fn === this.fn) && + (!context || context === this.context); + }, + + notifyCancelled: function(err) { + this.cancelFn.call(this.context, err||null); + } +}; + +module.exports = Observer; +},{"./util.js":31}],28:[function(require,module,exports){ +'use strict'; +var util = require('./util.js'); +var log = require('./logger.js'); + +function Args(fnName, args, minArgs, maxArgs) { + if( typeof(fnName) !== 'string' || !util.isObject(args) ) { + throw new Error('Args requires at least 2 args: fnName, arguments[, minArgs, maxArgs]'); + } + if( !(this instanceof Args) ) { // allow it to be called without `new` prefix + return new Args(fnName, args, minArgs, maxArgs); + } + this.fnName = fnName; + this.argList = util.toArray(args); + this.origArgs = util.toArray(args); + var len = this.length = this.argList.length; + this.pos = -1; + if(util.isUndefined(minArgs)) { minArgs = 0; } + if(util.isUndefined(maxArgs)) { maxArgs = this.argList.length; } + if( len < minArgs || len > maxArgs ) { + var rangeText = maxArgs > minArgs? util.printf('%d to %d', minArgs, maxArgs) : minArgs; + throw Error(util.printf('%s must be called with %s arguments, but received %d', fnName, rangeText, len)); + } +} + +Args.prototype = { + /** + * Grab the original list of args + * @return {Array} containing the original arguments + */ + orig: function() { return this.origArgs.slice(0); }, + + /** + * Return whatever args remain as a list + * @returns {Array|string|Buffer|Blob|*} + */ + restAsList: function(minLength, types) { + var list = this.argList.slice(0); + if( minLength || types ) { + for (var i = 0, len = list.length; i < len; i++) { + this._arg(types||true, null, i < minLength); + } + } + return list; + }, + + /** + * Advance the argument list by one and discard the value + * @return {Args} + */ + skip: function() { + if( this.argList.length ) { + this.pos++; + this.argList.shift(); + } + return this; + }, + + /** + * Read the next optional argument, but only if `types` is true, or it is of a type specified + * In the case that it is not present, return `defaultValue` + * @param {boolean|Array|string} types either `true` or one of array, string, object, number, int, boolean, boolean-like, or function + * @param [defaultValue] + */ + next: function(types, defaultValue) { + return this._arg(types, defaultValue, false); + }, + + /** + * Read the next optional argument, but only if `types` is true, or it is of a type specified. In the case + * that it is not present, return `defaultValue` and log a warning to the console + * @param {boolean|Array|string} types either `true` or one of array, string, object, number, int, boolean, boolean-like, or function + * @param [defaultValue] + */ + nextWarn: function(types, defaultValue) { + return this._arg(types, defaultValue, 'warn'); + }, + + /** + * Read the next required argument, but only if `types` is true, or it is of a type specified. In the case + * that it is not present, throw an Error + * @param {boolean|Array|string} types either `true` or one of array, string, object, number, int, boolean, boolean-like, or function + */ + nextReq: function(types) { + return this._arg(types, null, true); + }, + + /** + * Read the next optional argument, which must be one of the values in choices. If it is not present, + * return defaultValue. + * @param {Array} choices a list of allowed values + * @param [defaultValue] + */ + nextFrom: function(choices, defaultValue) { + return this._from(choices, defaultValue, false); + }, + + /** + * Read the next optional argument, which must be one of the values in choices. If it is not present, + * return defaultValue and log a warning to the console. + * @param {Array} choices a list of allowed values + * @param [defaultValue] + */ + nextFromWarn: function(choices, defaultValue) { + return this._from(choices, defaultValue, 'warn'); + }, + + /** + * Read the next optional argument, which must be one of the values in choices. If it is not present, + * throw an Error. + * @param {Array} choices a list of allowed values + */ + nextFromReq: function(choices) { + return this._from(choices, null, true); + }, + + /** + * Read the next optional argument and return it as an array (it can optionally be an array or a single value + * which will be coerced into an array). All values in the argument must be in choices or they are removed + * from the choices and a warning is logged. If no valid value is present, return defaultValue. + * @param {Array} choices a list of allowed values + * @param [defaultValue] a set of defaults, setting this to true uses the `choices` as default + */ + listFrom: function(choices, defaultValue) { + return this._list(choices, defaultValue, false); + }, + + /** + * Read the next optional argument and return it as an array (it can optionally be an array or a single value + * which will be coerced into an array). All values in the argument must be in choices or they are removed + * from the choices and a warning is logged. If no valid value is present, return defaultValue and log a warning. + * @param {Array} choices a list of allowed values + * @param [defaultValue] a set of defaults, setting this to true uses the `choices` as default + */ + listFromWarn: function(choices, defaultValue) { + return this._list(choices, defaultValue, 'warn'); + }, + + /** + * Read the next optional argument and return it as an array (it can optionally be an array or a single value + * which will be coerced into an array). All values in the argument must be in choices or they are removed + * from the choices and a warning is logged. If no valid value is present, throw an Error. + * @param {Array} choices a list of allowed values + */ + listFromReq: function(choices) { + return this._list(choices, null, true); + }, + + _arg: function(types, defaultValue, required) { + this.pos++; + if( util.isUndefined(types) || types === null ) { types = true; } + if( this.argList.length && isOfType(this.argList[0], types) ) { + return format(this.argList.shift(), types); + } + else { + if( required ) { + assertRequired(required, this.fnName, this.pos, util.printf('must be of type %s', types)); + } + return defaultValue; + } + }, + + _from: function(choices, defaultValue, required) { + this.pos++; + if( this.argList.length && util.contains(choices, this.argList[0]) ) { + return this.argList.shift(); + } + else { + if( required ) { + assertRequired(required, this.fnName, this.pos, util.printf('must be one of %s', choices)); + } + return defaultValue; + } + }, + + _list: function(choices, defaultValue, required) { + this.pos++; + var out = []; + var list = this.argList[0]; + if( this.argList.length && !util.isEmpty(list) && (util.isArray(list) || !util.isObject(list)) ) { + this.argList.shift(); + if( util.isArray(list) ) { + out = util.map(list, function(v) { + if( util.contains(choices, v) ) { + return v; + } + else { + badChoiceWarning(this.fnName, v, choices); + return undefined; + } + }, this); + } + else { + if( util.contains(choices, list) ) { + out = [list]; + } + else { + badChoiceWarning(this.fnName, list, choices); + } + } + } + if( util.isEmpty(out) ) { + if( required ) { + assertRequired(required, this.fnName, this.pos, + util.printf('choices must be in [%s]', choices)); + } + return defaultValue === true? choices : defaultValue; + } + return out; + } + +}; + +function isOfType(val, types) { + if( types === true ) { return true; } + if( !util.isArray(types) ) { types = [types]; } + return util.contains(types, function(type) { + switch(type) { + case 'array': + return util.isArray(val); + case 'string': + return typeof(val) === 'string'; + case 'number': + return isFinite(parseInt(val, 10)); + case 'int': + case 'integer': + return isFinite(parseFloat(val)); + case 'object': + return util.isObject(val); + case 'function': + return typeof(val) === 'function'; + case 'bool': + case 'boolean': + return typeof(val) === 'boolean'; + case 'boolean-like': + return !util.isObject(val); // be lenient here + default: + throw new Error('Args received an invalid data type: '+type); + } + }); +} + +function assertRequired(required, fnName, pos, msg) { + msg = util.printf('%s: invalid argument at pos %d, %s (received %s)', fnName, pos, msg); + if( required === true ) { + throw new Error(msg); + } + else if( util.has(log, required) ) { + log[required](msg); + } + else { + throw new Error('The `required` value passed to Args methods must either be true or a method name from logger'); + } +} + +function badChoiceWarning(fnName, val, choices) { + log.warn('%s: invalid choice %s, must be one of [%s]', fnName, val, choices); +} + +function format(val, types) { + if( types === true ) { return val; } + var type = util.isArray(types)? types[0] : types; + switch(type) { + case 'array': + return util.isArray(val)? val : [val]; + case 'string': + return val + ''; + case 'number': + return parseFloat(val); + case 'int': + case 'integer': + return parseInt(val, 10); + case 'bool': + case 'boolean': + case 'boolean-like': + return !!val; + case 'function': + case 'object': + return val; + default: + throw new Error('Args received an invalid data type: '+type); + } +} + +module.exports = Args; +},{"./logger.js":29,"./util.js":31}],29:[function(require,module,exports){ +'use strict'; +/*global window*/ +var DEFAULT_LEVEL = 2; // errors and warnings +var oldDebuggingLevel = false; +var fakeConsole = { + error: noop, warn: noop, info: noop, log: noop, debug: noop, time: noop, timeEnd: noop, group: noop, groupEnd: noop +}; +var util = require('./util.js'); + +var logger = function() { + logger.log.apply(null, util.toArray(arguments)); +}; + +/** hints for the IDEs */ +logger.warn = noop; +logger.error = noop; +logger.info = noop; +logger.log = noop; +logger.debug = noop; +logger.isErrorEnabled = noop; +logger.isWarnEnabled = noop; +logger.isInfoEnabled = noop; +logger.isLogEnabled = noop; +logger.isDebugEnabled = noop; + +/** + * @param {int} level use -1 to turn off all logging, use 5 for maximum debugging + * @param {string|RegExp} [grep] filter logs to those whose first value matches this text or expression + */ +logger.logLevel = function(level, grep) { + if( typeof(level) !== 'number' ) { level = levelInt(level); } + + if( oldDebuggingLevel === level ) { return function() {}; } + + util.each(['error', 'warn', 'info', 'log', 'debug'], function(k, i) { + var isEnabled = typeof(console) !== 'undefined' && level >= i+1; + if( isEnabled ) { + // binding is necessary to prevent IE 8/9 from having a spaz when + // .apply and .call are used on console methods + var fn = util.bind(console[k==='debug'? 'log' : k], console); + logger[k] = function() { + var args = util.toArray(arguments); + if( args.length > 1 && typeof args[0] === 'string' ) { + var m = args[0].match(/(%s|%d|%j)/g); + if( m ) { + var newArgs = [util.printf.apply(util, args)]; + args = args.length > m.length+1? newArgs.concat(args.slice(m.length+1)) : newArgs; + } + } + if( !grep || !filterThis(grep, args) ) { + fn.apply(typeof(console) === 'undefined'? fakeConsole : console, args); + } + }; + } + else { + logger[k] = noop; + } + logger['is' + ucfirst(k) + 'Enabled'] = function() { return isEnabled; }; + }); + + // provide a way to revert the debugging level if I want to change it temporarily + var off = (function(x) { + return function() { logger.logLevel(x); }; + })(oldDebuggingLevel); + oldDebuggingLevel = level; + + return off; +}; + +function ucfirst(s) { + return s.charAt(0).toUpperCase() + s.substr(1); +} + +function getDefaultLevel() { + var m; + if( typeof(window) !== 'undefined' && window.location && window.location.search ) { + m = window.location.search.match('\bdebugLevel=([0-9]+)\b'); + } + return m? parseInt(m[1], 10) : DEFAULT_LEVEL; +} + +function noop() { return true; } + +function filterThis(expr, args) { + if( !args.length ) { + return true; + } + else if( expr instanceof RegExp ) { + return !expr.test(args[0]+''); + } + else { + return !(args[0]+'').match(expr); + } +} + +function levelInt(x) { + switch(x) { + case false: return 0; + case 'off': return 0; + case 'none': return 0; + case 'error': return 1; + case 'warn': return 2; + case 'warning': return 2; + case 'info': return 3; + case 'log': return 4; + case 'debug': return 5; + case true: return DEFAULT_LEVEL; + case 'on': return DEFAULT_LEVEL; + case 'all': return DEFAULT_LEVEL; + default: return DEFAULT_LEVEL; + } +} + +logger.logLevel(getDefaultLevel()); +module.exports = logger; +},{"./util.js":31}],30:[function(require,module,exports){ +'use strict'; +var util = require('./util.js'); + +function Queue(criteriaFunctions) { + this.needs = 0; + this.met = 0; + this.queued = []; + this.errors = []; + this.criteria = []; + this.processing = false; + util.each(criteriaFunctions, this.addCriteria, this); +} + +Queue.prototype = { + /** + * @param {Function} criteriaFn + * @param {Object} [scope] + */ + addCriteria: function(criteriaFn, scope) { + if( this.processing ) { + throw new Error('Cannot call addCriteria() after invoking done(), fail(), or handler() methods'); + } + this.criteria.push(scope? [criteriaFn, scope] : criteriaFn); + return this; + }, + + /** + * Returns a node-like callback to be invoked when an op is completed. + * @returns {function} + */ + getHandler: function() { + var doneCallback, result; + this.addCriteria(function(done) { + if( result !== util.undef ) { + done(result); + } + else { + doneCallback = done; + } + }); + return function(err) { + if( doneCallback ) { doneCallback(err); } + else { result = err; } + }; + }, + + ready: function() { + return this.needs === this.met; + }, + + done: function(fn, context) { + if( fn ) { + this._runOrStore(function() { + if( !this.hasErrors() ) { fn.call(context); } + }); + } + return this; + }, + + fail: function(fn, context) { + this._runOrStore(function() { + if( this.hasErrors() ) { fn.apply(context, this.getErrors()); } + }); + return this; + }, + + handler: function(fn, context) { + this._runOrStore(function() { + fn.apply(context, this.getErrors()); + }); + return this; + }, + + /** + * @param {Queue} queue + */ + chain: function(queue) { + this.addCriteria(queue.handler, queue); + return this; + }, + + when: function(def) { + this._runOrStore(function() { + if( this.hasErrors() ) { + def.reject.apply(def, this.getErrors()); + } + else { + def.resolve(); + } + }); + }, + + addError: function(e) { + this.errors.push(e); + }, + + hasErrors: function() { + return this.errors.length; + }, + + getErrors: function() { + return this.errors.slice(0); + }, + + _process: function() { + this.processing = true; + this.needs = this.criteria.length; + util.each(this.criteria, this._evaluateCriteria, this); + }, + + _evaluateCriteria: function(criteriaFn) { + var scope = null; + if( util.isArray(criteriaFn) ) { + scope = criteriaFn[1]; + criteriaFn = criteriaFn[0]; + } + try { + criteriaFn.call(scope, util.bind(this._criteriaMet, this)); + } + catch(e) { + this.addError(e); + } + }, + + _criteriaMet: function(error) { + if( error ) { this.addError(error); } + this.met++; + if( this.ready() ) { + util.each(this.queued, this._run, this); + } + }, + + _runOrStore: function(fn) { + if( !this.processing ) { this._process(); } + if( this.ready() ) { + this._run(fn); + } + else { + this.queued.push(fn); + } + }, + + _run: function(fn) { + fn.call(this); + } +}; + +module.exports = function(criteriaFns, callback) { + var q = new Queue(criteriaFns); + if( callback ) { q.done(callback); } + return q; +}; + +},{"./util.js":31}],31:[function(require,module,exports){ +(function (global){ +/*jshint unused:vars */ +/*jshint bitwise:false */ + +'use strict'; + +var undef; +var util = exports; +var READ_EVENTS = ['value', 'child_added', 'child_removed', 'child_updated', 'child_changed']; + +util.undef = undef; +util.Firebase = global.Firebase || require('firebase'); + +util.isDefined = function(v) { + return v !== undef; +}; + +util.isUndefined = function(v) { + return v === undef; +}; + +util.isObject = function(v) { + return Object.prototype.isPrototypeOf(v); +}; + +util.isArray = function(v) { + return (Array.isArray || isArray).call(null, v); +}; + +util.isArrayLike = function(v) { + return util.isArray(v) || isArguments(v); +}; + +/** + * @param v value to test or if `key` is provided, the object containing method + * @param {string} [key] if provided, v is an object and this is the method we want to find + * @returns {*} + */ +util.isFunction = function(v, key) { + if( typeof(key) === 'string' ) { + return util.isObject(v) && util.has(v, key) && typeof(v[key]) === 'function'; + } + else { + return typeof(v) === 'function'; + } +}; + +util.toArray = function(vals, startFrom) { + var newVals = util.map(vals, function(v, k) { return v; }); + return startFrom > 0? newVals.slice(startFrom) : newVals; +}; + +/** + * @param {boolean} [recursive] if true, keys are merged recursively, otherwise, they replace the base + * @param {...object} base + * @returns {Object} + */ +util.extend = function(recursive, base) { + var args = util.toArray(arguments); + var recurse = typeof args[0] === 'boolean' && args.shift(); + var out = args.shift(); + util.each(args, function(o) { + if( util.isObject(o) ) { + util.each(o, function(v,k) { + out[k] = recurse && util.isObject(out[k])? util.extend(true, out[k], v) : v; + }); + } + }); + return out; +}; + +util.bind = function(fn, scope) { + var args = Array.prototype.slice.call(arguments, 1); + return (fn.bind || bind).apply(fn, args); +}; + +/** + * @param {Object|Array} vals + * @returns {boolean} + */ +util.isEmpty = function(vals) { + return vals === undef || vals === null || + (util.isArrayLike(vals) && vals.length === 0) || + (util.isObject(vals) && !util.contains(vals, function(v) { return true; })); +}; + +/** + * @param {Object|Array} vals + * @returns {Array} of keys + */ +util.keys = function(vals) { + var keys = []; + util.each(vals, function(v, k) { keys.push(k); }); + return keys; +}; + +/** + * Create an array using values returned by an iterator. Undefined values + * are discarded. + * + * @param vals + * @param iterator + * @param [scope] + * @returns {*} + */ +util.map = function(vals, iterator, scope) { + var out = []; + util.each(vals, function(v, k) { + var res = iterator.call(scope, v, k, vals); + if( res !== undef ) { out.push(res); } + }); + return out; +}; + +/** + * + * @param {Object} list + * @param {Function} iterator + * @param {Object} [scope] + */ +util.mapObject = function(list, iterator, scope) { + var out = {}; + util.each(list, function(v,k) { + var res = iterator.call(scope, v, k, list); + if( res !== undef ) { + out[k] = res; + } + }); + return out; +}; + +/** + * Returns the first match + * @param {Object|Array} vals + * @param {Function} iterator + * @param {Object} [scope] set `this` in the callback or undefined + */ +util.find = function(vals, iterator, scope) { + if( util.isArrayLike(vals) ) { + for(var i = 0, len = vals.length; i < len; i++) { + if( iterator.call(scope, vals[i], i, vals) === true ) { return vals[i]; } + } + } + else if( util.isObject(vals) ) { + var key; + for (key in vals) { + if (vals.hasOwnProperty(key) && iterator.call(scope, vals[key], key, vals) === true) { + return vals[key]; + } + } + } + return undef; +}; + +util.filter = function(list, iterator, scope) { + var isArray = util.isArrayLike(list); + var out = isArray? [] : {}; + util.each(list, function(v,k) { + if( iterator.call(scope, v, k, list) ) { + if( isArray ) { + out.push(v); + } + else { + out[k] = v; + } + } + }); + return out; +}; + +util.reduce = function(list, accumulator, iterator) { + util.each(list, function(v, k) { + accumulator = iterator(accumulator, v, k, list); + }); + return accumulator; +}; + +util.has = function(vals, key) { + return util.isObject(vals) && vals[key] !== undef; +}; + +util.val = function(list, key) { + return util.has(list, key)? list[key] : undef; +}; + +util.contains = function(vals, iterator, scope) { + if( typeof(iterator) !== 'function' ) { + if( util.isArray(vals) ) { + return util.indexOf(vals, iterator) > -1; + } + iterator = (function(testVal) { + return function(v) { return v === testVal; }; + })(iterator); + } + return util.find(vals, iterator, scope) !== undef; +}; + +util.each = function(vals, cb, scope) { + if( util.isArrayLike(vals) ) { + (vals.forEach || forEach).call(vals, cb, scope); + } + else if( util.isObject(vals) ) { + var key; + for (key in vals) { + if (vals.hasOwnProperty(key)) { + cb.call(scope, vals[key], key, vals); + } + } + } +}; + +util.indexOf = function(list, item) { + return (list.indexOf || indexOf).call(list, item); +}; + +util.remove = function(list, item) { + var res = false; + if( util.isArray(list) ) { + var i = util.indexOf(list, item); + if( i > -1 ) { + list.splice(i, 1); + res = true; + } + } + else if( util.isObject(list) ) { + var key; + for (key in list) { + if (list.hasOwnProperty(key) && item === list[key]) { + res = true; + delete list[key]; + break; + } + } + } + return res; +}; + +/** + * Invoke a function after a setTimeout(..., 0), to help convert synch callbacks to async ones. + * Additional args after `scope` will be passed to the fn when it is invoked + * + * @param {Function} fn + * @param {Object} scope the `this` scope inside `fn` + */ +util.defer = function(fn, scope) { + var args = util.toArray(arguments); + setTimeout(util.bind.apply(null, args), 0); +}; + +/** + * Call a method on each instance contained in the list. Any additional args are passed into the method. + * + * @param {Object|Array} list contains instantiated objects + * @param {String} methodName + * @return {Array} + */ +util.call = function(list, methodName) { + var args = util.toArray(arguments, 2); + var res = []; + util.each(list, function(o) { + if( typeof(o) === 'function' && !methodName ) { + return res.push(o.apply(null, args)); + } + if( util.isObject(o) && typeof(o[methodName]) === 'function' ) { + res.push(o[methodName].apply(o, args)); + } + }); + return res; +}; + +/** + * Determine if two variables are equal. They must be: + * - of the same type + * - arrays must be same length and all values pass isEqual() + * - arrays must be in same order + * - objects must contain the same keys and their values pass isEqual() + * - object keys do not have to be in same order unless objectsSameOrder === true + * - primitives must pass === + * + * @param a + * @param b + * @param {boolean} [objectsSameOrder] + * @returns {boolean} + */ +util.isEqual = function(a, b, objectsSameOrder) { + if( a === b ) { return true; } + else if( typeof(a) !== typeof(b) ) { + return false; + } + else if( util.isObject(a) && util.isObject(b) ) { + var isA = util.isArrayLike(a); + var isB = util.isArrayLike(b); + if( isA || isB ) { + return isA && isB && a.length === b.length && !util.contains(a, function(v, i) { + return !util.isEqual(v, b[i]); + }); + } + else { + var aKeys = objectsSameOrder? util.keys(a) : util.keys(a).sort(); + var bKeys = objectsSameOrder? util.keys(b) : util.keys(b).sort(); + return util.isEqual(aKeys, bKeys) && + !util.contains(a, function(v, k) { return !util.isEqual(v, b[k]); }); + } + } + else { + return false; + } +}; + +util.bindAll = function(context, methods) { + util.each(methods, function(m,k) { + if( typeof(m) === 'function' ) { + methods[k] = util.bind(m, context); + } + }); + return methods; +}; + +util.printf = function() { + var localArgs = util.toArray(arguments); + var template = localArgs.shift(); + var matches = template.match(/(%s|%d|%j)/g); + if( matches && localArgs.length ) { + util.find(matches, function (m) { + template = template.replace(m, format(localArgs.shift(), m)); + return localArgs.length === 0; + }); + } + return template; +}; + +util.mergeToString = function(list) { + if( list.length === 0 ) { return null; } + else if( list.length === 1 ) { return list[0]; } + else { + return '[' + list.join('][') + ']'; + } +}; + +// credits: http://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible +util.construct = function(constructor, args) { + function F() { + return constructor.apply(this, args); + } + F.prototype = constructor.prototype; + return new F(); +}; + +util.noop = function() {}; + +var wrappingClasses = []; +util.isFirebaseRef = function(x) { + // ES5 throws TypeError if getPrototypeOf() called on a non-object + if( !util.isObject(x) ) { + return false; + } + + // necessary because instanceof won't work on Firebase Query objects + // so we can't simply do instanceof here + var proto = Object.getPrototypeOf(x); + if( proto && proto.constructor === util.Firebase.prototype.constructor ) { + return true; + } + + //todo-hack: SDK 2.2.x no longer works with the above. This is a hack to make that work until fixed + if( typeof(x.ref) === 'function' && typeof(x.ref().transaction) === 'function' ) { + return true; + } + + return util.isFirebaseRefWrapper(x); +}; + +util.isFirebaseRefWrapper = function(x) { + return util.contains(wrappingClasses, function(C) { + return x instanceof C; + }); +}; + +util.isQueryRef = function(x) { + return util.isFirebaseRef(x) && typeof x.transaction !== 'function'; +}; + +// Add a Class as a valid substitute for a Firebase reference, so that it will +// pass util.isFirebaseRef(). This means that it must provide all the Firebase +// API methods and behave exactly like a Firebase instance in all cases. +util.registerFirebaseWrapper = function(WrappingClass) { + wrappingClasses.push(WrappingClass); +}; + +// for test units +util._mockFirebaseRef = function(mock) { + util.Firebase = mock; +}; + +util.escapeEmail = function(email) { + return (email||'').replace('.', ','); +}; + + +util.assertValidEvent = function(event) { + if( !util.contains(READ_EVENTS, event) ) { + throw new Error('Event must be one of ' + READ_EVENTS + ', but received ' + event); + } +}; + +/** + * Inherit prototype of another JS class. Adds an _super() method for the constructor to call. + * It takes any number of arguments (whatever the inherited classes constructor methods are), + * + * Limitations: + * 1. Inherited constructor must be callable with no arguments (to make instanceof work), but can be called + * properly during instantiation with arguments by using _super(this, args...) + * 2. Can only inherit one super class, no exceptions + * 3. Cannot call prototype = {} after using this method + * + * @param {Function} Child + * @param {Function} Parent a class which can be constructed without passing any arguments + * @returns {Function} + */ +util.inherits = function(Child, Parent) { + var methodSets = [Child.prototype].concat(util.toArray(arguments).slice(2)); + Child.prototype = new Parent(); + Child.prototype.constructor = Parent; + util.each(methodSets, function(fnSet) { + util.each(fnSet, function(fn, key) { + Child.prototype[key] = fn; + }); + }); + Child.prototype._super = function() { + Parent.apply(this, arguments); + }; + return Child; +}; + +util.deepCopy = function(data) { + if( !util.isObject(data) ) { return data; } + var out = util.isArrayLike(data)? [] : {}; + util.each(data, function(v,k) { + out[k] = util.deepCopy(v); + }); + return out; +}; + +util.pick = function(obj, keys) { + if( !util.isObject(obj) ) { return {}; } + var out = util.isArrayLike(obj)? [] : {}; + util.each(keys, function(k) { + out[k] = obj[k]; + }); + return out; +}; + +util.eachByPath = function(map, data, callback, context) { + var dataByPath = {}; + util.each(data, function(v,k) { + var p = map.pathFor(k); + var f = map.getField(k); + var key = f? f.id : k; + if( !util.has(dataByPath, p.name()) ) { + dataByPath[p.name()] = { path: p, data: {} }; + } + dataByPath[p.name()].data[key] = v; + }); + + util.each(dataByPath, function(collated) { + callback.call(context, collated.path, collated.data); + }); +}; + +function format(v, type) { + switch(type) { + case '%d': + return parseInt(v, 10); + case '%j': + v = util.isObject(v)? JSON.stringify(v) : v+''; + if(v.length > 500) { + v = v.substr(0, 500)+'.../*truncated*/...}'; + } + return v; + case '%s': + return v + ''; + default: + return v; + } +} + +function isArguments(o) { + return util.isObject(o) && o+'' === '[object Arguments]'; +} + +/**************************************** + * POLYFILLS + ****************************************/ + +// a polyfill for Function.prototype.bind (invoke using call or apply!) +// credits: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind +function bind(oThis) { + /*jshint validthis:true */ + if (typeof this !== 'function') { + // closest thing possible to the ECMAScript 5 internal IsCallable function + throw new TypeError('Function.prototype.bind - what is trying to be bound is not callable'); + } + + var aArgs = Array.prototype.slice.call(arguments, 1), + fToBind = this, + FNOP = function () {}, + fBound = function () { + return fToBind.apply( + this instanceof FNOP && oThis? this : oThis, + aArgs.concat(Array.prototype.slice.call(arguments)) + ); + }; + + FNOP.prototype = this.prototype; + fBound.prototype = new FNOP(); + + return fBound; +} + +// a polyfill for Array.prototype.forEach (invoke using call or apply!) +// credits: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/forEach +function forEach(fn, scope) { + /*jshint validthis:true */ + var i, len; + for (i = 0, len = this.length; i < len; ++i) { + if (i in this) { + fn.call(scope, this[i], i, this); + } + } +} + +// a polyfill for Array.isArray +// credits: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray +function isArray(vArg) { + return Object.prototype.toString.call(vArg) === '[object Array]'; +} + +// a polyfill for Array.indexOf +// credits: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/indexOf +function indexOf(searchElement, fromIndex) { + /*jshint validthis:true */ + if (this === null) { + throw new TypeError(); + } + var n, k, t = Object(this), + len = t.length >>> 0; + + if (len === 0) { + return -1; + } + n = 0; + if (arguments.length > 1) { + n = Number(arguments[1]); + if (n !== n) { // shortcut for verifying if it's NaN + n = 0; + } else if (n !== 0 && n !== Infinity && n !== -Infinity) { + n = (n > 0 || -1) * Math.floor(Math.abs(n)); + } + } + if (n >= len) { + return -1; + } + for (k = n >= 0 ? n : Math.max(len - Math.abs(n), 0); k < len; k++) { + if (k in t && t[k] === searchElement) { + return k; + } + } + return -1; +} + +}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{"firebase":"firebase"}],"firebase-util":[function(require,module,exports){ +/** + * This file establishes the Firebase.util namespace and + * defines the exports for all packages when using node.js + */ +'use strict'; + +var util = require('./common/index.js'); + +// put all our public methods into the exported scope +util.extend(exports, + require('./common/exports.js'), + require('./NormalizedCollection/exports.js'), + require('./Paginate/exports.js') +); + +/*global window */ +if( typeof window !== 'undefined' ) { + if( !window.hasOwnProperty('Firebase') ) { + console.warn('Firebase not found on the global window instance. Cannot add Firebase.util namespace.'); + } + else { + window.Firebase.util = util; + } +} +},{"./NormalizedCollection/exports.js":2,"./Paginate/exports.js":18,"./common/exports.js":24,"./common/index.js":25}]},{},[1]); diff --git a/dist/firebase-util.min.js b/dist/firebase-util.min.js new file mode 100644 index 0000000..80f7db6 --- /dev/null +++ b/dist/firebase-util.min.js @@ -0,0 +1,11 @@ +/*! + * Firebase-util: A set of experimental power tools for Firebase. + * + * Version: 0.2.6 + * URL: https://github.com/firebase/firebase-util + * Date: 2016-11-21T15:08:35.102Z + * License: MIT http://firebase.mit-license.org/ + */ +require=function t(e,i,r){function n(o,a){if(!i[o]){if(!e[o]){var h="function"==typeof require&&require;if(!a&&h)return h(o,!0);if(s)return s(o,!0);var u=new Error("Cannot find module '"+o+"'");throw u.code="MODULE_NOT_FOUND",u}var c=i[o]={exports:{}};e[o][0].call(c.exports,function(t){var i=e[o][1][t];return n(i?i:t)},c,c.exports,t,e,i,r)}return i[o].exports}for(var s="function"==typeof require&&require,o=0;o=0}function o(t,e){if(t instanceof s)return u.pick(t,["path","id","key","alias","pathName","url"]);"string"==typeof t&&(t={key:t});var i=t.key.split("."),r=e.getPath(i[0]);return{pathName:i[0],id:i[1],key:t.key,alias:t.alias||i[1],path:r,url:r?r.url()+"/"+i[1]:null}}function a(t,e,i){if(null!==i){if(e.indexOf(".")>0){for(var r,n=e.split(".").reverse();n.length>1&&(r=n.pop());)t=t[r]=u.has(t,r)?t[r]:{};e=n.pop()}t[e]=i}}function h(t,e){var i=e;if(!u.isObject(t))return u.undef;var r=t[e];if(e.indexOf(".")>0){var n=e.split(".").reverse();for(r=t;n.length;)i=n.pop(),r=u.isObject(r)&&r.hasOwnProperty(i)?r[i]:u.undef}return r}var u=t("../../common"),c=t("./PathManager");r.prototype={add:function(t){var e=new s(o(t,this.pathMgr));if(this.fields.hasOwnProperty(e.alias))throw new Error("Duplicate field alias "+e.alias+"("+e.key+")");if(null===e.path)throw new Error("Invalid path specified for field "+e.key+"; it was not in the paths provided, which are : "+this.pathMgr.getPathNames().join(","));var i=e.path.getDependency();if(null!==i&&null===n(this.fields,i))throw new Error("Dynamic paths must reference a field declared in the map. Please add "+r.key(i.path,i.field)+" to the select() criteria before using it in a dynamic field");this.fields[e.alias]=e,this.length++},forEach:function(t,e){return u.find(this.fields,t,e)!==u.undef},getField:function(t){return this.fields[t]||null},getPath:function(t){return this.getPathManager().getPath(t)},getPathManager:function(){return this.pathMgr},pathFor:function(t){var e=this.getField(t);return e?e.path:this.pathMgr.first()},fieldsFor:function(t){return u.filter(u.toArray(this.fields),function(e){return e.pathName===t})},aliasFor:function(t){var e=u.find(this.fields,function(e){return e.url===t},this);return e?e.alias:null},extractData:function(t,e){var i={},r=this.pathMgr.getPathName(t.ref().toString());if(null===r&&null!==t.ref().parent()){var n=this.pathMgr.getPathFor(t.ref().parent().toString());n&&n.hasDependency()&&(r=n.name())}var s=e?"exportVal":"val";return u.each(this.fieldsFor(r),function(e){switch(e.id){case"$key":a(i,e.alias,t.key());break;case"$value":a(i,e.alias,t[s]());break;default:t.hasChild(e.id)&&a(i,e.alias,t.child(e.id)[s]())}}),i},snapFor:function(t,e){var i,r=this.pathFor(e);if(!r)return null;var s=r.getDependency();if(null!==s){var o=n(this.fields,s),a=this.snapFor(t,o.alias);a&&(i="$key"===s.field?r.child(a.key()).url():"$value"===s.field?r.child(a.val()).url():r.child(a.child(s.field).val()).url())}else i=r.url();return i?u.find(t,function(t){return t.ref().toString()===i})||null:null},denest:function(t){var e={};return u.each(this.getPathManager().getPaths(),function(t){e[t.name()]={path:t,data:{}}}),this.forEach(function(i){var r=h(t,i.alias);if(r!==u.undef)switch(i.id){case"$value":e[i.pathName].data=r;break;case"$key":break;default:e[i.pathName].data[i.id]=r}}),e},idFor:function(t){var e=this.getField(t);return e?e.id:t}},r.key=function(t,e){return"string"!=typeof t&&(t=t.name()),t+"."+e},r.fieldMap=function(t,e){var i,n=t.getField(e);n?(i=n.path,"$value"!==n.id&&(i=i.child(n.id))):i=t.pathFor(e).child(e);var s=new c([i]),o=new r(s);return o.add({key:r.key(i,"$value"),alias:e}),o},r.recordMap=function(t,e){var i=t.getPathManager(),n=u.map(i.getPaths(),function(t){return t.normChild(e)}),s=new r(new c(n));return t.forEach(function(t){s.add({key:t.key,alias:t.alias})}),s},e.exports=r},{"../../common":25,"./PathManager":10}],5:[function(t,e,i){"use strict";function r(){this.criteria=[],s.each(arguments,this.add,this)}function n(t){this.match=t}var s=t("../../common");r.prototype={add:function(t){this.criteria.push(new n(t))},test:function(t,e,i){return s.contains(this.criteria,function(r){return!r.test(t,e,i)})===!1}},n.prototype.test=function(t,e,i){return this.match(t,e,i)===!0},e.exports=r},{"../../common":25}],6:[function(t,e,i){"use strict";function r(t){n(arguments),this.pathMgr=new u(h.toArray(arguments)),this.map=new l(this.pathMgr),this.filters=new c,this.finalized=!1}function n(t){function e(t){return h.isArray(t)&&(t=t[0]),!h.isFirebaseRef(t)}if(t.length<1)throw new Error("Must provide at least one path definition");if(h.contains(t,e))throw new Error("Each argument to the NormalizedCollection constructor must be a valid Firebase reference or an Array containing a Firebase ref as the first argument")}function s(t,e){if(t.finalized)throw new Error("Cannot call "+e+"() after ref() has been invoked")}function o(t){var e=[],i=[],r="";return h.each(t.pathMgr.getPaths(),function(t){var i=t.getDependency();e.push(h.printf('\t"%s%s"%s',t.url(),t.id()===t.name()?"":" as "+t.name(),i?"-> "+i.path+"."+i.field:""))}),t.map.forEach(function(t){i.push(h.printf('"%s%s"',t.key,t.alias===t.id?"":" as "+t.alias)),i.length%5===0&&i.push("\n")}),t.filters.criteria.length>0&&(r=h.printf("<%s filters applied>",t.filters.criteria.length)),h.printf("NormalizedCollection(\n%s\n).select(%s)%s.ref()",e.join("\n"),i.join(", "),r)}function a(t){var e;if(e="string"==typeof t?t:h.has(t,"key")?t.key:h.undef,"string"!=typeof e||e.indexOf(".")<=0)throw new Error('Each field passed to NormalizedCollection.select() must either be a string in the format "pathAlias.fieldId", or an object in the format {key: "pathAlias.fieldId", alias: "any_name_for_field"}, but I received '+JSON.stringify(t))}var h=t("../../common"),u=t("./PathManager"),c=t("./Filter"),l=t("./FieldMap"),f=t("./NormalizedRef"),d=t("./RecordSet");r.prototype={select:function(t){s(this,"select");var e=h.args("NormalizedCollection.select",arguments,1);return h.each(e.restAsList(0,["string","object"]),function(t){a(t),this.map.add(t)},this),this},filter:function(t){s(this,"filter");var e=h.args("NormalizedCollection.filter",arguments,1,1);return this.filters.add(e.nextReq("function")),this},ref:function(){if(!this.map.length)throw new Error("Must call select() with at least one field before creating a ref");this.finalized=!0,h.log.isInfoEnabled()&&h.log.info("NormalizedRef created using %s",o(this));var t=new d(this.map,this.filters);return new f(t)}},e.exports=r},{"../../common":25,"./FieldMap":4,"./Filter":5,"./NormalizedRef":7,"./PathManager":10,"./RecordSet":14}],7:[function(t,e,i){"use strict";function r(t,e){this._super(this,t),this._parent=e||null,this._key=t.getName(),this._toString=t.getUrl()}function n(t){return function(){var e=a.toArray(arguments);a.each(this.$getPaths(),function(i){var r=i.ref();r[t].apply(r,e)})}}function s(t){return function(){var e=a.toArray(arguments),i=this.$getMaster();return i[t].apply(i,e)}}function o(t){return function(){throw new Error(t+" is not supported for NormalizedCollection references. Try calling it on the original reference used to create the NormalizedCollection instead.")}}var a=t("../../common"),h=t("./Query");a.inherits(r,h,{child:function(t){for(var e=t.split("/").reverse(),i=this,n=this;e.length;){var s=e.pop();n=new r(n.$getRecord().makeChild(s),i),i=n}return n},parent:function(){return this._parent},root:function(){for(var t=this;null!==t.parent();)t=t.parent();return t},name:function(){return console.warn("The name() function has been deprecated. Use key() instead."),this.key()},key:function(){return this._key},toString:function(){return this._toString},set:function(t,e,i){this.$getRecord().saveData(t,{callback:e,context:i,isUpdate:!1})},update:function(t,e,i){this.$getRecord().saveData(t,{callback:e,context:i,isUpdate:!0})},remove:function(t,e){this.$getRecord().saveData(null,{callback:t,context:e,isUpdate:!1})},push:function(t,e,i){var r=this.$getMaster().push().key(),n=this.child(r);return arguments.length&&n.set.apply(n,arguments),n},setWithPriority:function(t,e,i,r){this.$getRecord().saveData(t,{callback:i,context:r,isUpdate:!1,priority:e})},setPriority:function(t,e,i){this.$getMaster().setPriority(t,e,i)},auth:s("auth"),unauth:s("unauth"),authWithCustomToken:s("authWithCustomToken"),authAnonymously:s("authAnonymously"),authWithPassword:s("authWithPassword"),authWithOAuthPopup:s("authWithOAuthPopup"),authWithOAuthRedirect:s("authWithOAuthRedirect"),authWithOAuthToken:s("authWithOAuthToken"),getAuth:s("getAuth"),onAuth:s("onAuth"),offAuth:s("offAuth"),createUser:s("createUser"),changePassword:s("changePassword"),removeUser:s("removeUser"),resetPassword:s("resetPassword"),changeEmail:s("changeEmail"),goOffline:n("goOffline"),goOnline:n("goOnline"),transaction:o("transaction"),onDisconnect:o("onDisconnect")}),e.exports=r},{"../../common":25,"./Query":11}],8:[function(t,e,i){"use strict";function r(t,e){if(this._ref=t,this._rec=t.$getRecord(),!n.isArray(e))throw new Error("Must provide an array of snapshots to merge");this._pri=this._rec.getPriority(e),this._snaps=e}var n=t("../../common");r.prototype={val:function(){return this._snaps.length?this._rec.mergeData(this._snaps,!1):null},child:function(t){var e,i=t.split("/").reverse(),n=i.pop();for(e=new r(this._ref.child(n),this._rec.getChildSnaps(this._snaps,n));i.length;)e=e.child(i.pop());return e},forEach:function(t,e){return this._rec.forEachKey(this._snaps,function(i,r){return"$value"!==i&&"$key"!==i&&t.call(e,this.child(r))},this)},hasChild:function(t){for(var e=t.split("/").reverse(),i=e.length>0,r=this._ref,n=this;i&&e.length;){var s=e.pop();i=r.$getRecord().hasChild(n._snaps,s),i&&e.length&&(r=r.child(s),n=n.child(s))}return i},hasChildren:function(){return this._rec.forEachKey(this._snaps,function(t){return"$key"!==t&&"$value"!==t})},name:function(){return console.warn("name() has been deprecated. Use key() instead."),this.key()},key:function(){return this._rec.getName()},numChildren:function(){var t=0;return this._rec.forEachKey(this._snaps,function(e){"$key"!==e&&"$value"!==e&&t++}),t},ref:function(){return this._ref.ref()},getPriority:function(){return this._pri},exportVal:function(){return this._rec.mergeData(this._snaps,!0)},exists:function(){return null!==this.val()}},e.exports=r},{"../../common":25}],9:[function(t,e,i){"use strict";function r(t,e){var i=n(t);this._ref=i.ref,this._alias=i.alias,this._dep=i.dep,this._parent=e||null}function n(t){var e,i,r=null;return o.isArray(t)?(e=t[0],i=t[1],r=t[2]):e=o.isFunction(t.ref)?t.ref():t,{ref:e,alias:i||e.key(),dep:s(r)}}function s(t){if(o.isObject(t))return t;if(t){var e=t.split(".");return{path:e[0],field:e[1]}}return null}var o=t("../../common");r.prototype={ref:function(){return this._ref},reff:function(){return this.ref().ref()},child:function(t){return new r(this.reff().child(t),this)},normChild:function(t){var e=this.getDependency();return null!==e?new r([this.reff(),this.name(),e.path+"."+e.field],this):new r([this.reff().child(t),this.name()],this)},hasDependency:function(){return null!==this._dep},getDependency:function(){return this._dep},url:function(){return this.reff().toString()},name:function(){return this._alias},id:function(){return this.reff().key()},parent:function(){return this._parent},clone:function(){return new r([this._ref,this._alias,this._dep],this._parent)}},e.exports=r},{"../../common":25}],10:[function(t,e,i){"use strict";function r(t){this.paths=[],this.pathsByUrl={},this.deps={},this.pathNames=[],o.each(t,this.add,this)}function n(t,e){return o.map(t,function(t){return e[t].path+"."+e[t].field}).join(" >> ")}var s=t("./Path"),o=t("../../common");r.prototype={add:function(t){var e=t instanceof s?t.clone():new s(t);if(!this.paths.length&&e.hasDependency())throw new Error("The master path (i.e. the first) may not declare a dependency. Perhaps you have put the wrong path first in the list?");if(o.has(this.pathsByUrl,e.url()))throw new Error("Duplicate path: "+e.url());if(o.contains(this.pathNames,e.name()))throw new Error("Duplicate path name. The .key() value for each path must be unique, or you can give each a path an alias by using [firebaseRef, alias] in the constructor. The aliases must also be unique.");this._map(e),this.paths.push(e),this.pathsByUrl[e.url()]=e.name(),this.pathNames.push(e.name())},count:function(){return this.paths.length},first:function(){return this.paths[0]},getPath:function(t){return o.find(this.paths,function(e){return e.name()===t})||null},getPathFor:function(t){var e=this.getPathName(t);return e?this.getPath(e):null},getPaths:function(){return this.paths.slice()},getPathName:function(t){return this.pathsByUrl[t]||null},getPathNames:function(){return this.pathNames.slice()},getUrls:function(){return o.keys(this.pathsByUrl)},getDependencyGraph:function(){return o.extend(!0,this.deps)},_map:function(t){var e=this.first(),i=t.getDependency();!i&&e&&(i={path:e.name(),field:"$key"}),i&&(this.deps[t.name()]=i,this._assertNotCircularDep(t.name()))},_assertNotCircularDep:function(t){for(var e=[t],i=this.deps[t];o.isDefined(i);){var r=i.path;if(o.contains(e,r))throw e.push(r),new Error("Circular dependencies in paths: "+n(e,this.deps));e.push(r),i=o.val(this.deps,r)}}},e.exports=r},{"../../common":25,"./Path":9}],11:[function(t,e,i){"use strict";function r(t,e){var i=this;i._ref=t,i._rec=e,e&&e.setRef(i)}var n=t("../../common"),s=t("./Transmogrifier");r.prototype={on:function(t,e,i,r){function s(t){"function"==typeof i&&null!==t&&i.call(r,t)}return 3===arguments.length&&n.isObject(i)&&(r=i,i=n.undef),this.$getRecord().watch(t,e,s,r),e},once:function(t,e,i,r){function s(i){a.off(t,s),e.call(r,i)}function o(t){"function"==typeof i&&null!==t&&i.call(r,t)}var a=this;return 3===arguments.length&&n.isObject(i)&&(r=i,i=n.undef),this.on(t,s,o)},off:function(t,e,i){this.$getRecord().unwatch(t,e,i)},orderByChild:function(){return this.$replicate("orderByChild",n.toArray(arguments))},orderByKey:function(){return this.$replicate("orderByKey",n.toArray(arguments))},orderByValue:function(){return this.$replicate("orderByValue",n.toArray(arguments))},orderByPriority:function(){return this.$replicate("orderByPriority",n.toArray(arguments))},limitToFirst:function(){return this.$replicate("limitToFirst",n.toArray(arguments))},limitToLast:function(){return this.$replicate("limitToLast",n.toArray(arguments))},limit:function(){return this.$replicate("limit",n.toArray(arguments))},startAt:function(){return this.$replicate("startAt",n.toArray(arguments))},endAt:function(){return this.$replicate("endAt",n.toArray(arguments))},equalTo:function(){return this.$replicate("equalTo",n.toArray(arguments))},ref:function(){return this._ref},$getRecord:function(){return this._rec},$getMaster:function(){return this._rec.getPathManager().first().ref()},$getPaths:function(){return this._rec.getPathManager().getPaths()},$replicate:function(t,e){var i=this.$getRecord(),n=this.$getMaster();return n=n[t].apply(n,e),new r(this._ref,s.replicate(i,n))}},n.registerFirebaseWrapper(r),e.exports=r},{"../../common":25,"./Transmogrifier":17}],12:[function(t,e,i){"use strict";function r(t){var e=t.getPathManager().first().id(),i=f.mergeToString(t.getPathManager().getUrls());this._super(t,e,i),this._eventManagers={},f.log.debug("Record created",this.getName(),this.getUrl())}function n(t){this.rec=t,this.pm=t.getPathManager(),this.running=!1,this._init()}function s(t,e){this.event=t,this.rec=e,this.map=e.getFieldMap(),this.pm=e.getPathManager(),this.subs=[],this.dyno=null}function o(t,e,i,r){var n=t.getDependency(),s=e.getPath(n.path),o=s.ref();if("$key"===n.field)throw new Error("Dynamic paths do not support $key (you should probably just join on this path)");"$value"!==n.field&&(o=o.child(n.field));var a,h=o.on("value",function(e){a&&a.key()!==e.val()&&(f.log.debug("Record.Dyno: stopped monitoring %s",a.toString()),a.off(i,r),r(null)),null!==e.val()&&(a=t.ref().child(e.val()),a.on(i,r),f.log("Record.Dyno: monitoring %s",a.toString()))});this.dispose=function(){o.off("value",h),a&&a.off(i,r)}}function a(t,e,i){f.each(t.fieldsFor(e.name()),function(t){switch(t.id){case"$key":break;case"$value":f.has(i,".value")||(i[".value"]=null);break;default:f.has(i,t.id)||(i[t.id]=null)}})}var h=t("./FieldMap"),u=t("./RecordField"),c=t("./AbstractRecord"),l=t("./SnapshotFactory"),f=t("../../common");f.inherits(r,c,{makeChild:function(t){var e=h.fieldMap(this.getFieldMap(),t);return new u(e)},hasChild:function(t,e){var i=this.getFieldMap().getField(e);if(!i)return!1;var r=this.getFieldMap().snapFor(t,e);return null!==r&&r.hasChild(e)},getChildSnaps:function(t,e){var i,r=this.getFieldMap().snapFor(t,e),n=this.getFieldMap().getField(e);if(n)switch(n.id){case"$key":throw new Error("Cannot get child snapshot from key (not a real child element)");case"$value":i=r;break;default:i=r.child(n.id)}else i=r.child(e);return[i]},forEachKey:function(t,e,i){function r(t,e){switch(e){case"$key":return!0;case"$value":return t&&null!==t.val();default:return t&&t.hasChild(e)}}var n=this.getFieldMap();return n.forEach(function(s){var o=n.snapFor(t,s.alias);return!!r(o,s.id)&&e.call(i,s.id,s.alias)===!0})},mergeData:function(t,e){var i=null,r=this.getFieldMap();return t.length>0&&null!==t[0].val()&&(i=f.extend.apply(null,f.map(t,function(t){return r.extractData(t,e)})),e&&null!==i&&null!==t[0].getPriority()&&(f.isObject(i)||(i={".value":i}),i[".priority"]=t[0].getPriority())),i},getPriority:function(t){return t[0].getPriority()},getClass:function(){return r},saveData:function(t,e){var i=f.queue(),r=this.getFieldMap(),n=this.getPathManager().getPaths();if(e.isUpdate&&!f.isObject(t))throw new Error("First argument to update() command must be an object");if(null===t)f.each(n,function(t){t.hasDependency()||t.reff().remove(i.getHandler())});else if(f.isObject(t)){var s=r.denest(t);f.each(s,function(t){var o=t.path,h=t.data,u=this._writeRef(s,o);null!==u?f.isEmpty(h)&&e.isUpdate||(f.isObject(h)||(h={".value":h}),e.isUpdate||a(r,o,h),f.isDefined(e.priority)&&(h[".priority"]=e.priority),f.has(h,".value")?u.set(h,i.getHandler()):u.update(h,i.getHandler())):f.log.info("No dynamic key found for master",n[0].ref().toString(),"with dynamic path",o.ref().toString())},this)}else{if(1!==n.length)throw new Error("Cannot set multiple paths to a non-object value. Since this is a NormalizedCollection, the data will be split between the paths. But I can't split a primitive value");f.isDefined(e.priority)?n[0].ref().setWithPriority(t,e.priority,i.getHandler()):n[0].ref().set(t,i.getHandler())}i.handler(e.callback||f.noop,e.context)},getName:function(){return this._name},getUrl:function(){return this._url},_start:function(t){f.has(this._eventManagers,t)||(f.log.debug("Record._start: event=%s, url=%s",t,this.getUrl()),this._eventManagers[t]="value"===t?new n(this):new s(t,this)),this._eventManagers[t].start()},_stop:function(t){f.has(this._eventManagers,t)&&(f.log.debug("Record._stop: event=%s, url=%s",t,this.getUrl()),this._eventManagers[t].stop())},_writeRef:function(t,e){var i=e.reff(),r=e.getDependency();if(null!==r){var n=this.getPathManager().getPath(r.path),s=this._depKey(t,n,r.field);i=null===s?null:i.child(s)}return i},_depKey:function(t,e,i){var r,n=t[e.name()].data;switch(i){case"$key":r=e.id();break;case"$value":r=f.has(n,".value")?n[".value"]:f.isEmpty(n)?null:n;break;default:r=f.has(n,i)?n[i]:null}var s=typeof r;if(null!==r&&"string"!==s)throw new Error("Dynamic key values must be a string. Type was "+s+" for "+e.ref().toString()+"->"+i);return r}}),n.prototype={start:function(){this.running||(this.running=!0,f.each(this.pm.getPathNames(),this._startPath,this))},stop:function(){this.running&&(this.running=!1,f.each(this.subs,function(t){t()}),this._init())},update:function(t,e){this.snaps[t]=e,this._checkLoadState(),f.log("Record.ValueEventManager.update: url=%s, loadCompleted=%s",e.ref().toString(),this.loadCompleted),this.loadCompleted&&this.rec.trigger(new l("value",this.rec.getName(),f.toArray(this.snaps)))},_startPath:function(t){var e=this,i=e.pm.getPath(t),r=f.bind(e.update,e,t);if(i.hasDependency()){var n=new o(i,this.rec.getFieldMap(),"value",r);this.subs.push(n.dispose)}else i.ref().on("value",r),e.subs.push(function(){i.ref().off("value",r)})},_checkLoadState:function(){if(!this.loadCompleted){var t=this.snaps,e=this.pm.getPathNames();this.loadCompleted=!f.contains(e,function(e){return!t.hasOwnProperty(e)})}},_init:function(){this.loadCompleted=!1,this.snaps={},this.subs=[]}},s.prototype={start:function(){f.each(this.pm.getPathNames(),function(t){var e=this.event,i=this.pm.getPath(t),r=f.bind(this.update,this);i.hasDependency()?(this.dyno=new o(i,this.map,e,r),this.subs.push(this.dyno.dispose)):(i.ref().on(e,r),this.subs.push(function(){i.ref().off(e,r)}))},this)},stop:function(){f.each(this.subs,function(t){t()}),this.subs=[]},update:function(t,e){null!==t&&null!==this.map.aliasFor(t.ref().toString())&&(f.log("Record.ChildEventManager.update: event=%s, key=%s/%s",this.event,t.ref().parent().key(),t.key()),this.rec.trigger(new l(this.event,t.key(),t,e)))}},e.exports=r},{"../../common":25,"./AbstractRecord":3,"./FieldMap":4,"./RecordField":13,"./SnapshotFactory":16}],13:[function(t,e,i){"use strict";function r(t){if(this.handlers={},this.path=t.getPathManager().first(),this._super(t,this.path.name(),this.path.url()),1!==t.getPathManager().count())throw new Error("RecordField must have exactly one path, but we got "+t.getPathManager().count());if(1!==t.length)throw new Error("RecordField must have exactly one field, but we found "+t.length);u.log.debug("RecordField created",this.getName(),this.getUrl())}function n(t){return t.callback?function(){t.callback.apply(t.context,arguments)}:u.noop}var s=t("./PathManager"),o=t("./FieldMap"),a=t("./AbstractRecord"),h=t("./SnapshotFactory"),u=t("../../common");u.inherits(r,a,{makeChild:function(t){var e=new s([this.path.child(t)]),i=new o(e);return i.add({key:o.key(e.first(),"$value"),alias:t}),new r(i)},hasChild:function(t,e){return t[0].hasChild(e)},getChildSnaps:function(t,e){return[t[0].child(e)]},mergeData:function(t,e){return e?t[0].exportVal():t[0].val()},getPriority:function(t){return t[0].getPriority()},forEachKey:function(t,e,i){var r=t[0];return r.forEach(function(t){e.call(i,t.key(),t.key())})},saveData:function(t,e){var i=this.path.ref();if(e.isUpdate){if(!u.isObject(t))throw new Error("When using update(), the data must be an object.");u.has(e,"priority")&&(t[".priority"]=e.priority),i.update(t,n(e))}else u.has(e,"priority")?i.setWithPriority(t,e.priority,n(e)):i.set(t,n(e))},getClass:function(){return r},_start:function(t){var e=this;this.handlers[t]=function(i,r){e.trigger(new h(t,i.key(),i,r))},this.path.ref().on(t,this.handlers[t],this._cancel,this)},_stop:function(t){this.handlers.hasOwnProperty(t)&&this.path.ref().off(t,this.handlers[t],this)}}),e.exports=r},{"../../common":25,"./AbstractRecord":3,"./FieldMap":4,"./PathManager":10,"./SnapshotFactory":16}],14:[function(t,e,i){"use strict";function r(t,e){var i=o.mergeToString(t.getPathManager().getPathNames()),r=o.mergeToString(t.getPathManager().getUrls());this._super(t,i,r),this.filters=e,this.monitor=new h(this)}var n=t("./Record"),s=t("./AbstractRecord"),o=t("../../common"),a=t("./FieldMap"),h=t("./RecordSetEventManager");o.inherits(r,s,{makeChild:function(t){var e=a.recordMap(this.getFieldMap(),t);return new n(e)},hasChild:function(t,e){return o.contains(t,function(t){return t.key()===e})},getChildSnaps:function(t,e){return o.filter(t,function(t){return t.key()===e})},mergeData:function(t,e){var i=this,r=null;return t.length&&null!==t[0].val()&&(r={},o.each(t,function(t){null!==t.val()&&i.filters.test(t.val(),t.key(),t.getPriority())&&(r[t.key()]=e?t.exportVal():t.val())})),r},getPriority:function(){return null},forEachKey:function(t,e,i){t.forEach(function(t){return e.call(i,t.key(),t.key())})},getClass:function(){return r},saveData:function(t,e){var i=o.queue();if(null===t)o.each(this.getPathManager().getPaths(),function(t){t.ref().remove(i.getHandler())});else{if(!o.isObject(t))throw new Error("Calls to set() or update() on a NormalizedCollection must pass either null or an object value. There is no way to split a primitive value between the paths");o.each(t,function(t,r){if(".value"===r||".priority"===r)throw new Error("Cannot use .priority or .value on the root path of a NormalizedCollection. You probably meant to sort the records anyway (i.e. one level lower).");this.child(r).saveData(t,{isUpdate:e.isUpdate,callback:i.getHandler()})},this),e.priority&&this.getPathManager().first().ref().setPriority(e.priority,i.getHandler())}i.handler(e.callback||o.noop,e.context)},_getChildKey:function(t,e,i){var r=i,n=this.getPathManager().getPathFor(t.ref().toString());if(n.hasDependency()){var s=n.getDependency(),a=this.getFieldMap().getPath(s.path);if(!a)throw new Error("Invalid dependency path. "+t.ref.toString()+" depends on "+s.path+", but that alias does not exist in the paths provided.");var h=o.find(e,function(t){return t.ref().toString()===a.url()});h?(h=h.child(i),"$value"!==s.field&&(h=h.child(s.field)),r=h.val()):r=null}return r},_start:function(){this.monitor.start()},_stop:function(t,e){0===e&&this.monitor.stop()}}),e.exports=r},{"../../common":25,"./AbstractRecord":3,"./FieldMap":4,"./Record":12,"./RecordSetEventManager":15}],15:[function(t,e,i){"use strict";function r(t){var e=t.getPathManager();this.masterRef=e.first().ref(),this.url=this.masterRef.toString(),this.recList=new n(t,this.url),this.running=!1}function n(t,e){this.obs=t,this.url=e,this._reset()}var s=t("./SnapshotFactory"),o=t("../../common");r.prototype={start:function(){return this.running||(o.log("RecordSetEventManager: Loading normalized records from master list %s",this.url),this.running=!0,this.masterRef.on("child_added",this._add,this),this.masterRef.on("child_removed",this._remove,this),this.masterRef.on("child_moved",this._move,this),this.masterRef.once("value",this.recList.masterPathLoaded,this.recList)),this},stop:function(){return this.running&&(o.log("RecordSetEventManager: Stopped monitoring master list %s",this.url),this.running=!1,this.masterRef.off("child_added",this._add,this),this.masterRef.off("child_removed",this._remove,this),this.masterRef.off("child_moved",this._move,this),this.recList.unloaded()),this},_add:function(t,e){this.recList.add(t.key(),e)},_remove:function(t){this.recList.remove(t.key())},_move:function(t,e){this.recList.move(t.key(),e)}},n.prototype={add:function(t,e){o.log.debug("RecordList.add: key=%s, prevChild=%s",t,e);var i=this.obs.child(t),r=o.bind(this._valueUpdated,this,t);this.loading[t]={rec:i,prev:e,fn:r,unwatch:function(){i.unwatch("value",r)}},this.loadComplete||this.initialKeysLeft.push(t),i.watch("value",r)},remove:function(t){o.log.debug("RecordList.remove: key=%s",t);var e=this._dropRecord(t);null!==e&&this._notify("child_removed",t,e)},move:function(t,e){if(o.has(this.recs,t)){var i=o.indexOf(this.recIds,t);this.recIds.splice(i,1),this._putAfter(t,e),this._notify("child_moved",t)}},masterPathLoaded:function(){o.log.debug("RecordList: Initial data has been loaded from master list at %s",this.url),this.masterLoaded=!0,this._checkLoadState()&&this._notifyValue()},unloaded:function(){this._reset()},findKey:function(t){return o.indexOf(this.recIds,t)},_reset:function(){o.each(this.recs,function(t,e){this.remove(e)},this),o.each(this.filtered,function(t,e){this._dropRecord(e)},this),o.each(this.loading,function(t,e){this._dropRecord(e)},this),this.recs={},this.recIds=[],this.snaps={},this.loading={},this.filtered={},this.loadComplete=!1,this.initialKeysLeft=[],this.masterLoaded=!1},_valueUpdated:function(t,e){var i;this.snaps[t]=e,o.has(this.loading,t)?(i=this.loading[t],delete this.loading[t],this._processAdd(e,i)):o.has(this.recs,t)?(i=this.recs[t],this._processChange(e,i)):o.has(this.filtered,t)?null!==e.val()&&this.obs.filters.test(e.val(),t,e.getPriority())&&(i=this.filtered[t],delete this.filtered[t],o.log("RecordList: Unfiltered key %s",t),this._processAdd(e,i)):o.log("RecordList: Orphan key %s ignored. Probably a concurrent edit.",t)},_processAdd:function(t,e){var i=t.key();this.obs.filters.test(t.val(),i,t.getPriority())?(this.recs[i]=e,this._putAfter(i,e.prev),this._notify("child_added",i)):(o.log("RecordList: Filtered key %s",i),this.filtered[i]=e),this._checkLoadState(i)&&this._notifyValue()},_processChange:function(t,e){if(null!==t.val()){var i=t.key();this.obs.filters.test(t.val(),i,t.getPriority())?this._notify("child_changed",i):(delete this.recs[i],this.filtered[i]=e,this._notify("child_removed",i,this.snaps[i]))}},_notify:function(t,e,i){var r;"child_added"!==t&&"child_moved"!==t||(r=this._getPrevChild(e)),o.log("RecordList._notify: event=%s, key=%s, prev=%s",t,e,r);var n=new s(t,e,i||this.snaps[e],r);this.obs.trigger(n),this._notifyValue()},_notifyValue:function(){if(this.loadComplete){o.log.debug("RecordList._notifyValue: snap_keys=%s",o.keys(this.snaps));var t=new s("value",null,o.toArray(this.snaps));this.obs.trigger(t)}},_getPrevChild:function(t){if(!this.recIds.length)return null;var e=this.findKey(t);return e===-1?this.recIds[this.recIds.length-1]:0===e?null:this.recIds[e-1]},_posFor:function(t){var e,i;return null===t?e=0:(i=this.findKey(t),e=i===-1?this.recIds.length:i+1),e},_putAfter:function(t,e){var i=this._posFor(e);this.recIds.splice(i,0,t)},_dropRecord:function(t){var e=null;return this.recs[t]&&(e=this.snaps[t],this.recs[t].unwatch()),this.loading[t]&&this.loading[t].unwatch(),o.has(this.filtered,t)&&this.filtered[t].unwatch(),delete this.loading[t],delete this.snaps[t],delete this.filtered[t],delete this.recs[t],o.remove(this.recIds,t),e},_checkLoadState:function(t){return!(this.loadComplete||(t&&o.remove(this.initialKeysLeft,t),this.initialKeysLeft.length||!this.masterLoaded))&&(this.loadComplete=!0, +!0)}},e.exports=r},{"../../common":25,"./SnapshotFactory":16}],16:[function(t,e,i){"use strict";function r(t,e,i,r){this.event=t,this.key=e,this.snaps=s(i),this.prevChild=r,n(this)}function n(t){switch(t.event){case"value":break;case"child_added":case"child_moved":if("string"!=typeof t.key||!t.key)throw new Error("Invalid trigger key "+t.key);if(t.prevChild===o.undef)throw new Error("Triggers must provide a valid prevChild value for child_added and child_moved events");break;case"child_removed":case"child_changed":if("string"!=typeof t.key||!t.key)throw new Error("Invalid trigger key "+t.key);break;default:throw new Error("Invalid trigger event type: "+t.event)}}function s(t){return t instanceof a?t._snaps.slice():o.isArray(t)?t.slice():[t]}var o=t("../../common"),a=t("./NormalizedSnapshot.js");r.prototype.create=function(t){var e;return e="value"===this.event?new a(t,this.snaps):new a(t.ref().child(this.key),this.snaps)},r.prototype.toString=function(){return o.printf("SnapshotFactory(event=%s, key=%s, numberOfSnapshots=%s, prevChild=%s",this.event,this.key,this.snaps.length,this.prevChild===o.undef?"undefined":this.prevChild)},e.exports=r},{"../../common":25,"./NormalizedSnapshot.js":8}],17:[function(t,e,i){"use strict";var r=t("../../common"),n=t("./PathManager"),s=t("./Path"),o=t("./FieldMap"),a=t("./RecordSet");e.exports={replicate:function(t,e){var i=t.getPathManager().getPaths().slice(0),h=i[0];i[0]=new s([e,h.name(),h.getDependency()]);var u=new n(r.map(i,function(t){return t.clone()})),c=new o(u);t.getFieldMap().forEach(c.add,c);var l,f=t.getClass();return l=f===a?new f(c,t.filters):new f(c)}}},{"../../common":25,"./FieldMap":4,"./Path":9,"./PathManager":10,"./RecordSet":14}],18:[function(t,e,i){"use strict";function r(t,e,i){var r=s.extend({},u,t);return r.maxCacheSize||(r.maxCacheSize=3*r[e]),n(r,e,i),n(r,"maxCacheSize",i),r}function n(t,e,i){if("number"!=typeof t[e])throw new Error("Argument "+e+" passed into opts for "+i+"must be a number")}var s=t("../common"),o=t("./libs/Scroll.js"),a=t("./libs/Paginate.js"),h=t("./libs/ReadOnlyRef.js"),u={field:null,pageSize:10,windowSize:250};i.Scroll=function(t,e,i,n){if(!s.isFirebaseRef(t)||s.isQueryRef(t))throw new Error("First argument to Firebase.util.Scroll must be a valid Firebase ref. It cannot be a Query (e.g. you have called orderByChild()).");if("string"!=typeof e)throw new Error("Second argument to Firebase.util.Scroll must be a valid string");if(arguments.length>2&&!s.isObject(i))throw new Error("Optional third argument to Firebase.util.Scroll must be an object of key/value pairs");var a=new h(t);return a.scroll=new o(a,e,r(i,"windowSize","Scroll"),n),a},i.Paginate=function(t,e,i){if(!s.isFirebaseRef(t)||s.isQueryRef(t))throw new Error("First argument to Firebase.util.Paginate must be a valid Firebase ref. It cannot be a Query (e.g. you have called orderByChild()).");if("string"!=typeof e)throw new Error("Second argument to Firebase.util.Paginate must be a valid string");if(arguments.length>2&&!s.isObject(i))throw new Error("Optional third argument to Firebase.util.Paginate must be an object of key/value pairs");var n=new h(t);return n.page=new a(n,e,r(i,"pageSize","Paginate")),n}},{"../common":25,"./libs/Paginate.js":21,"./libs/ReadOnlyRef.js":22,"./libs/Scroll.js":23}],19:[function(t,e,i){"use strict";function r(t,e,i,r){n.log.debug("Cache: caching %s using field=%s maxRecordsLoaded=%d",t.toString(),e,i),this.offset=new s({field:e,max:i,ref:t.ref()}),this.outRef=t,this.inRef=null,this.queryRef=null,this.countRef=null,this.keys={},this.start=0,this.count=-1,this.endCount=-1,this.nextListeners=[],this.offset.observe(this._keyChange,this),this.desc=r}var n=t("../../common"),s=t("./Offset");r.prototype.moveTo=function(t,e){n.log.debug("Cache.moveTo: startOffset=%d, numberOfRecords=%d",t,e);var i=this.start,r=this.count;this.start=t,this.count=e,this.endCount=this.start+this.count,i!==this.start?this.offset.goTo(t,e):r!==this.count&&this._refChange()},r.prototype.hasNext=function(){return this.count===-1||this.endCount>this.start+this.count},r.prototype.hasPrev=function(){return this.start>0},r.prototype.observeHasNext=function(t,e){var i=this.nextListeners,r=[t,e];return i.push(r),function(){n.remove(i,r)}},r.prototype.destroy=function(){this._unsubscribe(),this.offset.destroy(),this.offset=null,this.start=0,this.count=-1,this.inRef=null,this.outRef=null,this.queryRef=null,this.countRef=null,this.keys=null,this.nextListeners=null},r.prototype._keyChange=function(t,e,i){this.inRef=i,n.log.debug("Cache._keyChange: %s %s %s",t,e,i.toString()),this._refChange()},r.prototype._unsubscribe=function(){this.queryRef&&(this.queryRef.off("child_added",this._add,this),this.queryRef.off("child_removed",this._remove,this),this.queryRef.off("child_moved",this._move,this),this.queryRef.off("child_changed",this._change,this),this.queryRef.off("value",this._value,this),this.queryRef.off("value",this._removeOrphans,this),this.queryRef=null),this.countRef&&(this.countRef.off("value",this._count,this),this.countRef=null)},r.prototype._refChange=function(){this._unsubscribe(),this.inRef&&this.count>-1&&(this.countRef=this.desc?this.inRef.limitToLast(this.count+1):this.inRef.limitToFirst(this.count+1),this.countRef.on("value",this._count,this),this.queryRef=this.desc?this.inRef.limitToLast(this.count):this.inRef.limitToFirst(this.count),this.queryRef.on("child_added",this._add,this),this.queryRef.on("child_removed",this._remove,this),this.queryRef.on("child_moved",this._move,this),this.queryRef.on("child_changed",this._change,this),this.queryRef.on("value",this._value,this),this.queryRef.once("value",this._removeOrphans,this))},r.prototype._add=function(t,e){var i=t.key();n.has(this.keys,i)?n.isEqual(this.keys[i],t.val())||this._change(t):(this.keys[i]=t,this.outRef.$trigger("child_added",t,e))},r.prototype._remove=function(t){var e=t.key();n.has(this.keys,e)&&(this.outRef.$trigger("child_removed",t),delete this.keys[e])},r.prototype._move=function(t,e){var i=t.key();n.has(this.keys,i)&&(this.keys[i]=t,this.outRef.$trigger("child_moved",t,e))},r.prototype._change=function(t){this.keys[t.key()]=t,this.outRef.$trigger("child_changed",t)},r.prototype._value=function(t){this.outRef.$trigger("value",t)},r.prototype._count=function(t){this.endCount=this.start+t.numChildren();var e=this.hasNext();n.each(this.nextListeners,function(t){t[0].call(t[1],e)})},r.prototype._removeOrphans=function(t){n.each(this.keys,function(e,i){t.hasChild(i)||(this.outRef.$trigger("child_removed",e),delete this.keys[i])},this)},e.exports=r},{"../../common":25,"./Offset":20}],20:[function(t,e,i){"use strict";function r(t){this.keys=[],this.field=t.field,this.ref=o(t.ref,t.field),this.max=t.max,this.listeners=[],this.curr=0,this.sub=null,this.isSubscribing=!1,this.lastNotifyValue=u.undef,this._debouncedRecache=a(function(){u.log.debug("Offset._debouncedRecache: recaching keys for offset %d",this.curr),this.keys=[],this._grow(this._listen)},this,100,1e3)}function n(t,e){var i;switch(e){case"$key":i=t.key();break;case"$priority":i=t.getPriority();break;case"$value":i=t.val();break;default:var r=t.val();if(!u.isObject(r))throw new Error("A value of type "+typeof r+'Was found. But we are attempting to order by child field "'+e+"\". Pagination requires all records to be objects or it can't determine an appropriate offset value.");i=r[e]}return{val:i,key:t.key()}}function s(t,e){return e===!1?null:null===e?t:t.startAt(e.val,e.key)}function o(t,e){return"$key"===e?t.orderByKey():"$priority"===e?t.orderByPriority():"$value"===e?t.orderByValue():t.orderByChild(e)}function a(t,e,i,r){function n(){if(h&&(h(),h=null),a&&Date.now()-a>r)c||(c=!0,setTimeout(s,0));else{a||(a=Date.now());var t=setTimeout(s,i);h=function(){clearTimeout(t)}}}function s(){h=null,a=null,c=!1,t.apply(e,u)}function o(){u=Array.prototype.slice.call(arguments,0),n()}var a,h,u,c;if("number"==typeof e&&(r=i,i=e,e=null),"number"!=typeof i)throw new Error("Must provide a valid integer for wait. Try 0 for a default");if("function"!=typeof t)throw new Error("Must provide a valid function to debounce");return r||(r=10*i||100),o.running=function(){return a>0},o}function h(t){var e=t.length;return e?t[e-1]:null}var u=t("../../common");r.prototype.goTo=function(t){t!==this.curr&&(u.log("Offset.goTo: offset changed from %d to %d",this.curr,t),this.curr=t,this.lastNotifyValue=u.undef,this._listen())},r.prototype.observe=function(t,e){this.listeners.push([t,e]);var i=this.getKey(),r=s(this.ref,i);t.call(e,i&&i.val,i&&i.key,r)},r.prototype.getKey=function(t){return arguments.length||(t=this.curr),0===t?null:this.keys.length>t&&this.keys[t]},r.prototype.destroy=function(){this._unsubscribe(),this.curr=0,this.keys=[],this.lastNotifyValue=u.undef,this.isSubscribing=!1},r.prototype._notify=function(){var t=this.getKey();if(!u.isEqual(this.lastNotifyValue,t)){u.log("Offset._notify: key at offset %d is %s",this.curr,t&&t.key),this.lastNotifyValue=t;var e=s(this.ref,t);u.each(this.listeners,function(i){i[0].call(i[1],t&&t.val,t&&t.key,e)})}},r.prototype._recache=function(){this.isSubscribing||this._debouncedRecache()};var c=0;r.prototype._grow=function(t){var e=this,i=e.keys.length;if(e.curr>=i){var r=e.getKey(),s=h(e.keys),o=Math.min(e.curr+(s?2:1)-i,e.max),a=null!==s?e.ref.startAt(s.val,s.key):e.ref;a.limitToFirst(o).once("value",function(i){var a=null!==s;if(i.forEach(function(t){return a?void(a=!1):void e.keys.push(n(t,e.field))}),c++>1e4)throw new Error("Tried to fetch more than 10,000 pages to determine the correct offset. Giving up now. Sorry.");e.curr>=e.keys.length&&i.numChildren()===o?setTimeout(u.bind(e._grow,e,t),0):(c=0,u.log.debug("Offset._grow: Cached %d keys",e.keys.length),t.call(e,!u.isEqual(e.getKey(),r)))})}else t.call(e,!1)},r.prototype._startOffset=function(){return Math.max(0,this.curr-this.max,this.curr-10)},r.prototype._queryRef=function(){var t=this._startOffset(),e=this.ref;if(t>0){var i=this.getKey(t);e=e.startAt(i.val,i.key)}return e.limitToLast(Math.max(this.curr-t,1))},r.prototype._moved=function(t){t.key()===this.getKey()&&this._recache()},r.prototype._unsubscribe=function(){this.sub&&(this.sub.off("child_added",this._recache,this),this.sub.off("child_moved",this._moved,this),this.sub.off("child_removed",this._recache,this),this.sub.off("value",this._doneSubscribing,this),this.sub=null)},r.prototype._subscribe=function(){this._unsubscribe(),this.sub=this._queryRef(),this.isSubscribing=!0,this.sub.on("child_added",this._recache,this),this.sub.on("child_moved",this._moved,this),this.sub.on("child_removed",this._recache,this),this.sub.once("value",this._doneSubscribing,this)},r.prototype._doneSubscribing=function(){this.isSubscribing=!1,this._notify()},r.prototype._monitorEmptyOffset=function(){function t(n){var s=n.numChildren();s>(null===r?0:1)&&(u.log.debug("Offset._monitorEmptyOffset: A value exists now."),i.off("value",t),e._grow())}var e=this,i=e.ref,r=null;this.keys.length&&(r=h(this.keys),i=i.startAt(r.val,r.key)),u.log.debug("Offset._monitorEmptyOffset: No value exists at offset %d, currently %d keys at this path. Watching for a new value.",this.curr,this.keys.length),i.limitToFirst(2).on("value",t)},r.prototype._listen=function(){this._unsubscribe(),this.curr>=this.keys.length?this._grow(function(){this.keys.length>=this.curr?this._subscribe():(this._monitorEmptyOffset(),this._notify())}):this._subscribe()},e.exports=r},{"../../common":25}],21:[function(t,e,i){"use strict";function r(t,e,i){this.currPage=0,this.field=e,this.ref=t,this.pageSize=i.pageSize,this.max=i.maxCacheSize,this.subs=[],this.pageChangeListeners=[],this.pageCountListeners=[],this.cache=new a(t,e,i.maxCacheSize),this.pageCount=-1,this.couldHaveMore=!1,this.cache.observeHasNext(this._countPages,this)}function n(t,e){return 0===t?0:Math.ceil(t/e)}function s(t,e){var i={};if(i.bindFunction=function(t,e){return function(){return t.apply(e,[e])}},i.stateChange=function(t){4==i.request.readyState&&i.callbackFunction(i.request.responseText)},i.getRequest=function(){return window.ActiveXObject?new ActiveXObject("Microsoft.XMLHTTP"):!!window.XMLHttpRequest&&new XMLHttpRequest},i.postBody=arguments[2]||"",i.callbackFunction=e,i.url=t,i.request=i.getRequest(),i.request){var r=i.request;r.onreadystatechange=i.bindFunction(i.stateChange,i),""!==i.postBody?(r.open("POST",t,!0),r.setRequestHeader("X-Requested-With","XMLHttpRequest"),r.setRequestHeader("Content-type","application/x-www-form-urlencoded"),r.setRequestHeader("Connection","close")):r.open("GET",t,!0),r.send(i.postBody)}return i}var o=t("../../common"),a=t("./Cache");r.prototype.next=function(){return this.hasNext()&&(this.currPage++,o.log.debug("Paginate.next: current page is %d",this.currPage),this._pageChange()),this},r.prototype.prev=function(){return this.hasPrev()&&(this.currPage--,o.log.debug("Paginate.prev: current page is %d",this.currPage),this._pageChange()),this},r.prototype.setPage=function(t){t>0&&t<=this.pageCount?(this.currPage=t,o.log.debug("Paginate.setPage: current page is %d",this.currPage),this._pageChange()):o.log.warn("Paginate.setPage: invalid page number %d",t)},r.prototype.hasNext=function(){return this.cache.hasNext()},r.prototype.hasPrev=function(){return this.currPage>1},r.prototype.onPageChange=function(t,e){var i=this.pageChangeListeners,r=[t,e];return i.push(r),t.call(e,this.currPage),function(){o.remove(i,r)}},r.prototype.onPageCount=function(t,e){var i=this.pageCountListeners,r=[t,e];return i.push(r),this.pageCount>-1?t.call(e,this.pageCount,this.couldHaveMore):this._countPages(),function(){o.remove(i,r)}},r.prototype.getCountByDowloadingAllKeys=function(t,e){var i=this;i.downloadingEverything=!0;var r=i.ref.ref().toString();r.match(/\/$/)||(r+="/"),r+=".json?shallow=true",s(r,function(r){var s=0;try{s=o.keys(JSON.parse(r)).length}catch(a){o.log.warn(a)}o.log.debug("Paginate.getCountByDownloadingAllKeys: found %d keys",s),i.downloadingEverything=!1,i.pageCount=n(s,i.pageSize),i.couldHaveMore=!1,i._notifyPageCount(),t&&t.call(e,s)})},r.prototype.destroy=function(){this.cache.destroy(),this.cache=null,this.ref=null,o.each(this.subs,function(t){t()}),this.subs=[],this.pageCountListeners.length=0,this.pageChangeListeners.length=0},r.prototype._countPages=function(){var t=this,e=t.currPage;if(!this.downloadingEverything)if(t.pageCount===-1){var i=t.max,r=t.pageSize,n=this.ref.ref().limitToFirst(i);n.once("value",function(e){t.pageCount===-1&&(t.couldHaveMore=e.numChildren()===i,t.pageCount=Math.ceil(e.numChildren()/r),t._notifyPageCount(),t._countPages())})}else e>=t.pageCount&&(t.pageCount=e,t.couldHaveMore=t.cache.hasNext(),t._notifyPageCount())},r.prototype._pageChange=function(){var t=this.currPage,e=(t-1)*this.pageSize;this.cache.moveTo(e,this.pageSize),this._countPages(),o.each(this.pageChangeListeners,function(e){e[0].call(e[1],t)})},r.prototype._notifyPageCount=function(){var t=this.pageCount,e=this.couldHaveMore;o.each(this.pageCountListeners,function(i){i[0].call(i[1],t,e)})},e.exports=r},{"../../common":25,"./Cache":19}],22:[function(t,e,i){"use strict";function r(t){this._ref=t,this._obs=new a.Observable(["value","child_added","child_removed","child_moved","child_changed"])}function n(t){return function(){var e=a.toArray(arguments),i=this.ref();return i[t].apply(i,e)}}function s(t){return function(){throw new Error(t+" is not supported. This is a read-only reference. You can modify child records after calling .child(), or work with the original by using .ref().")}}function o(t){return function(){throw new Error(t+" is not supported for Paginate and Scroll references. Try calling it on the original reference used to create the instance instead.")}}var a=t("../../common");r.prototype={on:function(t,e,i,r){this._obs.observe(t,e,i,r)},once:function(t,e,i,r){function n(i){s.off(t,n,s),e.call(r,i)}var s=this;this.on(t,n,i,this)},off:function(t,e,i){this._obs.stopObserving(t,e,i)},ref:function(){return this._ref},child:n("child"),parent:n("parent"),root:n("root"),name:n("name"),key:n("key"),toString:n("toString"),auth:n("auth"),unauth:n("unauth"),authWithCustomToken:n("authWithCustomToken"),authAnonymously:n("authAnonymously"),authWithPassword:n("authWithPassword"),authWithOAuthPopup:n("authWithOAuthPopup"),authWithOAuthRedirect:n("authWithOAuthRedirect"),authWithOAuthToken:n("authWithOAuthToken"),getAuth:n("getAuth"),onAuth:n("onAuth"),offAuth:n("offAuth"),createUser:n("createUser"),changePassword:n("changePassword"),removeUser:n("removeUser"),resetPassword:n("resetPassword"),changeEmail:n("changeEmail"),goOffline:n("goOffline"),goOnline:n("goOnline"),set:s("set"),update:s("update"),remove:s("remove"),push:s("push"),setWithPriority:s("setWithPriority"),setPriority:s("setPriority"),transaction:s("transaction"),limit:o("limit"),onDisconnect:o("onDisconnect"),orderByChild:o("orderByChild"),orderByKey:o("orderByKey"),orderByPriority:o("orderByPriority"),limitToFirst:o("limitToFirst"),limitToLast:o("limitToLast"),startAt:o("startAt"),endAt:o("endAt"),equalTo:o("equalTo"),$trigger:function(){this._obs.triggerEvent.apply(this._obs,a.toArray(arguments))}},e.exports=r},{"../../common":25}],23:[function(t,e,i){"use strict";function r(t,e,i,r){this.max=i.windowSize,this.start=0,this.end=0,this.cache=new n(t,e,i.maxCacheSize,r)}var n=t("./Cache");r.prototype.next=function(t){this.hasNext()&&(this.end=this.end+t,this.start=Math.max(0,this.end-this.max,this.start),this.cache.moveTo(this.start,this.end-this.start))},r.prototype.prev=function(t){this.hasPrev()&&(this.start=Math.max(0,this.start-t),this.end=Math.min(this.start+this.max,this.end),this.cache.moveTo(this.start,this.end-this.start))},r.prototype.hasNext=function(){return this.cache.hasNext()},r.prototype.hasPrev=function(){return this.start>0},r.prototype.observeHasNext=function(t,e){return this.cache.observeHasNext(t,e)},r.prototype.destroy=function(){this.cache.destroy(),this.ref=null,this.cache=null},e.exports=r},{"./Cache":19}],24:[function(t,e,i){var r=t("./index.js");i.log=r.log,i.logLevel=r.logLevel,i.escapeEmail=r.escapeEmail},{"./index.js":25}],25:[function(t,e,i){var r=t("./libs/util.js"),n=t("./libs/logger.js");r.extend(i,r,{args:t("./libs/args.js"),log:n,logLevel:n.logLevel,Observable:t("./libs/Observable.js"),Observer:t("./libs/Observer.js"),queue:t("./libs/queue.js")})},{"./libs/Observable.js":26,"./libs/Observer.js":27,"./libs/args.js":28,"./libs/logger.js":29,"./libs/queue.js":30,"./libs/util.js":31}],26:[function(t,e,i){"use strict";function r(t,e){e||(e={}),this._observableProps=a(t,e),this.resetObservers()}function n(t,e){h.each(e,function(e){var i=h.indexOf(t,e);i>=0&&t.splice(i,1)})}function s(t,e){var i=[];return h.each(e,function(e){h.has(t.observers,e)?t.observers[e].length&&(i=i.concat(t.observers[e])):c.warn("Observable.hasObservers: invalid event type %s",e)}),i}function o(t,e,i){h.has(e.oneTimeResults,t)&&i.notify.apply(i,e.oneTimeResults[t])}function a(t,e){return h.extend({onAdd:h.noop,onRemove:h.noop,onEvent:h.noop,oneTimeEvents:[]},e,{eventsMonitored:t,observers:{},oneTimeResults:{}})}var h=t("./util.js"),u=t("./args.js"),c=t("./logger.js"),l=t("./Observer.js");r.prototype={observe:function(t,e,i,r){var n,s=u("observe",arguments,2,4);return t=s.nextFromReq(this._observableProps.eventsMonitored),t&&(e=s.nextReq("function"),i=s.next("function"),r=s.next("object"),n=new l(this,t,e,r,i),this._observableProps.observers[t].push(n),this._observableProps.onAdd(t,n),this.isOneTimeEvent(t)&&o(t,this._observableProps,n)),n},hasObservers:function(t){return this.getObservers(t).length>0},stopObserving:function(t,e,i){var r=u("stopObserving",arguments);t=r.next(["array","string"],this._observableProps.eventsMonitored),e=r.next(["function"]),i=r.next(["object"]),h.each(t,function(t){var r=[],s=this.getObservers(t);h.each(s,function(n){n.matches(t,e,i)&&(n.notifyCancelled(null),r.push(n))},this),n(this._observableProps.observers[t],r),r.length&&this._observableProps.onRemove(t,r)},this)},abortObservers:function(t){var e=[];if(this.hasObservers()){var i=this.getObservers().slice();h.each(i,function(i){i.notifyCancelled(t),e.push(i)},this),this.resetObservers(),e.length&&this._observableProps.onRemove(this.event,e)}},getObservers:function(t){return t=u("getObservers",arguments).listFrom(this._observableProps.eventsMonitored,!0),s(this._observableProps,t)},triggerEvent:function(t){var e=u("triggerEvent",arguments),i=e.listFromWarn(this._observableProps.eventsMonitored,!0),r=e.restAsList();i&&h.each(i,function(e){if(this.isOneTimeEvent(t)){if(h.isArray(this._observableProps.oneTimeResults,t))return void c.warn("One time event was triggered twice, should by definition be triggered once",t);this._observableProps.oneTimeResults[t]=r}var i=this.getObservers(e),n=0;h.each(i,function(t){t.notify.apply(t,r.slice(0)),n++}),this._observableProps.onEvent.apply(null,[e,n].concat(r.slice(0)))},this)},resetObservers:function(){h.each(this._observableProps.eventsMonitored,function(t){this._observableProps.observers[t]=[]},this)},isOneTimeEvent:function(t){return h.contains(this._observableProps.oneTimeEvents,t)},observeOnce:function(t,e,i,r){var n,s=u("observeOnce",arguments,2,4);return t=s.nextFromWarn(this._observableProps.eventsMonitored),t&&(e=s.nextReq("function"),i=s.next("function"),r=s.next("object"),n=new l(this,t,e,r,i,(!0)),this._observableProps.observers[t].push(n),this._observableProps.onAdd(t,n),this.isOneTimeEvent(t)&&o(t,this._observableProps,n)),n}},e.exports=r},{"./Observer.js":27,"./args.js":28,"./logger.js":29,"./util.js":31}],27:[function(t,e,i){"use strict";function r(t,e,i,r,n,s){if("function"!=typeof i)throw new Error("Must provide a valid notifyFn");this.observable=t,this.fn=i,this.event=e,this.cancelFn=n||function(){},this.context=r,this.oneTimeEvent=!!s}var n=t("./util.js");r.prototype={notify:function(){var t=n.toArray(arguments);this.fn.apply(this.context,t),this.oneTimeEvent&&this.observable.stopObserving(this.event,this.fn,this.context)},matches:function(t,e,i){return n.isArray(t)?n.contains(t,function(t){return this.matches(t,e,i)},this):!(t&&t!==this.event||e&&e!==this&&e!==this.fn||i&&i!==this.context)},notifyCancelled:function(t){this.cancelFn.call(this.context,t||null)}},e.exports=r},{"./util.js":31}],28:[function(t,e,i){"use strict";function r(t,e,i,n){if("string"!=typeof t||!h.isObject(e))throw new Error("Args requires at least 2 args: fnName, arguments[, minArgs, maxArgs]");if(!(this instanceof r))return new r(t,e,i,n);this.fnName=t,this.argList=h.toArray(e),this.origArgs=h.toArray(e);var s=this.length=this.argList.length;if(this.pos=-1,h.isUndefined(i)&&(i=0),h.isUndefined(n)&&(n=this.argList.length),sn){var o=n>i?h.printf("%d to %d",i,n):i;throw Error(h.printf("%s must be called with %s arguments, but received %d",t,o,s))}}function n(t,e){return e===!0||(h.isArray(e)||(e=[e]),h.contains(e,function(e){switch(e){case"array":return h.isArray(t);case"string":return"string"==typeof t;case"number":return isFinite(parseInt(t,10));case"int":case"integer":return isFinite(parseFloat(t));case"object":return h.isObject(t);case"function":return"function"==typeof t;case"bool":case"boolean":return"boolean"==typeof t;case"boolean-like":return!h.isObject(t);default:throw new Error("Args received an invalid data type: "+e)}}))}function s(t,e,i,r){if(r=h.printf("%s: invalid argument at pos %d, %s (received %s)",e,i,r),t===!0)throw new Error(r);if(!h.has(u,t))throw new Error("The `required` value passed to Args methods must either be true or a method name from logger");u[t](r)}function o(t,e,i){u.warn("%s: invalid choice %s, must be one of [%s]",t,e,i)}function a(t,e){if(e===!0)return t;var i=h.isArray(e)?e[0]:e;switch(i){case"array":return h.isArray(t)?t:[t];case"string":return t+"";case"number":return parseFloat(t);case"int":case"integer":return parseInt(t,10);case"bool":case"boolean":case"boolean-like":return!!t;case"function":case"object":return t;default:throw new Error("Args received an invalid data type: "+i)}}var h=t("./util.js"),u=t("./logger.js");r.prototype={orig:function(){return this.origArgs.slice(0)},restAsList:function(t,e){var i=this.argList.slice(0);if(t||e)for(var r=0,n=i.length;r=n+1;if(a){var h=l.bind(console["debug"===i?"log":i],console);f[i]=function(){var t=l.toArray(arguments);if(t.length>1&&"string"==typeof t[0]){var i=t[0].match(/(%s|%d|%j)/g);if(i){var r=[l.printf.apply(l,t)];t=t.length>i.length+1?r.concat(t.slice(i.length+1)):r}}e&&o(e,t)||h.apply("undefined"==typeof console?c:console,t)}}else f[i]=s;f["is"+r(i)+"Enabled"]=function(){return a}});var i=function(t){return function(){f.logLevel(t)}}(u);return u=t,i},f.logLevel(n()),e.exports=f},{"./util.js":31}],30:[function(t,e,i){"use strict";function r(t){this.needs=0,this.met=0,this.queued=[],this.errors=[],this.criteria=[],this.processing=!1,n.each(t,this.addCriteria,this)}var n=t("./util.js");r.prototype={addCriteria:function(t,e){if(this.processing)throw new Error("Cannot call addCriteria() after invoking done(), fail(), or handler() methods");return this.criteria.push(e?[t,e]:t),this},getHandler:function(){var t,e;return this.addCriteria(function(i){e!==n.undef?i(e):t=i}),function(i){t?t(i):e=i}},ready:function(){return this.needs===this.met},done:function(t,e){return t&&this._runOrStore(function(){this.hasErrors()||t.call(e)}),this},fail:function(t,e){return this._runOrStore(function(){this.hasErrors()&&t.apply(e,this.getErrors())}),this},handler:function(t,e){return this._runOrStore(function(){t.apply(e,this.getErrors())}),this},chain:function(t){return this.addCriteria(t.handler,t),this},when:function(t){this._runOrStore(function(){this.hasErrors()?t.reject.apply(t,this.getErrors()):t.resolve()})},addError:function(t){this.errors.push(t)},hasErrors:function(){return this.errors.length},getErrors:function(){return this.errors.slice(0)},_process:function(){this.processing=!0,this.needs=this.criteria.length,n.each(this.criteria,this._evaluateCriteria,this)},_evaluateCriteria:function(t){var e=null;n.isArray(t)&&(e=t[1],t=t[0]);try{t.call(e,n.bind(this._criteriaMet,this))}catch(i){this.addError(i)}},_criteriaMet:function(t){t&&this.addError(t),this.met++,this.ready()&&n.each(this.queued,this._run,this)},_runOrStore:function(t){this.processing||this._process(),this.ready()?this._run(t):this.queued.push(t)},_run:function(t){t.call(this)}},e.exports=function(t,e){var i=new r(t);return e&&i.done(e),i}},{"./util.js":31}],31:[function(t,e,i){(function(e){"use strict";function r(t,e){switch(e){case"%d":return parseInt(t,10);case"%j":return t=c.isObject(t)?JSON.stringify(t):t+"",t.length>500&&(t=t.substr(0,500)+".../*truncated*/...}"),t;case"%s":return t+"";default:return t}}function n(t){return c.isObject(t)&&t+""=="[object Arguments]"}function s(t){if("function"!=typeof this)throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");var e=Array.prototype.slice.call(arguments,1),i=this,r=function(){},n=function(){return i.apply(this instanceof r&&t?this:t,e.concat(Array.prototype.slice.call(arguments)))};return r.prototype=this.prototype,n.prototype=new r,n}function o(t,e){var i,r;for(i=0,r=this.length;i>>0;if(0===s)return-1;if(i=0,arguments.length>1&&(i=Number(arguments[1]),i!==i?i=0:0!==i&&i!==1/0&&i!==-(1/0)&&(i=(i>0||-1)*Math.floor(Math.abs(i)))),i>=s)return-1;for(r=i>=0?i:Math.max(s-Math.abs(i),0);r0?i.slice(e):i},c.extend=function(t,e){var i=c.toArray(arguments),r="boolean"==typeof i[0]&&i.shift(),n=i.shift();return c.each(i,function(t){c.isObject(t)&&c.each(t,function(t,e){n[e]=r&&c.isObject(n[e])?c.extend(!0,n[e],t):t})}),n},c.bind=function(t,e){var i=Array.prototype.slice.call(arguments,1);return(t.bind||s).apply(t,i)},c.isEmpty=function(t){return t===u||null===t||c.isArrayLike(t)&&0===t.length||c.isObject(t)&&!c.contains(t,function(t){return!0})},c.keys=function(t){var e=[];return c.each(t,function(t,i){e.push(i)}),e},c.map=function(t,e,i){var r=[];return c.each(t,function(n,s){var o=e.call(i,n,s,t);o!==u&&r.push(o)}),r},c.mapObject=function(t,e,i){var r={};return c.each(t,function(n,s){var o=e.call(i,n,s,t);o!==u&&(r[s]=o)}),r},c.find=function(t,e,i){if(c.isArrayLike(t)){for(var r=0,n=t.length;r-1;e=function(t){return function(e){return e===t}}(e)}return c.find(t,e,i)!==u},c.each=function(t,e,i){if(c.isArrayLike(t))(t.forEach||o).call(t,e,i);else if(c.isObject(t)){var r;for(r in t)t.hasOwnProperty(r)&&e.call(i,t[r],r,t)}},c.indexOf=function(t,e){return(t.indexOf||h).call(t,e)},c.remove=function(t,e){var i=!1;if(c.isArray(t)){var r=c.indexOf(t,e);r>-1&&(t.splice(r,1),i=!0)}else if(c.isObject(t)){var n;for(n in t)if(t.hasOwnProperty(n)&&e===t[n]){i=!0,delete t[n];break}}return i},c.defer=function(t,e){var i=c.toArray(arguments);setTimeout(c.bind.apply(null,i),0)},c.call=function(t,e){var i=c.toArray(arguments,2),r=[];return c.each(t,function(t){return"function"!=typeof t||e?void(c.isObject(t)&&"function"==typeof t[e]&&r.push(t[e].apply(t,i))):r.push(t.apply(null,i))}),r},c.isEqual=function(t,e,i){if(t===e)return!0;if(typeof t!=typeof e)return!1;if(c.isObject(t)&&c.isObject(e)){var r=c.isArrayLike(t),n=c.isArrayLike(e);if(r||n)return r&&n&&t.length===e.length&&!c.contains(t,function(t,i){return!c.isEqual(t,e[i])});var s=i?c.keys(t):c.keys(t).sort(),o=i?c.keys(e):c.keys(e).sort();return c.isEqual(s,o)&&!c.contains(t,function(t,i){return!c.isEqual(t,e[i])})}return!1},c.bindAll=function(t,e){return c.each(e,function(i,r){"function"==typeof i&&(e[r]=c.bind(i,t))}),e},c.printf=function(){var t=c.toArray(arguments),e=t.shift(),i=e.match(/(%s|%d|%j)/g);return i&&t.length&&c.find(i,function(i){return e=e.replace(i,r(t.shift(),i)),0===t.length}),e},c.mergeToString=function(t){return 0===t.length?null:1===t.length?t[0]:"["+t.join("][")+"]"},c.construct=function(t,e){function i(){return t.apply(this,e)}return i.prototype=t.prototype,new i},c.noop=function(){};var f=[];c.isFirebaseRef=function(t){if(!c.isObject(t))return!1;var e=Object.getPrototypeOf(t);return!(!e||e.constructor!==c.Firebase.prototype.constructor)||("function"==typeof t.ref&&"function"==typeof t.ref().transaction||c.isFirebaseRefWrapper(t))},c.isFirebaseRefWrapper=function(t){return c.contains(f,function(e){return t instanceof e})},c.isQueryRef=function(t){return c.isFirebaseRef(t)&&"function"!=typeof t.transaction},c.registerFirebaseWrapper=function(t){f.push(t)},c._mockFirebaseRef=function(t){c.Firebase=t},c.escapeEmail=function(t){return(t||"").replace(".",",")},c.assertValidEvent=function(t){if(!c.contains(l,t))throw new Error("Event must be one of "+l+", but received "+t)},c.inherits=function(t,e){var i=[t.prototype].concat(c.toArray(arguments).slice(2));return t.prototype=new e,t.prototype.constructor=e,c.each(i,function(e){c.each(e,function(e,i){t.prototype[i]=e})}),t.prototype._super=function(){e.apply(this,arguments)},t},c.deepCopy=function(t){if(!c.isObject(t))return t;var e=c.isArrayLike(t)?[]:{};return c.each(t,function(t,i){e[i]=c.deepCopy(t)}),e},c.pick=function(t,e){if(!c.isObject(t))return{};var i=c.isArrayLike(t)?[]:{};return c.each(e,function(e){i[e]=t[e]}),i},c.eachByPath=function(t,e,i,r){var n={};c.each(e,function(e,i){var r=t.pathFor(i),s=t.getField(i),o=s?s.id:i;c.has(n,r.name())||(n[r.name()]={path:r,data:{}}),n[r.name()].data[o]=e}),c.each(n,function(t){i.call(r,t.path,t.data)})}}).call(this,"undefined"!=typeof global?global:"undefined"!=typeof self?self:"undefined"!=typeof window?window:{})},{firebase:"firebase"}],"firebase-util":[function(t,e,i){"use strict";var r=t("./common/index.js");r.extend(i,t("./common/exports.js"),t("./NormalizedCollection/exports.js"),t("./Paginate/exports.js")),"undefined"!=typeof window&&(window.hasOwnProperty("Firebase")?window.Firebase.util=r:console.warn("Firebase not found on the global window instance. Cannot add Firebase.util namespace."))},{"./NormalizedCollection/exports.js":2,"./Paginate/exports.js":18,"./common/exports.js":24,"./common/index.js":25}]},{},[1]); \ No newline at end of file diff --git a/package.json b/package.json index 61b895a..b69a77f 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "firebase-util", "description": "A set of experimental power tools for Firebase.", - "version": "0.0.0", + "version": "0.2.6", "author": "Firebase (https://firebase.google.com/)", "homepage": "https://github.com/firebase/firebase-util", "repository": { diff --git a/src/Paginate/exports.js b/src/Paginate/exports.js index 2ea2094..9052c13 100644 --- a/src/Paginate/exports.js +++ b/src/Paginate/exports.js @@ -10,7 +10,7 @@ var DEFAULTS = { windowSize: 250 }; -exports.Scroll = function(baseRef, sortField, opts) { +exports.Scroll = function(baseRef, sortField, opts, desc) { if( !util.isFirebaseRef(baseRef) || util.isQueryRef(baseRef) ) { throw new Error('First argument to Firebase.util.Scroll must be a valid Firebase ref. It cannot be a Query (e.g. you have called orderByChild()).'); } @@ -22,7 +22,7 @@ exports.Scroll = function(baseRef, sortField, opts) { throw new Error('Optional third argument to Firebase.util.Scroll must be an object of key/value pairs'); } var ref = new ReadOnlyRef(baseRef); - ref.scroll = new Scroll(ref, sortField, calcOpts(opts, 'windowSize', 'Scroll')); + ref.scroll = new Scroll(ref, sortField, calcOpts(opts, 'windowSize', 'Scroll'), desc); return ref; }; diff --git a/src/Paginate/libs/Cache.js b/src/Paginate/libs/Cache.js index 50cdb70..9e9a83c 100644 --- a/src/Paginate/libs/Cache.js +++ b/src/Paginate/libs/Cache.js @@ -2,7 +2,7 @@ var util = require('../../common'); var Offset = require('./Offset'); -function Cache(outRef, sortField, maxRecordsLoaded) { +function Cache(outRef, sortField, maxRecordsLoaded, desc) { util.log.debug('Cache: caching %s using field=%s maxRecordsLoaded=%d', outRef.toString(), sortField, maxRecordsLoaded); this.offset = new Offset({field: sortField, max: maxRecordsLoaded, ref: outRef.ref()}); this.outRef = outRef; @@ -15,6 +15,7 @@ function Cache(outRef, sortField, maxRecordsLoaded) { this.endCount = -1; this.nextListeners = []; this.offset.observe(this._keyChange, this); + this.desc = desc; } Cache.prototype.moveTo = function(startOffset, numberOfRecords) { @@ -87,11 +88,11 @@ Cache.prototype._unsubscribe = function() { Cache.prototype._refChange = function() { this._unsubscribe(); if( this.inRef && this.count > -1 ) { - this.countRef = this.inRef.limitToFirst(this.count+1); + this.countRef = this.desc ? this.inRef.limitToLast(this.count+1) : this.inRef.limitToFirst(this.count+1); this.countRef.on('value', this._count, this); //todo we should queue all the events until the once('value') is completed //todo so that we can trigger removed before added - this.queryRef = this.inRef.limitToFirst(this.count); + this.queryRef = this.desc ? this.inRef.limitToLast(this.count) : this.inRef.limitToFirst(this.count); this.queryRef.on('child_added', this._add, this); this.queryRef.on('child_removed', this._remove, this); this.queryRef.on('child_moved', this._move, this); diff --git a/src/Paginate/libs/Scroll.js b/src/Paginate/libs/Scroll.js index 784c3ec..6d6d238 100644 --- a/src/Paginate/libs/Scroll.js +++ b/src/Paginate/libs/Scroll.js @@ -7,11 +7,11 @@ var Cache = require('./Cache'); * @param {Object} [opts] * @constructor */ -function Scroll(readOnlyRef, field, opts) { +function Scroll(readOnlyRef, field, opts, desc) { this.max = opts.windowSize; this.start = 0; this.end = 0; - this.cache = new Cache(readOnlyRef, field, opts.maxCacheSize); + this.cache = new Cache(readOnlyRef, field, opts.maxCacheSize, desc); } /**