Home | History | Annotate | Download | only in lib
      1 <!--
      2 Copyright 2014 The Chromium Authors. All rights reserved.
      3 Use of this source code is governed by a BSD-style license that can be
      4 found in the LICENSE file.
      5 -->
      6 
      7 <link rel="import" href="sugar.html">
      8 
      9 <script>
     10 
     11 var updateUtil = updateUtil || {};
     12 
     13 (function () {
     14 'use strict';
     15 
     16 // Returns true if |updateLeft| can do a field-wise merge from |source| to |target|.
     17 // We assume |target| and |source| have the same type.
     18 // If they're arrays, true if the elements have a |key| property.
     19 // If they're objects, true if they're not null. (null should be assigned).
     20 // Otherwise, false.
     21 function canUpdateLeft(target, source) {
     22   if (Array.isArray(target)) {
     23     // If |source| is empty, we'll return an empty array regardless.
     24     return source.length !== 0 && source[0].key !== undefined;
     25   } else if (target === null || source === null) {
     26     return false;
     27   } else if (typeof target === 'object') {
     28     return true;
     29   }
     30   return false;
     31 };
     32 
     33 // |target| and |source| must have the same type, which must return true from
     34 // canUpdateLeft() (see above). An array is treated like a dictionary where the
     35 // key of an object is its |key| property, except that no effort is made to
     36 // preserve the object identity of arrays. This function will:
     37 //
     38 //  * Ignore elements listed in an object's constructor's |transientProperties| array.
     39 //  * Remove elements from |target| whose key isn't in |source|.
     40 //  * Copy elements from |source| whose key isn't in |target| or which are !canUpdateLeft().
     41 //    In particular, we copy |null| rather than trying to merge it.
     42 //  * If a matching element defines an |updateLeft| method, call that to let types customize the update process.
     43 //    This method must return the updated object.
     44 //  * Call updateLeft recursively for other matching elements.
     45 //
     46 // You have to call this as "target = updateLeft(target, source);" because it
     47 // won't always update |target| in-place.
     48 updateUtil.updateLeft = function(target, source)
     49 {
     50   if (!canUpdateLeft(target, source)) {
     51     return source;
     52   }
     53 
     54   if (target.updateLeft) {
     55     return target.updateLeft(source);
     56   }
     57 
     58   if (Array.isArray(target)) {
     59     return updateLeftArray(target, source);
     60   } else {
     61     return updateLeftObject(target, source);
     62   }
     63 };
     64 
     65 // |target| and |source| must be arrays of objects with a |key| property.
     66 function updateLeftArray(target, source) {
     67   var oldElemByKey = {};
     68   target.forEach(function(elem) {
     69     oldElemByKey[elem.key] = elem;
     70   })
     71   // Polymer doesn't pay attention to array identity when deciding to recreate
     72   // elements, just object identity.
     73   return source.map(function(value) {
     74     return updateUtil.updateLeft(oldElemByKey[value.key], value);
     75   });
     76 };
     77 
     78 // |target| and |source| must be objects. |target|'s properties will be updated
     79 // to match |source|'s except for properties listed in its constructor's
     80 // |transientProperties| array.
     81 function updateLeftObject(target, source) {
     82   // Prepare to filter out properties that reflect local UI
     83   // state that wasn't loaded from the server.
     84   var transientProperties = target.constructor.transientProperties;
     85   function isTransientProperty(name) {
     86     return transientProperties && transientProperties.indexOf(name) !== -1;
     87   };
     88 
     89   // Remove elements from |target| that aren't in |source|.
     90   Object.keys(target, function(key) {
     91     if (!source.hasOwnProperty(key) && !isTransientProperty(key))
     92       delete target[key];
     93   });
     94 
     95   // Recursively update or assign properties that are in |source|.
     96   Object.keys(source, function(key, sourceValue) {
     97     if (!isTransientProperty(key))
     98       target[key] = updateUtil.updateLeft(target[key], source[key]);
     99   });
    100   return target;
    101 };
    102 
    103 })();
    104 
    105 </script>
    106