Complete rewrite of the Hash class.
This commit is contained in:
parent
4607e29fa2
commit
d25b863c9a
24
CHANGELOG
24
CHANGELOG
@ -1,5 +1,29 @@
|
||||
*SVN*
|
||||
|
||||
* Complete rewrite of the Hash class.
|
||||
|
||||
!! BACKWARDS COMPATIBILITY CHANGE !! This new version of Hash is NOT backwards compatible with the former Hash class.
|
||||
|
||||
Properties are now hidden away in an private store to prevent the risk of collision with Hash's instance and mixed-in methods.
|
||||
This implies that properties of the hash can no longer be set, accessed or deleted directly: use the new Hash#get(key), Hash#set(key, value) and Hash#unset(key) instance methods instead.
|
||||
|
||||
- Make $H(object) equivalent to new Hash(object). Both now return a new (cloned) instance of Hash in all circumstances.
|
||||
- Make Hash#merge non-destructive.
|
||||
|
||||
- Add Hash#update (a destructive version of Hash#merge).
|
||||
- Add Hash#clone (returns a new, cloned instance of Hash).
|
||||
- Add Hash#toObject (returns a clone of the contained object).
|
||||
- Add Hash#get(key) (returns the value of the specified property).
|
||||
- Add Hash#set(key, value) (sets the value of the given property. returns the value).
|
||||
- Add Hash#unset(key) (deletes the specified property and returns its value).
|
||||
- Add Hash.from as a alias to $H for consistency with Array.from.
|
||||
- Add Object.toQueryString.
|
||||
|
||||
- Deprecate Hash.toQueryString (use Object.toQueryString or the instance method Hash#toQueryString instead).
|
||||
|
||||
- Remove Hash#remove (use Hash#unset instead).
|
||||
- Remove Hash.toJSON (use Object.toJSON or the instance method Hash#toJSON instead). [sam, Tobie Langel]
|
||||
|
||||
* Element#wrap now returns the wrapper instead of the element being wrapped. [sam]
|
||||
|
||||
* Namespace all custom event names to avoid conflicts with native DOM events. [sam]
|
||||
|
@ -85,7 +85,7 @@ Ajax.Request = Class.create(Ajax.Base, {
|
||||
|
||||
this.parameters = params;
|
||||
|
||||
if (params = Hash.toQueryString(params)) {
|
||||
if (params = Object.toQueryString(params)) {
|
||||
// when GET, append parameters to URL
|
||||
if (this.method == 'get')
|
||||
this.url += (this.url.include('?') ? '&' : '?') + params;
|
||||
|
@ -97,6 +97,10 @@ Object.extend(Object, {
|
||||
return '{' + results.join(', ') + '}';
|
||||
},
|
||||
|
||||
toQueryString: function(object) {
|
||||
return $H(object).toQueryString();
|
||||
},
|
||||
|
||||
toHTML: function(object) {
|
||||
return object && object.toHTML ? object.toHTML() : String.interpret(object);
|
||||
},
|
||||
|
@ -1,5 +1,7 @@
|
||||
/*------------------------------- DEPRECATED -------------------------------*/
|
||||
|
||||
Hash.toQueryString = Object.toQueryString;
|
||||
|
||||
var Toggle = { display: Element.toggle };
|
||||
|
||||
Element.Methods.childOf = Element.Methods.descendantOf;
|
||||
|
@ -25,7 +25,7 @@ var Form = {
|
||||
return result;
|
||||
});
|
||||
|
||||
return options.hash ? data : Hash.toQueryString(data);
|
||||
return options.hash ? data : Object.toQueryString(data);
|
||||
}
|
||||
};
|
||||
|
||||
@ -132,7 +132,7 @@ Form.Element.Methods = {
|
||||
if (value != undefined) {
|
||||
var pair = { };
|
||||
pair[element.name] = value;
|
||||
return Hash.toQueryString(pair);
|
||||
return Object.toQueryString(pair);
|
||||
}
|
||||
}
|
||||
return '';
|
||||
|
241
src/hash.js
241
src/hash.js
@ -1,132 +1,119 @@
|
||||
var Hash = function(object) {
|
||||
if (object instanceof Hash) this.merge(object);
|
||||
else Object.extend(this, object || { });
|
||||
};
|
||||
|
||||
Object.extend(Hash, {
|
||||
toQueryString: function(obj) {
|
||||
var parts = [];
|
||||
parts.add = arguments.callee.addPair;
|
||||
|
||||
this.prototype._each.call(obj, function(pair) {
|
||||
if (!pair.key) return;
|
||||
var value = pair.value;
|
||||
|
||||
if (value && typeof value == 'object') {
|
||||
if (Object.isArray(value)) value.each(function(value) {
|
||||
parts.add(pair.key, value);
|
||||
});
|
||||
return;
|
||||
}
|
||||
parts.add(pair.key, value);
|
||||
});
|
||||
|
||||
return parts.join('&');
|
||||
},
|
||||
|
||||
toJSON: function(object) {
|
||||
var results = [];
|
||||
this.prototype._each.call(object, function(pair) {
|
||||
var value = Object.toJSON(pair.value);
|
||||
if (value !== undefined) results.push(pair.key.toJSON() + ': ' + value);
|
||||
});
|
||||
return '{' + results.join(', ') + '}';
|
||||
}
|
||||
});
|
||||
|
||||
Hash.toQueryString.addPair = function(key, value, prefix) {
|
||||
key = encodeURIComponent(key);
|
||||
if (value === undefined) this.push(key);
|
||||
else this.push(key + '=' + (value == null ? '' : encodeURIComponent(value)));
|
||||
};
|
||||
|
||||
Object.extend(Hash.prototype, Enumerable);
|
||||
Object.extend(Hash.prototype, {
|
||||
_each: function(iterator) {
|
||||
for (var key in this) {
|
||||
var value = this[key];
|
||||
if (value && value == Hash.prototype[key]) continue;
|
||||
|
||||
var pair = [key, value];
|
||||
pair.key = key;
|
||||
pair.value = value;
|
||||
iterator(pair);
|
||||
}
|
||||
},
|
||||
|
||||
keys: function() {
|
||||
return this.pluck('key');
|
||||
},
|
||||
|
||||
values: function() {
|
||||
return this.pluck('value');
|
||||
},
|
||||
|
||||
index: function(value) {
|
||||
var match = this.detect(function(pair) {
|
||||
return pair.value === value;
|
||||
});
|
||||
return match && match.key;
|
||||
},
|
||||
|
||||
merge: function(hash) {
|
||||
return $H(hash).inject(this, function(mergedHash, pair) {
|
||||
mergedHash[pair.key] = pair.value;
|
||||
return mergedHash;
|
||||
});
|
||||
},
|
||||
|
||||
remove: function() {
|
||||
var result;
|
||||
for(var i = 0, length = arguments.length; i < length; i++) {
|
||||
var value = this[arguments[i]];
|
||||
if (value !== undefined){
|
||||
if (result === undefined) result = value;
|
||||
else {
|
||||
if (!Object.isArray(result)) result = [result];
|
||||
result.push(value);
|
||||
}
|
||||
}
|
||||
delete this[arguments[i]];
|
||||
}
|
||||
return result;
|
||||
},
|
||||
|
||||
toQueryString: function() {
|
||||
return Hash.toQueryString(this);
|
||||
},
|
||||
|
||||
inspect: function() {
|
||||
return '#<Hash:{' + this.map(function(pair) {
|
||||
return pair.map(Object.inspect).join(': ');
|
||||
}).join(', ') + '}>';
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
return Hash.toJSON(this);
|
||||
}
|
||||
});
|
||||
|
||||
function $H(object) {
|
||||
if (object instanceof Hash) return object;
|
||||
return new Hash(object);
|
||||
};
|
||||
|
||||
// Safari iterates over shadowed properties
|
||||
if (function() {
|
||||
var i = 0, Test = function(value) { this.key = value };
|
||||
Test.prototype.key = 'foo';
|
||||
for (var property in new Test('bar')) i++;
|
||||
return i > 1;
|
||||
}()) Hash.prototype._each = function(iterator) {
|
||||
var cache = [];
|
||||
for (var key in this) {
|
||||
var value = this[key];
|
||||
if ((value && value == Hash.prototype[key]) || cache.include(key)) continue;
|
||||
cache.push(key);
|
||||
var pair = [key, value];
|
||||
pair.key = key;
|
||||
pair.value = value;
|
||||
iterator(pair);
|
||||
var Hash = Class.create(Enumerable, (function() {
|
||||
if (function() {
|
||||
var i = 0, Test = function(value) { this.key = value };
|
||||
Test.prototype.key = 'foo';
|
||||
for (var property in new Test('bar')) i++;
|
||||
return i > 1;
|
||||
}()) {
|
||||
function each(iterator) {
|
||||
var cache = [];
|
||||
for (var key in this._object) {
|
||||
var value = this._object[key];
|
||||
if (cache.include(key)) continue;
|
||||
cache.push(key);
|
||||
var pair = [key, value];
|
||||
pair.key = key;
|
||||
pair.value = value;
|
||||
iterator(pair);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
function each(iterator) {
|
||||
for (var key in this._object) {
|
||||
var value = this._object[key], pair = [key, value];
|
||||
pair.key = key;
|
||||
pair.value = value;
|
||||
iterator(pair);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
function toQueryPair(key, value) {
|
||||
if (Object.isUndefined(value)) return key;
|
||||
return key + '=' + encodeURIComponent(String.interpret(value));
|
||||
}
|
||||
|
||||
return {
|
||||
initialize: function(object) {
|
||||
this._object = object instanceof Hash ? object.toObject() : Object.clone(object);
|
||||
},
|
||||
|
||||
_each: each,
|
||||
|
||||
set: function(key, value) {
|
||||
return this._object[key] = value;
|
||||
},
|
||||
|
||||
get: function(key) {
|
||||
return this._object[key];
|
||||
},
|
||||
|
||||
unset: function(key) {
|
||||
var value = this._object[key];
|
||||
delete this._object[key];
|
||||
return value;
|
||||
},
|
||||
|
||||
toObject: function() {
|
||||
return Object.clone(this._object);
|
||||
},
|
||||
|
||||
keys: function() {
|
||||
return this.pluck('key');
|
||||
},
|
||||
|
||||
values: function() {
|
||||
return this.pluck('value');
|
||||
},
|
||||
|
||||
index: function(value) {
|
||||
var match = this.detect(function(pair) {
|
||||
return pair.value === value;
|
||||
});
|
||||
return match && match.key;
|
||||
},
|
||||
|
||||
merge: function(object) {
|
||||
return this.clone().update(object);
|
||||
},
|
||||
|
||||
update: function(object) {
|
||||
return new Hash(object).inject(this, function(result, pair) {
|
||||
result.set(pair.key, pair.value);
|
||||
return result;
|
||||
});
|
||||
},
|
||||
|
||||
toQueryString: function() {
|
||||
return this.map(function(pair) {
|
||||
var key = encodeURIComponent(pair.key), values = pair.value;
|
||||
|
||||
if (values && typeof values == 'object') {
|
||||
if (Object.isArray(values))
|
||||
return values.map(toQueryPair.curry(key)).join('&');
|
||||
}
|
||||
return toQueryPair(key, values);
|
||||
}).join('&');
|
||||
},
|
||||
|
||||
inspect: function() {
|
||||
return '#<Hash:{' + this.map(function(pair) {
|
||||
return pair.map(Object.inspect).join(': ');
|
||||
}).join(', ') + '}>';
|
||||
},
|
||||
|
||||
toJSON: function() {
|
||||
return Object.toJSON(this.toObject());
|
||||
},
|
||||
|
||||
clone: function() {
|
||||
return new Hash(this);
|
||||
}
|
||||
}
|
||||
})());
|
||||
|
||||
Hash.from = $H;
|
@ -230,7 +230,11 @@
|
||||
assertHashEqual({foo: 'foo', bar: [1, 2, 3], bla: null},
|
||||
Object.extend(object, {bla: null}));
|
||||
}},
|
||||
|
||||
|
||||
testObjectToQueryString: function() { with(this) {
|
||||
assertEqual('a=A&b=B&c=C&d=D%23', Object.toQueryString({a: 'A', b: 'B', c: 'C', d: 'D#'}));
|
||||
}},
|
||||
|
||||
testObjectClone: function() { with(this) {
|
||||
var object = {foo: 'foo', bar: [1, 2, 3]};
|
||||
assertNotIdentical(object, Object.clone(object));
|
||||
|
@ -368,7 +368,7 @@
|
||||
// return params
|
||||
assertHashEqual(expected, Form.serialize('various', true));
|
||||
// return string
|
||||
assertEnumEqual(Hash.toQueryString(expected).split('&').sort(),
|
||||
assertEnumEqual(Object.toQueryString(expected).split('&').sort(),
|
||||
Form.serialize('various').split('&').sort());
|
||||
assertEqual('string', typeof $('form').serialize({ hash:false }));
|
||||
|
||||
|
@ -50,28 +50,61 @@
|
||||
|
||||
value_undefined: { a:"b", c:undefined },
|
||||
value_null: { a:"b", c:null },
|
||||
value_zero: { a:"b", c:0 },
|
||||
|
||||
dangerous: {
|
||||
_each: 'E',
|
||||
map: 'M',
|
||||
keys: 'K',
|
||||
values: 'V',
|
||||
collect: 'C',
|
||||
inject: 'I'
|
||||
}
|
||||
value_zero: { a:"b", c:0 }
|
||||
};
|
||||
|
||||
new Test.Unit.Runner({
|
||||
testSet: function(){ with(this) {
|
||||
var h = $H({a: 'A'})
|
||||
|
||||
assertEqual('B', h.set('b', 'B'));
|
||||
assertHashEqual({a: 'A', b: 'B'}, h);
|
||||
|
||||
assertUndefined(h.set('c'));
|
||||
assertHashEqual({a: 'A', b: 'B', c: undefined}, h);
|
||||
}},
|
||||
|
||||
testGet: function(){ with(this) {
|
||||
assertEqual('A', $H({a: 'A'}).get('a'));
|
||||
assertUndefined($H({}).get('a'));
|
||||
}},
|
||||
|
||||
testUnset: function(){ with(this) {
|
||||
var hash = $H(Fixtures.many);
|
||||
assertEqual('B', hash.unset('b'));
|
||||
assertHashEqual({a:'A', c: 'C', d:'D#'}, hash);
|
||||
assertUndefined(hash.unset('z'));
|
||||
assertHashEqual({a:'A', c: 'C', d:'D#'}, hash);
|
||||
}},
|
||||
|
||||
testToObject: function(){ with(this) {
|
||||
var hash = $H(Fixtures.many), object = hash.toObject();
|
||||
assertInstanceOf(Object, object);
|
||||
assertHashEqual(Fixtures.many, object);
|
||||
assertNotIdentical(Fixtures.many, object);
|
||||
hash.set('foo', 'bar');
|
||||
assertHashNotEqual(object, hash.toObject());
|
||||
}},
|
||||
|
||||
testConstruct: function(){ with(this) {
|
||||
var h = $H(Fixtures.one);
|
||||
assertNotIdentical(Fixtures.one, h);
|
||||
assertIdentical(h, $H(h));
|
||||
|
||||
var h2 = new Hash(h);
|
||||
assertNotIdentical(h, h2);
|
||||
assertHashEqual(h, h2);
|
||||
var object = Object.clone(Fixtures.one);
|
||||
var h = new Hash(object), h2 = $H(object);
|
||||
assertInstanceOf(Hash, h);
|
||||
assertInstanceOf(Hash, h2);
|
||||
|
||||
assertHashEqual({}, new Hash());
|
||||
assertHashEqual(object, h);
|
||||
assertHashEqual(object, h2);
|
||||
|
||||
h.set('foo', 'bar');
|
||||
assertHashNotEqual(object, h);
|
||||
|
||||
var clone = $H(h);
|
||||
assertInstanceOf(Hash, clone);
|
||||
assertHashEqual(h, clone);
|
||||
h.set('foo', 'foo');
|
||||
assertHashNotEqual(h, clone);
|
||||
assertIdentical($H, Hash.from);
|
||||
}},
|
||||
|
||||
testKeys: function(){ with(this) {
|
||||
@ -87,8 +120,8 @@
|
||||
assertEnumEqual($w('A B C D#'), $H(Fixtures.many).values().sort());
|
||||
assertEnumEqual($w('function function'),
|
||||
$H(Fixtures.functions).values().map(function(i){ return typeof i }));
|
||||
assertEqual(9, $H(Fixtures.functions).quad(3));
|
||||
assertEqual(6, $H(Fixtures.functions).plus(3));
|
||||
assertEqual(9, $H(Fixtures.functions).get('quad')(3));
|
||||
assertEqual(6, $H(Fixtures.functions).get('plus')(3));
|
||||
}},
|
||||
|
||||
testIndex: function(){ with(this) {
|
||||
@ -104,22 +137,32 @@
|
||||
}},
|
||||
|
||||
testMerge: function(){ with(this) {
|
||||
assertHashEqual(Fixtures.many, $H(Fixtures.many).merge());
|
||||
assertHashEqual(Fixtures.many, $H(Fixtures.many).merge({}));
|
||||
assertHashEqual(Fixtures.many, $H(Fixtures.many).merge($H()));
|
||||
assertHashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, $H(Fixtures.many).merge({aaa: 'AAA'}));
|
||||
assertHashEqual({a:'A#', b:'B', c:'C', d:'D#' }, $H(Fixtures.many).merge(Fixtures.one));
|
||||
var h = $H(Fixtures.many);
|
||||
assertNotIdentical(h, h.merge());
|
||||
assertNotIdentical(h, h.merge({}));
|
||||
assertInstanceOf(Hash, h.merge());
|
||||
assertInstanceOf(Hash, h.merge({}));
|
||||
assertHashEqual(h, h.merge());
|
||||
assertHashEqual(h, h.merge({}));
|
||||
assertHashEqual(h, h.merge($H()));
|
||||
assertHashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.merge({aaa: 'AAA'}));
|
||||
assertHashEqual({a:'A#', b:'B', c:'C', d:'D#' }, h.merge(Fixtures.one));
|
||||
}},
|
||||
|
||||
testRemove: function(){ with(this) {
|
||||
var hash = $H(Fixtures.many);
|
||||
var values = hash.remove('b', 'c');
|
||||
assertHashEqual({a:'A', d:'D#'}, hash);
|
||||
assertEnumEqual($w('B C'), values);
|
||||
|
||||
testUpdate: function(){ with(this) {
|
||||
var h = $H(Fixtures.many);
|
||||
assertIdentical(h, h.update());
|
||||
assertIdentical(h, h.update({}));
|
||||
assertHashEqual(h, h.update());
|
||||
assertHashEqual(h, h.update({}));
|
||||
assertHashEqual(h, h.update($H()));
|
||||
assertHashEqual({a:'A', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.update({aaa: 'AAA'}));
|
||||
assertHashEqual({a:'A#', b:'B', c:'C', d:'D#', aaa:'AAA' }, h.update(Fixtures.one));
|
||||
}},
|
||||
|
||||
testToQueryString: function(){ with(this) {
|
||||
assertEqual('', $H({}).toQueryString());
|
||||
assertEqual('a%23=A', $H({'a#': 'A'}).toQueryString());
|
||||
assertEqual('a=A%23', $H(Fixtures.one).toQueryString());
|
||||
assertEqual('a=A&b=B&c=C&d=D%23', $H(Fixtures.many).toQueryString());
|
||||
assertEqual("a=b&c", $H(Fixtures.value_undefined).toQueryString());
|
||||
@ -132,10 +175,7 @@
|
||||
assertEqual("", $H(Fixtures.multiple_empty).toQueryString());
|
||||
assertEqual("stuff%5B%5D=%24&stuff%5B%5D=a&stuff%5B%5D=%3B", $H(Fixtures.multiple_special).toQueryString());
|
||||
assertHashEqual(Fixtures.multiple_special, $H(Fixtures.multiple_special).toQueryString().toQueryParams());
|
||||
|
||||
var danger = $w("_each=E collect=C inject=I keys=K map=M values=V");
|
||||
assertEnumEqual(danger, Hash.toQueryString(Fixtures.dangerous).split('&').sort());
|
||||
assertEnumEqual(danger, $H(Fixtures.dangerous).toQueryString().split('&').sort());
|
||||
assertIdentical(Object.toQueryString, Hash.toQueryString);
|
||||
}},
|
||||
|
||||
testInspect: function(){ with(this) {
|
||||
@ -143,12 +183,17 @@
|
||||
assertEqual("#<Hash:{'a': 'A#'}>", $H(Fixtures.one).inspect());
|
||||
assertEqual("#<Hash:{'a': 'A', 'b': 'B', 'c': 'C', 'd': 'D#'}>", $H(Fixtures.many).inspect());
|
||||
}},
|
||||
|
||||
testClone: function(){ with(this) {
|
||||
var h = $H(Fixtures.many);
|
||||
assertHashEqual(h, h.clone());
|
||||
assertInstanceOf(Hash, h.clone());
|
||||
assertNotIdentical(h, h.clone());
|
||||
}},
|
||||
|
||||
testToJSON: function(){ with(this) {
|
||||
assertEqual('{\"b\": [false, true], \"c\": {\"a\": \"hello!\"}}',
|
||||
$H({'b': [undefined, false, true, undefined], c: {a: 'hello!'}}).toJSON());
|
||||
|
||||
assertEqual('E', eval('(' + $H(Fixtures.dangerous).toJSON() + ')')._each);
|
||||
}}
|
||||
}, 'testlog');
|
||||
// ]]>
|
||||
|
@ -58,7 +58,7 @@
|
||||
testIsRunningFromRake: function() { with(this) {
|
||||
if (window.location.toString().startsWith('http')) {
|
||||
assert(isRunningFromRake);
|
||||
info('These tests are runingn from rake.')
|
||||
info('These tests are running from rake.')
|
||||
} else {
|
||||
assert(!isRunningFromRake);
|
||||
info('These tests are *not* running from rake.')
|
||||
|
Loading…
Reference in New Issue
Block a user