launchpad-reviewers team mailing list archive
-
launchpad-reviewers team
-
Mailing list archive
-
Message #04512
[Merge] lp:~allenap/launchpad/javascript-utils-2 into lp:launchpad
Gavin Panella has proposed merging lp:~allenap/launchpad/javascript-utils-2 into lp:launchpad.
Requested reviews:
Launchpad code reviewers (launchpad-reviewers)
For more details, see:
https://code.launchpad.net/~allenap/launchpad/javascript-utils-2/+merge/70760
This adds a Dict type to lp.extras. This is basically sugar around the
standard JavaScript object type. It's convenient because it's an API
we know, and because it brings together useful functionality we'd
otherwise be looking around YUI for (how do I merge two objects? Maybe
Y.mix... what's the argument order again?).
It is experimental. I want to see if it catches on.
--
https://code.launchpad.net/~allenap/launchpad/javascript-utils-2/+merge/70760
Your team Launchpad code reviewers is requested to review the proposed merge of lp:~allenap/launchpad/javascript-utils-2 into lp:launchpad.
=== added file 'lib/lp/app/javascript/extras/extras-dict.js'
--- lib/lp/app/javascript/extras/extras-dict.js 1970-01-01 00:00:00 +0000
+++ lib/lp/app/javascript/extras/extras-dict.js 2011-08-08 16:01:32 +0000
@@ -0,0 +1,240 @@
+/**
+ * Copyright 2011 Canonical Ltd. This software is licensed under the
+ * GNU Affero General Public License version 3 (see the file LICENSE).
+ *
+ * Things that YUI3 really needs.
+ *
+ * @module lp
+ * @submodule extras-dict
+ */
+
+YUI.add('lp.extras-dict', function(Y) {
+
+Y.log('loading lp.extras-dict');
+
+var namespace = Y.namespace("lp.extras"),
+ isArray = Y.Lang.isArray;
+
+/**
+ * An object type that resembles Python's dict.
+ *
+ * This is *experimental*. It may be useful to have a dict-like type
+ * in JavaScript, hence this, but it may turn out to be a distraction,
+ * hence why it's in a separate module.
+ *
+ * @constructor
+ * @param {Object} arguments Optional objects that will be copied (by
+ * being passed into Dict.update).
+ */
+var Dict;
+
+Dict = function() {
+ var self = this instanceof Dict ? this : new Dict();
+ return self.update.apply(self, arguments);
+};
+
+// Static members.
+Y.mix(Dict, {
+
+ /**
+ * Creates a new dict with the given keys, with the value
+ * defaulting to def.
+ *
+ * @static
+ * @param {Array of String} keys
+ * @param {Any} def
+ * @return A new dict.
+ */
+ fromKeys: function(keys, def) {
+ var dict = new Dict();
+ Y.Array.each(keys, function(key) { dict[key] = def; });
+ return dict;
+ }
+
+}, true);
+
+// Prototype members.
+Y.mix(Dict.prototype, {
+
+ /**
+ * Remove all items from this dict.
+ *
+ * @return This dict.
+ */
+ clear: function() {
+ var del = function(key) { delete this[key]; };
+ this.keys().forEach(del, this);
+ return this;
+ },
+
+ /**
+ * Make a shallow copy of this dict.
+ *
+ * @return A new dict.
+ */
+ copy: function() {
+ return new Dict(this);
+ },
+
+ /**
+ * Returns the value for the given key, or def if the key is not
+ * present in this dict.
+ *
+ * @param {String} key
+ */
+ get: function(key, def) {
+ return this.hasOwnProperty(key) ? this[key] : def;
+ },
+
+ /**
+ * Returns true if the given key is present in this dict.
+ *
+ * @param {String} key
+ */
+ hasKey: function(key) {
+ return this.hasOwnProperty(key);
+ },
+
+ /**
+ * Returns an array of {key, value} items in this dict.
+ */
+ items: function() {
+ return this.keys().map(function(key) {
+ return {key: key, value: this[key]};
+ }, this);
+ },
+
+ /**
+ * Returns an array of keys in this dict.
+ */
+ keys: function() {
+ return Y.Object.keys(this);
+ },
+
+ /**
+ * Remove the given key (and value) from this dict, returning the
+ * value. If key is not found, return def.
+ *
+ * @param {String} key
+ * @param {Any} def The value to return if key is not found.
+ */
+ pop: function(key, def) {
+ var value;
+ if (this.hasOwnProperty(key)) {
+ value = this[key];
+ delete this[key];
+ return value;
+ }
+ else {
+ return def;
+ }
+ },
+
+ /**
+ * Remove any key+value from this dict, returning a {key, value}
+ * object. Returns undefined if the dict is empty.
+ */
+ popItem: function() {
+ var key, value;
+ for (key in this) {
+ if (this.hasOwnProperty(key)) {
+ value = this[key];
+ delete this[key];
+ return {key: key, value: value};
+ }
+ }
+ return undefined;
+ },
+
+ /**
+ * If key is not found in this dict, set it to def, then return
+ * the value corresponding to key.
+ *
+ * @param {String} key
+ */
+ setDefault: function(key, def) {
+ if (this.hasOwnProperty(key)) {
+ return this[key];
+ }
+ else {
+ this[key] = def;
+ return def;
+ }
+ },
+
+ /**
+ * Update the dict in place given one or more source objects.
+ *
+ * If a source is an object, its *own* properties are copied. If a
+ * source is array-like, each entry should either be a {key,
+ * value} object or a [key, value] array.
+ *
+ * @param {Object|Dict|Array} arguments
+ * @return This dict.
+ */
+ update: function() {
+ Y.each(arguments, function(source) {
+ var key;
+ if (isArray(source)) {
+ Y.each(source, function(item) {
+ if (isArray(item)) {
+ this[item[0]] = item[1];
+ }
+ else {
+ this[item.key] = item.value;
+ }
+ }, this);
+ }
+ else {
+ for (key in source) {
+ if (source.hasOwnProperty(key)) {
+ this[key] = source[key];
+ }
+ }
+ }
+ }, this);
+ return this;
+ },
+
+ /**
+ * Returns an array of values in this dict.
+ */
+ values: function() {
+ return Y.Object.values(this);
+ },
+
+ /**
+ * Returns true if this dict is empty, i.e. devoid of keys.
+ */
+ isEmpty: function() {
+ return Y.Object.isEmpty(this);
+ },
+
+ /**
+ * Compares this dict to another. Returns true only if they both
+ * have the same keys and values (and no others).
+ *
+ * @param {Dict|Object} other The object to compare.
+ */
+ isSame: function(other) {
+ var key, keys = Y.Object.keys(this),
+ other_key, other_keys = Y.Object.keys(other);
+ if (keys.length !== other_keys.length) {
+ return false;
+ }
+ keys.sort(); other_keys.sort();
+ while (keys.length > 0) {
+ key = keys.pop(); other_key = other_keys.pop();
+ if (key !== other_key || this[key] !== other[key]) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+}, true);
+
+// Exports.
+namespace.Dict = Dict;
+
+}, "0.1", {requires: ["yui-base"]});
=== modified file 'lib/lp/app/javascript/extras/tests/test_extras.html'
--- lib/lp/app/javascript/extras/tests/test_extras.html 2011-08-04 19:43:41 +0000
+++ lib/lp/app/javascript/extras/tests/test_extras.html 2011-08-08 16:01:32 +0000
@@ -12,9 +12,11 @@
<script type="text/javascript"
src="../../testing/testrunner.js"></script>
- <!-- The module under test -->
+ <!-- The modules under test -->
<script type="text/javascript"
src="../extras.js"></script>
+ <script type="text/javascript"
+ src="../extras-dict.js"></script>
<!-- The test suite -->
<script type="text/javascript"
=== modified file 'lib/lp/app/javascript/extras/tests/test_extras.js'
--- lib/lp/app/javascript/extras/tests/test_extras.js 2011-08-08 08:16:09 +0000
+++ lib/lp/app/javascript/extras/tests/test_extras.js 2011-08-08 16:01:32 +0000
@@ -113,11 +113,197 @@
};
+ var TestDict = {
+ name: 'TestDict',
+
+ test_new: function() {
+ var dict = new extras.Dict();
+ Assert.isInstanceOf(extras.Dict, dict);
+ Assert.isTrue(dict.isEmpty());
+ },
+
+ test_new_with_initial: function() {
+ var initial = {foo: 123, bar: 456};
+ var dict = new extras.Dict(initial);
+ Assert.isInstanceOf(extras.Dict, dict);
+ Assert.isFalse(dict.isEmpty());
+ Assert.areSame(123, dict.foo);
+ Assert.areSame(456, dict.bar);
+ },
+
+ test_new_without_new: function() {
+ // If the new keyword is omitted, Dict DTRT.
+ var dict = extras.Dict();
+ Assert.isInstanceOf(extras.Dict, dict);
+ },
+
+ test_fromKeys: function() {
+ var dict = extras.Dict.fromKeys(["foo", "bar"], "baz");
+ Assert.isInstanceOf(extras.Dict, dict);
+ Assert.areSame("baz", dict.foo);
+ Assert.areSame("baz", dict.bar);
+ },
+
+ test_fromKeys_no_value: function() {
+ // undefined is used as the default value.
+ var dict = extras.Dict.fromKeys(["foo"]);
+ Assert.isInstanceOf(extras.Dict, dict);
+ Assert.isTrue(dict.hasKey("foo"));
+ Assert.isUndefined(dict.foo);
+ },
+
+ test_isEmpty: function() {
+ var dict = new extras.Dict();
+ dict.foo = 123, dict.bar = 456;
+ Assert.isFalse(dict.isEmpty());
+ delete dict.foo, delete dict.bar;
+ Assert.isTrue(dict.isEmpty());
+ },
+
+ test_isSame: function() {
+ var dict_a = new extras.Dict(),
+ dict_b = new extras.Dict();
+ Assert.isTrue(dict_a.isSame(dict_b));
+ dict_a.foo = 123;
+ Assert.isFalse(dict_a.isSame(dict_b));
+ dict_b.foo = 123;
+ Assert.isTrue(dict_a.isSame(dict_b));
+ dict_a.bar = 456;
+ Assert.isFalse(dict_a.isSame(dict_b));
+ },
+
+ test_clear: function() {
+ var dict = new extras.Dict();
+ dict.foo = 123, dict.bar = 456;
+ var result = dict.clear();
+ Assert.isTrue(dict.isEmpty());
+ // dict.clear() is chainable.
+ Assert.areSame(result, dict);
+ },
+
+ test_copy: function() {
+ var dict = new extras.Dict();
+ dict.foo = 123, dict.bar = 456;
+ var copy = dict.copy();
+ Assert.areNotSame(copy, dict);
+ Assert.isTrue(copy.isSame(dict));
+ },
+
+ test_get: function() {
+ var dict = new extras.Dict({foo: 123});
+ Assert.areSame(123, dict.get("foo"));
+ Assert.areSame(123, dict.get("foo", 456));
+ Assert.areSame(789, dict.get("bar", 789));
+ },
+
+ test_hasKey: function() {
+ var dict = new extras.Dict({foo: 123});
+ Assert.isTrue(dict.hasKey("foo"));
+ Assert.isFalse(dict.hasKey("bar"));
+ // hasKey() does not consider prototype properties.
+ Assert.isFalse(dict.hasKey("hasOwnProperty"));
+ Assert.isFalse(dict.hasKey("hasKey"));
+ },
+
+ test_items: function() {
+ var dict = new extras.Dict({foo: 123, bar: 456});
+ var items = dict.items().sort(function(a, b) {
+ if (a.key == b.key) {
+ return 0;
+ }
+ else {
+ return a.key > b.key ? 1 : -1;
+ }
+ });
+ ArrayAssert.itemsAreSame(
+ ["bar", "foo"], extras.attrselect("key")(items));
+ ArrayAssert.itemsAreSame(
+ [456, 123], extras.attrselect("value")(items));
+ },
+
+ test_keys: function() {
+ var dict = new extras.Dict({foo: 123, bar: 456});
+ ArrayAssert.itemsAreSame(
+ ["bar", "foo"], dict.keys().sort());
+ },
+
+ test_pop: function() {
+ var dict = new extras.Dict({foo: 123, bar: 456});
+ var value = dict.pop("foo");
+ Assert.areSame(123, value);
+ Assert.isFalse(dict.hasKey("foo"));
+ Assert.isTrue(dict.hasKey("bar"));
+ },
+
+ test_popItem: function() {
+ var dict = new extras.Dict({foo: 123, bar: 456});
+ var item = dict.popItem();
+ if (item.value === 123) {
+ Assert.areSame("foo", item.key);
+ Assert.isFalse(dict.hasKey("foo"));
+ Assert.isTrue(dict.hasKey("bar"));
+ }
+ else if (item.value === 456) {
+ Assert.areSame("bar", item.key);
+ Assert.isTrue(dict.hasKey("foo"));
+ Assert.isFalse(dict.hasKey("bar"));
+ }
+ else {
+ Y.fail("Unexpected: " + value);
+ }
+ },
+
+ test_setDefault: function() {
+ var dict = new extras.Dict({foo: 123});
+ Assert.areSame(123, dict.setDefault("foo", 789));
+ Assert.areSame(456, dict.setDefault("bar", 456));
+ Assert.areSame(456, dict.bar);
+ },
+
+ test_update_with_object: function() {
+ var dict = new extras.Dict({foo: 123});
+ var result = dict.update({bar: 456});
+ Assert.areSame(123, dict.foo);
+ Assert.areSame(456, dict.bar);
+ // dict.update() is chainable.
+ Assert.areSame(result, dict);
+ },
+
+ test_update_with_objects: function() {
+ var dict = new extras.Dict({foo: 123});
+ var result = dict.update({bar: 456}, {baz: 789});
+ Assert.areSame(123, dict.foo);
+ Assert.areSame(456, dict.bar);
+ Assert.areSame(789, dict.baz);
+ // dict.update() is chainable.
+ Assert.areSame(result, dict);
+ },
+
+ test_update_with_array: function() {
+ var dict = new extras.Dict({foo: 123});
+ var result = dict.update(
+ [["bar", 456], {key: "baz", value: 789}]);
+ Assert.areSame(123, dict.foo);
+ Assert.areSame(456, dict.bar);
+ Assert.areSame(789, dict.baz);
+ // dict.update() is chainable.
+ Assert.areSame(result, dict);
+ },
+
+ test_values: function() {
+ var dict = new extras.Dict({foo: 123, bar: 456});
+ ArrayAssert.itemsAreSame(
+ [123, 456], dict.values().sort());
+ }
+
+ };
+
// Populate the suite.
suite.add(new Y.Test.Case(TestNodeListMap));
suite.add(new Y.Test.Case(TestAttributeFunctions));
+ suite.add(new Y.Test.Case(TestDict));
// Exports.
namespace.suite = suite;
-}, "0.1", {requires: ["test", "lp.extras"]});
+}, "0.1", {requires: ["test", "lp.extras", "lp.extras-dict"]});