For a long time, I have been using a simple JavaScript file along with in-line, onclick events in 'a' tags to open a new window when the link is clicked. As you can see from the example below, I add the type and size of the window in the HTML. In the example below, the window cannot be resized by the user, is centered and is 1010 wide by 730 high.
Example HTML:
<a href="https://example.com" target='_blank' onclick="popUp(this.href,'elasticNoC',1010,730);return false;">
JavaScript file:
var newWin = null;
function popUp(strURL, strType, strWidth, strHeight) {
LeftPosition = (screen.width) ? (screen.width-strWidth)/2 : 0;
TopPosition = (screen.height) ? (screen.height-strHeight)/2 : 0;
if (newWin !== null && !newWin.closed)
newWin.close();
var strOptions="";
if (strType=="consoleC")
strOptions="resizable,top="+TopPosition+',left='+LeftPosition+",height="+
strHeight+",width="+strWidth;
if (strType=="fixedC")
strOptions="status,top="+TopPosition+',left='+LeftPosition+",height="+
strHeight+",width="+strWidth;
if (strType=="elasticC")
strOptions="toolbar,menubar,scrollbars,"+
"resizable,location,top="+TopPosition+',left='+LeftPosition+",height="+
strHeight+",width="+strWidth;
if (strType=="elasticNoC")
strOptions="scrollbars,"+
"resizable,top="+TopPosition+',left='+LeftPosition+",height="+
strHeight+",width="+strWidth;
if (strType=="console")
strOptions="resizable,height="+
strHeight+",width="+strWidth;
if (strType=="fixed")
strOptions="status,height="+
strHeight+",width="+strWidth;
if (strType=="elastic")
strOptions="toolbar,menubar,scrollbars,"+
"resizable,location,height="+
strHeight+",width="+strWidth;
if (strType=="elasticNo")
strOptions="scrollbars,"+
"resizable,height="+
strHeight+",width="+strWidth;
newWin = window.open(strURL, 'newWin', strOptions);
newWin.focus();
}
A recent update of a web application is now stripping in-line JavaScript so my old way of doing things no longer works.
I can still include separate JavaScript files but no in-line JavaScript.
I am thinking that the best option is to replace the in-line, onclick events with specific class names and use JavaScript to get the window type and size from the class name.
Example new HTML:
<a class="red elasticNoC-1010-730" href="https://example.com" target="_blank">
I can't figure out the correct JavaScript to use. Can someone please provide JavaScript code that can be used as a replacement? As you can see in the new HTML example, some of my links may contain more than one class name. In this example, the class name "red" would be ignored because it does not match any of the 'strType' in the JavaScript file.
Don't use classes, use data-* attribute:
<a class="red" data-popup="elasticNoC-1010-730" href="https://example.com" target="_blank">TEST CLICK</a>
and than the JS would be like:
// var newWin = null; function popUp( ............etc
const handlePopup = (ev) => {
ev.preventDefault(); // Prevent browser default action
const EL = ev.currentTarget; // Get the element
const args = EL.dataset.popup.split("-"); // Get the data-* parts
args.unshift(EL.getAttribute("href")); // Prepend HREF to parts
return popUp(...args); // Call popUp with arguments
};
const EL_popup = document.querySelectorAll('[data-popup]');
EL_popup.forEach(el => el.addEventListener('click', handlePopup));
Handling classes
Handling classes for custom attributes values is never a great idea, since it mostly becomes a parsing-things problem, because HTML attribute class can contain a multitude of classes in any order and number.
But luckily you could create a specific prefixed classname (with popUp-) like
popUp-elasticNoC-1010-730
and than use that specific prefix as your reference for splitting the specific string parts of interest, but also as your Elements selector:
querySelectorAll('[class*=" popUp-"], [class^="popUp-"]')
Here's an example:
const handlePopup = (ev) => {
ev.preventDefault(); // Prevent browser default action
const EL = ev.currentTarget; // Get the element
const classPopUp = [...EL.classList].filter(cl => cl.startsWith("popUp-"))[0];
const args = classPopUp.split('-'); // Convert class item to array
args.shift(); // remove "popUp-" prefix
args.unshift(EL.getAttribute("href")); // Prepend HREF to parts
return popUp(...args); // Call popUp with arguments
};
const EL_popup = document.querySelectorAll('[class*=" popUp-"], [class^="popUp-"]');
EL_popup.forEach(el => el.addEventListener('click', handlePopup));
<a class="red popUp-elasticNoC-1010-730 foo-bar" href="https://example.com" target="_blank">TEST CLICK</a>
Related
I want to build a note taker app with html css and js but when i want add second note there is a problem.
let myNote = "";
let myTitle = "";
let noteInput = document.getElementById("note-input");
let titleInput = document.getElementById("title-input");
let title = document.getElementById("title");
let note = document.getElementById("first-note-p");
let addButton = document.getElementById("addButton");
let removeButton = document.getElementById("remove-button");
let newDiv = document.createElement("div");
let newP = document.createElement("p");
let newH3 = document.createElement("h3");
let newButton = document.createElement("button");
let notePlace = document.getElementById("note-place");
let button = document.getElementsByTagName("button");
let div = document.getElementsByTagName("div");
let paragrapgh = document.getElementsByTagName("p");
let head3 = document.getElementsByTagName("h3");
let tally = 0;
const addNote = () => {
myNote = noteInput.value;
myTitle = titleInput.value;
notePlace.appendChild(newDiv);
div[tally].appendChild(newH3);
div[tally].appendChild(newP);
div[tally].appendChild(newButton);
notePlace = document.getElementById("note-place");
head3[tally].innerText = myTitle;
paragrapgh[tally].innerText = myNote;
button[tally + 1].innerText = "remove";
tally += 1;
};
const removeNote = () => {
title.innerHTML = "";
note.innerHTML = "";
};
addButton.onclick = addNote;
<h1>Take your notes</h1>
<input id="title-input" onfocus="this.value=''" type="text" value="title" />
<input id="note-input" onfocus="this.value=''" type="text" value="note" />
<button id="addButton">add</button>
<div id="note-place"></div>
I use addNote function to add a new note but for second note I encounter to the following error.
Cannot set properties of undefined (setting 'innerText')
at HTMLButtonElement.addNote (notetaker.js:37:26)
Sorry for my bad English.
The main problem with your attempt is that you're selecting the elements before actually creating and appending them to the DOM and that will lead to problems because those elements that were initially selected are no longer there when a new note is added.
The fix is fairly easy, select the elements at the time you create a new note. Actually, I won't just stop here and I will happily invite you to follow along with my answer as we approach your task (of making notes and showing them in the screen) in a better approach that, i think, will be more helpful than just giving a fix.
So, here's what we're going to do, we're firstly go by tackling the task and see what are the main sub-tasks to do in order to have a working demo (with add and remove notes features):
To have a better performance, we'll select and cache the elements that we will use extensively in our task. Mainly, the element div#note-place should be cached because we're going to use many times when we add and remove notes.
The inputs, for the note title and text, the button that adds a note, those elements should be cached as well.
The main thing we will be doing is creating some elements and appending them to div#note-place so we can assign that sub-task to a separate function (that we will create). This function will create an element, add the wanted attributes (text, class etc...) then it returns that created element.
At this stage, our solution has started to take shape. Now, to create a note we will listen for the click event on the add note button and then we will have a listener that will handle the creation of the new note based on the values found on the inputs and then append that to the DOM. We will use addEventListener to attach a click event listener on the add note button (modern JS, no more onclicks!).
Now, for the remove note feature. The initial thinking that comes to mind is that we will listen for click events on the remover buttons and then do the work. This can work, but here's a better solution, Event Delegation, which basically allow us to have 1 listener set on div#note-place element that will call the remove note logic only when a remove button was clicked (see the code below for more info).
So, let's not take more time, the live demo below should allow you to easily understand what's being said:
/** cache the elemnts that we know we will use later on */
const notesContainer = document.getElementById('note-place'),
titleInp = document.getElementById('title-input'),
noteInp = document.getElementById('note-input'),
addNoteBtn = document.getElementById('add-note-btn'),
/** this class will be added to all remove note buttons This will allow us to catch clicks on those buttons using event delegation */
noteRemoverBtnClass = 'note-remover-btn',
/**
* a simple function that create an element, add the requested attribute and return the newly created element.
* tag: the tag name of the element to create (like div, h3 etc...).
* text: the text to show on the element (using textContent attribute).
* attributes: an object that holds "key: value" pairs where the keys are the attributes (like id, type etc...) and the values are the values for each attribute set on that parameter (see usage below).
*/
createElement = (tag, text, attributes) => {
const el = document.createElement(tag);
attributes = attributes || {};
!!text && (el.textContent = text);
for (let attr in attributes)
attributes.hasOwnProperty(attr) && el.setAttribute(attr, attributes[attr]);
return el;
};
/** listen for click events on the add note button */
addNoteBtn.addEventListener('click', () => {
/** create a div that will wrap the new note */
const noteEl = createElement('div');
/**
* create an "h3" for the note title, a "p" for the note text and a "button" that acts as the remove note button
* then loop through them and add them to the note wrapper that we just created
*/
[
createElement('h3', titleInp.value),
createElement('p', noteInp.value),
createElement('button', 'Remove', {
type: 'button',
class: noteRemoverBtnClass
})
].forEach(el => noteEl.appendChild(el));
/** append the entire note element (including the "h3", "p"p and "button" to "div#note-place" */
notesContainer.appendChild(noteEl);
});
/** implement event delegation by listening to click events on "div#note-place" and execute a set of logic (to remove a note) only when the clicked element is actually a remove button (thanks to "noteRemoverBtnClass" that we add to each created remove button) */
notesContainer.addEventListener('click', e => e.target.classList.contains(noteRemoverBtnClass) && e.target.parentNode.remove());
<h1>Take your notes</h1>
<input id="title-input" onfocus="this.value=''" type="text" value="title" />
<input id="note-input" onfocus="this.value=''" type="text" value="note" />
<button id="add-note-btn">add</button>
<div id="note-place"></div>
The above code sample is definitely NOT the only way to get things done, it only aims to be simple while recommending the use of some modern JS technics and logics. There always be more ways to do the task and even some better ways to do it.
I want to ensure that when I click on the divs (A, B, C), the link of the button changes and gets the values of the data attributes in the appropriate places. I wrote a small script, but it does not work, and there is still not enough knowledge to understand exactly where I went wrong. Any help would be welcome.
document.getElementById("product").onclick = function() {
document.getElementById("purchase").href =
"/?add-to-cart=" + this.data-product +
"&variation_id=" + this.data-id + "/";
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="product__items" id="product">
<div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div>
<div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div>
<div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div>
<div class="product__items---btn">
Button
</div><!-- btn -->
</div>
You have several problems here.
First, I suggest you consult the documentation for HTMLElement.dataset or jQuery's .data().
Also, if you intend on using event delegation, you can't use this to refer to the event source element in a vanilla event listener as it will refer to the delegate.
Since you do have jQuery involved, you might as well use it since it makes this a lot easier (see also vanilla JS version below)
const button = $("#purchase")
$("#product").on("click", ".items-uniqueItem[data-id][data-product]", function() {
// Due to the selector above, `this` is now the clicked `<div>`
// Extract data properties
const { product, id } = $(this).data()
// Construct URL parameters
const params = new URLSearchParams({
"add-to-cart": product,
"variation_id": id
})
// Set the `href`
button.prop("href", `/?${params}/`)
})
/* this is just for visibility */
.items-uniqueItem{cursor:pointer;}#purchase{display:block;text-decoration:none;margin: 1rem;}#purchase:after{content:attr(href);display:block;color:#ccc;margin:.5rem;}
<!-- your HTML, just minified -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script><div class="product__items" id="product"><div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div><div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div><div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div><div class="product__items---btn">Button</div></div>
A vanilla JS version would look something more like this. You can use Element.closest() to locate the delegated event source
const button = document.getElementById("purchase")
document.getElementById("product").addEventListener("click", e => {
// find the required event source element
const el = e.target.closest(".items-uniqueItem[data-id][data-product]")
if (el) {
// Extract data properties
const { product, id } = el.dataset
// Construct URL parameters
const params = new URLSearchParams({
"add-to-cart": product,
"variation_id": id
})
// Set the `href`
button.href = `/?${params}/`
}
})
.items-uniqueItem{cursor:pointer;}#purchase{display:block;text-decoration:none;margin: 1rem;}#purchase:after{content:attr(href);display:block;color:#ccc;margin:.5rem;}
<!-- your HTML, just minified -->
<div class="product__items" id="product"><div data-id="338" data-product="A" id="uI-1" class="items-uniqueItem">A</div><div data-id="339" data-product="B" id="uI-2" class="items-uniqueItem">B</div><div data-id="340" data-product="C" id="uI-3" class="items-uniqueItem">C</div><div class="product__items---btn">Button</div></div>
As you can see, it's not very different to the jQuery version so maybe you might not need jQuery
I've never personally used the element.onlick = function() {...} notation, so I'll be usingelement.addEventListener('click', (e) => ...), but it should work the same way.
What you are doing is selecting the object that has the id "product". But "product" is the parent os the elements you want to select.
If you want to select several elements and do something with them, you can't use the id attribute, since id is unique for html page. So you'll want to use classes for that.
Create a class and add that class to each child (the ones with the data-product).
Select all children with .querySelectorAll(). Here is the doc. This returns a NodeList, but it's similar to an Array.
Iterate thought the List with a .forEach(item => ...) where item represents each element of the list.
Add an Event Listener (or .click, I guess) on each item.
*theList*.forEach( (item) => {
item.addEventListener('click', (event) => {
event.target.href = "/?add-to-cart=" + event.target.dataset.product + "&" + "variation_id=" + event.target.dataset.id + "/";
})
));
To access a dataset in JS you use the .dataset property.
First, grab all the divs that have a given class so that we can use their data.
const items = document.querySelectorAll('.items-uniqueItem');
items.forEach(item => item.addEventListener('click', (e) => console.log(e.target)))
Then inside you click handler you can get the button reference and assign the properties you want to get from it.
Context: I'm lazy, and I'm trying to dynamically/automatically create menu buttons which are hyperlinked to the headers of a page with raw JavaScript.
My site loads the content of the body from an external file located at the same folder with document.onload, and I'm trying to set up a menu function, which then should load the default page's menu items. Loading the menu manually each time I change from one page to another works, as I've included loadMenues(thispage) on the end loadContents(), but it doesn't work as soon as the page is loaded, as loading the body content does. I don't understand this behaviour.
function setVisible(thisdiv){
var alldivs = document.getElementsByClassName("container");
[].forEach.call(alldivs, function(uniquediv){
document.getElementById(uniquediv.id).style.display = "none";
return;
});
document.getElementById(thisdiv).style.display = "block";
window.scrollTo(0,0);
loadMenues(thisdiv);
}
window.onload = function(){
loadContent("personalinfo");
loadContent("contactdetails");
setVisible("personalinfo");
loadMenues("personalinfo");
}
I'm explaining this, secondary question, in order to contextualize my main problem.
loadContents(file) is a function which extracts the contents from the requested file. The layout of each of these files is the same, pretty much, with each section of the file being separated by a custompadding div, where its first child is a h1 element as shown below:
<html>
<div class="custompadding">
<h1 id="headerpersonaldetails">Personal details</h1>
<p>Lorem ipsum</p>
</div>
<div class="custompadding">
<h1 id="headercontactdetails">Contact details</h1>
<p>Lorem ipsum</p>
</div>
</html>
I'm trying to set up a menu item for each of these headings, which scrolls to the clicked-on header. Setting up each menu-item manually works as expected, but I want to automatize it, so changing any file will automatically add the menu items to whichever page we change to. Following is my code which adds these elements to the divisor, but I'm having issues handling the onclick function.
function loadMenues(file) {
var rightmenu = document.getElementById("right-menu");
while(rightmenu.firstChild){
rightmenu.removeChild(rightmenu.firstChild);
}
[].forEach.call(document.getElementById(file).children, function(custompaddingchild) {
console.log(custompaddingchild);
headerelement = custompaddingchild.getElementsByTagName("h1")[0]
newbutton = document.createElement("div");
newbutton.setAttribute("class", "menu-item");
let movehere = function() { location.href="#"+headerelement.id; console.log(headerelement.id); }
newbutton.onclick = movehere;
/*rightmenu = document.getElementById("right-menu");*/
buttonspanner = document.createElement("span");
buttoncontent = document.createTextNode(headerelement.innerHTML);
buttonspanner.appendChild(buttoncontent);
newbutton.appendChild(buttonspanner);
rightmenu.appendChild(newbutton);
});
}
The first part of the function deletes all the nodes which already are in the menu, in order to add the new ones when changing pages.
Trying to define newbutton.setAttribute() with onclick results in a SyntaxError (fields are not currently supported) in Firefox. It doesn't work if I set a static string as newbutton.setAttribute("onclick", "location.href=#headerpersonalinfo"); either.
Trying to set a static anchor link with newbutton.onclick set to a function, instead, works, such that
newbutton.onclick = function() {
location.href = "#headerpersonalinfo";
}
and this is pretty much how my current code is set up, except that I have given this function a unique variable, which I then call.
The problem I have is this, as I see it: The variable is redefined each time it finds a new header, so calling the function sends the user to the last header, and not the expected one. How can I set the function to be parsed at the moment I define onclick with it, and not call the function when the user presses the button?
PS: I'm using my own internal naming convention of files, headers, and items, in order to modularize my site as much as I can. Since this is a website only intended for my Curriculum Vitae, I'm its only developer.
The issue occurs because the you are hoisting "variables" to the global scope (newbutton and headerelement).
Set them to block scoped variables (const or let) and you will see that it works:
https://codesandbox.io/s/rm4ko35vnm
function loadMenues(file) {
var rightmenu = document.getElementById("right-menu");
while (rightmenu.firstChild) {
rightmenu.removeChild(rightmenu.firstChild);
}
[].forEach.call(document.getElementById(file).children, function(
custompaddingchild
) {
console.log(custompaddingchild);
const headerelement = custompaddingchild.getElementsByTagName("h1")[0];
console.log(headerelement.innerHTML);
const newbutton = document.createElement("div");
newbutton.setAttribute("class", "menu-item");
console.log(headerelement.id);
let movehere = function() {
location.href = "#" + headerelement.id;
console.log(headerelement.id);
};
newbutton.addEventListener('click', movehere);
const rightmenu = document.getElementById("right-menu");
const buttonspanner = document.createElement("span");
buttoncontent = document.createTextNode(headerelement.innerHTML);
buttonspanner.appendChild(buttoncontent);
newbutton.appendChild(buttonspanner);
rightmenu.appendChild(newbutton);
});
}
Right now I have some element
<span class="CLASSNAME" id="FOO">Barack Obama</span>
Referenced by
document.getElementById('FOO')
And I want to make it so the text "Barack Obama" is turned into a blue link where the text is the same, but it links to (for example) www.google.com
I've seen this method, but innerHTML is apparently BAD especially since the link I'll be using is a value returned from an ajax call ("potential for bad js"?).
document.getElementById('FOO').innerHTML = desiredText.link(desiredLink);
What is the best way to go about this without a huge perfomance hit or potentially "bad js"? Will also be adding mouseover features to said element later on, so if this is worth consideration I figured I'd mention it. No jQuery.
var el=document.getElementById('FOO');
el.innerHTML="<a href='whitehouse.gov'>"+el.textContent+"</a>";
Simply wrap it into a link. Note that html injection is possible. And do not care about performance, were talking about milliseconds...
If you want to prevent html injectin, you may build it up manually:
var el=document.getElementById('FOO');
var a=document.createElement("a");
a.href="whitehouse.hov";
a.textContent=el.textContent;
el.innerHTML="";
el.appendChild(a);
You have two possibilities:
Add an <a> element as a child of the <span> element
Replace the text node ("Barack Obama") with an <a> element:
function addAnchor (wrapper, target) {
var a = document.createElement('a');
a.href = target;
a.textContent = wrapper.textContent;
wrapper.replaceChild(a, wrapper.firstChild);
}
addAnchor(document.getElementById('foo'), 'http://www.google.com');
<span id="foo">Barack Obama</span>
This will result in the following DOM structure:
<span id="foo">
Barack Obama
</span>
Replace the <span> element with an <a> element
Replace the entire <span> element with an <a> element:
function addAnchor (wrapper, target) {
var a = document.createElement('a');
a.href = target;
a.textContent = wrapper.textContent;
wrapper.parentNode.replaceChild(a, wrapper);
}
addAnchor(document.getElementById('foo'), 'http://www.google.com');
<span id="foo">Barack Obama</span>
This will result in the following DOM structure:
Barack Obama
Use methods like createElement, appendChild and replaceChild to add element instead of innerHTML.
var changeIntoLink = function(element, href) {
// Create a link wrapper
var link = document.createElement('a');
link.href = href;
// Move all nodes from original element to a link
while (element.childNodes.length) {
var node = element.childNodes[0];
link.appendChild(node);
}
// Insert link into element
element.appendChild(link);
};
var potus = document.getElementById('potus');
changeIntoLink(potus, 'http://google.com/');
<span id="potus">Barack Obama</span>
I am stumped as to why my query .click() is not working. I am trying to change the href tag on an a element, before it goes to the next page.
here is my jquery
$('.individualFormSections').click(function() {
var formSectionTitle = $(this).siblings('div').text(); // gets section title that was clicked
console.log(formSectionTitle);
assetConfigIdForURL = assetConfigIdForURL.replace(/\s+/g,'-');
woTypeCodeForURL = woTypeCodeForURL.replace(/\s+/g,'-');
woMaintTypeCode = woMaintTypeCode.replace(/\s+/g,'-');
formSectionTitle = formSectionTitle.replace(/\s+/g,'-');
// Change href dynamically to set url parameters
$(this).attr("href",'airSystem.html?insp_asset_config_id='+assetConfigIdForURL+'&wo_type_code='+woTypeCodeForURL+'&wo_maint_type_code='+woMaintTypeCode+'&formSection='+formSectionTitle+'&wo_id='+woIdForURL+'');
});
Here is the html
<a class="individualFormSections" href="">
<img class="bus-form-img" src="pull-up.jpg" alt="Trolltunga Norway">
</a>
<div class="desc" id="bodyDamageDesc">AirSystem</div>
I also tried doing a simple alert and its not even targeting the a tag. My javascript link is set up correctly.
A little background, the html is getting generated dynamically from a previous javascript function. When I use chrome developer tools, all the html shows just fine. Any ideas on what could be causing this?
Always use prevent default in such cases
$('.individualFormSections').click(function(e) {
e.preventDefault();
var formSectionTitle = $(this).siblings('div').text(); // gets section title that was clicked
console.log(formSectionTitle);
assetConfigIdForURL = assetConfigIdForURL.replace(/\s+/g,'-');
woTypeCodeForURL = woTypeCodeForURL.replace(/\s+/g,'-');
woMaintTypeCode = woMaintTypeCode.replace(/\s+/g,'-');
formSectionTitle = formSectionTitle.replace(/\s+/g,'-');
// Change href dynamically to set url parameters
$(this).attr("href",'airSystem.html?insp_asset_config_id='+assetConfigIdForURL+'&wo_type_code='+woTypeCodeForURL+'&wo_maint_type_code='+woMaintTypeCode+'&formSection='+formSectionTitle+'&wo_id='+woIdForURL+'');
});
Change to this.
$('.individualFormSections').click(function(e) {
e.preventDefault();
var formSectionTitle = $(this).siblings('div').text(); // gets section title that was clicked
console.log(formSectionTitle);
assetConfigIdForURL = assetConfigIdForURL.replace(/\s+/g,'-');
woTypeCodeForURL = woTypeCodeForURL.replace(/\s+/g,'-');
woMaintTypeCode = woMaintTypeCode.replace(/\s+/g,'-');
formSectionTitle = formSectionTitle.replace(/\s+/g,'-');
// Change href dynamically to set url parameters
$(this).attr("href",'airSystem.html?insp_asset_config_id='+assetConfigIdForURL+'&wo_type_code='+woTypeCodeForURL+'&wo_maint_type_code='+woMaintTypeCode+'&formSection='+formSectionTitle+'&wo_id='+woIdForURL+'');
});