I know you can SET multiple css properties like so:
$('#element').css({property: value, property: value});
But how do I GET multiple properties with CSS?
Is there any solution at all?
jquery's css method (as of 1.9) says you can pass an array of property strings and it will return an object with key/value pairs.
eg:
$( elem ).css([ 'property1', 'property2', 'property3' ]);
http://api.jquery.com/css/
Easiest way? Drop the jQuery.
var e = document.getElementById('element');
var css = e.currentStyle || getComputedStyle(e);
// now access things like css.color, css.backgroundImage, etc.
You can create your own jQuery function to do this:
//create a jQuery function named `cssGet`
$.fn.cssGet = function (propertyArray) {
//create an output variable and limit this function to finding info for only the first element passed into the function
var output = {},
self = this.eq(0);
//iterate through the properties passed into the function and add them to the output variable
for (var i = 0, len = propertyArray.length; i < len; i++) {
output[propertyArray[i]] = this.css(propertyArray[i]);
}
return output;
};
Here is a demo: http://jsfiddle.net/6qfQx/1/ (check your console log to see the output)
This function requires an array to be passed in containing the CSS properties to look-up. Usage for this would be something like:
var elementProperties = $('#my-element').cssGet(['color', 'paddingTop', 'paddingLeft']);
console.log(elementProperties.color);//this will output the `color` CSS property for the selected element
Related
I am creating an object that stores various elements and their CSS properties.
The code I have now:
// My object
var cssStorage = {};
function store(element, cssProperty, value) {
// Initialize the (sub-)objects if they don't exist
cssStorage[element.id] = cssStorage[element] || {};
cssStorage[element.id][cssProperty] = cssStorage[element][cssProperty] || {};
// Set the cssProperty to equal the value
cssStorage[element.id][cssProperty] = value;
};
Example:
// My element
var box = document.getElementById("box");
// Let's call the function twice to save to properties
store(box, "display", "block");
store(box, "height", "74px");
Now my Object is populated like so:
cssStorage = {
box: { // <- box is the id of the HTML element <div id = "box"></div>
// The property-value pairs
display: "block",
height: "74px"
}
};
So now, if I type the code in the console:
return cssStorage.box.display; // Returns "block"
As you saw in the first block of code I posted, I used element.id as the element's unique identifier, to be able to use it as shown right above.
My problem is the dependency of my script upon element.id. Some elements of my DOM don't have an id and therefore the function is useless for these elements.
In essence, what I want to achieve is to call the function store when my element doesn't have an ID as follows:
// Some ways to get an element
var box = document.getElementsByClassName("boxes")[0];
var box = document.getElementsByTagName("div")[0];
var box = document.getElementsByName("jack")[0];
// It'll show an error, as the function uses 'element.id' and my element doesn't have one
store(box, "display", "block");
Is there a unique identifier for every node in the DOM?
Something that I could use as the name of:
cssStorage = {
[THE NAME]: {}
};
If not, how can I create a unique identifier for my elements, so that I can use the function as shown above without needing an id, class or other property that my element may not have?
You can easily coin a unique identifier for any element that doesn't yet have one:
var customIDprefix = "__myCustomPrefix__";
var customIDcntr = 0;
function getNextID() {
return customIDprefix + customIDCntr++;
}
And, then you can make sure any element you're using has a unique ID:
function checkID(elem) {
if (!elem.id) {
elem.id = getNextID();
}
}
If you're using ES6, you can also just use a WeakMap or Map object as your CSSStorage mechanism which let the DOM element itself be the key so you don't have to make a string key.
In that case, you'd just do this:
var cssStorage = new Map();
cssStorage[elem] = { // <- elem (your DOM element itself) becomes your key into the cssStorage
// The property-value pairs
display: "block",
height: "74px"
}
You could use an integer to handle a sequence and set the id to elements that does not have it, prefixing to avoid duplicates (for example 'myid' + idSequence++).
Please check if this works. Basically trying to clone the original element and assign it back to the original after adding id with random generator.
function store(element, cssProperty, value) {
if ( element.id == undefined ) {
var clonedElem = element.cloneNode(true);
clonedElem.id = Math.floor((Math.random() * 1000) + 1);
element = clonedElem;
}
// Initialize the (sub-)objects if they don't exist
cssStorage.[element.id] = cssStorage[element] || {};
cssStorage.[element.id][cssProperty] = cssStorage.[element][cssProperty] || {};
// Set the cssProperty to equal the value
cssStorage.[element.id][cssProperty] = value;
};
According to the documentation, to get a single attribute by name you can use .getAttribute() on a WebElement:
var myElement = element(by.id('myId'));
expect(myElement.getAttribute('myAttr')).toEqual('myValue');
But how can I get all of the attributes that an element has?
There is no information about this use case/functionality in the Protractor API.
You can expand javascript's Element type and add getAttributes() function:
Element.prototype.getAttributes = function() {
return (function (node) {
var attrs = {};
for (var i=0;i<node.length;i++) {
attrs[node.item(i).name] = node.item(i).value;
}
return attrs;
})(this.attributes);
};
demo
then you can test integrity of attributes using the same method you use for one attribute:
var myElement = element(by.id('myId'));
expect(myElement.getAttributes()).toEqual({'attr1': 'value1', 'attr1': 'value1', ... });
If your attributes that you need are prefixed with data you should be able to use the dataset for the element which will shrink your execute script by a bit:
browser.executeScript('return arguments[0].dataset;', elm).then(function (attrs) {
console.log(attrs);
});
Use executeScript() to execute a script that forms a list of attributes reading them from element.attributes (js part inside is taken from here):
var elm = element(by.id('runButton')).getWebElement();
browser.executeScript(
'var items = {}; \
for (index = 0; index < arguments[0].attributes.length; ++index) { \
items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value \
}; \
return items;', elm).then(function (attrs) {
console.log(attrs);
});
Here attrs would contain a dictionary/object of element attributes with keys as attribute names and values as attribute values.
Demo (using angularjs.org tutorial page, getting all attributes for a header):
$ node node_modules/protractor/bin/elementexplorer.js https://docs.angularjs.org/tutorial
Getting page at: https://docs.angularjs.org/tutorial
> var elm = element(by.tagName('header')).getWebElement();
> browser.executeScript('var items = {}; for (index = 0; index < arguments[0].attributes.length; ++index) { items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value }; return items;', elm).then(function (attrs) {
... console.log(attrs);
... });
{ class: 'header header-fixed', 'scroll-y-offset-element': '' }
Not really beautiful and compact, but works for me. Would be happy to see better alternatives.
UPDATE (an improvement to the approach above):
It would also work if I would define a regular function and pass it in:
function getAllAttributes (arguments) {
var items = {};
for (index = 0; index < arguments[0].attributes.length; ++index) {
items[arguments[0].attributes[index].name] = arguments[0].attributes[index].value;
}
return items;
}
browser.executeScript(getAllAttributes, elm).then(function (attrs) {
console.log(attrs);
});
You have to use browser.executeScript() function call instead of protractor API since Element.attributes is out of protractor API implementation:
var elem = element(by.id('runButton'));
browser.executeScript("return arguments[0].attributes", elem.getWebElement())
.then(function (attrs) {
console.log(attrs.length); // outputs numbers of attributes.
// access collection of Attr objects
console.log(attrs[0].isId); // outputs `true`
console.log(attrs[0].name); // outputs `id`
console.log(attrs[0].value); // outputs `runButton`
});
Remember that when saying attributes, it means a named map structure instead an array in the context of DOM model. Which means you have to use the NamedNodeMap to access collection of Attr objects.
It works as the same way as that in #alecxe's answer without the iteration part.
It seems complicated for me.
First, I have this list:
liste_path_categories.push(
{ index: null
, letter: "letter1"
, type: key
, picture_url: "url1"
, id_categ: null
, response: "Answer here"
});
What I want is to extract from this big list an object in this form:
data["String1"]["String2"]= String3
With :
String1=list_path_categories[i].letter
String2=list_path_categories[i].id_categ
String3=list_path_categories[i].response
example:
data['A']['12'] : "A_answer"
To declare the data i make this:
var data = new Object(new Object);
How I can set all the values in data?
You can use the Array.forEach method to iterate through liste_path_categories and construct your data object.
Example:
var liste_path_categories = [];
var data = {};
liste_path_categories.push(...);
...
liste_path_categories.push(...);
liste_path_categories.forEach(function(element) {
data[element.letter] = {};
data[element.letter][element.id_categ] = element.response;
});
jsFiddle example : http://jsfiddle.net/3ZvNf/
Your question is pretty vague but do you mean something like this?
Setting a dynamic property in an object wich belongs to another object?
data['A']['12'].answer = "A_answer"
Instead of using strings, you have to use the variables in your property access:
var data = {};
if (!data[String1]) {
data[String1] = {}; // make sure that data[String1] exists and is an object
}
data[String1][String2] = String3;
If you want to do this for elements in the array, you have to iterate over the array.
P.S.: I recommend to use more expressive variable names than StringX.
first create the constructor (in OOP terminology):
var ctor_object = function(letter,id_categ,response)
{
this.letter = letter;
this.id_cated = id_categ;
this.response = response;
}
(in genereal you should omit the ctor_ syntax and name it directly after the name of the class of your object)
then use your constructor upon your list of categories:
var length = liste_path_categories.length,
element = null;
for (var i = 0; i < length; i++)
{
element = liste_path_categories[i];
my_obj = new ctor_object(element.letter,element.id_categ,element.reponse)
// Do something with my_obj
}
What is the cleanest way to put the source attribute string of all images within a div into an array?
I was hoping this would work -
var imageSourceArray = $("#leDiv img").attr('src');
alert(imageSourceArray[3]); //not alerting the source, boo hoo.
Do I need to loop through $("#leDiv img") and add each src string to an array individually? Or is there a more elegant way to do this?
You can use jQuery's map function which is described as:
Pass each element in the current matched set through a function, producing a new jQuery object containing the return values.
For your example:
var mySources = $('#leDiv img').map(function() {
return $(this).attr('src');
}).get();
Edit: Far more elegant solution, there's obviously still some looping involved internally:
var img_sources = $('#leDiv img').map(function(){ return $(this).attr('src') });
You will in fact need to loop over the collection and add sources individually.
var img_sources = [];
$('#leDiv img').each(function(i,e){
img_sources.push($(e).attr('src'))
})
Some background: jQuery.fn.attr() maps to jQuery.access() internally, the key part of which looks like this:
function( elems, key, value, exec, fn, pass ) {
var length = elems.length;
// setter functions omitted here …
// Getting an attribute
return length ? fn( elems[0], key ) : undefined;
}
Note the elems[0] part – only the first item in the collection is fed to the subsequent callback function (jQuery.attr() in fact) responsible for extracting the information.
var imageSourceArray = [];
$('#leDiv img').each(function(){
var src = $(this).attr("src");
imageSourceArray.push(src);
});
alert(imageSourceArray[3]);
you already have the src in a collection when you fetch the the images. It may be more efficient to not store the src attributes in another array:
$('#leDiv img').each(function(i,e){
var dosomethingwith = $(e).attr('src');
})
or you could do:
var ImageCol = $('#leDiv img');
alert(ImageCol[3].attr('src'));
I've a function that takes an object as a parameter, and uses the structure of the object to create nested DOM nodes, but I receive the following error:
http://new.app/:75NOT_FOUND_ERR: DOM Exception 8: An attempt was made to reference a Node in a context where it does not exist.
What I would like my function to do, is, when supplied with a suitable object as a parameter, example:
var nodes = {
tweet: {
children: {
screen_name: {
tag: "h2"
},
text: {
tag: "p"
}
},
tag: "article"
}
};
It would create the following DOM nodes:
<article>
<h2></h2>
<p></p>
</article>
Here is my attempt so far:
function create(obj) {
for(i in obj){
var tmp = document.createElement(obj[i].tag);
if(obj[i].children) {
tmp.appendChild(create(obj[i].children)); /* error */
};
document.getElementById("tweets").appendChild(tmp);
};
};
I'm already struggling!
Ideally I'd like to eventually add more child key's to each object, not just tag, but also id, innerHTML, class etc.
Any hel would be much appreciated, though please: I'm sure a framework or library could do this for me in just a few lines of code, or something similar, but I'd prefer not to use one for this particular project.
If you could briefly explain your answers too it'd really help me learn how this all works, and where I went wrong!
Thank you!
NB: I've changed and marked the line in my function that the error message is talking about.
I changed it from:
mp.appendChild(obj[i].children);
to:
mp.appendChild(create(obj[i].children));
This is because I want any nested keys in the children object to also be created, so screen_name had a children key, they too would be created. Sorry, I hope you can understand this!
I'm looking at http://jsperf.com/create-nested-dom-structure for some pointers, this may help you too!
Your "create" function is going to have to be written recursively.
To create a node from your data (in general), you need to:
Find the "tag" property and create a new element
Give the element the "id" value of the element (taken from the data)
For each element in "children", make a node and append it
Thus:
function create(elementDescription) {
var nodes = [];
for (var n in elementDescription) {
if (!elementDescription.hasOwnProperty(n)) continue;
var elem = elementDescription[n];
var node = document.createElement(elem.tag);
node.id = n; // optional step
var cnodes = create(elem.children);
for (var c = 0; c < cnodes.length; ++c)
node.appendChild(cnodes[c]);
nodes.push(node);
}
return nodes;
}
That will return an array of document elements created from the original "specification" object. Thus from your example, you'd call:
var createdNodes = create(nodes);
and "createdNodes" would be an array of one element, an <article> tag with id "tweets". That element would have two children, an <h2> tag with id "screen_name" and a <p> tag with id "text". (Now that I think of it, you might want to skip the "id" assignment unless the node description has an explicit "id" entry, or something.)
Thus if you have a <div> in your page called "tweets" (to use your example, though if so you'd definitely want to cut out the "id" setting part of my function), you'd add the results like this:
var createdNodes = create(nodes), tweets = document.getElementById('tweets');
for (var eindex = 0; eindex < createdNodes.length; ++eindex)
tweets.appendChild(createdNodes[eindex]);
I added a function appendList that accepts a list of elements, and the container to append to. I removed the append to "tweets" part out of the create function to more effectively separate your code.
function create(obj) {
var els = [];
for(i in obj){
var tmp = document.createElement(obj[i].tag);
var children;
if(children = obj[i].children) {
var childEls = create(children);
appendList(childEls, tmp);
}
els.push(tmp);
};
return els;
};
function appendList(list, container){
for(var i = 0, el; el = list[i]; i++){
container.appendChild(el);
}
};
// gets an array of root elements populated with children
var els = create(nodes);
// appends the array to "tweets"
appendList(els, document.getElementById("tweets"));
Building on the previous answer:
I think you still need to create the element you're trying to append:
tmp.appendChild(children[prop].tag);
should be
tmp.appendChild(document.createElement(children[prop].tag));
function create(obj) {
for(i in obj){
var tmp = document.createElement(obj[i].tag);
var children;
if(children = obj[i].children) {
for(var prop in children)
tmp.appendChild(document.createElement(children[prop].tag));
}
document.getElementById("tweets").appendChild(tmp);
};
};