From 44ef74813bba065517cb66809b99dcdab4e7e035 Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Tue, 24 Jul 2007 18:52:17 +0000 Subject: [PATCH] prototype: Improvements for Element#replace, Element#update and Element#insert. Closes #7429, #9060. --- CHANGELOG | 9 ++++ src/array.js | 4 +- src/base.js | 14 ++++++- src/dom.js | 100 +++++++++++++++++++++++++++++--------------- src/form.js | 4 +- src/hash.js | 4 +- src/string.js | 2 +- test/unit/base.html | 24 +++++++++++ test/unit/dom.html | 72 +++++++++++++++++++++++++++---- 9 files changed, 183 insertions(+), 50 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 1945f7f..b8afa3d 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -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'); diff --git a/src/array.js b/src/array.js index 9f51547..c868136 100644 --- a/src/array.js +++ b/src/array.js @@ -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 { diff --git a/src/base.js b/src/base.js index 36a7fb2..143aa4c 100644 --- a/src/base.js +++ b/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; } }); diff --git a/src/dom.js b/src/dom.js index bfc19de..1b70bfd 100644 --- a/src/dom.js +++ b/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); } diff --git a/src/form.js b/src/form.js index 35af437..b0bf231 100644 --- a/src/form.js +++ b/src/form.js @@ -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); diff --git a/src/hash.js b/src/hash.js index d3b089c..0867f24 100644 --- a/src/hash.js +++ b/src/hash.js @@ -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) } } diff --git a/src/string.js b/src/string.js index bbd8770..26811d7 100644 --- a/src/string.js +++ b/src/string.js @@ -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; diff --git a/test/unit/base.html b/test/unit/base.html index 8bd781c..fb0b210 100644 --- a/test/unit/base.html +++ b/test/unit/base.html @@ -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 diff --git a/test/unit/dom.html b/test/unit/dom.html index 39fe29c..b178956 100644 --- a/test/unit/dom.html +++ b/test/unit/dom.html @@ -159,6 +159,12 @@
+
+
+
+
+
+

some text

some text

V
@@ -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('

node before

')); 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('

toelement

')); + Element.insert('insertions-node-main', {bottom: {toElement: createParagraph.curry('bottom toElement') }}); + assert(getInnerHTML('insertions-node-main').endsWith('

bottom toelement

')); + }}, + + testElementInsertWithToHTMLMethod: function() {with(this) { + Element.insert('insertions-node-main', {toHTML: function() { return '

toHTML

'} }); + assert(getInnerHTML('insertions-node-main').endsWith('

tohtml

')); + Element.insert('insertions-node-main', {bottom: {toHTML: function() { return '

bottom toHTML

'} }}); + assert(getInnerHTML('insertions-node-main').endsWith('

bottom tohtml

')); + }}, + testElementInsertWithNonString: function() {with(this) { Element.insert('insertions-main', {bottom:3}); assert(getInnerHTML('insertions-main').endsWith('3')); @@ -637,6 +657,21 @@ select.update(''); assertEqual('option 4', select.getValue()); }}, + + testElementUpdateWithDOMNode: function() {with(this) { + $('testdiv').update(new Element('div').insert('bla')); + assertEqual('
bla
', getInnerHTML('testdiv')); + }}, + + testElementUpdateWithToElementMethod: function() {with(this) { + $('testdiv').update({toElement: createParagraph.curry('foo')}); + assertEqual('

foo

', 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('hello'); + assert(getInnerHTML('testrow-replace-container').include('hello')); + + $('testoption-replace').replace(''); + assert($('testoption-replace-container').innerHTML.include('hello')); + $('testinput-replace').replace('

hello world

'); - assertEqual('

hello world

', - $('testform-replace').innerHTML.gsub('\r\n', '').toLowerCase()); + assertEqual('

hello world

', getInnerHTML('testform-replace')); $('testform-replace').replace('
'); - assertEqual('

some text

some text

', - $('testform-replace-container').innerHTML.gsub('\r\n', '').toLowerCase()); + assertEqual('

some text

some text

', getInnerHTML('testform-replace-container')); }}, testElementReplaceWithScript: function() {with(this) { @@ -672,6 +711,21 @@ }); }); }}, + + testElementReplaceWithDOMNode: function() {with(this) { + $('testdiv-replace-element').replace(createParagraph('hello')); + assertEqual('

hello

', getInnerHTML('testdiv-replace-container-element')); + }}, + + testElementReplaceWithToElementMethod: function() {with(this) { + $('testdiv-replace-toelement').replace({toElement: createParagraph.curry('hello')}); + assertEqual('

hello

', 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){