Passing onclick event into template literals without global function - javascript

I've been working on adding onclick event in template literals with plain javascript (without jquery only javascript). As you can see the result html knows that onclick event on each div has function which will alert as I click but when I click, it didn't respond. It seems like suppose to work but somehow it is not working.
I got lots of help from Stackoverflow but most of the anwser was using global function. but I don't personally want to use global function since sometimes it cause some trouble.
so how can I actually pass the function into onclick event by using template literals?
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<div id="ul"></div>
</body>
<script src="/index.js"></script>
</html>
index.js
function test() {
const list = [
{ number: 1, check: () => alert("1") },
{ number: 2, check: () => alert("2") },
{ number: 3, check: () => alert("3") },
];
const $list = list.reduce((acc, item) =>
acc + `<div onclick='${item.check}'>${item.number}</div>`,""
);
const $ul = document.querySelector("#ul");
$ul.innerHTML = $list;
}
test();
result

Instead of building the DOM via HTML strings, create actual DOM elements.
const $ul = document.querySelector("#ul");
for (const item of list) {
const element = document.createElement('div');
element.textContent = item.number;
element.onclick = item.check;
$ul.appendChild(element);
}

Related

Jsdom load javascript code but doesn't run correctly

What is wrong with this code? It displays the says: hello bar from the out.js console.log but does not run the rest of the script doesn't add the link inside <div id="link"></div>
If I put the script directly in the code it works, but not in a .js file
teste2.js
const jsdom = require("jsdom");
const { JSDOM } = jsdom;
const dom = new JSDOM(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<div id="link"></div>
<p>Hello world</p>
<script src="http://localhost/2022/jsdom/out.js"></script>
</body>
</html>`, { resources: "usable", runScripts: "dangerously"});
const document = dom.window.document;
console.log(document.getElementsByTagName('html')[0].innerHTML);
out.js
let T = document.getElementById('link');
T.innerHTML = 'LINK';
console.log('bar says: hello');
JSDOM loads sub-resources asynchronously, so your Node.js code is accessing the DOM before the <script> code has executed.
This is also why the log of document.getElementsByTagName('html')[0].innerHTML appears before the log of 'bar says: hello'.
You can handle this by explicitly waiting for the load event:
const document = dom.window.document;
document.addEventListener('load', () => {
console.log(document.getElementsByTagName('html')[0].innerHTML);
});
You'll need more complex logic if the script itself does anything asynchronously. Firing a custom event that you can listen for is a good approach.

How to import js file from url and initialize a class in React?

I want to embed PitchPrint app on a React website. They have a vanilla html/js integration tutorial here. I added script tags with links to jQuery and their app file in my index.html file, as they require and then created a separate jsx file that suposed to return a button witch opens the app. The problem is, when I try to build, it throws an error 'PitchPrintClient' is not defined witch suposed to come from their files.
My index.html file:
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<script src="https://code.jquery.com/jquery-3.1.1.min.js"></script>
<script src="https://pitchprint.io/rsc/js/client.js"></script>
<title>App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
My jsx file:
import React from 'react';
const AppButton = () => {
let _launchButton = document.getElementById('launch_btn');
let _previewDiv = document.getElementById('pp_preview_div');
let _loaderDiv = document.getElementById('pp_loader_div');
_launchButton.setAttribute('disabled', 'disabled');
var ppclient = new PitchPrintClient({
apiKey: 'f80b84b4eb5cc81a140cb90f52e824f6', //Kinldy provide your own APIKey
designId: '3d8f3899904ef2392795c681091600d0', //Change this to your designId
custom: true
});
//Function to run once the app is validated (ready to be used)
var appValidated = () => {
_launchButton.removeAttribute('disabled'); //Enable the Launch button
_launchButton.onclick = () => ppclient.showApp(); //Attach event listener to the button when clicked to show the app
_loaderDiv.style.display = 'none'; //Hide the loader
};
//Function to run once the user has saved their project
var projectSaved = (_val) => {
let _data = _val.data; //You can console.log the _data varaible to see all that's passed down
if (_data && _data.previews && _data.previews.length) {
_previewDiv.innerHTML = _data.previews.reduce((_str, _prev) => `${_str}<img src="${_prev}">`, ''); //Show the preview images
}
};
ppclient.on('app-validated', appValidated);
ppclient.on('project-saved', projectSaved);
return <div>
<div id="pp_loader_div"><img src="https://pitchprint.io/rsc/images/loaders/spinner_new.svg" /></div>
<button id="launch_btn" >Launch Designer</button>
<div id="pp_preview_div"></div>
</div>;
};
export default AppButton;
PS: I know getElementById does not realy work with react, I'll deal with that later, for now I just want to initialize this app.
that's because the component is not mounted yet.
you need to call document.getElementById once the component is mounted, and in order to access dom elements inside the component you need to call it inside useEffect hook
useEffect(() => {
let _launchButton = document.getElementById("launch_btn");
let _previewDiv = document.getElementById("pp_preview_div");
let _loaderDiv = document.getElementById("pp_loader_div");
_launchButton.setAttribute("disabled", "disabled");
var ppclient = new PitchPrintClient({
apiKey: "f80b84b4eb5cc81a140cb90f52e824f6", //Kinldy provide your own APIKey
designId: "3d8f3899904ef2392795c681091600d0", //Change this to your designId
custom: true,
});
//Function to run once the app is validated (ready to be used)
var appValidated = () => {
_launchButton.removeAttribute("disabled"); //Enable the Launch button
_launchButton.onclick = () => ppclient.showApp(); //Attach event listener to the button when clicked to show the app
_loaderDiv.style.display = "none"; //Hide the loader
};
//Function to run once the user has saved their project
var projectSaved = (_val) => {
let _data = _val.data; //You can console.log the _data varaible to see all that's passed down
if (_data && _data.previews && _data.previews.length) {
_previewDiv.innerHTML = _data.previews.reduce(
(_str, _prev) => `${_str}<img src="${_prev}">`,
""
); //Show the preview images
}
};
ppclient.on("app-validated", appValidated);
ppclient.on("project-saved", projectSaved);
}, []);
Read more about React hooks and their constraints.
https://reactjs.org/docs/hooks-intro.html
also make sure to access global variables using window.PitchPrintClient
also, make sure your app is mounted once the dom is ready. by moving your script tags to the end of the body tag or using jquery on ready callback.
PS: The answer is not considering the best practices of writing react components, but I encourage you to use refs and minimize accessing to dom as much as you could.

How can I set up a function that can be referenced from several event listeners and use different properties for each element? (JS)

Edit: I found the solution (found at the bottom).
The problem that I am having is that I want to consolidate my code into one small function that can be referenced multiple times. I have a currently working code, which has several functions, one for each event-listener that my webpage has. I am trying to render objects after a user clicks a "button," which is really a div element. Here's some code:
//Constructor Function
function Object(objectType) {
this.objectType = objectType;
}
//Render function for the objects
Object.prototype.render = function() {
//Getting the elements, creating them, setting to variables to be used later.
let objectDiv = document.createElement('div');
objectDiv.setAttribute('class', `${this.objectType}Object`);
objectDiv.textContent = this.objectType;
let makeButton = document.querySelector(`#${this.objectType}Section`);
//Appending element to webpage
makeButton.appendChild(objectDiv);
};
//All the current code above works like it is supposed to. I have a few buttons already that work, but they each have their own function. Like so:
//Setting up Button1 code.
let button1 = document.getElementById("makeObj1");
//Event listener for Button1, which calls the Render function for the Button1 object.
button1.addEventListener("click", goMakeButton1);
function goMakeButton() {
let button1 = new Object('Obj1');
button1.render();
}
//And so forth three times, for each button.
//I want to make it so that there is one section, and I currently have it set up like so:
let button1 = {
element: document.getElementById('makeObj1'),
name: 'Obj1',
}
let button2 = {
element: document.getElementById('makeObj2'),
name: 'Obj2',
}
let button3 = {
element: document.getElementById('makeObj3'),
name: 'Obj3',
}
let button4 = {
element: document.getElementById('makeObj4'),
name: 'obj4',
}
catButton.element.addEventListener('click', makeObj);
dogButton.element.addEventListener('click', makeObj);
sheepButton.element.addEventListener('click', makeObj);
horseButton.element.addEventListener('click', makeObj);
function makeObj(object) {
console.log('I am called');
//To check my function gets called - it does.
let myObject = new Animal(this.name);
myObject.render();
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Animal Farm</title>
<link rel="stylesheet" href="./style/style.css" />
</head>
<body>
<h1>DOM Manipulation and Events</h1>
<!-- create one big container -->
<main id="mainContainer">
<section id="Obj1Section">
<div class="make" id="makeButton1">Make Object 1</div>
</section>
<section id="Obj2Section">
<div class="make" id="makeButton2">Make Object 2</div>
</section>
<section id="Obj3Section">
<div class="make" id="makeButton3">Make Object 3</div>
</section>
<section id="HorseSection">
<div class="make" id="makeButton4">Make Objecvt 4</div>
</section>
</main>
</body>
<script src="./script/script.js"></script>
</html>
This just gives me the error of not being able to append child of null. When I try to take in a parameter and give each event listener its own parameter to pass, that fails as well and doesn't even allow me to click them. What do I do here?
The fix:
What I did was run an arrow function that called another function, passing a parameter defined in the arrow function, rather than trying to pass a parameter directly to the function from the event listener (it would just run, and then become useless.)
"use strict";
//Creating the constructor function for the buttons
function Button(buttonName) {
this.buttonName = buttonName;
}
//Render function for Buttons. Able to be used for any button name.
Button.prototype.render = function () {
//Getting the elements, creating them, setting to variables to be used later.
let buttonDiv = document.createElement('div');
buttonDiv.setAttribute('class',`${this.buttonName}Object`);
buttonDiv.textContent = this.buttonName;
let makeButton = document.querySelector(`#${this.buttonName}Section`);
//Appending element to webpage
makeButton.appendChild(buttonDiv);
};
//Setting up the condensed function:
let buttonOne = document.getElementById('Obj1');
let buttonTwo = document.getElementById('Obj2');
let buttonThree = document.getElementById('Obj3');
let buttonFour = document.getElementById('Obj4');
function buttonMake(button){
let myButton = new Button (button);
myButton.render();
}
buttonOne.addEventListener('click', ()=>{
let theButton = 'Obj1';
makeButton(theButton);
})
buttonTwo.addEventListener('click', ()=>{
let theButton = 'Obj2';
makeButton(theButton);
})
buttonThree.addEventListener('click', ()=>{
let theButton = 'Obj3';
makeButton(theButton);
})
buttonFour.addEventListener('click', ()=>{
let theButton = 'Obj4';
makeButton(theButton);
})

Javascript Mediator In Browser Can't Find Instance Variables

Am working on a HTML/JS Mediator that filters a data_model when a user enters text to a field. Have used window.onload = init, and spent four hours trying to find why 'this' in the browser makes it print the calling object, and thus I can't get a class instance to refer to itself using 'this'
console.log(this.text_field)
in setup_list_view() works fine, seemingly because it's within the constructors scope. Running it outside the constructor, as per below, gives:
Cannot set property 'innerHTML' of undefined at HTMLInputElement.handle_text_field_changed
...
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
<body>
<script>
function init() {
var text_field = document.getElementById("text-field");
var list_view = document.getElementById("list-view")
form_mediator = new FormMediator(text_field, list_view)
}
class FormMediator {
constructor(text_field, list_view) {
this.setup_text_field(text_field)
this.setup_list_view(list_view)
}
setup_text_field(text_field) {
this.text_field = text_field;
this.text_field.onchange = this.handle_text_field_changed
}
setup_list_view(list_view) {
this.data_model = ['England', 'Estonia', 'France', 'Germany']
this.list_view = list_view
this.list_view.innerHTML = this.data_model
}
does_string_start_with_text_field_text(text) {
return false;
return text.startsWith('E')
}
handle_text_field_changed(){
this.list_view.innerHTML = 'new content' //this.data_model.filter(this.does_string_start_with_text_field_text)
}
}
window.onload = init
</script>
<input id="text-field"><button>Search</button>
<span id="list-view"></span>
</body>
</html>
Any help much appreciated.
The problem in your code occurs in this line:
this.text_field.onchange = this.handle_text_field_changed
A method, by default, won't carry its original binding context if assigned to a variable or another object's property. You need to bind this handle_text_field_changed method first, this way:
this.text_field.onchange = this.handle_text_field_changed.bind(this)

RxJS detect when observable has been subscribed to

I have a need to detect when an observable (observedEvents) has been subscribed to, and then subscribe to another observable (triggerEvent). I don't want to subscribe to triggerEvent manually, but only once and when observedEvents has a subscription.
Here is some code explaining what I am looking for:
// This just emits events
let emitting = new EventEmitter();
// This is the main Observable which someone else might
// have access to
let observedEvents = Rx.Observable.merge(
Rx.Observable.fromEvent(emitting, 'aba'),
Rx.Observable.fromEvent(emitting, 'bob')
)
// This trigger should get a subscription if observedEvents
// has one, i.e. when I subscribe to observedEvents
// that subscription activates this trigger
// I have made an attempt at this by calling skipUntil
// this however skips one event, but I don't want that
let triggerEvent = Rx.Observable.merge(
// these actions are things that can
// happen when the trigger is active
Rx.Observable.of('a').delay(200),
Rx.Observable.of('b').delay(400),
Rx.Observable.of('c').delay(600)
)
.skipUntil(observedEvents);
// Something else should be used to activate trigger
// I don't want to do this part manually
triggerEvent.subscribe(val => {
console.log(`Do something fancy with ${val}`);
});
//----------------------------------------------------
// Somewhere else in the code...
//----------------------------------------------------
observedEvents.subscribe(evt => {
console.log(`Some event: ${evt}`);
});
// At this point I want triggerEvent to become active
// because observedEvents has a subscription
setTimeout(() => {
emitting.emit('bob', 'world');
setTimeout(() => emitting.emit('aba', 'stackoverflow!'), 500);
}, 200);
<!DOCTYPE html>
<html>
<head>
<script src="https://npmcdn.com/#reactivex/rxjs#5.0.0-beta.7/dist/global/Rx.umd.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/5.1.0/EventEmitter.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
Is this possible?
I hope that explains what I'm looking for.
As I'm writing this, I'm thinking a solution with Subjects is probably what I need. I'm not sure, but I just need a nudge in the right direction or a solution if possible.
For rxjs > v7, see this answer
Answer
Sure enough I was right about using Subjects. The key was the observers list for Subject. Here is what I finally did:
let emitting = new EventEmitter();
let sub = new Rx.Subject();
// return this to users
let myGlobalSub = sub.merge(Rx.Observable.of(1, 2, 3));
// For internal use
let myObservers = Rx.Observable.fromEvent(emitting, 'evt');
console.log(`The number of subscribers is ${sub.observers.length}`);
// Only do something if myGlobalSub has subscribers
myObservers.subscribe(l => {
if (sub.observers.length) { // here we check observers
console.log(l);
}
});
// Somewhere in the code...
emitting.emit('evt', "I don't want to see this"); // No output because no subscribers
myGlobalSub.subscribe(l => console.log(l)); // One sub
emitting.emit('evt', 'I want to see this'); // Output because of one sub
console.log(`The number of subscribers is ${sub.observers.length}`);
<!DOCTYPE html>
<html lang=en>
<head>
<script src="https://unpkg.com/rxjs#5.5.11/bundles/Rx.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/5.2.5/EventEmitter.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>
The answer by #smac89 use the observersproperty which has been deprecated in RxJS 7 and is scheduled to be removed in version 8.
If you only want to know if an Observable has been subscribed to, you may use the observed boolean property instead.
There is currently no real equivalent to the observers array.
let emitting = new EventEmitter();
let sub = new rxjs.Subject();
// return this to users
let myGlobalSub = rxjs.merge(sub,rxjs.of(1, 2, 3));
// For internal use
let myObservers = rxjs.fromEvent(emitting, 'evt');
console.log(`Has the subject been subscribed to ? ${sub.observed}`);
// Only do something if myGlobalSub has subscribers
myObservers.subscribe(l => {
if (sub.observed) { // here we check observers
console.log(l);
}
});
// Somewhere in the code...
emitting.emit('evt', "I don't want to see this"); // No output because no subscribers
myGlobalSub.subscribe(l => console.log(l)); // One sub
emitting.emit('evt', 'I want to see this'); // Output because of one sub
console.log(`Has the subject been subscribed to ? ${sub.observed}`);
<!DOCTYPE html>
<html lang=en>
<head>
<script src="https://unpkg.com/rxjs#%5E7/dist/bundles/rxjs.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/EventEmitter/5.2.5/EventEmitter.min.js"></script>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>JS Bin</title>
</head>
<body>
</body>
</html>

Categories

Resources