| OLD | NEW |
| (Empty) | |
| 1 <!-- |
| 2 @license |
| 3 Copyright (c) 2015 The Polymer Project Authors. All rights reserved. |
| 4 This code may only be used under the BSD style license found at http://polymer.g
ithub.io/LICENSE.txt |
| 5 The complete set of authors may be found at http://polymer.github.io/AUTHORS.txt |
| 6 The complete set of contributors may be found at http://polymer.github.io/CONTRI
BUTORS.txt |
| 7 Code distributed by Google as part of the polymer project is also |
| 8 subject to an additional IP rights grant found at http://polymer.github.io/PATEN
TS.txt |
| 9 --> |
| 10 |
| 11 <link rel="import" href="../polymer/polymer.html"> |
| 12 <link rel="import" href="firebase-query-behavior.html"> |
| 13 |
| 14 <!-- |
| 15 An element wrapper for the Firebase API that provides a view into the provided |
| 16 Firebase location as an ordered collection. |
| 17 |
| 18 By default, Firebase yields values as unsorted document objects, where each of |
| 19 the children are accessible via object keys. The `<firebase-collection>` |
| 20 element allows Firebase API orderBy*, limitTo* and other query-oriented methods |
| 21 to be specified declaratively. The element then produces and maintains an Array |
| 22 of ordered child items of the document that is convenient for iterating over |
| 23 in other elements such as `<template is="dom-repeat">`. |
| 24 |
| 25 Example: |
| 26 |
| 27 <template is="dom-bind"> |
| 28 <firebase-collection |
| 29 order-by-child="height" |
| 30 limit-to-first="3" |
| 31 location="https://dinosaur-facts.firebaseio.com/dinosaurs" |
| 32 data="{{dinosaurs}}"></firebase-collection> |
| 33 <template is="dom-repeat" items="[[dinosaurs]]" as="dinosaur"> |
| 34 <h4>[[dinosaur.__firebaseKey__]]</h4> |
| 35 <span>Height: </span><span>[[dinosaur.height]]</span><span>m</span> |
| 36 </template> |
| 37 </template> |
| 38 |
| 39 As you may have noticed above, the original key of each item is available as |
| 40 the `__firebaseKey__` property on the item. |
| 41 |
| 42 The element makes special accomodations for documents whose children are |
| 43 primitive values. A primitive value is wrapped in an object with the form: |
| 44 |
| 45 ```javascript |
| 46 { |
| 47 value: /* original primitive value */ |
| 48 __firebaseKey__: /* key of primitive value in parent document */ |
| 49 } |
| 50 ``` |
| 51 |
| 52 Accessor methods such as `add` and `remove` are provided to enable convenient |
| 53 manipulation of the collection without direct knowledge of Firebase key values. |
| 54 --> |
| 55 |
| 56 <script> |
| 57 Polymer({ |
| 58 is: 'firebase-collection', |
| 59 |
| 60 behaviors: [ |
| 61 Polymer.FirebaseQueryBehavior |
| 62 ], |
| 63 |
| 64 properties: { |
| 65 /** |
| 66 * A pointer to the current Firebase Query instance being used to |
| 67 * populate `data`. |
| 68 */ |
| 69 query: { |
| 70 type: Object, |
| 71 notify: true, |
| 72 computed: '_computeQuery(location, limitToFirst, limitToLast, _orderByMe
thodName, _startAt, _endAt, _equalTo)', |
| 73 observer: '_queryChanged' |
| 74 }, |
| 75 |
| 76 /** |
| 77 * An ordered array of data items produced by the current Firebase Query |
| 78 * instance. |
| 79 */ |
| 80 data: { |
| 81 type: Array, |
| 82 readOnly: true, |
| 83 notify: true, |
| 84 value: function() { |
| 85 return []; |
| 86 } |
| 87 }, |
| 88 |
| 89 /** |
| 90 * Specify a child key to order the set of records reflected on the |
| 91 * client. |
| 92 */ |
| 93 orderByChild: { |
| 94 type: String, |
| 95 value: null, |
| 96 reflectToAttribute: true |
| 97 }, |
| 98 |
| 99 /** |
| 100 * Specify to order by key the set of records reflected on the client. |
| 101 */ |
| 102 orderByKey: { |
| 103 type: Boolean, |
| 104 value: false, |
| 105 reflectToAttribute: true |
| 106 }, |
| 107 |
| 108 /** |
| 109 * Specify to order by value the set of records reflected on the client. |
| 110 */ |
| 111 orderByValue: { |
| 112 type: Boolean, |
| 113 value: false, |
| 114 reflectToAttribute: true |
| 115 }, |
| 116 |
| 117 /** |
| 118 * Specify to order by priority the set of records reflected on the |
| 119 * client. |
| 120 */ |
| 121 orderByPriority: { |
| 122 type: Boolean, |
| 123 value: false, |
| 124 reflectToAttribute: true |
| 125 }, |
| 126 |
| 127 /** |
| 128 * Specify a maximum number of records reflected on the client limited to |
| 129 * the first certain number of children. |
| 130 */ |
| 131 limitToFirst: { |
| 132 type: Number, |
| 133 value: null, |
| 134 reflectToAttribute: true, |
| 135 }, |
| 136 |
| 137 /** |
| 138 * Specify a maximum number of records reflected on the client limited to |
| 139 * the last certain number of children. |
| 140 */ |
| 141 limitToLast: { |
| 142 type: Number, |
| 143 value: null, |
| 144 reflectToAttribute: true |
| 145 }, |
| 146 |
| 147 /** |
| 148 * Specify a start record for the set of records reflected in the |
| 149 * collection. |
| 150 */ |
| 151 startAt: { |
| 152 type: String, |
| 153 value: null, |
| 154 reflectToAttribute: true |
| 155 }, |
| 156 |
| 157 /** |
| 158 * Specify an end record for the set of records reflected in the |
| 159 * collection. |
| 160 */ |
| 161 endAt: { |
| 162 type: String, |
| 163 value: null, |
| 164 reflectToAttribute: true |
| 165 }, |
| 166 |
| 167 /** |
| 168 * Specify to create a query which includes children which match the |
| 169 * specified value. The argument type depends on which orderBy*() function |
| 170 * was used in this query. Specify a value that matches the orderBy*() |
| 171 * type. |
| 172 */ |
| 173 equalTo: { |
| 174 type: String, |
| 175 value: null, |
| 176 reflectToAttribute: true |
| 177 }, |
| 178 |
| 179 _valueMap: { |
| 180 type: Object, |
| 181 value: function() { |
| 182 return {}; |
| 183 } |
| 184 }, |
| 185 |
| 186 _orderByMethodName: { |
| 187 computed: '_computeOrderByMethodName(orderByChild, orderByKey, orderByVa
lue, orderByPriority)' |
| 188 }, |
| 189 |
| 190 _orderByTypeCast: { |
| 191 computed: '_computeOrderByTypeCast(orderByChild, orderByKey, orderByValu
e, orderByPriority)' |
| 192 }, |
| 193 |
| 194 _startAt: { |
| 195 computed: '_computeStartAt(startAt, _orderByTypeCast)' |
| 196 }, |
| 197 |
| 198 _endAt: { |
| 199 computed: '_computeEndAt(endAt, _orderByTypeCast)' |
| 200 }, |
| 201 |
| 202 _equalTo: { |
| 203 computed: '_computeEqualTo(equalTo, _orderByTypeCast)' |
| 204 } |
| 205 }, |
| 206 |
| 207 listeners: { |
| 208 'firebase-child-added': '_onFirebaseChildAdded', |
| 209 'firebase-child-removed': '_onFirebaseChildRemoved', |
| 210 'firebase-child-changed': '_onFirebaseChildChanged', |
| 211 'firebase-child-moved': '_onFirebaseChildChanged', |
| 212 }, |
| 213 |
| 214 /** |
| 215 * Add an item to the document referenced at `location`. A key associated |
| 216 * with the item will be created by Firebase, and can be accessed via the |
| 217 * Firebase Query instance returned by this method. |
| 218 * |
| 219 * @param {Object} data A value to add to the document. |
| 220 * @return {Object} A Firebase Query instance referring to the added item. |
| 221 */ |
| 222 add: function(data) { |
| 223 var query; |
| 224 |
| 225 this._log('Adding new item to collection with value:', data); |
| 226 |
| 227 query = this.query.ref().push(); |
| 228 query.set(data); |
| 229 |
| 230 return query; |
| 231 }, |
| 232 |
| 233 /** |
| 234 * Remove an item from the document referenced at `location`. The item |
| 235 * is assumed to be an identical reference to an item already in the |
| 236 * `data` Array. |
| 237 * |
| 238 * @param {Object} data An identical reference to an item in `this.data`. |
| 239 */ |
| 240 remove: function(data) { |
| 241 if (data == null || data.__firebaseKey__ == null) { |
| 242 this._error('Failed to remove unknown value:', data); |
| 243 return; |
| 244 } |
| 245 |
| 246 this._log('Removing collection item "' + data.__firebaseKey__ + '"', data.
value); |
| 247 |
| 248 this.removeByKey(data.__firebaseKey__); |
| 249 }, |
| 250 |
| 251 /** |
| 252 * Look up an item in the local `data` Array by key. |
| 253 * |
| 254 * @param {String} key The key associated with the item in the parent |
| 255 * document. |
| 256 */ |
| 257 getByKey: function(key) { |
| 258 return this._valueMap[key]; |
| 259 }, |
| 260 |
| 261 /** |
| 262 * Remove an item from the document associated with `location` by key. |
| 263 * |
| 264 * @param {String} key The key associated with the item in the document |
| 265 * located at `location`. |
| 266 */ |
| 267 removeByKey: function(key) { |
| 268 this.query.ref().child(key).remove(); |
| 269 }, |
| 270 |
| 271 _applyLocalDataChanges: function(change) { |
| 272 var pathParts = change.path.split('.'); |
| 273 var index; |
| 274 var value; |
| 275 |
| 276 if (pathParts.length < 2 || pathParts[1] === 'splices') { |
| 277 return; |
| 278 } |
| 279 |
| 280 index = parseInt(pathParts[1], 10); |
| 281 value = this.data[index]; |
| 282 this.query.ref().child(value.__firebaseKey__).set(value); |
| 283 }, |
| 284 |
| 285 _computeQuery: function(location, limitToFirst, limitToLast, orderByMethodNa
me, startAt, endAt, equalTo) { |
| 286 var query; |
| 287 |
| 288 if (!location) { |
| 289 return; |
| 290 } |
| 291 |
| 292 query = new Firebase(location); |
| 293 |
| 294 if (orderByMethodName) { |
| 295 if (orderByMethodName === 'orderByChild') { |
| 296 query = query[orderByMethodName](this.orderByChild); |
| 297 } else { |
| 298 query = query[orderByMethodName](); |
| 299 } |
| 300 } |
| 301 |
| 302 if (startAt != null) { |
| 303 query = query.startAt(startAt); |
| 304 } |
| 305 |
| 306 if (endAt != null) { |
| 307 query = query.endAt(endAt); |
| 308 } |
| 309 |
| 310 if (equalTo != null) { |
| 311 query = query.equalTo(equalTo); |
| 312 } |
| 313 |
| 314 if (limitToLast != null) { |
| 315 query = query.limitToLast(limitToLast); |
| 316 } |
| 317 |
| 318 if (limitToFirst != null) { |
| 319 query = query.limitToFirst(limitToFirst); |
| 320 } |
| 321 |
| 322 return query; |
| 323 }, |
| 324 |
| 325 _queryChanged: function() { |
| 326 Polymer.FirebaseQueryBehavior._queryChanged.apply(this, arguments); |
| 327 this._valueMap = {}; |
| 328 this._setData([]); |
| 329 }, |
| 330 |
| 331 _computeOrderByMethodName: function(orderByChild, orderByKey, orderByValue,
orderByPriority) { |
| 332 if (orderByChild) { |
| 333 return 'orderByChild'; |
| 334 } |
| 335 |
| 336 if (orderByKey) { |
| 337 return 'orderByKey'; |
| 338 } |
| 339 |
| 340 if (orderByValue) { |
| 341 return 'orderByValue'; |
| 342 } |
| 343 |
| 344 if (orderByPriority) { |
| 345 return 'orderByPriority'; |
| 346 } |
| 347 |
| 348 return null; |
| 349 }, |
| 350 |
| 351 _computeOrderByTypeCast: function(orderByChild, orderByKey, orderByValue, or
derByPriority) { |
| 352 if (orderByKey) { |
| 353 return String; |
| 354 } |
| 355 |
| 356 return function(value) { |
| 357 return value; |
| 358 } |
| 359 }, |
| 360 |
| 361 _computeStartAt: function(startAt, orderByTypeCast) { |
| 362 return orderByTypeCast(startAt); |
| 363 }, |
| 364 |
| 365 _computeEndAt: function(endAt, orderByTypeCast) { |
| 366 return orderByTypeCast(endAt); |
| 367 }, |
| 368 |
| 369 _computeEqualTo: function(equalTo, orderByTypeCast) { |
| 370 return orderByTypeCast(equalTo); |
| 371 }, |
| 372 |
| 373 _valueFromSnapshot: function(snapshot) { |
| 374 var value = snapshot.val(); |
| 375 |
| 376 if (!(value instanceof Object)) { |
| 377 value = { |
| 378 value: value, |
| 379 __firebasePrimitive__: true |
| 380 }; |
| 381 } |
| 382 |
| 383 value.__firebaseKey__ = snapshot.key(); |
| 384 |
| 385 return value; |
| 386 }, |
| 387 |
| 388 _valueToPersistable: function(value) { |
| 389 var persistable; |
| 390 |
| 391 if (value.__firebasePrimitive__) { |
| 392 return value.value; |
| 393 } |
| 394 |
| 395 persistable = {}; |
| 396 |
| 397 for (var property in value) { |
| 398 if (property === '__firebaseKey__') { |
| 399 continue; |
| 400 } |
| 401 |
| 402 persistable[property] = value[property]; |
| 403 } |
| 404 |
| 405 return persistable; |
| 406 }, |
| 407 |
| 408 _onFirebaseChildAdded: function(event) { |
| 409 this._applyRemoteDataChange(function() { |
| 410 var value = this._valueFromSnapshot(event.detail.childSnapshot); |
| 411 var previousValueKey = event.detail.previousChildName; |
| 412 var index = previousValueKey != null ? |
| 413 this.data.indexOf(this._valueMap[previousValueKey]) + 1 : 0; |
| 414 |
| 415 this._valueMap[value.__firebaseKey__] = value; |
| 416 |
| 417 this.splice('data', index, 0, value); |
| 418 }); |
| 419 }, |
| 420 |
| 421 _onFirebaseChildRemoved: function(event) { |
| 422 this._applyRemoteDataChange(function() { |
| 423 var key = event.detail.oldChildSnapshot.key(); |
| 424 var value = this._valueMap[key]; |
| 425 |
| 426 if (!value) { |
| 427 this._error('Received firebase-child-removed event for unknown child "
' + key + '"'); |
| 428 return; |
| 429 } |
| 430 |
| 431 this._valueMap[key] = null; |
| 432 this.splice('data', this.data.indexOf(value), 1); |
| 433 }); |
| 434 }, |
| 435 |
| 436 _onFirebaseChildChanged: function(event) { |
| 437 this._applyRemoteDataChange(function() { |
| 438 var value = this._valueFromSnapshot(event.detail.childSnapshot); |
| 439 var oldValue = this._valueMap[value.__firebaseKey__]; |
| 440 |
| 441 if (!oldValue) { |
| 442 this._error('Received firebase-child-changed event for unknown child "
' + value.__firebaseKey__ + '"'); |
| 443 return; |
| 444 } |
| 445 |
| 446 this._valueMap[oldValue.__firebaseKey__] = null; |
| 447 this._valueMap[value.__firebaseKey__] = value; |
| 448 this.splice('data', this.data.indexOf(oldValue), 1, value); |
| 449 }); |
| 450 }, |
| 451 |
| 452 _onFirebaseChildMoved: function(event) { |
| 453 this._applyRemoteDataChange(function() { |
| 454 var key = event.detail.childSnapshot.key(); |
| 455 var value = this._valueMap[key]; |
| 456 var previousChild; |
| 457 var newIndex; |
| 458 |
| 459 if (!value) { |
| 460 this._error('Received firebase-child-moved event for unknown child "'
+ key + '"'); |
| 461 return; |
| 462 } |
| 463 |
| 464 previousValue = event.detail.previousChildName != null ? |
| 465 this._valueMap[event.detail.previousChildName] : null; |
| 466 newIndex = previousValue != null ? |
| 467 this.data.indexOf(previousValue) + 1 : 0; |
| 468 |
| 469 this.splice('data', this.data.indexOf(value), 1); |
| 470 this.splice('data', newIndex, 0, value); |
| 471 }); |
| 472 } |
| 473 }); |
| 474 </script> |
| OLD | NEW |