Index: Tools/GardeningServer/lib/util.html |
diff --git a/Tools/GardeningServer/lib/util.html b/Tools/GardeningServer/lib/util.html |
new file mode 100644 |
index 0000000000000000000000000000000000000000..1f5e8b1611eba81ac90848259b6ec4774b9fb135 |
--- /dev/null |
+++ b/Tools/GardeningServer/lib/util.html |
@@ -0,0 +1,130 @@ |
+<!-- |
+Copyright 2014 The Chromium Authors. All rights reserved. |
+Use of this source code is governed by a BSD-style license that can be |
+found in the LICENSE file. |
+--> |
+ |
+<link rel="import" href="sugar.html"> |
+ |
+<script> |
+ |
+var util = util || {}; |
ojan
2014/09/02 04:08:02
I have an allergy to generic util namespaces. They
Jeffrey Yasskin
2014/09/02 21:45:51
Done.
|
+ |
+(function () { |
+'use strict'; |
+ |
+// Returns true if |updateLeft| can do a field-wise merge from |source| to |target|. |
+// We assume |target| and |source| have the same type. |
+// If they're arrays, true if the elements have a |fingerprint| property. |
Jeffrey Yasskin
2014/08/30 03:07:24
Other possible names for the |fingerprint| propert
ojan
2014/09/02 04:08:02
I probalby would go with key, but fingerprint is f
Jeffrey Yasskin
2014/09/02 21:45:51
Done.
|
+// If they're objects, true if they're not null. (null should be assigned). |
+// Otherwise, false. |
+util.canUpdateLeft = function(target, source) { |
ojan
2014/09/02 04:08:02
This is only used in side this file, so no need to
Jeffrey Yasskin
2014/09/02 21:45:51
Right, although it made it a little easier to desc
|
+ if (Array.isArray(target)) { |
+ if (target.length === 0) { |
+ // If both arrays are empty, do the no-op merge instead of changing the |
+ // object identity. |
+ return source.length === 0 || source[0].fingerprint !== undefined; |
+ } |
+ return target[0].fingerprint !== undefined; |
+ } else if (target === null || source === null) { |
+ return false; |
+ } else if (typeof target === 'object') { |
+ return true; |
+ } |
+ return false; |
+}; |
+ |
+// |target| and |source| must have the same type, which must return true from |
+// util.canUpdateLeft(). An array is treated the same as a dictionary where the |
+// key of an object is its fingerprint. This function will: |
+// |
+// * Ignore elements listed in an object's constructor's |uiStateProperties| array. |
+// * Remove elements from |target| whose key isn't in |source|. |
+// * Copy elements from |source| whose key isn't in |target| or which are !canUpdateLeft(). |
+// In particular, we copy |null| rather than trying to merge it. |
+// * If a matching element defines an |updateLeft| method, call that to let types customize the update process. |
+// * Call updateLeft recursively for other matching elements. |
+util.updateLeft = function(target, source) |
Jeffrey Yasskin
2014/08/30 03:07:24
Other names for |updateLeft| include |assign|, |in
ojan
2014/09/02 04:08:02
updateLeft sgtm
|
+{ |
+ if (!util.canUpdateLeft(target, source)) { |
+ throw new TypeError('Unexpected types to updateLeft(): ' + |
+ [target, source].map(JSON.stringify).join(', ')); |
+ } |
+ |
+ if (target.updateLeft) { |
+ target.updateLeft(source); |
+ return; |
+ } |
+ |
+ function updateByIndices(targetIndex, sourceIndex) { |
+ if (util.canUpdateLeft(target[targetIndex], source[sourceIndex])) { |
+ util.updateLeft(target[targetIndex], source[sourceIndex]); |
+ } else { |
+ target[targetIndex] = source[sourceIndex]; |
+ } |
+ } |
+ |
+ function indicesByFingerprint(array) { |
ojan
2014/09/02 04:08:02
Since this isn't using any closed variables, it co
Jeffrey Yasskin
2014/09/02 21:45:51
Done.
|
+ var indices = {}; |
+ array.forEach(function(elem, index) { |
+ indices[elem.fingerprint] = index; |
+ }); |
+ return indices; |
+ } |
+ |
+ if (Array.isArray(target)) { |
+ var sourceIndices = indicesByFingerprint(source); |
ojan
2014/09/02 04:08:02
This would be a bit easier to read if you move the
Jeffrey Yasskin
2014/09/02 21:45:51
Done. I decided to pull these all the way out of u
|
+ // Remove elements from |target| that aren't in |source|. |
+ target.remove(function(elem) { |
+ return !sourceIndices.hasOwnProperty(elem.fingerprint); |
+ }); |
+ |
+ var targetIndices = indicesByFingerprint(target); |
+ Object.keys(sourceIndices, function(fingerprint, sourceIndex) { |
+ if (targetIndices.hasOwnProperty(fingerprint)) { |
+ // Recursively update elements that are present in both arrays. |
+ updateByIndices(targetIndices[fingerprint], sourceIndex); |
+ } else { |
+ // And add elements in |source| that weren't in |target|. Because this |
+ // adds to the end, it doesn't mess up |targetIndices|. |
+ target.push(source[sourceIndex]); |
+ } |
+ }); |
+ |
+ // Sort |target| into the same order as |source|. |
+ target.sort(function(a, b) { |
+ return sourceIndices[a.fingerprint] - sourceIndices[b.fingerprint]; |
+ }); |
+ } else { |
+ // They're objects. Prepare to filter out properties that reflect local UI |
+ // state that wasn't loaded from the server. |
+ var uiStateProperties = target.constructor.uiStateProperties; |
Jeffrey Yasskin
2014/08/30 03:07:24
|uiStateProperties| could be |transientProperties|
ojan
2014/09/02 04:08:02
transient sgtm.
Jeffrey Yasskin
2014/09/02 21:45:51
Done.
|
+ var isUiStateProperty = function(name) { |
+ return uiStateProperties && uiStateProperties.indexOf(name) !== -1; |
+ }; |
+ |
+ // Remove elements from |target| that aren't in |source|. |
+ Object.keys(target, function(key) { |
+ if (isUiStateProperty(key)) |
+ return; |
+ if (!source.hasOwnProperty(key)) |
+ delete target[key]; |
+ }); |
+ |
+ Object.keys(source, function(key, sourceValue) { |
+ if (isUiStateProperty(key)) |
+ return; |
+ if (target.hasOwnProperty(key)) { |
+ // Recursively update elements that are present in both arrays. |
+ updateByIndices(key, key); |
+ } else { |
+ // And add elements in |source| that weren't in |target|. |
+ target[key] = sourceValue; |
+ } |
+ }); |
+ } |
+}; |
+ |
+})(); |
+ |
+</script> |