How to use querySelectorAll with a variable? - javascript

I have two HTML elements that are alternatives of each other and I am trying to write a JS function that removes one if the other is present (they originated as words within <sic> and <corr> beneath <choice> in a TEI document). In my transformation, they are both assigned a code (not an #id: #id is randomly generated and has to remain so for other purposes) with a unique prefix:
<a id="abc" choicePOS="sic0">Element1</a>
<a id="xyz" choicePOS="corr0">Element2</a>
In a JS function that 'belongs' to Element1, I want to select Element2 so as to remove it. This is what I have tried (el is element1):
var choicePOS = el.getAttribute("choicePOS").slice(3); // produces 0
var corrID = "corr" + choicePOS; // produces corr0
var corr = document.querySelectorAll("a[choicePOS=corrID]");
This fails, presumably because the corrID variable in the last line is in quote marks and is being taken as a string. I have read various tutorials on CSS selectors and can't find any guidance on how to use them with a variable attribute value. Is this possible? If so, how? If not, any alternatives?
EDIT: A number of other questions relating how to concatenate strings with variables in JS have been suggested as duplicates of this one. To clarify, I am asking specifically about querySelectorAll, as I cannot find any examples this being used with variables. If the answer is that its selector is to be treated as any other JS string (i.e. variables can be concatenated in), then that is perfectly satisfactory.

Use template literals to evaluate that
var corr = document.querySelectorAll(`a[choicePOS=${corrID}]`);

Related

Rails/Rspec/Capybara: Interpreting quotes for javascript string for execute script

Given that I need to set an element's selected index with javascript in capybara by the input name...
var element = document.querySelector("select[name='user[user_locations_attributes][0][location_attributes][state]']").selectedIndex = '50';
What is the proper way to interpret this as a string so it can be executed in Capybara with execute_script(function_name_string)? Because I keep getting syntax errors, unsure how to nest the " and ' quotations.
Easiest solution to your question is to use a heredoc
page.execute_script <<~JS
var element = document.querySelector("select[name='user[user_locations_attributes][0][location_attributes][state]']").selectedIndex = '50';
JS
Although if you have need for the element for anything else it's probably nicer to find the element in ruby and then just call execute_script on the element
el = find("select[name='user[user_locations_attributes][0][location_attributes][state]']")
el.execute_script('this.selectedIndex = 50;')
As a related question - is there a reason you're doing this via JS rather than just clicking on the correct option? If you're just scraping a page there's no issue, but if you're actually testing something this basically makes your test invalid since you could potentially be doing things a user couldn't
Since you commented that you are testing, you really shouldn't be doing this via JS, but should instead be using select or select_option. select takes the options string (which you should have - otherwise why have a select element in the first place)
select('the text of option', from: 'user[user_locations_attributes][0][location_attributes][state]')
select_option is called on the option element directly, which can be found in a number of ways, such as
find("select[name='user[user_locations_attributes][0][location_attributes][state]'] option:nth-child(50)").select_option

Javascript: how to convert an element into an HTML-evaluated string?

Here's a simplified example of what I'd like to do:
var footnote = somewhere.innerHTML // This is <q>the note</q>.
var result = ???(footnote)
target.setAttribute("title", result) // This is "the note".
I've tried various methods and functions for the "???", but end up with either the raw tags displayed in the title, or with plain text and no quotation marks.
Other than processing all the inner tags myself, is there a way to convert an element into a string that contains how it would appear when HTML expanded?
Clarification:
I thought it was obvious from the "I have" and "I want" values indicated in the code comments, but this is what I want to do:
I have an element (say a <p> if you need a specific type)
that has content "This is <q>the note</q>."
I want something that will convert it into a string suitable for use in a title="..." attribute in some other element.
Displayable internal tags (in this specific example <q>) need to be HTML-interpreted so that they display as actual quotation marks, ideally handling nested quotations.
innerHTML conversion to string leaves the raw tags in place.
innerText conversion to string ignores the tags and produces no quotation marks.
Is there some other way of doing the HTML interpretation other than by writing my own function to process it?
When you add the q tag you're actually adding a text node(this is what you get with textContent, innerText, etc) that has two CSS pseudo-elements around it, the open and closing quotations.
Neither pseudo-elements nor pseudo-classes appear in the document source or document tree. They basically don't actually exist in the DOM and are therefore not selectable/won't show up in any values of the element properties.
In short, using <q></q> is more semantic mark-up, but if you're looking to represent those quotations outside the scope of the view you may want to use the traditional "
example:
let p = document.querySelector("p"), div = document.querySelector("div");
div.title = p.textContent;
console.log(div.title);
<p>"Example Text"</p>
<div></div>
Additionally, though I will say that I don't recommend it, if you really wanted to keep what you have and you're not too concerned with optimization you could simply use a replace:
let p = document.querySelector("p"), div = document.querySelector("div");
div.title = p.innerHTML.replace(/<q>|<\/q>/gmi, '"');
console.log(div.title);
<p><q>Example Text</q></p>
<div></div>

How to find a value of an attribute of an element

Here is a piece of code
<span class="balance themecolor" data-balance="2800">
I'm looking for a way to extract the value of data-balance and set it as variable x, but I have absolutely no idea how to do it. I know of the existence of .val() but I don't know if I can apply it to this code. I'm looking a for a one line long solution.
If you're using jQuery (as you've mentioned .val()):
var x = $("[data-balance]").attr("data-balance");
Or if you aren't:
var x = document.querySelector("[data-balance]").getAttribute("data-balance");
In both cases, the [data-balance] is a CSS selector for the element; adjust as needed. For instance, with that element, and assuming no other elements with either of its classes, you could use .balance, .themecolor, or even .balance.themecolor.
jQuery will find all elements matching the selector (you can change that if it's an issue, usually it isn't), but then only give you the attribute value for the first one.
querySelector will stop with the first one. If you want to find them all and get a list, use querySelectorAll (and then index into it to get the individual ones).

Adding Javascript variables to HTML elements

So, I have some code that should do four things:
remove the ".mp4" extension from every title
change my video category
put the same description in all of the videos
put the same keywords in all of the videos
Note: All of this would be done on the YouTube upload page. I'm using Greasemonkey in Mozilla Firefox.
I wrote this, but my question is: how do I change the HTML title in the actual HTML page to the new title (which is a Javascript variable)?
This is my code:
function remove_mp4()
{
var title = document.getElementsByName("title").value;
var new_title = title.replace(title.match(".mp4"), "");
}
function add_description()
{
var description = document.getElementsByName("description").value;
var new_description = "Subscribe."
}
function add_keywords()
{
var keywords = document.getElementsByName("keywords").value;
var new_keywords = prompt("Enter keywords.", "");
}
function change_category()
{
var category = document.getElementsByName("category").value;
var new_category = "<option value="27">Education</option>"
}
remove_mp4();
add_description();
add_keywords();
change_category();
Note: If you see any mistakes in the JavaScript code, please let me know.
Note 2: If you wonder why I stored the current HTML values in variables, that's because I think I will have to use them in order to replace HTML values (I may be wrong).
A lot of things have been covered already, but still i would like to remind you that if you are looking for cross browser compatibility innerHTML won't be enough, as you may need innerText too or textContent to tackle some old versions of IE or even using some other way to modify the content of an element.
As a side note innerHTML is considered from a great majority of people as deprecated though some others still use it. (i'm not here to debate about is it good or not to use it but this is just a little remark for you to checkabout)
Regarding remarks, i would suggest minimizing the number of functions you create by creating some more generic versions for editing or adding purposes, eg you could do the following :
/*
* #param $affectedElements the collection of elements to be changed
* #param $attribute here means the attribute to be added to each of those elements
* #param $attributeValue the value of that attribute
*/
function add($affectedElements, $attribute, $attributeValue){
for(int i=0; i<$affectedElements.length; i++){
($affectedElements[i]).setAttribute($attribute, $attributeValue);
}
}
If you use a global function to do the work for you, not only your coce is gonna be easier to maintain but also you'll avoid fetching for elements in the DOM many many times, which will considerably make your script run faster. For example, in your previous code you fetch the DOM for a set of specific elements before you can add a value to them, in other words everytime your function is executed you'll have to go through the whole DOM to retrieve your elements, while if you just fetch your elements once then store in a var and just pass them to a function that's focusing on adding or changing only, you're clearly avoiding some repetitive tasks to be done.
Concerning the last function i think code is still incomplete, but i would suggest you use the built in methods for manipulating HTMLOption stuff, if i remember well, using plain JavaScript you'll find yourself typing this :
var category = document.getElem.... . options[put-index-here];
//JavaScript also lets you create <option> elements with the Option() constructor
Anyway, my point is that you would better use JavaScript's available methods to do the work instead of relying on innerHTML fpr anything you may need, i know innerHTML is the simplest and fastest way to get your work done, but if i can say it's like if you built a whole HTML page using and tags only instead of using various semantic tags that would help make everything clearer.
As a last point for future use, if you're interested by jQuery, this will give you a different way to manipulate your DOM through CSS selectors in a much more advanced way than plain JavaScript can do.
you can check out this link too :
replacement for innerHTML
I assume that your question is only about the title changing, and not about the rest; also, I assume you mean changing all elements in the document that have "title" as name attribute, and not the document title.
In that case, you could indeed use document.getElementsByName("title").
To handle the name="title" elements, you could do:
titleElems=document.getElementsByName("title");
for(i=0;i<titleElems.length;i++){
titleInner=titleElems[i].innerHTML;
titleElems[i].innerHTML=titleInner.replace(titleInner.match(".mp4"), "");
}
For the name="description" element, use this: (assuming there's only one name="description" element on the page, or you want the first one)
document.getElementsByName("description")[0].value="Subscribe.";
I wasn't really sure about the keywords (I haven't got a YouTube page in front of me right now), so this assumes it's a text field/area just like the description:
document.getElementsByName("keywords")[0].value=prompt("Please enter keywords:","");
Again, based on your question which just sets the .value of the category thingy:
document.getElementsByName("description")[0].value="<option value='27'>Education</option>";
At the last one, though, note that I changed the "27" into '27': you can't put double quotes inside a double-quoted string assuming they're handled just like any other character :)
Did this help a little more? :)
Sry, but your question is not quite clear. What exactly is your HTML title that you are referring to?
If it's an element that you wish to modify, use this :
element.setAttribute('title', 'new-title-here');
If you want to modify the window title (shown in the browser tab), you can do the following :
document.title = "the new title";
You've reading elements from .value property, so you should write back it too:
document.getElementsByName("title").value = new_title
If you are refering to changing text content in an element called title try using innerHTML
var title = document.getElementsByName("title").value;
document.getElementsByName("title").innerHTML = title.replace(title.match(".mp4"), "");
source: https://developer.mozilla.org/en-US/docs/DOM/element.innerHTML
The <title> element is an invisible one, it is only displayed indirectly - in the window or tab title. This means that you want to change whatever is displayed in the window/tab title and not the HTML code itself. You can do this by changing the document.title property:
function remove_mp4()
{
document.title = document.title.replace(title.match(".mp4"), "");
}

How can I separately retrieve the HTML that's before and after a child element inside a parent element?

We're writing a web app that relies on Javascript/jQuery. It involves users filling out individual words in a large block of text, kind of like Mad Libs. We've created a sort of HTML format that we use to write the large block of text, which we then manipulate with jQuery as the user fills it out.
Part of a block of text might look like this:
<span class="fillmeout">This is a test of the <span>NOUN</span> Broadcast System.</span>
Given that markup, I need to separately retrieve and manipulate the text before and after the inner <span>; we're calling those the "prefix" and "suffix".
I know that you can't parse HTML with simple string manipulation, but I tried anyway; I tried using split() on the <span> and </span> tags. It seemed simple enough. Unfortunately, Internet Explorer casts all HTML tags to uppercase, so that technique fails. I could write a special case, but the error has taught me to do this the right way.
I know I could simply use extra HTML tags to manually denote the prefix and suffix, but that seems ugly and redundant; I'd like to keep our markup format as lean and readable and writable as possible.
I've looked through the jQuery docs, and can't find a function that does exactly what I need. There are all sorts of functions to add stuff before and after and around and inside elements, but none that I can find to retrieve what's already there. I could remove the inner <span>, but then I don't know how I can tell what came before the deleted element apart from what came after it.
Is there a "right" way to do what I'm trying to do?
With simple string manipulations you can also use Regex.
That should solve your problem.
var array = $('.fillmeout').html().split(/<\/?span>/i);
Use your jQuery API! $('.fillmeout').children() and then you can manipulate that element as required.
http://api.jquery.com/children/
For completeness, I thought I should point out that the cleanest answer is to put the prefix and suffix text in it's own <span> like this and then you can use jQuery selectors and methods to directly access the desired text:
<span class="fillmeout">
<span class="prefix">This is a test of the </span>
<span>NOUN</span>
<span class="suffix"> Broadcast System.</span>
</span>
Then, the code would be as simple as:
var fillme = $(".fillmeout").eq(0);
var prefix = fillme.find(".prefix").text();
var suffix = fillme.find(".suffix").text();
FYI, I would not call this level of simplicity "ugly and redundant" as you theorized. You're using HTML markup to delineate the text into separate elements that you want to separately access. That's just smart, not redundant.
By way of analogy, imagine you have toys of three separate colors (red, white and blue) and they are initially organized by color and you know that sometime in the future you are going to need to have them separated by color again. You also have three boxes to store them in. You can either put them all in one box now and manually sort them out by color again later or you can just take the already separated colors and put them each into their own box so there's no separation work to do later. Which is easier? Which is smarter?
HTML elements are like the boxes. They are containers for your text. If you want the text separated out in the future, you might as well put each piece of text into it's own named container so it's easy to access just that piece of text in the future.
Several of these answers almost got me what I needed, but in the end I found a function not mentioned here: .contents(). It returns an array of all child nodes, including text nodes, that I can then iterate over (recursively if needed) to find what I need.
I'm not sure if this is the 'right' way either, but you could replace the SPANs with an element you could consistently split the string on:
jQuery('.fillmeout span').replaceWith('|');
http://api.jquery.com/replaceWith/
http://jsfiddle.net/mdarnell/P24se/
You could use
$('.fillmeout span').get(0).previousSibling.textContent
$('.fillmeout span').get(0).nextSibling.textContent
This works in IE9, but sadly not in IE versions smaller than 9.
Based on your example, you could use your target as a delimiter to split the sentence.
var str = $('.fillmeout').html();
str = str.split('<span>NOUN</span>');
This would return an array of ["This is a test of the ", " Broadcast System."]. Here's a jsFiddle example.
You could just use the nextSibling and previousSibling native JavaScript (coupled with jQuery selectors):
$('.fillmeout span').each(
function(){
var prefix = this.previousSibling.nodeValue,
suffix = this.nextSibling.nodeValue;
});
JS Fiddle proof of concept.
References:
each().
node.nextSibling.
node.previousSibling.
If you want to use the DOM instead of parsing the HTML yourself and you can't put the desired text in it's own elements, then you will need to look through the DOM for text nodes and find the text nodes before and after the span tag.
jQuery isn't a whole lot of help when dealing with text nodes instead of element nodes so the work is mostly done in plain javascript like this:
$(".fillmeout").each(function() {
var node = this.firstChild, prefix = "", suffix = "", foundSpan = false;
while (node) {
if (node.nodeType == 3) {
// if text node
if (!foundSpan) {
prefix += node.nodeValue;
} else {
suffix += node.nodeValue;
}
} else if (node.nodeType == 1 && node.tagName == "SPAN") {
// if element and span tag
foundSpan = true;
}
node = node.nextSibling;
}
// here prefix and suffix are the text before and after the first
// <span> tag in the HTML
// You can do with them what you want here
});
Note: This code does not assume that all text before the span is located in one text node and one text node only. It might be, but it also might not be so it collates all the text nodes together that are before and after the span tag. The code would be simpler if you could just reference one text node on each side, but it isn't 100% certain that that is a safe assumption.
This code also handles the case where there is no text before or after the span.
You can see it work here: http://jsfiddle.net/jfriend00/P9YQ6/

Categories