Sample of what I am talking about: http://jsfiddle.net/bsnxp/1/
If you check the source .show().clone() display is inline-block (what it should be) and .clone().show() is display: block (not what it should be).
jQuery .show documentation (http://api.jquery.com/show/) says "This is roughly equivalent to calling .css('display', 'block'), except that the display property is restored to whatever it was initially".
Is there a reason that cloning removes from memory what the original CSS display property was? Is there a better way to bypass this than using .show().clone() then .hide()?
.clone without any arguments doesn't copy the element's data - which includes information about what the display was initially.
Use .clone(true) to clone the data as well.
Documentation for .clone() http://api.jquery.com/clone/
Related
I'm creating a plugin for CKEditor by tweaking the existing 'Image' plugin so it better suits our purpose.
An issue with it is that it tends to mess around with the existing attributes if you edit an image. For example, anything with a left or right 'align=' attribute gets changed to a 'float:' style after the image has been edited, even if you don't make any changes. Also, anything set to 'auto' is also removed.
With normal html, this isn't a problem but we are creating responsive emails and these attributes are needed to get them to display correctly in Outlook.
I'm trying to make it keep certain attributes based on their original values which are working well. However, I've run into an issue with the 'margin' attribute.
An image in one of our email templates has the margin: style attribute set to '0 auto'. CKEditor seems to process all the Html into a DOM structure with added IDs which it stores and works from - however by the time this value has been processed and included in this internal DOM, it's been changed into
margin: "0px"
margin-top: "0px"
margin-bottom: "0px"
margin-left: "0px"
margin-right: "0px"
with no sign of the original 'auto' value.
Is there any way to access the original un-DOMed HTML string of the element so I can retrieve the original style property?
I've poked about inside both the dialog and the CKEDITOR object; I've found that the CKEDITOR object does store the 'raw' DOM structure itself with the unprocessed style attribute, however short of some hideously complicated traversal of this I can't find a way of referencing back to the element within this unprocessed DOM.
Found it!
The Dialog object holds an object called 'cleanImageElement'. This contains the unmolested style attribute from where I can read the style tag I need.
It's said by this article that one of the important reasons for HTML properties to be reflected back to the DOM is because CSS selectors rely on attributes, but why's that? This could be done without the reflection based on the spec.
For people who don't know what I'm talking about, read below:
In browsers, CSS selectors rely on attributes to work.
#myButton[someAttribute] {
opacity: 0.5;
font-weight: bold
}
So in our JavaScript if we change the property of an element, eventually we have to reflect it to the HTML DOM as well like this:
// we have changed some property
myButton.someAttribute= true;
// but this is not adequate, we need to reflect as well
myButton.setAttribute('someAttribute', '');
so we get this:
<button id="myButton" someAttribute></button>
not this non-reflected button:
<button id="myButton"></button>
Not all DOM properties map to attributes. The ones that do reflect to and from attributes, do so to maintain parity with the document language — in this case, HTML, which only has a concept of attributes, which as you've correctly pointed out is relied on by Selectors.
If attribute selectors mapped directly to DOM properties without the DOM discriminating between attribute properties and other kinds of properties, then attribute selectors such as the following would match, even though none of these exist as attributes in HTML:
[classList]
[className]
[dataset]
[offsetLeft]
[offsetTop]
[offsetWidth]
[offsetHeight]
... as well as [someAttribute] matching elements with your non-existent someAttribute as a property even when you don't reflect it with setAttribute().
In fact, this is exactly why label[htmlFor] incorrectly matches label[for] elements in Internet Explorer 7, even though the for attribute in HTML is simply called for, not htmlFor — the DOM uses htmlFor to make up for the fact that for is a reserved word in many languages including JavaScript, the main DOM scripting language, preventing it from being used as a property ident.
DOM attributes and properties are not equivalent, but they're related.
Attributes are intended to be used to initialize DOM properties. When the HTML is parsed, all the attributes are used to initialize the corresponding DOM properties. If you later modify an attribute with setAttribute or removeAttribute, the corresponding property is also updated (similar to reloading the HTML with the new attribute).
But it doesn't go the other way. Updating a property doesn't change the corresponding attribute. This is why you can assign to the .value of an input, and see this reflected in the browser display, but when you look at the element in Developer Tools you still see the original value="whatever" attribute. In some cases this has special benefits -- when you click on the Reset button of a form, it resets all the value properties from their value attributes.
IMHO; Some attributes have a 1:1 mapping with their respective properties in the DOM. The reflection is automatically made for common attributes like id. You can also define your own attributes (your HTML will be considered invalid, but you can use the doctype to validate them). My guess is that due to this uncertainty created by rogue attributes. They preferred to map only the attribute:property which has predictable behaviour and usage. You can still use your custom attributes in your css but you're in manual mode. You got to keep your s**t together and reflect them yourself. This far west(freedom) mentality is one the things that made web tech so popular and easy to use. You can do things as you see fit. I do not recommend it for maintainability reasons but you could.
Your example uses a button, but the article is using the disabled property but with something other than a button. On a button, the browser will automatically reflect changes to the disabled property onto the attribute, and vice versa, but this doesn't happen with all elements. Change your example to use a div and you'll see that you'd need to manually coordinate the two if desired.
Or for custom attributes, use data- attributes instead. If you delete the property from my_element.dataset, I'm pretty sure the attribute will be deleted too.
This is to keep the HTML and DOM synchronized, because at some point CSS selectors will be checking the DOM element and relying on the attributes to be accurate.
If the DOM isn't accurate, then the CSS won't be accurate either. What if HTML didn't bother to reflect attributes back to the DOM?
Let's say the text of an input field is initially black, and you want the text to be red when it is disabled. Now let's say the user did something and a function you wrote disabled the input field through javascript.
If HTML didn't reflect that 'disabled' attribute back to the DOM, CSS would NEVER KNOW that the element was disabled.
So the text color would never be changed to red. Remember, CSS checks and relies on DOM attributes. If HTML doesn't change the DOM attributes, for all CSS cares about, nothing has changed so everything will remain the same.
For a less technical analogy, let's say CSS is Batman, HTML is Gotham Police Department, an Attribute is the bat-signal, and the DOM is the sky.
Batman(css) constantly checks the sky(dom) to see if his bat-signal light(attribute) is being shown by the Gotham Police Department(html). If there was some event(an attribute changed) which happened in Gotham where the Gotham Police Department(html) needed Batman(css) to help, but they just didn't bother to send him an update through the sky(dom) with the bat-signal(attribute update), Batman would never know there was a job that needs to be done.
I mean he's an awesome super hero so he would eventually find out but sadly, CSS is no Batman.
The article speaks about custom elements, and takes the example of a <div> element with it's natural behaviour for some properties like hidden or disabled.
So, first of all, don't take the sentence you mention as a directive from your god, because it's not.
Simply, if you have an application with some css using the disasbled property for specific styling, be aware that, if you want to :
create some custom elements
manipulate their attributes through Javascript, including disasbled
see the css applied for disasbled property of custom elements you are manipulating
Then, yes, you'll need to reflect back to DOM
Well, this is the first question I'm answering but I'll try either way.
To be honest, it's kinda hard to tell what you're asking but if you're looking to reflect HTMLElement property changes back on the DOM (via attributes). Then here's the code (using HTMLElement's):
// Defines a new property on an Object.
Object.defineProperty(HTMLElement.prototype, "someAttribute", {
// Configurable
configurable: true,
// Enumerable
enumerable: true,
/* Getter
(Allows you get the value like this =>
element.someAttribute // returns the value of "someAttribute"
)
*/
get: function() {
return this.getAttribute("someAttribute")
},
/* Setter
(Allows you to modify/ update the value like this =>
element.someAttribute = "lorem ipsum"
)
*/
set: function(data) {
this.setAttribute("someAttribute", data)
}
})
Hope this answered your question.
I want to get CSS selector of jQuery items set.
By jQuery items set in this case I mean jQuery set created basing on some css selector and extended by some jQuery functions like .children etc like $(".basic-selector").children();
It's easy to get for simple scenario like direct selection:
$("div.someclaass").selector //works ok - gives div.someclass
But I also need to get css selector for more complex scenarios (and here jQuery is not returning valid css selector)
$("div.someclass").children("p").selector //schould give div.someclass > p
And for more comples scenarios like
$("ul, ol").children("li").selector //should give ul > li, ol >li
Is it possible?
Requirements:
It must be valid css selector
I cant change js code of creating sets
It should work for all this kind of jQuery functions that can have
css selector - like children, find, next, nextAll, .filter (with css param) etc.
There are some functions like .prev or .parent that will not have css selector - I'm aware of that.
Why do I need that? - I've got my plugin. It takes some jQuery set that was created basing on selector and executes some function on those items. But also - function needs to be executed in the future on new items added later that would match the set if they would exisit in the time of set creation code execution. For good understanding please check plugin page.
The .selector property is deprecated and can only be used when the jQuery element set was created by passing a selector string directly. As you have seen, .selector stops representing a valid selector altogether once you start chaining traversal or filtering methods from the original set. Judging from the sort of values that .selector can have it would seem that it was simply not intended for anything other than internal use.
But the real problem here is that not all element sets can be represented with a valid CSS selector (not without enumerating all possible :nth-child() permutations anyway), not the least of which is because many of jQuery's non-standard selectors have no standard equivalents. Even if you limit yourself to standard selectors and traversal methods that can be represented with a selector, if the original element set was obtained through some other means, or if it was obtained normally and then tampered with, you won't be able to detect this, not even using .selector which as I mentioned is deprecated anyway.
If you really need a selector string to match your selection, the easiest way is to just write it manually and store it in a separate variable.
jQuery (all versions tested up through 2.1.0) allows me to call .val("some value") on a DIV object to set a value on the DIV. It is not displayed and doesn't show up as an HTML5 data property in Chrome Developer Tools. And yet I can fetch the result later with a call to .val().
For example (from http://jsfiddle.net/X2nr6/ ):
HTML:
<div id="mydiv" style="display: none;">Some text</div>
<div id="debug"></div>
Javascript:
$('#mydiv').val('A value attached .');
$('#debug').text( $('#mydiv').val() );
Displayed result:
A value attached.
Where is the value stored? Not knowing where it is stored makes me worry that I am relying on a hack.
jQuery is just assigning to a value property on the div object (the HTMLDivElement instance for that div), even though it doesn't normally have one. Creating new properties on elements is allowed in every browser I've ever seen, so it works. I wouldn't use val with divs on a regular basis, though.
Here's a non-jQuery example:
var div = document.createElement('div');
console.log('value' in div); // false, divs don't normally have a value property
div.value = 42;
console.log('value' in div); // true, we've created a property on the element
console.log(div.value); // 42
Or the same sort of thing using jQuery:
var $div = $("<div>");
display(typeof $div.prop('value'));
$div.val(42);
display(typeof $div.prop('value'));
display($div.prop('value'));
This business of creating new, custom, non-standard properties on elements is called creating "expando" properties. They can be very handy. (jQuery uses them internally, for instance, to manage the data cache and a few other things — if you look closely at a DOM element you've set data on using data, you'll see a property with a name like jQuery1110028597884019836783; that's the key jQuery uses to find the element's data in jQuery's internal data cache. jQuery doesn't store the data in an expando on the element, because of IE garbage collection issues; it stores the key there, and the data in a JavaScript object.)
It stores it on a value property on the DOM object. You can see if by running your code and then inspecting the element in a DOM inspector. In Chrome, the value property will be listed under div#mydiv in the properties tab.
HTMLDivElement objects don't officially support such a property, so you are relying on a hack.
Use data() to store arbitrary data on an element.
$('#mydiv').data("myCustomValue", 'A value attached .');
Although the above answers are accurate, I'd like to complete something out.
jQuery is designed around the concept of wrapping all HTML elements in the jQuery object. That jQuery object happens to be an array that can hold more than one element.
jQuery also goes out of its way to hide this fact from you so that the average jQuery developer never has to worry about exactly what he has -- simply call the right method and the magic happens.
(You see this if you do a $(".someClassYouHaveLotsOf").hide() or $(".someClassYouHaveNoneOf").hide()`.)
jQuery's val() method is just a wrapper for accessing an HTML input element's value property. Since jQuery doesn't throw errors unless there is really no way what-so-ever, it silently helps you by accessing the value property on whatever HTML element it happens to have. div span or whatever.
In most browsers, this works -- mostly enough.
If you are really interested in setting values on HTML elements for use later, the data() method is far better suited. Straight HTML would use <element>.setAttribute("data-key", "value");
And that is about the only time you'll see me using the HTML attributes over properties, BTW.
Insofar as I can tell, the following code should work, creating a <div> element, and then creating a <p> element; the expression should result in a jQuery object with two elements:
$("<div>first element's content</div>").after("<p>second element's content</p>");
However, what I get is very different. The documentation (see the heading "Inserting Disconnected DOM Nodes") tells me the above code should result in a jQuery object, grabbing both HTML snippets and building the two DOM elements. But, what I've gotten, in several different versions of jQuery, all above 1.4, is a jQuery object with only 1 node. However, the following code works just fine, returning (what I believe is) the correct jQuery object, two elements inside:
$("<div></div>").after("<p>second element's content</p>");
And this example works as well:
$("<div></div>").after("<p>second element's content</p>").after("<p>third element's content</p>");
It seems the .after() method works fine if the first DOM node being created is empty, but does not when it is not (irrespective of the contents of subsequent DOM nodes being appended to it).
Am I missing something about jQuery's internals, quirky DOM issues and/or JavaScript peculiarities, or is this simply a jQuery bug that's persisted from version 1.4 on through 1.7?
(Here's a meager JSFiddle demonstrating the issue pretty plainly.)
This was a known bug in jQuery < 1.9. See http://bugs.jquery.com/ticket/8759
In jQuery >= 1.9, the following information from the upgrade guide should be noted:
Prior to 1.9, .after(), .before(), and .replaceWith() would attempt to add or change nodes in the current jQuery set if the first node in the set was not connected to a document, and in those cases return a new jQuery set rather than the original set. This created several inconsistencies and outright bugs--the method might or might not return a new result depending on its arguments! As of 1.9, these methods always return the original unmodified set and attempting to use .after(), .before(), or .replaceWith() on a node without a parent has no effect--that is, neither the set or the nodes it contains are changed.
Use add() to add objects to the collection. I use after() more in DOM elements that already exist or that are cached in a variable, but most of the time, if you work with dynamic markup is more practical to use the equivalent insertAfter().
$("<div>first element's content</div>").add("<p>second element's content</p>");
EDIT:
This works...
var $el = $('<div/>', {
text: 'hey there'
}).after('<p>Lorem</p>');
I found I was still sometimes having issues with .add() in place of .after(), so another easy way to get around this bug is to make a throw away wrapper element, .append() the sibling elements, and use .html() to just get the inner contents of the wrapper.
Example:
$('body').append(
$('<span/>').append(
$("<div>first element's content</div>")
).append(
$("<p>second element's content</p>")
).html()
);
This will add the <div> and <p> but discard the outer <span>. I have found this usually works fine with .append() but can have problems with .appendTo()
In jQuery 1.12.4, I found out that using a class selector instead of an ID selector solves this issue.
If you are struggling with
$("#myelement1").after("<p>test</p>"),
add a unique class to your element and try this:
$(".myelement1").after("<p>test</p>")