prototype: Improvements for Element#replace, Element#update and Element#insert. Closes #7429, #9060.

This commit is contained in:
Sam Stephenson 2007-07-24 18:52:17 +00:00
parent 7b2ce66e88
commit 44ef74813b
9 changed files with 183 additions and 50 deletions

View File

@ -1,5 +1,14 @@
*SVN*
* Improvements for Element#replace, Element#update and Element#insert. Closes #7429, #9060. [Tobie Langel]
- Element#replace/update/insert uses the argument's toElement or toHTML method if present (toElement has precedence if both are present).
- Element#replace and Element#update now also accept DOM elements.
- Element#replace better handles table-related elements in IE and Opera.
* Add Object.isArray and Object.isElement (returns true if the object is a DOM node of type 1). [Tobie Langel]
* Add Object.toHTML (uses the object's toHTML method if present or else passes the object to String.interpret). [Tobie Langel]
* Make Element#setStyle accept a string argument of CSS rules. Deprecate uncamelized style property names when setting styles using an object (for performance reasons). Closes #9059. [Tobie Langel]
Examples:
$('id').setStyle('font-size: 12px; float: left; opacity: 0.5');

View File

@ -57,7 +57,7 @@ Object.extend(Array.prototype, {
flatten: function() {
return this.inject([], function(array, value) {
return array.concat(value && value.constructor == Array ?
return array.concat(Object.isArray(value) ?
value.flatten() : [value]);
});
},
@ -144,7 +144,7 @@ if (Prototype.Browser.Opera){
var array = [];
for (var i = 0, length = this.length; i < length; i++) array.push(this[i]);
for (var i = 0, length = arguments.length; i < length; i++) {
if (arguments[i].constructor == Array) {
if (Object.isArray(arguments[i])) {
for (var j = 0, arrayLength = arguments[i].length; j < arrayLength; j++)
array.push(arguments[i][j]);
} else {

View File

@ -37,7 +37,7 @@ Object.extend(Object, {
}
if (object === null) return 'null';
if (object.toJSON) return object.toJSON();
if (object.ownerDocument === document) return;
if (Object.isElement(object)) return;
var results = [];
for (var property in object) {
var value = Object.toJSON(object[property]);
@ -47,6 +47,10 @@ Object.extend(Object, {
return '{' + results.join(', ') + '}';
},
toHTML: function(object) {
return object && object.toHTML ? object.toHTML() : String.interpret(object);
},
keys: function(object) {
var keys = [];
for (var property in object)
@ -63,6 +67,14 @@ Object.extend(Object, {
clone: function(object) {
return Object.extend({}, object);
},
isElement: function(object) {
return object && object.nodeType == 1;
},
isArray: function(object) {
return object && object.constructor === Array;
}
});

View File

@ -68,33 +68,35 @@ Element.Methods = {
return element;
},
update: function(element, html) {
html = typeof html == 'undefined' ? '' : html.toString();
$(element).innerHTML = html.stripScripts();
html.evalScripts.bind(html).defer();
update: function(element, content) {
element = $(element);
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) return element.update().insert(content);
content = Object.toHTML(content);
element.innerHTML = content.stripScripts();
content.evalScripts.bind(content).defer();
return element;
},
replace: function(element, html) {
replace: function(element, content) {
element = $(element);
html = typeof html == 'undefined' ? '' : html.toString();
if (element.outerHTML) {
element.outerHTML = html.stripScripts();
} else {
if (content && content.toElement) content = content.toElement();
else if (!Object.isElement(content)) {
content = Object.toHTML(content);
var range = element.ownerDocument.createRange();
range.selectNode(element);
element.parentNode.replaceChild(
range.createContextualFragment(html.stripScripts()), element);
content.evalScripts.bind(content).defer();
content = range.createContextualFragment(content.stripScripts());
}
html.evalScripts.bind(html).defer();
element.parentNode.replaceChild(content, element);
return element;
},
insert: function(element, insertions) {
element = $(element);
if (typeof insertions == 'string' || typeof insertions == 'number' ||
(insertions && insertions.ownerDocument === document))
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
insertions = {bottom:insertions};
var content, t, range;
@ -103,13 +105,14 @@ Element.Methods = {
content = insertions[position];
position = position.toLowerCase();
t = Element._insertionTranslations[position];
if (content && content.ownerDocument === document) {
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) {
t.insert(element, content);
continue;
}
content = String.interpret(content);
content = Object.toHTML(content);
range = element.ownerDocument.createRange();
t.initializeRange(element, range);
@ -613,9 +616,6 @@ Element.Methods = {
Element.Methods.identify.counter = 1;
if (!document.getElementsByClassName) document.getElementsByClassName = function(instanceMethods){
function isArray(className) {
return ;
}
function iter(name) {
return name.blank() ? null : "[contains(concat(' ', @class, ' '), ' " + name + " ')]";
}
@ -667,9 +667,9 @@ Element._attributeTranslations = {
if (!document.createRange || Prototype.Browser.Opera) {
Element.Methods.insert = function(element, insertions) {
element = $(element);
if (typeof insertions == 'string' || typeof insertions == 'number' ||
(insertions && insertions.ownerDocument === document))
Object.isElement(insertions) || (insertions && (insertions.toElement || insertions.toHTML)))
insertions = {bottom:insertions};
var t = Element._insertionTranslations, content, position, pos, tagName;
@ -678,13 +678,14 @@ if (!document.createRange || Prototype.Browser.Opera) {
content = insertions[position];
position = position.toLowerCase();
pos = t[position];
if (content && content.ownerDocument === document) {
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) {
pos.insert(element, content);
continue;
}
content = String.interpret(content);
content = Object.toHTML(content);
tagName = ((position == 'before' || position == 'after')
? element.parentNode : element).tagName.toUpperCase();
@ -888,19 +889,52 @@ else if (Prototype.Browser.WebKit) {
if (Prototype.Browser.IE || Prototype.Browser.Opera) {
// IE and Opera are missing .innerHTML support for TABLE-related and SELECT elements
Element.Methods.update = function(element, html) {
Element.Methods.update = function(element, content) {
element = $(element);
html = typeof html == 'undefined' ? '' : html.toString();
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) return element.update().insert(content);
content = Object.toHTML(content);
var tagName = element.tagName.toUpperCase();
if (Element._insertionTranslations.tags[tagName]) {
if (tagName in Element._insertionTranslations.tags) {
$A(element.childNodes).each(function(node) { element.removeChild(node) });
Element._getContentFromAnonymousElement(tagName, html.stripScripts())
Element._getContentFromAnonymousElement(tagName, content.stripScripts())
.each(function(node) { element.appendChild(node) });
}
else element.innerHTML = html.stripScripts();
}
else element.innerHTML = content.stripScripts();
html.evalScripts.bind(html).defer();
content.evalScripts.bind(content).defer();
return element;
};
}
if (document.createElement('div').outerHTML) {
Element.Methods.replace = function(element, content) {
element = $(element);
if (content && content.toElement) content = content.toElement();
if (Object.isElement(content)) {
element.parentNode.replaceChild(content, element);
return element;
}
content = Object.toHTML(content);
var parent = element.parentNode, tagName = parent.tagName.toUpperCase();
if (Element._insertionTranslations.tags[tagName]) {
var nextSibling = element.next();
var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts());
parent.removeChild(element);
if (nextSibling)
fragments.each(function(node) { parent.insertBefore(node, nextSibling) });
else
fragments.each(function(node) { parent.appendChild(node) });
}
else element.outerHTML = content.stripScripts();
content.evalScripts.bind(content).defer();
return element;
};
}
@ -1057,7 +1091,7 @@ Element.addMethods = function(methods) {
if (!tagName) Object.extend(Element.Methods, methods || {});
else {
if (tagName.constructor == Array) tagName.each(extend);
if (Object.isArray(tagName)) tagName.each(extend);
else extend(tagName);
}

View File

@ -16,7 +16,7 @@ var Form = {
submit !== false && (!submit || key == submit) && (submitted = true)))) {
if (key in result) {
// a key is already present; construct an array of values
if (result[key].constructor != Array) result[key] = [result[key]];
if (!Object.isArray(result[key])) result[key] = [result[key]];
result[key].push(value);
}
else result[key] = value;
@ -218,7 +218,7 @@ Form.Element.Serializers = {
return this[element.type == 'select-one' ?
'selectOne' : 'selectMany'](element);
else {
var opt, value, single = index.constructor != Array;
var opt, value, single = !Object.isArray(index);
for (var i = 0, length = element.length; i < length; i++) {
opt = element.options[i];
value = this.optionValue(opt);

View File

@ -13,7 +13,7 @@ Object.extend(Hash, {
var value = pair.value;
if (value && typeof value == 'object') {
if (value.constructor == Array) value.each(function(value) {
if (Object.isArray(value)) value.each(function(value) {
parts.add(pair.key, value);
});
return;
@ -83,7 +83,7 @@ Object.extend(Hash.prototype, {
if (value !== undefined){
if (result === undefined) result = value;
else {
if (result.constructor != Array) result = [result];
if (!Object.isArray(result)) result = [result];
result.push(value)
}
}

View File

@ -100,7 +100,7 @@ Object.extend(String.prototype, {
if (value != undefined) value = decodeURIComponent(value);
if (key in hash) {
if (hash[key].constructor != Array) hash[key] = [hash[key]];
if (!Object.isArray(hash[key])) hash[key] = [hash[key]];
hash[key].push(value);
}
else hash[key] = value;

View File

@ -170,6 +170,30 @@
assertEqual('I\'m a div with id test', Object.toJSON(element));
}},
testObjectToHTML: function() { with(this) {
assertIdentical('', Object.toHTML());
assertIdentical('', Object.toHTML(''));
assertIdentical('', Object.toHTML(null));
assertIdentical('0', Object.toHTML(0));
assertIdentical('123', Object.toHTML(123));
assertEqual('hello world', Object.toHTML('hello world'));
assertEqual('hello world', Object.toHTML({toHTML: function() { return 'hello world' }}));
}},
testObjectIsArray: function() { with(this) {
assert(Object.isArray([]));
assert(Object.isArray([0]));
assert(Object.isArray([0, 1]));
assert(!Object.isArray({}));
}},
testObjectIsElement: function() { with(this) {
assert(Object.isElement(document.createElement('div')));
assert(Object.isElement(new Element('div')));
assert(Object.isElement($('testlog')));
assert(!Object.isElement(document.createTextNode('bla')));
}},
// sanity check
testDoesntExtendObjectPrototype: function() {with(this) {
// for-in is supported with objects

View File

@ -159,6 +159,12 @@
<div id="testdiv-replace-container-3"><div id="testdiv-replace-3"></div></div>
<div id="testdiv-replace-container-4"><div id="testdiv-replace-4"></div></div>
<div id="testdiv-replace-container-5"><div id="testdiv-replace-5"></div></div>
<div id="testdiv-replace-container-element"><div id="testdiv-replace-element"></div></div>
<div id="testdiv-replace-container-toelement"><div id="testdiv-replace-toelement"></div></div>
<div id="testdiv-replace-container-tohtml"><div id="testdiv-replace-tohtml"></div></div>
<div id="testtable-replace-container"><table id="testtable-replace"></table></div>
<table id="testrow-replace-container"><tr id="testrow-replace"></tr></table>
<select id="testoption-replace-container"><option id="testoption-replace"></option><option>stays</option></select>
<div id="testform-replace-container"><p>some text</p><form id="testform-replace"><input id="testinput-replace" type="text" /></form><p>some text</p></div>
<div id="element_with_visible_overflow" style="overflow:visible">V</div>
@ -364,6 +370,11 @@
var getInnerHTML = function(id) {
return $(id).innerHTML.toString().toLowerCase().gsub(/[\r\n\t]/, '');
};
var createParagraph = function(text) {
var p = document.createElement('p');
p.appendChild(document.createTextNode(text));
return p;
}
Element.addMethods({
hashBrowns: function(element) { return 'hash browns'; }
});
@ -440,11 +451,6 @@
}},
testElementInsertWithDOMNode: function() {with(this) {
var createParagraph = function(text) {
var p = document.createElement('p');
p.appendChild(document.createTextNode(text));
return p;
}
Element.insert('insertions-node-main', {before: createParagraph('node before')});
assert(getInnerHTML('insertions-node-container').startsWith('<p>node before</p>'));
Element.insert('insertions-node-main', {after: createParagraph('node after')});
@ -456,6 +462,20 @@
assertEqual($('insertions-node-main'), $('insertions-node-main').insert(document.createElement('p')));
}},
testElementInsertWithToElementMethod: function() {with(this) {
Element.insert('insertions-node-main', {toElement: createParagraph.curry('toElement') });
assert(getInnerHTML('insertions-node-main').endsWith('<p>toelement</p>'));
Element.insert('insertions-node-main', {bottom: {toElement: createParagraph.curry('bottom toElement') }});
assert(getInnerHTML('insertions-node-main').endsWith('<p>bottom toelement</p>'));
}},
testElementInsertWithToHTMLMethod: function() {with(this) {
Element.insert('insertions-node-main', {toHTML: function() { return '<p>toHTML</p>'} });
assert(getInnerHTML('insertions-node-main').endsWith('<p>tohtml</p>'));
Element.insert('insertions-node-main', {bottom: {toHTML: function() { return '<p>bottom toHTML</p>'} }});
assert(getInnerHTML('insertions-node-main').endsWith('<p>bottom tohtml</p>'));
}},
testElementInsertWithNonString: function() {with(this) {
Element.insert('insertions-main', {bottom:3});
assert(getInnerHTML('insertions-main').endsWith('3'));
@ -637,6 +657,21 @@
select.update('<option value="3">option 3</option><option selected="selected">option 4</option>');
assertEqual('option 4', select.getValue());
}},
testElementUpdateWithDOMNode: function() {with(this) {
$('testdiv').update(new Element('div').insert('bla'));
assertEqual('<div>bla</div>', getInnerHTML('testdiv'));
}},
testElementUpdateWithToElementMethod: function() {with(this) {
$('testdiv').update({toElement: createParagraph.curry('foo')});
assertEqual('<p>foo</p>', getInnerHTML('testdiv'));
}},
testElementUpdateWithToHTMLMethod: function() {with(this) {
$('testdiv').update({toHTML: function() { return 'hello world' }});
assertEqual('hello world', getInnerHTML('testdiv'));
}},
testElementReplace: function() {with(this) {
$('testdiv-replace-1').replace('hello from div!');
@ -648,13 +683,17 @@
$('testdiv-replace-3').replace();
assertEqual('', $('testdiv-replace-container-3').innerHTML);
$('testrow-replace').replace('<tr><td>hello</td></tr>');
assert(getInnerHTML('testrow-replace-container').include('<tr><td>hello</td></tr>'));
$('testoption-replace').replace('<option>hello</option>');
assert($('testoption-replace-container').innerHTML.include('hello'));
$('testinput-replace').replace('<p>hello world</p>');
assertEqual('<p>hello world</p>',
$('testform-replace').innerHTML.gsub('\r\n', '').toLowerCase());
assertEqual('<p>hello world</p>', getInnerHTML('testform-replace'));
$('testform-replace').replace('<form></form>');
assertEqual('<p>some text</p><form></form><p>some text</p>',
$('testform-replace-container').innerHTML.gsub('\r\n', '').toLowerCase());
assertEqual('<p>some text</p><form></form><p>some text</p>', getInnerHTML('testform-replace-container'));
}},
testElementReplaceWithScript: function() {with(this) {
@ -672,6 +711,21 @@
});
});
}},
testElementReplaceWithDOMNode: function() {with(this) {
$('testdiv-replace-element').replace(createParagraph('hello'));
assertEqual('<p>hello</p>', getInnerHTML('testdiv-replace-container-element'));
}},
testElementReplaceWithToElementMethod: function() {with(this) {
$('testdiv-replace-toelement').replace({toElement: createParagraph.curry('hello')});
assertEqual('<p>hello</p>', getInnerHTML('testdiv-replace-container-toelement'));
}},
testElementReplaceWithToHTMLMethod: function() {with(this) {
$('testdiv-replace-tohtml').replace({toHTML: function() { return 'hello' }});
assertEqual('hello', getInnerHTML('testdiv-replace-container-tohtml'));
}},
testElementSelectorMethod: function() {with(this) {
['getElementsBySelector','select'].each(function(method){