Using a jQuery clone outside the DOM
Asked Answered
K

1

8

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.

Kaluga answered 15/12, 2014 at 10:7 Comment(3)
Not a direct answer, but why would you want to work with getComputedStyle this much? Wouldn't it be easier and way more predictable to style your elements using classes instead of having to find out CSS styles your way?Ashy
I guess this may help youHeartwhole
Thanks, anpsmn. I can't believe I didn't find that one. I had indeed decided to go the way Felix Kling suggests there (copy style from the original objects). Although the way Mozilla handles it would be quite wonderful if it were cross browser available. Matijs, you are right of course but the code this is extracted from is meant as a small plugin (magnifier), where I don't want to force people to have to change their file structure. I'll recommend using classes of course. Cheers for the replies.Kaluga
K
1

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.

Kaluga answered 16/12, 2014 at 21:11 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.