How do you clone an Element

610 views
Skip to first unread message

fobster

unread,
Aug 9, 2008, 10:55:30 PM8/9/08
to Prototype & script.aculo.us
Hi,

How do you clone an Element and have it positioned in the same
location as the cloned Element in the DOM.

Thanks,

-kent

jdalton

unread,
Aug 10, 2008, 6:14:12 PM8/10/08
to Prototype & script.aculo.us
var clone = element.cloneNode(true);
element.up().insert(clone);

RobG

unread,
Aug 10, 2008, 11:58:38 PM8/10/08
to Prototype & script.aculo.us


On Aug 11, 8:14 am, jdalton <John.David.Dal...@gmail.com> wrote:
> var clone = element.cloneNode(true);
> element.up().insert(clone);

There are a few issues with that.

The element(s) returned by cloneNode will have duplicate IDs if any of
the cloned nodes have IDs. That should be dealt with before adding
them to the document.

The element clone may not have an up() method, so either ensure it
does (see below) or stick to pure DOM methods to append the clone:

var clone = element.cloneNode(true);
/*
deal with duplicate IDs
*/
element.parentNode.insertBefore(clone, element);

to insert the clone immediately before the element or:

element.parentNode.insertBefore(clone, element.nextSibling);

to insert it immediately after.

What the OP means by "in the same place" is not clear to me, however
it is unlikely that the up().insert() is appropriate. It will result
in the clone being inserted as the last child of the element's
parent. If that is what the OP wants, then:

element.parentNode.appendChild(clone);


will do the job.

To use Prototype.js methods it is necessary to ensure clone an
extended element, either:

var clone = $(element.cloneNode(true));

or

var clone = element.cloneNode(true);
$(element)...

To insert the clone before or after the cloned element, use:

$(el).insert({before: clone});

or

$(el).insert({after: clone});

respectively. There is no need for up().

If there is no issue with ID, the node can be cloned and inserted in
one go, e.g.:

el.parentNode.insertBefore(el.cloneNode(true), el);

or

$(el).insert({before: el.cloneNode(true)});


--
Rob

Tobie Langel

unread,
Aug 11, 2008, 8:53:10 AM8/11/08
to Prototype & script.aculo.us
Try this:

http://gist.github.com/4856

Best,

Tobie

RobG

unread,
Aug 11, 2008, 9:44:29 AM8/11/08
to Prototype & script.aculo.us


On Aug 11, 10:53 pm, Tobie Langel <tobie.lan...@gmail.com> wrote:
> Try this:
>
> http://gist.github.com/4856

It seems to me that where an attribute value is specified as being a
string[1] it is a good idea to assign it a string value, so:

$(clone)...invoke('writeAttribute', { id: '' });

if standards compliance matters.

Passing 'null' to writeAttribute results in a call to removeAttribute,
so why bother with the obfuscation when you can call removeAttribute
directly?


1. <URL: http://www.w3.org/TR/html4/struct/global.html#adef-id >


--
Rob

Tobie Langel

unread,
Aug 11, 2008, 9:56:38 AM8/11/08
to Prototype & script.aculo.us
As per specs, DOM attributes are *always* strings.

As you mentioned, when you pass null to writeAttribute, it internally
uses removeAttribute.

I'm actually not sure that removeAttribute behaves as a true function
in all browsers (i.e. that it has a call method) and thus that you
could pass it to Enum.invoke.

One of the initial reasons getAttribute was wrapped into readAttribute
was to avoid that particular issue.

Best,

Tobie

jdalton

unread,
Aug 11, 2008, 10:21:06 AM8/11/08
to Prototype & script.aculo.us
RobG,

My example was valid assuming the element was previously
extended and that there wasn't an ID conflict.
That’s a lot to assume but hey it was just a quick reply and fobsters
post left a lot to the imagination ;P

If the internals of Prototype remove the attribute when passed null,
how is that any less standards compliant than passing a string ?

It seems to me like you are splitting hairs on your initial reply and
those following.
$(element).up().insert(..) vs element.parentNode.appendChild(..) //
split hair

Since fobster didn't give much detail on the positioning of the
elements I figured
the correct way was to insert the clone into the parent of the
target element (so any inherited styles or what not would be applied).

If he needed further assistance using Element#clonePosition or
something further
replies would have allowed for the topic to branch.

I think your argument about simply using removeAttribute instead of
the indirectly calling it is valid.
You are also right insert({after: .. }) is shorter, but I think it
comes down to personal preference.
Lets wait for fobster's reply before speculating this thread to death.

Meh.

- JDD

Tobie Langel

unread,
Aug 11, 2008, 11:15:53 AM8/11/08
to Prototype & script.aculo.us
JDD, Rob, here's why ...invoke('removeAttribute', 'id'); wouldn't
work.

As you know, invoke relies on the apply method of function instances:

invoke: function(method) {
var args = $A(arguments).slice(1);
return this.map(function(value) {
return value[method].apply(value, args);
});
},

Unfortunately, IE doesn't grant all DOM methods a full function
status. Methods such as (get/set/remove)Attribute are deprived of call/
apply methods.

For a quick test, just try the following in IE: http://gist.github.com/4867

Hence the need to use the writeAttribute proxy.

Hope that helps.

Best,

Tobie

kangax

unread,
Aug 11, 2008, 11:56:10 AM8/11/08
to Prototype & script.aculo.us
I'm not sure why, but certain native host methods are not overwritten
in IE (when extending an element):
Is this a bug or was it done intentionally?

...
if (Object.isFunction(value) && !(property in element))
element[property] = value.methodize();
...
"!(property in element)" is what makes Element.extend skip the
properties.

#focus and #select (and maybe others) are not replaced with Prototype
"alternatives", resulting in failure to call `invoke` and returning
`undefined` rather than an element-receiver.

The issue came up in a recent thread
http://groups.google.com/group/prototype-scriptaculous/browse_thread/thread/0db0ba1ff0dac201/

--
kangax

Tobie Langel

unread,
Aug 11, 2008, 12:00:26 PM8/11/08
to Prototype & script.aculo.us
> I'm not sure why, but certain native host methods are not overwritten
> in IE (when extending an element):
> Is this a bug or was it done intentionally?

That's intentional.

> #focus and #select (and maybe others) are not replaced with Prototype
> "alternatives", resulting in failure to call `invoke` and returning
> `undefined` rather than an element-receiver.

Yet another pointer in the direction of dropping Element extension.

Best,

Tobie

jdalton

unread,
Aug 11, 2008, 12:09:02 PM8/11/08
to Prototype & script.aculo.us


> Unfortunately, IE doesn't grant all DOM methods a full function
> status. Methods such as (get/set/remove)Attribute are deprived of call/
> apply methods.
>

Ya good point Tobie :D.
I replied without trying Rob's suggested code.
I just meant that if additional complexity could be avoided
then it was a good thing.

I did a quick test of my own in the url of IE:
javascript:alert($$('*').invoke('removeAttribute','id')); // errors
javascript:alert(typeof document.body.removeAttribute); // object
javascript:alert(typeof document.body.removeAttribute.apply); //
undefined

BTW I have totally got to start using gist :)

@kangax Good catch. Another reason to move to wrappers asap :)

- JDD



RobG

unread,
Aug 11, 2008, 9:28:14 PM8/11/08
to Prototype & script.aculo.us


On Aug 12, 1:56 am, kangax <kan...@gmail.com> wrote:
> On Aug 11, 11:15 am, Tobie Langel <tobie.lan...@gmail.com> wrote:
>
> > JDD, Rob, here's why ...invoke('removeAttribute', 'id'); wouldn't
> > work.
>
> > As you know, invoke relies on the apply method of function instances:
>
> > invoke: function(method) {
> > var args = $A(arguments).slice(1);
> > return this.map(function(value) {
> > return value[method].apply(value, args);
> > });
> > },
>
> > Unfortunately, IE doesn't grant all DOM methods a full function
> > status. Methods such as (get/set/remove)Attribute are deprived of call/
> > apply methods.
>
> > For a quick test, just try the following in IE:http://gist.github.com/4867

A simpler, though probably less conclusive, test is:

alert( typeof element.removeAttribute.apply ) // undefined in IE


> > Hence the need to use the writeAttribute proxy.

That is one way, another is to use each instead of invoke to call host
methods.


> I'm not sure why, but certain native host methods are not overwritten
> in IE (when extending an element):

There is no guarantee that you will be able to overwrite host
properties in the future, the proposed ECMA-262 ed4 spec makes it a
problematic exercise for native objects, there are many places where
it says things like:

"The new properties are visible to “in” and “hasOwnProperty” tests.
They are all DontDelete, typed, and in some cases ReadOnly, and
user programs cannot generally use them for anything other than
their intended purpose."

It will be interesting to see the extent that host objects are
similarly "improved". I seem to remember that Mozilla was going to do
the same with some host methods for security reasons, I can't find a
reference (it might have been to ES4...).

> Is this a bug or was it done intentionally?
>
> ...
> if (Object.isFunction(value) && !(property in element))
> element[property] = value.methodize();
> ...
> "!(property in element)" is what makes Element.extend skip the
> properties.

I don't think so, the test is never reached in IE - its host methods
will return false from isFunction (which is why I think having an
isFunction function is not a good idea). If the use of isFunction is
to determine if the object has call or apply methods, they should be
tested explicitly and the function called 'hasCall' or 'hasApply'.
This goes to the heart of inferring certain properties by testing some
other property (i.e. testing if typeof returns function to infer that
the object has call or apply methods).


> #focus and #select (and maybe others) are not replaced with Prototype
> "alternatives", resulting in failure to call `invoke` and returning
> `undefined` rather than an element-receiver.

Use each instead. For the original example and borrowing from Tobie's
code:

$(clone).select('*[id]').concat(clone).each(
function(n){n.removeAttribute('id');}
);

presuming that the IDs really did need to be removed, it is more
likely that they need to be modified to make them unique (otherwise
why did they have IDs in the first place?). Of course that is up to
the OP. :-)


--
Rob

Tobie Langel

unread,
Aug 11, 2008, 10:55:12 PM8/11/08
to Prototype & script.aculo.us

> I don't think so, the test is never reached in IE - its host methods
> will return false from isFunction.

good point!

kangax

unread,
Aug 12, 2008, 12:42:41 AM8/12/08
to Prototype & script.aculo.us
On Aug 11, 9:28 pm, RobG <rg...@iinet.net.au> wrote:
> [snip]
> There is no guarantee that you will be able to overwrite host
> properties in the future, the proposed ECMA-262 ed4 spec makes it a

Rob,
My confusion was with prototype.js behavior, not the host objects'
one : )
The fact that host objects' behavior is implementation dependent (and
so can differ drastically) is exactly why we want to get rid of
element extension in favor of element wrappers (take a look at this
thread in "Prototype Core" http://groups.google.com/group/prototype-core/browse_thread/thread/16d0517ecc605a00)

> problematic exercise for native objects, there are many places where
> it says things like:
>
> "The new properties are visible to “in” and “hasOwnProperty” tests.
> They are all DontDelete, typed, and in some cases ReadOnly, and
> user programs cannot generally use them for anything other than
> their intended purpose."
>
> It will be interesting to see the extent that host objects are
> similarly "improved". I seem to remember that Mozilla was going to do
> the same with some host methods for security reasons, I can't find a
> reference (it might have been to ES4...).
>
> > Is this a bug or was it done intentionally?
>
> > ...
> > if (Object.isFunction(value) && !(property in element))
> > element[property] = value.methodize();
> > ...
> > "!(property in element)" is what makes Element.extend skip the
> > properties.
>
> I don't think so, the test is never reached in IE - its host methods
> will return false from isFunction (which is why I think having an

Good point. I didn't think about host methods returning "object" for
`typeof` (and so failing the test earlier).

> isFunction function is not a good idea). If the use of isFunction is
> to determine if the object has call or apply methods, they should be
> tested explicitly and the function called 'hasCall' or 'hasApply'.
> This goes to the heart of inferring certain properties by testing some
> other property (i.e. testing if typeof returns function to infer that
> the object has call or apply methods).

If we are talking feature detection, wouldn't it make sense to check
for `methodize` (before trying to call it on an object)?
`methodize` does assume existence of `apply` on an object, but the
assignment might still fail if an object does not implement it.

Not invoking `isFunction` would also be faster:

if (value.methodize && !(property in element))
element[property] = value.methodize();
}

>
> > #focus and #select (and maybe others) are not replaced with Prototype
> > "alternatives", resulting in failure to call `invoke` and returning
> > `undefined` rather than an element-receiver.
>
> Use each instead. For the original example and borrowing from Tobie's
> code:
>
> $(clone).select('*[id]').concat(clone).each(
> function(n){n.removeAttribute('id');}
> );
>
> presuming that the IDs really did need to be removed, it is more
> likely that they need to be modified to make them unique (otherwise
> why did they have IDs in the first place?). Of course that is up to
> the OP. :-)

--
kangax

kangax

unread,
Aug 13, 2008, 8:01:38 AM8/13/08
to Prototype & script.aculo.us
On Aug 12, 12:42 am, kangax <kan...@gmail.com> wrote:
> [snip]
> > I don't think so, the test is never reached in IE - its host methods
> > will return false from isFunction (which is why I think having an
>
> Good point. I didn't think about host methods returning "object" for
> `typeof` (and so failing the test earlier).

Just realized that it's not host objects' method that is tested with
`Object.isFunction`, but a property of `methods` object : )
"!(property in element)" is what prevents IE from being extended with
certain methods.

--
kangax

jdalton

unread,
Aug 31, 2008, 2:22:07 AM8/31/08
to Prototype & script.aculo.us
Here is a ticket which allows Array#invoke to be used on host methods.
Function.prototype.apply.call(value[method], value, args);
http://prototype.lighthouseapp.com/projects/8886/tickets/278-invoke-to-support-host-objects-methods

- JDD
Reply all
Reply to author
Forward
0 new messages