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;
};
Related
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
I set up a search function for a content system.
The user is able to publish and unpublish his elements.
Every element has two keywords + the condition state = 3 keywords for the search in sum.
Every element has an attribute namend "data-meta" in which the three keywords are stored.
eg.
data-meta="banana blue public"
How can i edit the last value "public", if the user wants to unpublish his element and set it to private?
Without actually altering how you store these values, such as creating custom attributes like
data-keyone="banana" data-keytwo="blue" data-state="public"
you can pull the value of the attribute, split it, modify the third element, join it, then set the attribute value to the new string.
Starting with this:
<myelement id="example" data-meta="banana blue public">
Pull the value:
var oElem = document.getElementById("example");
var strTemp = oElem.getAttribute("data-meta"); //javascript
var strTemp = $('myelement#example').attr('data-meta'); // jquery
Split it:
var astrVals = strTemp.split(" ");
Modify the third value:
astrVals[2] = "private";
Rejoin it:
strTemp = astrVals.join(" ");
Then set the value again:
$('myelement#example').attr('data-meta', strTemp); //jquery
oElem.setAttribute("data-meta", strTemp); // javascript
Edit with jQuery not with javascript:
First:
$('#anyID').removeAttr("meta-data")
After:
$('#anyID').attr("data-meta", "banana blue private")
I've made a few small functions that can make toggle like functionality.
The first one takes the string from attribute and splits it by spaces to make an array, the next one looks at the array to see if it contains what you want to toggle. Third and forth add and remove from the array and then join the array back together as a string with spaces.
I haven't added any error handling so if you try and remove an attribute that isn't there it will have issues but other than that is should get you started.
// take contents of attribute and return an array
function arrFromAttr(selector, attribute) {
return selector.getAttribute(attribute).split(" ");
}
// check the array and toggle the value
function toggleAttr(selector, attribute, value) {
let attrs = arrFromAttr(selector, attribute);
if (attrs.includes(value)) {
removeAttr(selector, attribute, value)
} else {
addAttr(selector, attribute, value)
}
}
// add to the array and set the attribute
function addAttr(selector, attribute, value) {
let attrs = arrFromAttr(selector, attribute);
attrs.push(value);
selector.setAttribute(attribute, attrs.join(' '));
}
// remove from the array and set the attribute
function removeAttr(selector, attribute, value) {
let attrs = arrFromAttr(selector, attribute);
attrs.splice(attrs.indexOf(value), 1);
selector.setAttribute(attribute, attrs.join(' '));
}
// toggle the attribute on button click
document.querySelector('button').addEventListener('click', () => {
toggleAttr(document.querySelector('[data-meta]'), "data-meta", "public");
})
div[data-meta]:after {
content: attr(data-meta)
}
<div data-meta="banana blue public"></div>
<button>Toggle public</button>
I hope you find this helpful 🙂
If you're feeling fancy you can have your own polyfill you make a toggle function, though I wouldn't recommend doing something like this until you're confident in JS
if (!(Element.prototype.toggleAttribute || Element.prototype.deleteAttribute || Element.prototype.addAttribute)) {
Element.prototype.toggleAttribute = function(name, value) {
let attrs = this.getAttribute(name).split(" ");
if (attrs.includes(value)) {
this.deleteAttribute(name, value)
} else {
this.addAttribute(name, value);
}
}
Element.prototype.addAttribute = function(name, value) {
let attrs = this.getAttribute(name).split(" ");
attrs.push(value);
this.setAttribute(name, attrs.join(' '));
}
Element.prototype.deleteAttribute = function(name, value) {
let attrs = this.getAttribute(name).split(" ");
attrs.splice(attrs.indexOf(value), 1);
this.setAttribute(name, attrs.join(' '));
}
}
document.querySelector('button').addEventListener('click', () => {
document.querySelector('div').toggleAttribute('data-meta', 'public');
})
div[data-meta]:after {
content: attr(data-meta)
}
<div data-meta="banana blue public"></div>
<button>Toggle public</button>
This code adds the functions to the native code so you can call the functions on a element rather than having to pass the element as an arguement.
At the moment, I have a lot of duplicate functions, that only differ by an element name they're concatenated to.
Because they're designed in the Page Object Model pattern, they follow this structure:
Declaring elements:
module.exports = {
commands: [fixtureCommands],
elements: {
navbarPreferences: '#preferences',
}
};
And then consequently, I can reference the element in the test itself (using the # symbol preceding the element's name:
this.click('#navbarPreferences')
Currently - I have this function that concatenates a variable with text. The text is actually in an element that has been declared below it (as shown above in my example)
function: function (name) {
var i;
for (i = 0; i < names.length; i++) {
var elementName = '#preferences-' + strippedName;
this.click(elementName);
}
return this.api;
},
However, how can I reference an element (like I can in the above example) and concatenate it with another variable.
var elementName = '#navbarPreferences' + strippedName;
The above prints the actual text out, not the element itself.
Many thanks.
you can try adding another declaration structure say "props" with your variable in it. than you can call the variable like this: this.props.your_var
example:
props: {
navbarPreferences: '#preferences',
},
elements: {
someElement: '#some-element'
},
var elementName = this.props.navbarPreferences + strippedName;
Firstly, I'm trying to create multiple DOM nodes and cache them as a variable for use in a function. What I want to do is create a function that sets-up the elements by classname. Then call that function as variable for use later.
Secondly, I'm not sure what the correct syntax is when manipulating inserted nodes via classname, when you want to select all classes with that name.
i.e for (var i = 0; i < insertedNodes.length; i++) {
To clarify what exactly I'm asking, my questions are this:
How to insert nodes as variables for use later on in function.
How to call those each of those variables.
How to call both of those variables together.
Hopefully my code will help explain what I'm trying to understand a little further:
var div1 = document.querySelector('.div1');
var div2 = document.querySelector('.div2');
var node1 = {};
var node2 = {};
var bothNodes = {};
function nodes() {
function insertNodes() {
node1 = div1.appendChild(nodeBase);
node2 = div2.appendChild(nodeBase);
bothNodes = [node1, node2];
}
function nodeBase() {
var node = document.createElement('div');
node.className = 'newNode';
}
function dosomething(node1, node2) {
//
}
function dosomethingElse(bothNodes) {
//
}
}
new nodes();
You don't return node here..
function nodeBase() {
var node = document.createElement('div');
node.className = 'newNode';
// ADD THIS LINE
return node;
}
And, as pointed out by Felix in the comment:
function insertNodes() {
node1 = div1.appendChild(nodeBase()); // Fixed
node2 = div2.appendChild(nodeBase()); // Fixed
bothNodes = [node1, node2];
}
This might get drowned with down votes, but pointing you to another direction.
How to insert nodes as variables for use later on in function.
you don't actually have to store those elements. You can always query them later.
How to call each of those variables.
You can select the first matching element with a particular class as follows:
var div1 = document.getElementsByClassName('.div1')[0];
OR
var div1 = document.querySelector('.div1');
How to call both of those variables together.
You can select all elements with a particular class, iterate over the collection and apply your logic as follows:
var nodes = document.getElementsByClassName('.div1');
OR
var nodes = document.querySelectorAll('.div1');
for(var i=0;i<nodes.length;i++){
// your loic
}
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);
};
};