Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Using a jQuery clone outside the DOM

I've been working on a small project where I'm using the jQuery .clone() method. Pitfall with this is using it on HTML that has unique identifiers. So I went on implementing getComputedStyle to find the style properties of the original unique elements, in order to copy it to the clone and give that an new id afterwards (yes, it can give performance issues but it's experimental).

According to the jQuery spec, doing this after cloning but before appending will make the manipulation happen outside of the DOM (so no id 'violation' will occur). But I noticed some strange behaviour across browsers when I try to find style properties of the elements after the object was cloned. Before it, all browsers return the same values but after being cloned :

  • Firefox - carefree, and interestingly the clone's computed style is the actual CSS value rather than computed data (in pixels).

  • IE - seems to work but value is not necessarily correct.

  • Chrome - does not compute. Here's an example :

http://codepen.io/anon/pen/zxqmNK?editors=011

var elements = [];
var objects = [];

$('body').find('[id]').each(function() {
    elements.push(this);
});

$('body').clone().find('[id]').each(function() {
    objects.push(this);
});

$.each(elements, function(key, element) {
    var current = window.getComputedStyle(element, null).getPropertyValue('width');
    $('#log').append('<p>' + element.id + ': ' + current + '</p>');
});

$('#log').append('</br>');

$.each(objects, function(count, object) {
    var current = window.getComputedStyle(object, null).getPropertyValue('width');
    $('#log').append('<p>' + object.id + ': ' + current + '</p>');
});

Anybody know if this is a bug or has similar behaviour been seen before? Not a lot to go on web-wise (not even Stackoverflow). Thanks in advance for any insight.

Edit - did some more testing and it looks like IE behaves the same as Chrome. Only instead of not returning anything, everything is set to 'auto'. If the style of the cloned objects is accessed by using .css(), all values return 0px (including properties like background). Seems only Mozilla treats the cloned object as if any style has been applied to it at all.

like image 606
Shikkediel Avatar asked Dec 15 '14 10:12

Shikkediel


People also ask

How do you clone an object in jQuery?

Syntax: // Create a clone of the object using the extend() method let newObj = jQuery. extend({}, obj); // Create a deep clone of the object using the deep parameter let newDeepObj = jQuery. extend(true, {}, obj);

How does jQuery clone work?

The . clone() method performs a deep copy of the set of matched elements, meaning that it copies the matched elements as well as all of their descendant elements and text nodes.

How do you clone an element in Javascript?

You call the cloneNode() method on the element you want to copy. If you want to also copy elements nested inside it, pass in true as an argument. // Get the element var elem = document. querySelector('#elem1'); // Create a copy of it var clone = elem.

How do I copy a div?

First, select the div element which need to be copy into another div element. Select the target element where div element is copied. Use the append() method to copy the element as its child.


1 Answers

First approach

Here's how I solved it initially... treating Mozilla differently is tempting but that would require browser sniffing so we'll work around not being able to access the clone's style.

Creating two arrays of the objects that have unique identifiers - first one will contain the elements to copy the style from and to second holds the cloned elements to which the style will be transferred :

var individual = [], singular = [];

$('.target').find('[id]').each(function() {

    individual.push(this);
})
.end().clone().find('[id]').each(function() {

    singular.push(this);
});

Now the properties and their values are copied from the array of objects that were stored inside the DOM to the clones - after that the name of the current identifier is changed to something unique :

$.each(individual, function(key) {

    var tag = this.id,
    styles = window.getComputedStyle(this, null),
    element = singular[key];

    $.each(styles, function() {

        var value = styles.getPropertyValue(this);
        $(element).css(this, value);
    });

    $(element).attr('id', tag + '-cloned');
});

The cloned item is inserted after this, so no double identifiers are ever present. Note that this may produce a lot of style properties (about 220 for each object in Firefox for example).

Demo

var individual = [], singular = [];

$('.target').find('[id]').each(function() {

    individual.push(this);
})
.end().clone().find('[id]').each(function() {

    singular.push(this);
})
.end().queue(function() {

    transFigure();
    $(this).dequeue();
})
.appendTo('body');

function transFigure() {

$.each(individual, function(key) {

    var tag = this.id,
    styles = window.getComputedStyle(this, null),
    element = singular[key];

    $.each(styles, function() {

        var value = styles.getPropertyValue(this);
        $(element).css(this, value);
    });

    $(element).attr('id', tag + '-cloned');
});
}

+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Second approach

Even though the above works fine, it is not very efficient and on page resize values may start to differ. So I have found a better solution after coming across and then doing some digging around on JavaScript's cssRules. With this you can actually access all style sheets directly!

Below is a pen that tries to explain the process but it comes down to matching (with .test) the unique identifiers within the clone against cssText found inside the stylesheet. Then changing the id and storing it within an array, for it to later be inserted/added to the stylesheet itself.

Pen

Besides a more efficient approach (no transferring all the default values), the actual CSS is copied for all browsers instead of the computed value. And any derivatives like img, p and such can also be included. It even copies @rules and keeps responsiveness intact this way.

The essence of it :

var singular = [], rules = [];

$('#target').clone().find('[id]').each(function() {

    singular.push(this);
});

var sheet = document.styleSheets[0],
styles = sheet.cssRules;

$.each(singular, function() {

    var selector = '#' + this.id,
    pattern = new RegExp(selector + '(:| |,)');

    $.each(styles, function() {

        var string = this.cssText;

        if (pattern.test(string)) {
        var rule = string.replace(selector, selector + '-duplicate');
        rules.push(rule);
        }
    });
});

$.each(rules, function() {

    var index = styles.length;
    sheet.insertRule(this, index);
});

After this the clone can be inserted to the DOM, with all unique identifiers and full style being applied. Note that in the above example this hasn't been actually done to keep the code as readable as possible when it comes to using cssRules. The image has been put inside the markup beforehand with a different id - one that will match the copied style rules.

like image 182
Shikkediel Avatar answered Nov 10 '22 01:11

Shikkediel