What am I doing wrong?
If I select the same element from Console and add a click listener… it works… however, this code doesn't work
const Template = require('./Template')
const mustache = require('mustache')
const DOMHelper = require('./DOMHelper')
class SymbolDefiner extends Template {
constructor (key, data) {
super(key)
this._data = data
}
render () {
super.render(arguments)
const parent = this._parent
const props = this._props
const eventListener = this._listener
const addSymbol = DOMHelper.createElement('button.btn.btn-primary', parent)
const btnText = document.createTextNode('Add Symbol')
addSymbol.appendChild(btnText)
console.log(addSymbol) // this log succeeds
addSymbol.addEventListener('click', function () {
console.log('xx')
})
}
}
module.exports = SymbolDefiner
The DOMHelper is just an easy function to add elements into the DOM by using very simple 'div#id.class-1.class-2' string.
Here is the code for DOMHelper:
const obj = {}
obj.createElement = function (selector, parent, props) {
const details = selector.split('#')
let eleName, eleId, classList
if (details.length > 1) {
// id is present
eleName = details[0]
const attribs = details[1].split('.')
eleId = attribs.shift()
classList = attribs
} else {
const attribs = details[0].split('.')
eleName = attribs.shift()
classList = attribs
}
const element = document.createElement(eleName)
element.setAttribute('id', eleId || '')
for (var i in classList) {
element.classList.add(classList[i])
}
if (props) {
for (var key in props) {
element.setAttribute(key, props[key])
}
}
if (parent) {
parent.appendChild(element)
}
return element
}
module.exports = obj
in the index.html page I added all the code to the script, this is based off of a blank electron quick getting start CLI from here
the only things I changed was I didn't separate out the DOMHelper, I just added it inline and I use obj.createElement() directly. It worked perfectly, so everything you have written is correct. The only possible thing might be checking every reference of the import require('./renderer.js') but most likely not since the file itself is blank for me.
so your code is all golden hopefully this helps where to look
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Hello World!</title>
</head>
<body>
<h1>Hello World!</h1>
<!-- All of the Node.js APIs are available in this renderer process. -->
We are using Node.js <script>document.write(process.versions.node)</script>, Chromium
<script>document.write(process.versions.chrome)</script>, and Electron
<script>document.write(process.versions.electron)</script>.
<div id="parentNode"></div>
</body>
<script>
// You can also require other files to run in this process
require('./renderer.js')
var obj = {};
obj.createElement = function(selector, parent, props) {
const details = selector.split('#')
let eleName, eleId, classList
if (details.length > 1) {
// id is present
eleName = details[0]
const attribs = details[1].split('.')
eleId = attribs.shift()
classList = attribs
} else {
const attribs = details[0].split('.')
eleName = attribs.shift()
classList = attribs
}
const element = document.createElement(eleName)
element.setAttribute('id', eleId || '')
for (var i in classList) {
element.classList.add(classList[i])
}
if (props) {
for (var key in props) {
element.setAttribute(key, props[key])
}
}
if (parent) {
parent.appendChild(element)
}
return element
}
var parent = document.getElementById('parentNode');
const addSymbol = obj.createElement('button.btn.btn-primary', parent)
const btnText = document.createTextNode('Add Symbol')
addSymbol.appendChild(btnText)
console.log(addSymbol) // this log succeeds
addSymbol.addEventListener('click', function() {
console.log('xx')
})
</script>
</html>
Related
JS newbie here.
Hi, I am trying to add an instance as a Node to a div element I created,
but it gives this error:
TypeError: Failed to execute 'appendChild' on 'Node': parameter 1 is not of type 'Node'.
Should I not do it this way? is this wrong?
I want to create div element for every GitHub user I am searching for, adding 3 tags for each under a div.
I thought doing so by building a class with methods will help with DRY.
class AddElement {
constructor(name, tag) {
this.name = name;
this.tag = tag;
}
elem() {
this.name = document.createElement(this.tag);
document.body.append(this.name);
}
elemProp(property) {
this.elem();
this.name.innerHTML = property;
}
elemError() {
this.elem();
this.name.innerHTML = this.name + ' does not exist.';
this.name.style.color = 'red';
}
elemImg(property) {
this.elem();
this.name.src = property;
this.name.height = 150;
}
}
async function getJson(name) {
let response = await fetch(`https://api.github.com/users/${name}`);
if (response.status === 404) {
new AddElement('notFound', 'h3').elemError();
throw new Error('User not found!');
} else {
let data = await response.json();
return data;
}
}
async function gitUserInfo(name) {
let jsonData = await getJson(name);
let img = jsonData.avatar_url;
let login = jsonData.login;
let bio = jsonData.bio;
let arr = [
new AddElement('userimg', 'img').elemImg(img),
new AddElement('login', 'h2').elemProp(login),
new AddElement('bio', 'h3').elemProp(bio),
]
let div = document.createElement('div');
document.body.append(div);
arr.forEach(item => div.appendChild(item))
}
let search = document.getElementById('searchUser');
search.addEventListener('keyup', function(e) {
if (e.key === 'Enter') {
gitUserInfo(search.value)
} else if (search.value === '') {
removeTag('h2');
removeTag('h3');
removeTag('img');
}
});
function removeTag(tag) {
for (let i = 0; i < document.getElementsByTagName.length; i++) {
document.getElementsByTagName(tag)[i].remove();
}
}
You are not creating an array of htmlElement's but an array of AddElements.
However you were getting the exception as you were creating an array of undefined objects because you were calling the ctor and then some properties that had no return statement so arr looked like this:
arr[ undefined, undefined, undefined ]
It is bad practice to initialize an array in this manner. Initialize your objects first, apply the jsonData, THEN add them to the array. OR simply bypass the array and just modify the dom as you go.
Also, inside elem() you are modifying the dom by adding a newly created element to <body> before you've processed the data. If it were to work as you have it written you would be appending these objects twice.
In the code below I've stripped out the async function and just created a hard-coded json object for brevity.
Also, elements created by document.createElement() contain all the properties you need so there is no reason to create your own.
class AddElement {
constructor(name, tag) {
this.obj = document.createElement(tag);
this.obj.name = name;
}
elemProp(txt) {
this.obj.innerHTML = txt;
return this.obj;
}
elemImg(imgUrl) {
this.obj.src = imgUrl;
this.obj.height = 150;
return this.obj;
}
}
let jsonData = {
avatar_url: "https://picsum.photos/200",
login: "somelogin",
bio: "Lorem ipsum dolor sit amet"
}
function gitUserInfo(data) {
let arr = [
new AddElement('userimg', 'img').elemImg(data.avatar_url),
new AddElement('login', 'h2').elemProp(data.login),
new AddElement('bio', 'h3').elemProp(data.bio),
]
let div = document.createElement('div');
arr.forEach(item => div.appendChild(item));
document.body.append(div);
}
gitUserInfo(jsonData)
I have the following piece of JQuery code which searches an html document for instances of elements with the class <xyz>-annotation-invisible and replaces it with <xyz>-annotation-visible. The catch is that if a particular element already has class <abc>-annotation-visible, then I want to remove all classes of the form *-annotation-visible and *-annotation-invisible and replace them with the class multiple-annotation-visible. How can I check if a particular element already has <abc>-annotation-visible?
const urlParams = new URLSearchParams(window.location.search);
const annotationTypes = urlParams.get('annotypes').split(',');
const multipleVisibleClass = "multiple-annotation-visible";
$(document).ready(function () {
for (var i=0; i<annotationTypes.length; i++)
{
var annotype = annotationTypes[i];
var annotationVisibleClass = `${annotype}-annotation-visible`;
var annotationInvisibleClass = `${annotype}-annotation-invisible`;
var elem = $(`.${annotationInvisibleClass}`);
if (elem.hasClass(anyVisibleClass)) # not sure how to do this part
{
elem.removeClass(anyVisibleClass);
elem.addClass(multipleVisibleClass);
}
else
{
elem.addClass(annotationVisibleClass);
}
elem.removeClass(annotationInvisibleClass);
}
});
You could use is() and the attribute ends with selecor :
let elem = $("div");
console.log(elem.is("[class$='-annotation-visible']"));
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
<div class="<abc>-annotation-visible">test</div>
You can get the classes of the targeted elements, split the classes into array and then manipulate the array to get desired results. Try this
const urlParams = new URLSearchParams(window.location.search);
const annotationTypes = urlParams?.get('annotypes')?.split(',') ?? [];
$(document).ready(function(){
annotationTypes.forEach(annotype => {
let visible = `${annotype}-annotation-visible`;
let invisible = `${annotype}-annotation-invisible`;
let multiple = "multiple-annotation-visible";
$(`.${invisible}, .${visible}`).each(function(){
let arr = [];
$(this).attr('class').split(' ').forEach(cls => {
if(cls === invisible){
arr.push(visible);
}else if(cls === visible){
arr.push(multiple);
}else{
arr.push(cls);
}
});
$(this).attr('class', arr.join(' '));
})
})
});
This is a socket.io lobby library for managing the users list.
I created an Array extension class with custom methods. My removeUser method does not work.
Logging shows that inside of the method, it does work - the user has been removed.
Logging outside shows no change.
I believe my issue is one of references. The reference in index.js 'userList' is one reference.
var userList = require("./userList")();
However when I reassign userList in the method, it creates another reference.
userArray = result;
This newly-created reference is not known by the index.js, which sees no change in the userList object.
index.js
My server (simplified for example)
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
const server = require("http").createServer(app);
const io = require("socket.io")(server);
var userList = require("./userList")();
io.on("connection", (socket) => {
// get user from session
userList.addUser(s_user);
socket.on("disconnect", () => {
userList.removeUser(s_user);
});
});
userList.js (Original)
An extended Array class for managing the list of users in my lobby.
function createUserList() {
let userArray = [];
userArray.addUser = (user) => {
userArray.push(user);
};
userArray.removeUser = (user) => {
let userId = user.user_id;
for (let i = 0; i < userArray.length; i++) {
if (userArray[i]["user_id"] === userId && userId !== undefined) {
let firstHalf = userArray.slice(0, i);
let secondHalf = userArray.slice(i+1, userArray.length);
let result = firstHalf.concat(secondHalf);
userArray = result;
}
}
};
return userArray;
}
My Solution
My solution was to create a closure. An array manager object contains the methods for userList management, and through a closure has access to the userArray. (code below this paragraph)
Pros
The userArray can modified or reassigned without reference issue (within the userList library)
userArray = [] //ok
(I also don't have to re-attach methods on re-assignment)
Cons
I can't use Array prototype methods
let length = userList.length // method doesn't exist on management object
let listCopy = userList // returns an object, not the list
I must use my custom built methods
let length = userList.getLength()
let listCopy = userList.getList()
Does anyone have other interesting solutions?
userList.js (V2)
function createUserList() {
let userArray = []
let arrayManager = {
addUser: (user) => {
userArray.push(user);
},
removeUser: (user) => {
let userId = user.user_id;
for (let i = 0; i < userArray.length; i++) {
if (userArray[i]["user_id"] === userId && userId !== undefined) {
let firstHalf = userArray.slice(0, i);
let secondHalf = userArray.slice(i+1, userArray.length);
let result = firstHalf.concat(secondHalf);
userArray = result;
}
}
},
getList: () => {
return userArray;
},
getLength: () => {
return userArray.length;
}
};
return arrayManager;
}
module.exports = createUserList;
this are the global variables
const navigation = document.getElementById('nav_list');
const sections = document.querySelectorAll('section');
const links = document.querySelectorAll('li')
let navLinks = '';
this function was use to create the nav.
const navMaker = function() {
sections.forEach(section => {
const sectId = section.id;
const sectNav = section.dataset.name;
navLinks = navLinks + `<li><a class="links_menu" href="#${sectId}">${sectNav}</a>
</li>`;
});
navigation.innerHTML = navLinks;
}
navMaker();
This functions were use to to add and remove the active class
this const is to access all Lists:
const linkErrays = document.querySelectorAll('li');
const level = (section) => {
return Math.floor(section.getBoundingClientRect().top)
}
const delateClass = (section) => {
section.classList.remove('active_section');
}
const addClass = (condition, section) => {
if (condition) {
section.classList.add('active_section');
}
}
this is the class activation when someone scroll
const classActivation = () => {
linkErrays.forEach(section => {
const elementLevel = level(section);
inviewport = () => elementLevel < 50 && elementLevel >= -650
delateClass(section);
addClass(inviewport(), section);
});
}
window.addEventListener('scroll', classActivation);
Just add a scroll event to the window and then either remove the class or toggle it.
i.e
window.addEventListener('scroll', ()=>{ element.classList.remove('active'); })
I may be wrong but I think selecting your li only once and before actually generating them may be what cause the issue, I would suggest generating them before like so:
const navigation = document.getElementById('nav_list');
const sections = document.querySelectorAll('section');
// using map / join to avoid the navLinks variable
navigation.innerHTML = sections.map(section => {
const sectId = section.id;
const sectNav = section.dataset.name;
return `<li><a class="links_menu" href="#${sectId}">${sectNav}</a></li>`;
}).join('\n');
// query the li after creation
const links = document.querySelectorAll('li')
tell me if this help.
edit: I completly missed that you re-query them after in the linkErrays variables, so my answer will not help with your current problem.
I am new to programming, and I don't quite grasp the idea of utilizing class constructor in real life. For instance, let's just say I am trying to create a DOM event handler so I can take user input and push it into CreateTodoList.todos array.
class CreateTodoList {
constructor(list) {
this.todoList = list;
this.todos = [];
}
Then let's just assume that I have built addTodo() function which takes text parameter where an user enters her/his todo.
addTodo(text) {
this.todos.push(text);
this.todoList.appendChild(CreateTodoList.addtoList(text));
}
Here, addtoList creates DOM element that takes value of the user input.
This addTodo function, then pushes the text parameter into the array I made in constructor, while also calling addtoList that makes the DOM element.
Now, let's say I click on "add" button where it takes user input value.
I will build an event handler that responds to click which will add user input to the todoList.
CreateTodoList.eventHandler('click', (e) => {
let userText.todos = document.querySelector(#userInput).value;
addTodo(userText);
})
I am trying to build an eventHandler here, so I can add user input to todoList, and have implemented this several times, but had no luck but receiving reference error.
Below is my full code.
/** #format */
const add = document.querySelector('#btn_add');
let addInput = document.querySelector('#add');
const form = document.querySelector('#form');
class CreateTodoList {
constructor(list) {
this.todoList = list;
this.todos = [];
}
addtoList(text) {
let checkboxEl = document.createElement('span');
checkboxEl.classList.add('round');
let checkboxEl2 = document.createElement('input');
checkboxEl2.id = 'checkbox';
checkboxEl2.type = 'checkbox';
let checkboxEl3 = document.createElement('label');
checkboxEl3.htmlFor = 'checkbox';
checkboxEl.appendChild(checkboxEl2);
checkboxEl.appendChild(checkboxEl3);
let todoTextEl = document.createElement('input');
todoTextEl.value = text;
todoTextEl.disabled = true;
todoTextEl.classList.add('edit_input');
todoTextEl.id = 'edit_input';
todoTextEl.type = 'text';
todoTextEl.name = 'edit_input';
let todoTextEl2 = document.createElement('label');
todoTextEl2.htmlFor = 'edit_input';
let editEl = document.createElement('i');
editEl.classList.add('far');
editEl.classList.add('fa-edit');
let deleteEl = document.createElement('i');
deleteEl.classList.add('far');
deleteEl.classList.add('fa-trash-alt');
let dateEl = document.createElement('small');
dateEl.textContent = timeago.format(new Date());
let liEl = document.createElement('li');
liEl.appendChild(checkboxEl);
liEl.appendChild(todoTextEl);
liEl.appendChild(todoTextEl2);
liEl.appendChild(editEl);
liEl.appendChild(deleteEl);
liEl.appendChild(dateEl);
let list = document.querySelector('ul');
list.appendChild(li);
return liEl;
}
removeFromList(text) {
let list = document.querySelector('ul');
let childs = Array.from(list.childNodes);
let removable = child.find((i) => i.innerText === text);
return item;
}
//todos 배열(todo 데이터에) text를 추가한다.
//todoList 에 liEL(리스트 엘레먼트) 를 append 한다.
addTodo(text) {
this.todos.push(text);
this.todoList.appendChild(CreateTodoList.addtoList(text));
}
removeTodo(text) {
let removed = this.todos.filter((el) => el !== text);
todo.todoList.removeChild(CreateTodoList.removeFromList(text));
this.todos = removed;
}
get getList() {
return this.todos;
}
}
class Handlers {}