PDA

View Full Version : Ext.ux.clone() - Object or Array cloning function



jsakalos
17 Feb 2008, 5:00 PM
Maybe I've reinvented the wheel..., anyway I haven't found a method of cloning objects or arrays in Ext.

Theory:

If you have an object or array in javascript and you assign this object to another variable only reference is copied but both variables point to same data. That comes very handy in many situations but there are cases when you really need clone of an array or an object, e.i. you need new fresh copy of data. Changing of data of the clone doesn't affect data of the original and vice versa.

So, here it is:



/**
* Clone Function
* @param {Object/Array} o Object or array to clone
* @return {Object/Array} Deep clone of an object or an array
* @author Ing. Jozef Sakáloš
*/
Ext.ux.util.clone = function(o) {
if(!o || 'object' !== typeof o) {
return o;
}
if('function' === typeof o.clone) {
return o.clone();
}
var c = '[object Array]' === Object.prototype.toString.call(o) ? [] : {};
var p, v;
for(p in o) {
if(o.hasOwnProperty(p)) {
v = o[p];
if(v && 'object' === typeof v) {
c[p] = Ext.ux.util.clone(v);
}
else {
c[p] = v;
}
}
}
return c;
}; // eo function clone

Usage is simple:




var o = {p:'text', p2:'text2', a:['a', 'b', 'c']};
var c = Ext.ux.clone(o);

I've done only brief tests so torture it please. (I haven't tested what happens if you pass funcion as the argument, I've tested only 1 level nested objects and 1 level nested arrays, seems to work for 'em)

mabello
18 Feb 2008, 7:25 AM
Hi,
first of all thanks a lot for ExtJS and for all your extension, all of them are really good and useful!!!!

I had already implemented my Clone function that is really similar to your implementation,


//Clone the obj
Util.Clone = function(myObj)
{
if(Util.isNullOrUndefined(myObj))
return myObj;
var objectClone = new myObj.constructor();
for (var property in myObj)
if (typeof myObj[property] == 'object')
objectClone[property] = Util.Clone(myObj[property]);
else
objectClone[property] = myObj[property];
return objectClone;
}


but in one case I think your has some problem, try this:



MyNamespace = {};

MyNamespace.Person = function(){

this.name = null;

this.init = function(tName){
//Add preoconditions
if(tName == null || tName == "")//Add the preconditions u want...
throw 'tName == null || tName == "" ';
this.name = tName;
}

this.getName = function(){
return this.name;
}

this.setName = function(value){
this.name = value;
}
}


MyNamespace.Person2 = function(){

var name = null;

this.init = function(tName){
//Add preoconditions
if(tName == null || tName == "")//Add the preconditions u want...
throw 'tName == null || tName == "" ';
name = tName;
}

this.getName = function(){
return name;
}

this.setName = function(value){
name = value;
}
}

MyNamespace.TreeNode = function(){

this.parent = null;
this.child = null;

this.init = function(){

}

this.getParent = function(){
return this.parent;
}

this.setParent = function(value){
this.parent = value;
}

this.getChild = function(){
return this.child;
}

this.setChild = function(value){
this.child = value;
}
}

Util = {};

Util.IsUndefined = function(a)
{
return (typeof a == 'undefined');
}

Util.isNullOrUndefined = function(element)
{
return (Util.IsUndefined(element) || element == null );
}

//Clone the obj
Util.Clone = function(myObj)
{
if(Util.isNullOrUndefined(myObj))
return myObj;
var objectClone = new myObj.constructor();
for (var property in myObj)
if (typeof myObj[property] == 'object')
objectClone[property] = Util.Clone(myObj[property]);
else
objectClone[property] = myObj[property];
return objectClone;
}

Ext.onReady(function(){

Ext.ux.clone = function(o) {
if('object' !== typeof o) {
return o;
}
var c = 'function' === typeof o.pop ? [] : {};
var p, v;
for(p in o) {
v = o[p];
if('object' === typeof v) {
c[p] = Ext.ux.clone(v);
}
else {
c[p] = v;
}
}
return c;
}

var marco = new MyNamespace.Person();
marco.init('marco ');
//Change the name for the cloned class
var marcoCloned = Ext.ux.clone(marco);
marcoCloned.setName('marcoCloned');
alert("marco.getName() is " + marco.getName());
alert("marcoCloned.getName() is " + marcoCloned.getName());
alert("Using Ext.ux.clone, marco.getName() == marcoCloned.getName(); Expected false, the result is " + (marco.getName() == marcoCloned.getName()));

var mario = new MyNamespace.Person();
mario.init('mario ');
var marioCloned = Util.Clone(marco);
//Change the name for the cloned class
marioCloned.setName('marioCloned');
alert("mario.getName() is " + mario.getName());
alert("marioCloned.getName() is " + marioCloned.getName());
alert("Using Util.Clone mario.getName() == marioCloned.getName(); Expected false, the result is " + (mario.getName() == marioCloned.getName()));

//*****************
var marco = new MyNamespace.Person2();
marco.init('marco ');
//Change the name for the cloned class
var marcoCloned = Ext.ux.clone(marco);
marcoCloned.setName('marcoCloned');
alert("marco.getName() is " + marco.getName());
alert("marcoCloned.getName() is " + marcoCloned.getName());
alert("Using Ext.ux.clone, marco.getName() == marcoCloned.getName(); Expected false, the result is " + (marco.getName() == marcoCloned.getName()));

var mario = new MyNamespace.Person2();
mario.init('mario ');
var marioCloned = Util.Clone(marco);
//Change the name for the cloned class
marioCloned.setName('marioCloned');
alert("mario.getName() is " + mario.getName());
alert("marioCloned.getName() is " + marioCloned.getName());
alert("Using Util.Clone, mario.getName() == marioCloned.getName(); Expected false, the result is " + (mario.getName() == marioCloned.getName()));
//*****************

});

The case is when I use some private variable in a class and I expose them only through methods get and set.

Both of us are failing with this:


var parent = new MyNamespace.TreeNode();
var child = new MyNamespace.TreeNode();
parent.setChild(child);
child.setParent(parent);

var parentClone1 = Ext.ux.clone(parent);

var parentClone2 = Util.Clone(parent);

alert(parentClone1.getChild());
alert(parentClone2.getChild());


I haven't check yours, but mine is because a reference b, which reference a and so on, I already know this problem, but I'm lazy...I have not stressed a lot also my implementation I have to say...
What about a merge?
Thanks for sharing and keep up the great work

jsakalos
18 Feb 2008, 7:30 AM
The clone function is in no case meant to clone classes or instantiated Ext objects. It is almost impossible as these install event handlers almost always so cloning would definitely lead to unpredictable results.

The clone function is meant for cloning simple (multi-nested) object or arrays. For example, if you need to clone baseParams object or a data array for stores or similar.

jsakalos
18 Feb 2008, 7:33 AM
BTW, it's not possible to clone objects with private vars if they don't provide special methods for that...

mabello
18 Feb 2008, 7:45 AM
Are you sure about the private variable of a class?
I think with my Util.Clone function seems to work fine also in this case..

Anyway, I agree with you that the clone of a "complex" Ext object could be dangerous, but my clone method is born before ExtJS, so for a normal business object class works

I'd like to try with an Exj object, just curious...for extJS object, to avoid the problem could be good to implement a clone method, like in .NET where you can implement the interface IClonable with the clone method...I think it is the best solution

Thanks, you've been really kind and keep up the good work!

jsakalos
18 Feb 2008, 8:10 AM
Re private vars:



var o = function() {
var priv = 4;

return {
pub:44;
};
}();

You have no access to priv from outside so you cannot clone o from outside.

Re cloning Ext objects: Trying to clone them is just waste of time.

Re .NET: What is it? I do not use Mirco$oft products.

mabello
18 Feb 2008, 8:34 AM
Sorry, I was trying to have an idea for the cloning of an Ext obj, I don't care about it, I'm not using my clone method to clone any ExtJS in my code, I usually clone the configObj like you.

Let's say that you can extend an ExtJS obj by implementing a method clone like a class method, and the clone method has the responsability to clone the object in the right way, instead of using an utility (static) method like mine or yours clone.

An idea could be to mantain a reference to the configObj as a class field and use it to create a new cloned object and then synchronize the "state" between the new cloned object and the old one...

.NET was an example...Cloneable in java is another example, this interface has the same responsability...it's not important the language, I was only trying to give an example...

Besides, I was talking about my "javascript class" in my example and about the "private" variable name



MyNamespace.Person2 = function(){
var name = null;

this.init = function(tName){
//Add preoconditions
if(tName == null || tName == "")//Add the preconditions u want...
throw 'tName == null || tName == "" ';
name = tName;
}

this.getName = function(){
return name;
}

this.setName = function(value){
name = value;
}
}


var name is a private variable...

Thanks

jsakalos
18 Feb 2008, 8:47 AM
Let's say that you can extend an ExtJS obj by implementing a method clone like a class method, and the clone method has the responsability to clone the object in the right way, instead of using an utility (static) method like mine or yours clone.

An idea could be to mantain a reference to the configObj as a class field and use it to create a new cloned object and then synchronize the "state" between the new cloned object and the old one...



Yes, this would be possible. Maybe I'll think of it when writing sth in the future. I have no idea of pros and cons of such approach; I've never tried it...

mabello
18 Feb 2008, 8:50 AM
Thanks again for your kind answer, ExtJS rocks! :-)

wm003
13 Mar 2008, 4:09 AM
Works VERY well for my needs. Thank you very much jsakalos for sharing this! You saved me much time. =D>

thomasf
8 Apr 2008, 12:57 AM
Thank you for sharing this!

But i am wondering why this is not in the Ext Core? Is it planned to integrate this into the Ext Core?

jsakalos
8 Apr 2008, 3:19 AM
It may easily be in an upcoming releases...

nico
10 Apr 2008, 12:21 AM
Thank you for this helpful function!

I have a little bugfix for you.

The line

if('object' === typeof v)
evaluates to true if v is null (seems to be an object too)
and should be extended to:

if(null != v && 'object' === typeof v)

Best regards,
Nico

jsakalos
10 Apr 2008, 3:10 AM
You're right, I've just tried in Firebug console: typeof null and it returns 'object'. We can simplify the if into:


if(v && 'object' === typeof v)

because null is falsie. We can get rid of undefined, false and '' in this one step.

Modifying source.

Thank you very much for debugging.

jsakalos
10 Apr 2008, 3:16 AM
Also first if needs this test:


if(!o || 'object' !== typeof(o)

hakunin
24 Apr 2008, 5:36 AM
Thank you for this essential function.

pokerking400
30 Apr 2008, 11:26 PM
Getting recursion error when i try to clone gridpanel.
Anyway it will help for other stuff.
Thanks.

jsakalos
1 May 2008, 1:37 AM
Getting recursion error when i try to clone gridpanel.
Anyway it will help for other stuff.
Thanks.
http://extjs.com/forum/showthread.php?p=125336#post125336

wm003
26 May 2008, 9:12 PM
To support Date Objects correctly the following code needs to be added. Otherwise Dateobjects are handled as simple objects ({}) which is wrong.



if('function' === typeof o.getFullYear) {
return new Date(o.getTime());
}

jsakalos
27 May 2008, 2:41 AM
To tell truth, I've never anticipated that somebody will try to clone objects or arrays containing Dates. However, the code you post does no harm so it could be included. I'm only thinking that if clone is going to support Dates it should support also RegExp - first one that comes to my mind. Any other?

mabello
27 May 2008, 2:57 AM
Hi there,

if you want to clone object, like Date, I think you could check my previous posted code out



/**
* Clone Function
*/
Ext.ux.isUndefined = function(a)
{
return (typeof a == 'undefined');
}

Ext.ux.isNullOrUndefined = function(element)
{
return (Ext.ux.isUndefined(element) || element == null );
}

Ext.ux.clone = function(myObj) {
if(Ext.ux.isNullOrUndefined(myObj))
return myObj;
var objectClone = new myObj.constructor();
for (var property in myObj)
if (typeof myObj[property] == 'object')
objectClone[property] = Ext.ux.clone(myObj[property]);
else
objectClone[property] = myObj[property];
return objectClone;
};
//Use it
var date = new Date();
alert(date);

var date1 = Ext.ux.clone(date);
alert(date1);


It works for standard object and json object, so give it a go please and let me know.

dj
10 Aug 2008, 4:54 AM
Hi mabello,

I tried your code with this:


var date = new Date();
date.setFullYear(2000);
console.log(date);

var date1 = Ext.ux.clone(date);
console.log(date1);


doesn't work. Your code only executes the standard constructor. And the standard constructor initializes the Date to the current date/time.

jsakalos
10 Aug 2008, 7:42 AM
Ext.ux.clone is for use only with plain, but possibly nested, objects or arrays. It is not for use with other objects, neither for Ext components. I think that dates can be easily cloned like this:



var d1 = new Date();
var d2 = new Date(d1);

jsakalos
10 Aug 2008, 7:44 AM
@dj, If you're trying mabello's clone, I have nothing to do with it, I haven't written it, I do not support it and I don't use it.

mystix
10 Aug 2008, 8:06 AM
Ext.ux.clone is for use only with plain, but possibly nested, objects or arrays. It is not for use with other objects, neither for Ext components. I think that dates can be easily cloned like this:



var d1 = new Date();
var d2 = new Date(d1);


or the Ext way ;):
http://extjs.com/docs/?class=Date&member=clone

dj
10 Aug 2008, 11:48 AM
@Saki, thanks for pointing that out. Actually I didn't want support or anything - just pointing out that it doesn't work.

Here's a clone function (modification of Sakis function) that also works for Dates and RegExps.


Ext.ux.clone = function(o){
if (!o || 'object' !== typeof o) {
return o;
}
if ('function' === typeof o.clone) {
return o.clone();
}
var c = 'function' === typeof o.pop ? [] : {};
var p, v;
for (p in o) {
if (o.hasOwnProperty(p)) {
v = o[p];
if (v && 'object' === typeof v) {
c[p] = Ext.ux.clone(v);
}
else {
c[p] = v;
}
}
}
return c;
}; // eo function clone

Ext.override(RegExp, {
clone: function(){
return new RegExp(this);
}
});
As Saki mentioned before - it is not for cloning Grids or so; just for config objects.

antimatter15
10 Aug 2008, 12:02 PM
A really easy way to clone arrays is array2 = array1.slice(0) :D

dj
10 Aug 2008, 12:29 PM
A really easy way to clone arrays is array2 = array1.slice(0) :D
that's a swallow copy - Ext.ux.clone does a deep copy.

mabello
19 Sep 2008, 3:06 AM
Hi there,
I apologize to have posted my code here, and I know that it's not correct, my prev post is out of date and I have created my own extension thread about cloning function that works for me so far, but I forgot to reply to the previous msg of DJ...
I'm really sorry Saki for my late answer.

captrespect
15 Oct 2008, 8:02 AM
Ext.ux.clone = function(o){
return Ext.util.JSON.decode(Ext.util.JSON.encode(o))
}


Win!

wm003
15 Oct 2008, 10:27 AM
Ext.ux.clone = function(o){
return Ext.util.JSON.decode(Ext.util.JSON.encode(o))
}
Win!

This does not work for Date Objects.

glenn
15 Jan 2009, 8:05 PM
Saki (et al) this is great!

I thought Ext.apply() did a deep copy until I started getting some weird problems and a quick check of the source code shows it only goes one level deep. Some warnings about this in the docs would be good.

I love your code, my only nitpick being the way that it checks for the object being an array by checking for the pop method, which of course isn't foolproof since an object could also have such a method.

Doug Crockford recommends another way which he attributes to Mark Miller of Google at:
http://blog.360.yahoo.com/blog-TBPekxc1dLNy5DOloPfzVvFIVOWMB0li?p=916

It looks like this:


Object.prototype.toString.apply(value) === '[object Array]'


where value is the object being tested.

I know the isArray functionality is hard to do reliably across all browsers, but if the Crock recommends it it's probably good to go.

Cheers

jsakalos
16 Jan 2009, 5:53 AM
To tell truth, I've borrowed array check from core Ext. Have you tested the above if it works cross-browser?

glenn
16 Jan 2009, 1:02 PM
Nothing wrong with borrowing code Saki - I do it from you all the time ;)

No I haven't tested cross browser, but there seem to be some respectable folks who claim to have done so:

http://www.nabble.com/fun-historic-facts-about-cross-frame-solutions-td21265995.html
http://thinkweb2.com/projects/prototype/instanceof-considered-harmful-or-how-to-write-a-robust-isarray/

It looks like Prototype.js has moved across to this technique recently.

Cheers,
Glenn

tryanDLS
16 Jan 2009, 1:10 PM
Ajaxian just had an article on array checking and alternate approaches
http://ajaxian.com/archives/isarray-why-is-it-so-bloody-hard-to-get-right

jsakalos
16 Jan 2009, 3:39 PM
Perfect! Nothing but trust 'em... ;)

jsakalos
16 Jan 2009, 3:41 PM
BTW, I've updated the code in the first post. Briefly tested in Firefox, just works.

mschwartz
28 Sep 2009, 11:21 AM
I had a need to make a deep copy of an object and stumbled onto this thread.

The code in the first post is nice and short, but it does not copy regex or date. A caveat to anyone else who finds this.

mystix
28 Sep 2009, 11:34 AM
I had a need to make a deep copy of an object and stumbled onto this thread.

The code in the first post is nice and short, but it does not copy regex or date. A caveat to anyone else who finds this.

you might want to dig into @hendricd's clone() method in the ext-basex extension.
iirc it handles both dates and regexes, though the code might be more involved than @saki's.

jsakalos
29 Sep 2009, 6:56 AM
Are you using the latest version?


/**
* Clone Function
* @param {Object/Array} o Object or array to clone
* @return {Object/Array} Deep clone of an object or an array
* @author Ing. Jozef Sakáloš
*/
Ext.ux.util.clone = function(o) {
if(!o || 'object' !== typeof o) {
return o;
}
if('function' === typeof o.clone) {
return o.clone();
}
var c = '[object Array]' === Object.prototype.toString.call(o) ? [] : {};
var p, v;
for(p in o) {
if(o.hasOwnProperty(p)) {
v = o[p];
if(v && 'object' === typeof v) {
c[p] = Ext.ux.util.clone(v);
}
else {
c[p] = v;
}
}
}
return c;
}; // eo function clone
typeof returns 'object' for both RegExp and Date, and both Date and RegExp have clone method so it should work.

mschwartz
29 Sep 2009, 9:07 AM
I guess I wasn't clear.

Your code relies on Ext, which adds a clone() function to Date. Otherwise it's quite useful to clone objects in environments not Ext. Like Rhino for shell scripts.

I'm not sure about regex clone method, since there's nothing in the API docs and I am too lazy right now to grep through the Ext sources.


Are you using the latest version?


/**
* Clone Function
* @param {Object/Array} o Object or array to clone
* @return {Object/Array} Deep clone of an object or an array
* @author Ing. Jozef Sakáloš
*/
Ext.ux.util.clone = function(o) {
if(!o || 'object' !== typeof o) {
return o;
}
if('function' === typeof o.clone) {
return o.clone();
}
var c = '[object Array]' === Object.prototype.toString.call(o) ? [] : {};
var p, v;
for(p in o) {
if(o.hasOwnProperty(p)) {
v = o[p];
if(v && 'object' === typeof v) {
c[p] = Ext.ux.util.clone(v);
}
else {
c[p] = v;
}
}
}
return c;
}; // eo function clone
typeof returns 'object' for both RegExp and Date, and both Date and RegExp have clone method so it should work.

jsakalos
30 Sep 2009, 2:11 AM
I was not thinking outside of Ext realm while writing this. It is Ext.ux and was written for Ext. For another libraries/non-Ext environment you're on your own.

prakashpaudel
3 Dec 2009, 12:33 AM
:( didn't worked for me. says too many recursions

jsakalos
3 Dec 2009, 12:37 AM
I use it in at least 50 places in my app so the problem must be in the argument you pass to it. Is the argument an instance of an Ext class? Mind that clone is for plain objects/arrays only - see the first post.

prakashpaudel
3 Dec 2009, 12:46 AM
:) Yea, I went through cursory so didn't noticed this was for plain JS objects and arrays.
I need to clone a Ext.Panel
Or simply I want to show the panel in a container, and on clicking on some button the same panel is displayed in a window too. How this can be done? any idea??

And thanks for your quick reply..

jsakalos
3 Dec 2009, 12:56 AM
Cloning a Panel is not a very good idea. Create another instance with same config instead.

mschwartz
3 Dec 2009, 6:41 AM
Cloning a Panel is not a very good idea. Create another instance with same config instead.

Factory!

prakashpaudel
4 Dec 2009, 3:01 AM
http://www.extjs.com/forum/showthread.php?t=20662

This is what my requirement.
Ext.ux.MaximizeTool :)

jsakalos
4 Dec 2009, 3:22 AM
How does it relate to clone function?

alexpotemkin
27 Apr 2010, 1:26 PM
in firebug
too much recursion
c[p] = Ext.ux.clone(v);
???
deepclone.js code from sample


Ext.ux.clone = function(o) {
if(!o || 'object' !== typeof o) {
return o;
}
if('function' === typeof o.clone) {
return o.clone();
}
var c = '[object Array]' === Object.prototype.toString.call(o) ? [] : {};
var p, v;
for(p in o) {
if(o.hasOwnProperty(p)) {
v = o[p];
if(v && 'object' === typeof v) {
c[p] = Ext.ux.clone(v);
}
else {
c[p] = v;
}
}
}
return c;
}; // eo function clone

Ext.override(RegExp, {
clone: function(){
return new RegExp(this);
}
});

jsakalos
27 Apr 2010, 1:38 PM
What are you trying to clone?

alexpotemkin
27 Apr 2010, 1:52 PM
Jaffa use clone in example for column model


// Cache the orignal column model, before state is applied
if(config.cm)
this.origColModel = Ext.ux.clone(config.cm.config);
else if(config.colModel)
this.origColModel = Ext.ux.clone(config.colModel.config);

i try use his example, but have problem
my implementation his example not work

jsakalos
27 Apr 2010, 2:06 PM
Read please the beginning of the thread. Cloning function is not for cloning Ext components/objects.

alexpotemkin
27 Apr 2010, 2:11 PM
How Jaffa use this function in examle? Example (multigroup) works perfectly.

jsakalos
27 Apr 2010, 2:17 PM
Well, I don't know how users use this function, anyway, the fact is that it is not suitable for cloning Ext components/objects.

dlgoodchild
2 Jul 2010, 1:14 AM
Hi guys,

What is this UX (or infact all UX's) licensed under?

jsakalos
2 Jul 2010, 2:42 AM
It depends on author. This is LGPL.

farmalay
5 Aug 2010, 10:22 AM
This implementation of clone function does not work if there is a circular reference in the object tree.
For example:

obj = { innerObj: { }};
obj.innerObj.circularRef = obj;

The following implementation fixes this issue:


Ext.ux.util.clone = function(obj) {
function doClone(o) {
if (!o || 'object' !== typeof o) {
return o;
}
if ('function' === typeof o.clone) {
return o.clone();
}

// Handle circular references: if object is already cloned, return the clone
if (o.hasOwnProperty('__clonedTo')) {
return o.__clonedTo;
}
var c = '[object Array]' === Object.prototype.toString.call(o) ? [] : {};
o.__clonedTo = c;
var p, v;
for (p in o) {
if ((p !== '__clonedTo') && o.hasOwnProperty(p)) {
v = o[p];
if (v && 'object' === typeof v) {
c[p] = doClone(v);
}
else {
c[p] = v;
}
}
}
return c;
}
function finalizeClone(o) {
if (o.hasOwnProperty('__clonedTo')) {
delete o.__clonedTo;
var p, v;
for (p in o) {
if (o.hasOwnProperty(p)) {
v = o[p];
if (v && 'object' === typeof v) {
finalizeClone(v);
}
}
}
}
}

var clone = doClone(obj);
finalizeClone(obj);
return clone;
};

jsakalos
5 Aug 2010, 11:15 AM
Thank you for the patch. However, I've never encountered such a problem; I have no use of circular references in simple objects.

farmalay
5 Aug 2010, 11:21 AM
Thank you for the patch. However, I've never encountered such a problem; I have no use of circular references in simple objects.

I hit the issue trying to clone Ext.grid.ColumnModel configuration:



var colConfig = Ext.ux.util.clone(grid.getColumnModel().columns);
return new Ext.grid.ColumnModel(colConfig)


Each column has a scope property which is a self reference.

jsakalos
5 Aug 2010, 12:28 PM
You MUST NOT clone Ext components with this function. It is for cloning simple (nested) arrays and objects. See the first post.

oceangravity
26 Oct 2013, 4:59 PM
Ext.ux.clone = function(o){
return Ext.decode(Ext.encode(o))
}