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.
Related
I have a text phrase on an HTML page that is built, in part, from dropdown lists of words (built from arrays).
I want to be able to push a button and transfer the text of the phrase to another div.
The problem is - it transfers ALL of the options in the dropdown list and not just the one I select.
Here is a working example: https://nessify.co.uk/sandbox/textbuilder/textbuilder.html
function transfer() {
total = document.getElementById('original').innerText;
document.getElementById('result').innerHTML = total;
}
This is the function I'm using to 'transfer'. I've also tried...
function transfer() {
total = document.getElementById('original').innerHTML;
document.getElementById('result').innerHTML = total;
}
I'm guessing I have to convert the HTML in 'original' to plain text somehow... but I don't know how.
You can read the selected value using the value property of the select tag. Try this:
function transfer() {
const originalNode = document.getElementById('original')
const resultNode = document.getElementById('result')
// Iterate over all the nodes within the original node
const resultFragment = document.createDocumentFragment()
originalNode.childNodes.forEach(cn => {
if (cn.nodeName === 'SELECT') {
// If the node is a 'select' tag, read its value as text
resultFragment.appendChild(document.createTextNode(cn.value))
} else {
// If the node is a text node (or anything else), copy it as-is
resultFragment.appendChild(cn.cloneNode(true))
}
})
// Reset the result node
resultNode.innerHTML = ""
// Set the result to the fragment created above
resultNode.appendChild(resultFragment)
}
I am mostly familiar with java selenium, and I am new to both JS and Protractor. Lets say I am trying to click an option from a list of options with a common identifier..
var options = $('.options');
How could I get all elements with that common identifier, and then select one by its text? I can not do driver.findElements like I could in java since there is no reference to driver..
This is what I have tried so far but its not working and I think its due to my inexperience with JS
this.selectCompanyCode = function(companyCode) {
dropDownMenus[0].click();
var companyCodeOptions = $('[ng-bind-html="companyCode"]');
companyCodeOptions.filter(function (elem) {
return elem.getText().then(function text() {
return text === companyCode;
});
}).first().click();
};
Select all elements with common identifier: $$('.options'); That selects all elements with a class of .options -- equivalent of element.all(by.css('.options')). This returns an ElementArrayFinder. Also see .get() for how to choose an element by index from the ElementArrayFinder.
Find by text, you could use cssContainingText(css, text). For example,
var loginBtn = element(by.cssContainingText('button.ng-scope', 'Login'));
But if for some reason those are not providing the expected results, you can use .filter() (docs here) on an ElementArrayFinder to go through the array of elements and find an element based on a condition you specify. For example,
var allOptions = $$('.options');
allOptions.filter(function (elem) {
return elem.getText().then(function (text) {
return text === 'What you want';
});
}).first().click();
And, although I've never used regular Java Selenium (so I don't know if this is the same), but there is indeed a browser reference (and therefore findElements function): http://www.protractortest.org/#/api?view=ProtractorBrowser.
Hope it helps!
Edit:
Using your code:
this.selectCompanyCode = function(companyCode) {
// where is dropDownMenus defined? This has function no reference to it.
dropDownMenus.get(0).click(); // should be this
var companyCodeOptions = $$('[ng-bind-html="' + companyCode + '"]');
return companyCodeOptions.filter(function (elem) {
return elem.getText().then(function text() {
return text === companyCode;
});
}).first().click();
};
second edit:
Assuming company code is unique, you probably don't need to use filter. Try this:
this.selectCompanyCode = function(companyCode) {
dropDownMenus.get(0).click();
var companyCodeOptions = $('[ng-bind-html="' + companyCode + '"]');
return companyCodeOptions.click();
};
Use cssContainingText
element(by.cssContainingText(".option", "text")).click();
http://www.protractortest.org/#/api?view=ProtractorBy.prototype.cssContainingText
I am using the values input by the user to perform this action.
Here is the full code: https://jsfiddle.net/7196dfyz/
This is part of the code where the elements are traversed, where I'm having trouble:
var lists = $ul.getElementsByTagName("li");
for (var i = 0; i < lists.length; ++i) {
if (lists[i] == value) {
$("ul").css("background-color","black");
}
}
The first input should take the value in some li
and the second input should take the respective parent ul class name.
I think this is what you're looking for. (Here is an updated jsfiddle):
function func() {
var key = $("#key").val();
var value = $("#entry").val();
var $ul = $("." + key);
var lists = $ul.find("li");
for (var i = 0; i < lists.length; ++i) {
console.log($(lists[i]).text(), value);
if ($(lists[i]).text() === value) {
$(lists[i]).css("background-color","black");
}
}
}
You have several issues:
$ul.getElementsByTagName is not a valid function. Because $ul at this point is a jQuery array-like object, it wont work. You'd need to do $ul[0].getElementsByTagName, or simply use jQuery's find() like my example above.
You're trying to compare lists[i] to value, which happens to be an HTML element. When comparing to a string, it will return <li>Say</li> which will never match anything you type in. Using $(lists[i]).text() should get you what you need.
$("ul").css("background-color","black");: You were setting every ul to black if a match was found. I assume you only want to match the one that was matched. $(lists[i]).css("background-color","black"); fixes this.
You can even simplify this entire function down to this:
function func() {
var key = $("#key").val();
var value = $("#entry").val();
$("." + key).find("li").filter(function() {
return $(this).text() === value;
}).css("background-color","black");
}
Broken down:
$("." + key): Find the ul that has the class of key.
.find("li") Find all list items within each unordered list found.
.filter(...) For each element in this list, and return to me only the items that match my criteria: $(this).text() === value.
And finally .css("background-color","black"): Set all the background colors to black of the list items that were returned from the filter() function.
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;
};
I have 2 arrays, one with cities, and another array with ID's...
I would like to loop both, and then output some HTML
Right now im only looping cities, and then appending some HTML to a as following:
if ($jq(".area").length > 0) {
var region = $jq(".hiddenFieldRegion").val();
var cityIDs = $jq(".hiddenFieldChosenAreas").val();
var strcities = $jq(".hiddenFieldChosenCities").val();
//var cities = strcities.split('|');
//Set region to be marked
if (region) {
$jq(".mapdk").attr("class", region);
}
//Appending to searchform
if (strcities) {
$jq.each(strcities.toString().split("|"), function (k, v) {
var v = v.split("|");
$jq("<option selected />").text(v[0]).appendTo("select.chosen-cities");
});
}
}
I would like the place where im appending to the searchform, add an attribute value, with an ID from the cityIDs array...
So i get some HTML like:
<option value="XXX">CityName</option>
Is that possible?
Assuming your cities IDs and your cities names are orderred in the same way, you can access each name/id by this index in the arrays :
var cityIDs = $jq(".hiddenFieldChosenAreas").val();
var strcities = $jq(".hiddenFieldChosenCities").val();
var cityIDs_array = cityIds.split("|");
var strcities_array = strcities.split("|");
....
if (strcities) {
$jq.each(strcities_array, function (k, v) {
//var v = v.split("|"); Unnecessary if strcities is like "city1|city2|city3"
$jq("<option value='"+cityIDs_array[k]+"' />").text(v).appendTo("select.chosen-cities");
});
}
In this, way, you can access each city ID in your loop.
But I think you should store city name and city id in the same string. For example "city1:1|city2:2|city3:3". Then, with some use of split function, you'll get id and name.
Assuming the order is the same in both arrays you can use the index parameter (k) of the each function:
if (strcities) {
var aCityIDs = cityIDs.split("|");
$jq.each(strcities.toString().split("|"), function (k, v) {
var v = v.split("|");
var id = aCityIDs[k];
$jq("<option selected value='"+id+"' />").text(v[0]).appendTo("select.chosen-cities");
});
}
This is easy enough if your two arrays are in parity with one another with regards to indexing - loop over one, then look up a value in the other array at the same index.
//simulate environment
var strcities = 'one|two|three',
cityIDs = '1|2|3';
//main code
var ids = cityIDs.split('|');
$.each(strcities.toString().split("|"), function (index, val) {
var opt = $("<option />", {value: ids[index], text: val}).appendTo("select.chosen-cities");
/* if (index == 2) opt.prop('selected', 'selected'); /*
});
A few points of note:
you were splitting on | once in the $.each and again inside it. Since this would be illogical (any | would have disappeared after the first split) I removed it.
you were adding selected to each option. If you simply want the first option to be selected, you don't need to add anything in. If you want a specific option to be selected, see the line I commented out, which selects the 3rd element.
http://jsfiddle.net/DhH3U/
Another example:
var strict = ('aaa|eeee|dddd').split('|');
var id = ('1|2|3').split('|');
$.each(strict, function (k) {
$("<option selected />").text(strict[k]).appendTo("select.chosen-cities").attr('value',id[k]);
});