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