prototype: Improvements for Element#replace, Element#update and Element#insert. Closes #7429, #9060.
This commit is contained in:
parent
7b2ce66e88
commit
44ef74813b
|
@ -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');
|
||||
|
|
|
@ -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 {
|
||||
|
|
14
src/base.js
14
src/base.js
|
@ -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;
|
||||
}
|
||||
});
|
||||
|
||||
|
|
100
src/dom.js
100
src/dom.js
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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){
|
||||
|
|
Loading…
Reference in New Issue