I'm learning the basics with Javascript and I'm trying to do a modal that replace an alert, I'm almost done but I have a problem with the querySelector on the button to close it. It returns undefined even if I check it with the if conditional.
function getTemplate(templateName) {
let template = document.querySelector(templateName);
return template.innerHTML;
}
function createFragment(htmlStr) {
let frag = document.createDocumentFragment();
let temp = document.createElement('div');
temp.innerHTML = htmlStr;
while (temp.firstChild) {
frag.appendChild(temp.firstChild);
}
return frag;
}
function putTemplate(template) {
document.body.appendChild(createFragment(template));
}
function openAlert(alertName, btnOpen) {
let openBtn = document.querySelector(btnOpen);
openBtn.addEventListener('click', function () {
putTemplate(getTemplate(alertName));
});
}
function closeAlert(alertName, btnClose) {
let closeBtn = document.querySelector(btnClose);
if (closeBtn) {
closeBtn.addEventListener('click', function () {
let alertWrapper = document.querySelector(alertName);
alertWrapper.parentNode.removeChild(alertWrapper);
});
}
}
function Alert(alertName, btnOpen, btnClose) {
openAlert(alertName, btnOpen);
closeAlert(alertName, btnClose);
}
Alert('#alertTemplate', '.activeBtn', '.deactive');
And this is the markup:
<template id="alertTemplate">
<div id="alertWrapper">
<h1></h1>
<div class="alertBox confirmAlert" role="alert">
<p></p>
<button class="closeBtn deactive troll"></button>
<button class="acceptBtn deactive"></button>
</div>
</div>
</template>
``
Related
I am stuck on this problem. I am coding a task platform app. Whenever I try to save, the task clones itself. After each "Save Changes," there are more and more clones. I have rewritten the code so many times. But still, I am not successful. Please help me to find the error.
$("#taskSave").click(() => {
const task = {
id: Date.now(),
imageUrl: $("#imageInput").val(),
title: $("#titleInput").val(),
description: $("#descriptionInput").val(),
type: $("#typeInput").val(),
};
$("#overlay").hide();
todos.push(task);
saveStorage(todos);
// reset input values
$("#imageInput").val("");
$("#titleInput").val("");
$("#descriptionInput").val("");
$("#typeInput").val("");
});
function saveStorage(todos) {
localStorage.setItem("todos", JSON.stringify(todos));
display(todos);
};
function display(todos) {
$("#taskBoard").innerHTML = "";
// .html("");
todos.forEach(item => {
let c = document.createElement("div");
c.setAttribute("class", "card");
c.setAttribute('id', item.id);
c.innerHTML = `
<div class="cardTop">
<div class="binContainer">
<div class="binImage"></div>
</div>
</div>
<img src="${item.imageUrl}" alt="task image">
<h2>${item.title}<h2>
<p>${item.description}</p>
<div class="cardType">${item.type}</div>
`;
$("#taskBoard").append(c);
// end
});
};
I've created a minimal working example, and the problem is in the cleanup of the HTML. You cannot use innerHTML on the JQuery object, or you use its html function or you need to retrieve the javascript object with $("#taskBoard")[0].
// You can use:
$("#taskBoard").html("");
// or
// document.getElementById("taskBoard").innerHTML = "";
// or
// $("#taskBoard")[0].innerHTML = "";
// But not:
// $("#taskBoard").innerHTML = "";
The working example here on JSFiddle (on SO dont work localStorage)
let todos = [];
$("#taskSave").click(() => {
const task = {
id: Date.now()
};
todos.push(task);
saveStorage(todos);
});
function saveStorage(todos) {
localStorage.setItem("todos", JSON.stringify(todos));
display(todos);
console.log(todos);
};
function display(todos) {
$("#taskBoard").html("");
// or
// document.getElementById("taskBoard").innerHTML = "";
// or
// $("#taskBoard")[0].innerHTML = "";
// But not
// $("#taskBoard").innerHTML = "";
todos.forEach(item => {
let c = document.createElement("div");
c.innerHTML = `
<p>${item.id}</p>
`;
$("#taskBoard").append(c);
});
};
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<button id="taskSave">
SAVE
</button>
<div id="taskBoard">
</div>
I use the JavaScript code below to get and log the attribute value of a div.
I want to rewrite the JavaScript code using React. When I tried doing the same code in React. I get error: e.path.find is not a function
How The Function Works:
First,after clicking mealsEl, e.path.find is used to go through all the child elements of meals EL,
Then it returns the child elements containing the class Name: 'meal-info'
Then it checks if the child element containing class Name 'meal-info' also has an attribute of 'data-meal-id.
Then it logs the value of 'data-meal-id'
const mealsEL = document.getElementById('meals')
mealsEL.addEventListener('click', (e) => {
const mealInfo = e.path.find((item) => {
console.log(item)
if (item.classList) {
return item.classList.contains('meal-info');
} else {
return false;
}
});
console.log(mealInfo)
if (mealInfo) {
const mealID = mealInfo.getAttribute('data-mealid');
getMealByID(mealID);
}
});
<div class="container">
<div id="result-heading"></div>
<div id="meals" class="meals">
<div class="meal">
<img class="meal-img" src="https://source.unsplash.com/random" alt="" style="width:180px; height: 180px;border: solid #000 4px;"/>
<div class="meal-info" >
</div>
</div>
</div>
<div id="single-meal"></div>
</div>
<div id="result-heading"></div>
<div id="meals" class="meals">
<div class="meal">
<img class="meal-img" src="https://source.unsplash.com/random" alt="" style="width:180px; height: 180px;border: solid #000 4px;"/>
<div class="meal-info" >
</div>
</div>
const mealsEL = document.getElementById('meals')
mealsEL.addEventListener('click', (e) => {
const mealInfo = e.path.find((item) => {
if (item.classList) {
console.log(item.classList)
return item.classList.contains("meal-info");
} else {
return false;
}
});
// console.log(mealInfo)
if (mealInfo) {
const mealID = mealInfo.getAttribute('data-mealid');
// getMealByID(mealID);
console.log(mealID)
} else {
console.log('no')
}
});
<div id="meals" class="meals">
<div class="meal">
<img class="meal-img" src="" alt="">
<div class="meal-info" data-mealid="75757">
<h3>Click To Show Data Meal Id</h3>
</div>
</div>
<!-- When mealsEL is clicked, the function uses e.path.find to check if the mealsEl children element contain a className of 'meal-info' and stores the result in the variable `const meal-info`
// The second if statement checks if the child element containing className meal-info has an attribute of 'data-mealid'
// Then the value of data-mealid attribute from the child element is logged to the console
</div> -->
// React Code
const getMealByID = (e) => {
const NativePath = e.nativeEvent()
const mealInfo = NativePath.path.find((item) => {
console.log(mealInfo)
if (item.classList) {
return item.classList.contains('meal-info');
} else {
return false;
}
});
if (mealInfo) {
const mealID = mealInfo.getAttribute('data-mealid');
getMealByID(mealID);
}
}
<div id="meals" className="meals" onClick={getMealByID}>
{meals &&
meals.map((meal) => {
const src = meal.strMealThumb;
const mealID = meal.idMeal;
const alt = meal.strMeal;
const index = meal.idMeal;
// const mealIng = meal.strIngredient1;
const mealIng = [];
for (let i = 1; i <= 20; i++) {
if (meal[`strIngredient${i}`]) {
mealIng.push(
`${meal[`strIngredient${i}`]} - ${meal[`strMeasure${i}`]}`
);
} else {
break;
}
}
return (
<div className="meal" key={index}>
<img className="meal-img" src={src} alt="{alt}" />
<div className="meal-info" data-mealid={mealID}>
<h3>{alt}</h3>
</div>
<h2>{mealIng}</h2>
</div>
);
})}
You are getting the error because you are calling getMealByID with an invalid argument, the function expect an event as argument:
if (mealInfo) {
const mealID = mealInfo.getAttribute('data-mealid');
getMealByID(mealID); //--> mealID is not an event
}
Also I think you can get the mealInfo node using less code:
const getMealByID = e => {
const node = e.target;
const mealInfo = node.querySelector(".meal-info");
if (mealInfo) {
const mealID = mealInfo.getAttribute("data-mealid");
...
}
};
// In order to get the data-mealid attribute value from a div
// by using its parent's element . I had to use
// e.target.getAttribute("data-mealid") on a click event from the parents element.
function App() {
const getMealInfoAttribute = e => {
const mealIDData = e.target.getAttribute("data-mealid")
console.log(mealID)
};
return (
<div id="meals" className="meals" onClick={getMealInfoAttribute}>
<div className="meal" key={index}>
<img className="meal-img" src={src} alt="{alt}" />
<div className="meal-info" data-mealid={mealID}>
<h3>{alt}</h3>
</div>
</div>
);
</div>)
}
Is there a way to simplify the below code by using an array? For example, when button 1 (with the index of 0) in the HTML is clicked, could that be used to get a value at index 0 in another array?
function f1() {
document.getElementById("dis").innerHTML = "JoeMae";
}
function f2() {
document.getElementById("dis").innerHTML = "TanakaMae";
}
function f3() {
document.getElementById("dis").innerHTML = "James";
}
function f4() {
document.getElementById("dis").innerHTML = "Deus";
}
<button onclick="f1()">no.1</button>
<button onclick="f2()">no.2</button>
<button onclick="f3()">no.3</button>
<button onclick="f4()">no.4</button>
<p id="dis"></p>
You can simplify without using array:
<button onclick="f('JoeMae')">no.1</button>
<button onclick="f('TanakaMae')">no.2</button>
<button onclick="f('James')">no.3</button>
<button onclick="f('Deus')">no.4</button>
<p id="dis"></p>
function f(str) {
document.getElementById("dis").innerHTML = str;
}
Use another array such that the nth index of that array corresponds to the nth button:
const texts = [
"JoeMae",
"TanakaMae",
"James",
"Deus"
];
const dis = document.getElementById("dis");
document.querySelectorAll('button').forEach((button, i) => {
button.addEventListener('click', () => {
dis.textContent = texts[i];
});
});
<button>no.1</button>
<button>no.2</button>
<button>no.3</button>
<button>no.4</button>
<p id="dis"></p>
Note that unless you're deliberately inserting HTML markup, you should probably use textContent, not innerHTML. (textContent is faster and safer)
Here's an approach that's vanilla JS. I used the dataset API to connect each button to its data, then a single handler to retrieve and display this data.
"use strict";
function byId(id){return document.getElementById(id)}
function newEl(tag){return document.createElement(tag)}
window.addEventListener('load', onLoaded, false);
function onLoaded(evt)
{
var responseArray = ['JoeMae', 'TanakaMae', 'James', 'Deus'];
responseArray.forEach( function(arrElem, elemIndex, arr)
{
var btn = newEl('button');
btn.textContent = `no.${elemIndex+1}`;
btn.dataset.response = arrElem;
btn.addEventListener('click', onClick, false);
document.body.appendChild(btn);
}
);
function onClick(evt)
{
let text = this.dataset.response;
byId('dis').textContent = text;
}
}
<p id='dis'></p>
Here's a slightly cleaner and more flexible example how to implement this type of functionality.
If you are having a lot of rendering functionality like this, I would recommend you to use a library/framework for it, though.
const buttonDefinitions = [
{title: 'no.1', name: 'Monica'},
{title: 'no.2', name: 'Erica'},
{title: 'no.3', name: 'Rita'},
{title: 'no.4', name: 'Tina'}
];
const buttonContainer = document.getElementById('buttonContainer');
const resultContainer = document.getElementById('resultContainer');
for (const buttonDefinition of buttonDefinitions) {
const button = document.createElement('button');
button.innerHTML = buttonDefinition.title;
button.onclick = () => {
resultContainer.innerHTML = buttonDefinition.name;
};
buttonContainer.appendChild(button);
}
<div id="buttonContainer"></div>
<div id="resultContainer"></div>
You can pass the element to the function and access the element data-attributes
In the below example I am passing data-name
function f(element) {
document.getElementById("dis").innerHTML = element.dataset["name"];
}
<button data-name="JoeMae" onclick="f(this)">no.1</button>
<button data-name="TanakaMae" onclick="f(this)">no.2</button>
<button data-name="James" onclick="f(this)">no.3</button>
<button data-name="Deus" onclick="f(this)">no.4</button>
<p id="dis"></p>
Explanation to why this is not a duplicate: My code is already working, I have included as a comment. The question is why the this context change when I include it to click handler function.
I'm attempting a calculator project in React. The goal is to attach onclick handlers to number buttons so the numbers are displayed on the calculator display area. If the handler is written directly to render method it is working, however, if I'm trying from the ComponentDidMount I get an error this.inputDigit is not a function. How do I bind this.inputDigit(digit) properly?
import React from 'react';
import './App.css';
export default class Calculator extends React.Component {
// display of calculator initially zero
state = {
displayValue: '0'
}
//click handler function
inputDigit(digit){
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
componentDidMount(){
//Get all number keys and attach click handler function
var numberKeys = document.getElementsByClassName("number-keys");
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber); // This is not working
};
for (var i = 0; i < numberKeys.length; i++) {
numberKeys[i].onclick = myFunction;
}
}
render() {
const { displayValue } = this.state;
return (
<div className="calculator">
<div className="calculator-display">{displayValue}</div>
<div className="calculator-keypad">
<div className="input-keys">
<div className="digit-keys">
{/*<button className="number-keys" onClick={()=> this.inputDigit(0)}>0</button> This will Work*/}}
<button className="number-keys">0</button>
<button className="number-keys1">1</button>
<button className="number-keys">2</button>
<button className="number-keys">3</button>
<button className="number-keys">4</button>
<button className="number-keys">5</button>
<button className="number-keys">6</button>
<button className="number-keys">7</button>
<button className="number-keys">8</button>
<button className="number-keys">9</button>
</div>
</div>
</div>
</div>
)
}
}
Thats because you are writing it inside a function which is not bound,
Use
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber);
}.bind(this);
or
const myFunction = () => {
var targetNumber = Number(this.innerHTML);
return this.inputDigit(targetNumber);
}
After this you need to bind the inputDigit function as well since it also uses setState
//click handler function
inputDigit = (digit) => {
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
Since you want to use the button text as well, in that case you should use a separate variable in place of this to call the inputDigit function like
class Calculator extends React.Component {
// display of calculator initially zero
state = {
displayValue: '0'
}
//click handler function
inputDigit(digit){
const { displayValue } = this.state;
this.setState({
displayValue: displayValue+String(digit)
})
}
componentDidMount(){
//Get all number keys and attach click handler function
var numberKeys = document.getElementsByClassName("number-keys");
var that = this;
var myFunction = function() {
var targetNumber = Number(this.innerHTML);
console.log(targetNumber);
return that.inputDigit(targetNumber); // This is not working
};
for (var i = 0; i < numberKeys.length; i++) {
numberKeys[i].onclick = myFunction;
}
}
render() {
const { displayValue } = this.state;
return (
<div className="calculator">
<div className="calculator-display">{displayValue}</div>
<div className="calculator-keypad">
<div className="input-keys">
<div className="digit-keys">
{/*<button className="number-keys" onClick={()=> this.inputDigit(0)}>0</button> This will Work*/}
<button className="number-keys">0</button>
<button className="number-keys">1</button>
<button className="number-keys">2</button>
<button className="number-keys">3</button>
<button className="number-keys">4</button>
<button className="number-keys">5</button>
<button className="number-keys">6</button>
<button className="number-keys">7</button>
<button className="number-keys">8</button>
<button className="number-keys">9</button>
</div>
</div>
</div>
</div>
)
}
}
ReactDOM.render(<Calculator/>, document.getElementById('app'))
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>
Bind it in the constructor
constructor(props) {
super(props);
this.inputDigit = this.inputDigit.bind(this);
}
I have the following html:
<div ng-show=showMarketingNav>
...
</div>
<div ng-show=showProductsNav>
...
</div>
<div ng-show=showSettingsNav>
...
</div>
What I want to do is to easily be able to hide all but one of the divs from another controller. I thought I could be clever and do the following:
var subNavMenuDisplays = {
Marketing: $scope.showMarketingNav,
Products: $scope.showProductsNav,
Settings: $scope.showSettingsNav
}
$rootScope.hideContextMenu = function () {
for (var category in subNavMenuDisplays) {
subNavMenuDisplays[category] = false;
}
}
$rootScope.setContextMenu = function (name) {
$rootScope.hideContextMenu();
subNavMenuDisplays[name] = true;
}
but this obviously does not work as $scope.showMarketingNav etc. will be passed as value, not reference.
The following works, but is not exactly nice to work with:
$rootScope.hideContextMenu = function () {
$scope.showMarketingNav = false;
$scope.showProductsNav = false;
$scope.showSettingsNav = false;
}
$rootScope.setContextMenu = function (name) {
$rootScope.hideContextMenu();
if (name == "Marketing") {
$scope.showMarketingNav = true;
}
if (name == "Products") {
$scope.showProductsNav = true;
}
if (name == "Settings") {
$scope.showSettingsNav = true;
}
}
Is there a way to grab $scope.showMarketingNav by reference, or another clever way around this?
I'd prefer not using eval to concatenate variable names.
You can place an object on the $scope and then toggle it dynamically:
$scope.show = {};
$rootScope.setContextMenu = function (name) {
$scope.show = {};
$scope.show[name] = true;
}
And the Html:
<div ng-show="show.Marketing">
...
</div>
<div ng-show="show.Products">
...
</div>
<div ng-show="show.Settings">
...
</div>
Here's a plunker demonstrating the change.
You can assign simple updater functions in that object:
Marketing: function(val) { $scope.showMarketingNav = val },
Products: function(val) { $scope.showProductsNav = val},
Settings: function(val) { $scope.showSettingsNav = val}
Then call it:
subNavMenuDisplays[name](true);