I want to modify all elements with the same query - javascript

I want to modify all elements that use the same query via a function. (The function is not changed.)
r is read, w is write, and a is write appended to existing content. When using this as get(query).r , .w , .a , I want to make all elements corresponding.
For example, if the class of 3 p tags is html, if you write get(".html").w("hello") in js, all tags with the class of 3 html should be changed to 'hello'.
function get(id){
if (id) {
if (window === this) {
return new get(id);
}
var query = document.querySelectorAll(id)
for(var i = 0; i < query.length; i++) {
this.e = query[i];
return this;
}
} else {
return "getError : The attribute and value of a tag such as id or class specified by get does not exist. ";
}
}
get.prototype = {
r: function () {
return this.e.innerText;
},
w: function (writing) {
return this.e.innerText = writing;
},
a: function (writing) {
return this.e.innerText = this.e.innerText += writing;
}
};
js
<p href="#" class="test">html</p>
test
<script>
get(".test").w("hello")
</script>

For this line
for(var i = 0; i < query.length; i++) {
this.e = query[i];
return this;
}
You are only returning the first element. Thus, it only changes the inner text of the first element.
It seems that you are trying to assign a prototype to HTML elements, which is not recommended, so I modified your code.
I changed it so that the get function will return the whole HTMLCollection instead. Then I used the prototype of the get function to loop through the collection and set the text of the HTML elements.
js
<p href="#" class="test">html</p>
test
<script>
function get(id){
if (id) {
if (window === this) {
return new get(id);
}
var query = document.querySelectorAll(id)
return query
} else {
return "getError : The attribute and value of a tag such as id or class specified by get does not exist. ";
}
}
get.prototype = {
r: function () {
return this.e.innerText;
},
w: function (elems, writing) {
return elems.forEach(x => x.innerText = writing);
},
a: function (writing) {
return this.e.innerText = this.e.innerText += writing;
}
};
var elems = get(".test")
get.prototype.w(elems, "html")
</script>

You can also do this with a newer ES2015 class method, that uses static methods. Static methods are called directly on the class, without creating an instance/object of the class. That way, you can call the static method and pass the class object back into those methods.
More info here:
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes/static
https://www.w3schools.com/JsrEF/jsref_class_static.asp
See snippet below:
`use strict`
class Get {
constructor(selector) {
if (selector) return Array.from(document.querySelectorAll(selector));
return [];
}
static Read(elements) {
return elements.map(e => e.innerText)
};
static Write(elements, string) {
elements.forEach(e => e.innerText = string)
};
static Append(elements, string) {
elements.forEach(e => e.innerText = `${e.innerText}${string}`)
};
}
console.log(Get.Read(new Get(`.test`)));
Get.Append(new Get(`.test`), `: Appended Text`);
js
<p href="#" class="test">html</p>
test

Related

Javascript create custom attribute getter (like in python)

Say I have a class in JavaScript (yes bad bad class, bad class in JS, but its for web components, one has to use classes).
I would like to create a cached attribute getter for elements on a class, in python it would be this:
class Foo(object):
_elements = {}
def __getattr__(self, name):
if name in ['widget1', 'widget2', 'widget3']: # A long list of items, don't want to create getters for each one individiually
if not _elements.get(name):
self._elements[name] = self.getElementById(name)
return self._elements[name]
else:
# Default behaviour
return object.__getattr__(self, name)
This is the closest I got, but its ugly to use:
One must call it as this.el['widget1']
Instead of this.widget1
class Foo extends HTMLElement {
el(id) {
// Cached get element by id
this._els = this._els || {};
if (!this._els[id]) {
this._els[id] = this.getElementById(id)
}
return this._els[id]
}
getElementById is slower
But does your cached performance gain outway extra code, code complexity and time coding?
1 PICO second equals 0.00000001 Milliseconds
<div style="display:grid;grid-template-columns:1fr 1fr">
<test-component id="CACHED"></test-component>
<test-component id="BYID"></test-component>
</div>
<div id="DIFF"><b>100.000 calls per run:</b></div>
<script>
customElements.define("test-component", class extends HTMLElement {
el(id) {
this._els = this._els || {};
if (!this._els[id]) this._els[id] = document.getElementById(id);
return this._els[id]
}
get CACHED() {
return this.el("CACHED");
}
get BYID() {
return document.getElementById("BYID");
}
connectedCallback() {
let count = 100000;
for (let run = 1; run < 9; run++) {
let log = [];
for (let cnt = 0; cnt < count; cnt++) {
const t0 = performance.now();
this == CACHED ? this.CACHED : this.BYID;// test performance
log.push(performance.now() - t0);
}
this.average = (log.reduce((a, b) => a + b) / log.length)*1e9;
let diff = (BYID.average - CACHED.average).toPrecision(2);
if (this == BYID) DIFF.innerHTML += `<div>Run #${run} = <b>${diff/count}</b> PICO seconds slower per call<div>`;
}
}
})
</script>
For anyone else who has the same issue, this is what I went with, quite a lot of code upfront, but at least its easy to use, e.g. this.PROGRESS
class Foo extends HTMLElement {
el(id, docEl=false) {
// Cached get element by name
// Parameter: docEl
// Set to true to search the document instead of the shadowRoot
this._els = this._els || {};
if (!this._els[id]) {
const searchNode = docEl ? document : this.shadowRoot;
this._els[id] = searchNode.getElementById(id)
}
return this._els[id]
}
// Document Elements
get TEMPLATE() { return this.el('TEMPLATE_wc-uploader', true) }
get TEMPLATE_WC_UPLOAD(){ return this.el('TEMPLATE_wc-upload', true) }
// Shadow Root Elements
get PROGRESS() { return this.el('PROGRESS') }
get PROGRESS_HEADING() { return this.el('PROGRESS_HEADING') }
get DRAG_OVERLAY() { return this.el('DRAG_OVERLAY') }
get UPLOAD_LIST() { return this.el('UPLOAD_LIST') }

How to nest and chain my class functions with pure JS?

How can I make such code parts work?
Trying to complete task with custom HTML builder - it needs to support chaining and nesting.
Understand that I need to return this, but how then to output string with HTML?
// nesting
const template = Templater().div(
Templater().p('Hello'),
Templater().p('World')
)
console.log(template.toString())
// chaining
console.log(Templater().br('error').toString())
example of Class that I try to create:
class Templater {
constructor() {}
div(tags) {
return `<div>${tags}</div>`
}
span(tags) {
return `<span>${tags}</span>`
}
br(argument) {
if(argument) {
return new Error('Nested content is not allowed');
} else {
return '<br>'
}
}
p(tags) {
return `<p>${tags}</p>`
}
}
Have no idea what I need to do.
Sounds like you primarily need a custom toString method that returns the state of the tags called so far.
You'll also need to change the class to a function, because classes cannot be used without new.
const templater = () => {
let markup = '';
const transform = tags => tags.join('');
return {
div(...tags) {
markup += `<div>${transform(tags)}</div>`
return this;
},
span(...tags) {
markup += `<span>${transform(tags)}</span>`
return this;
},
br(argument) {
if (argument) {
throw new Error('Nested content is not allowed');
} else {
markup += '<br>'
return this;
}
},
p(...tags) {
markup += `<p>${transform(tags)}</p>`
return this;
},
toString() {
return markup;
},
};
};
// nesting
const template = templater().div(
templater().p('Hello'),
templater().p('World')
)
console.log(template.toString())
console.log(templater().br('error').toString())

I have a problem with 2 Classes to create element on page and display it

So I made 2 classes,first is the create element one:
class CElement {
constructor(value,elementType,elementStyle){
this.element = document.createElement(elementType);
this.element.innerHTML = value;
this.element.style = elementStyle;
}
display(displayTo) {
document.getElementsByClassName(displayTo).appendChild(element);
}
}
The second is the create mob and diplay to:
class Mob {
constructor(level) {
this.mobLvl = level;
}
display(elementClass) {
ele = new CElement(this.mobLvl + "<br>",'p',"color:red;");
ele.display(elementId);
}
}
I checked my code online for syntax errors , and I don't have any?????
So why doesn't it work when I call:
var mob = new Mob(1,"div","color:red;");
mob.display("someClassName");
You have a couple of issues in your code:
You forgot this in the display() function.
getElementsByClassName() returns an array, so you can't use appendChild() directly, you have to loop over your array or use another selector, for example querySelector().
ele is not defined in your display() function.
You use elementClass as argument of the display() function but then use elementId inside your function.
Finally, a working version could be something like this, that you can of course adapt to your need:
class CElement {
constructor(value, elementType, elementStyle) {
this.element = document.createElement(elementType);
this.element.innerHTML = value;
this.element.style = elementStyle;
}
display(displayTo) {
document.querySelector(displayTo).appendChild(this.element);
}
}
class Mob {
constructor(level) {
this.mobLvl = level;
}
display(elementClass) {
var ele = new CElement(this.mobLvl + "<br>", 'p', "color:red;");
ele.display(elementClass);
}
}
var mob = new Mob(1, "div", "color:red;");
mob.display(".someClassName");
<div class="someClassName"></div>
Looks like you forgot the this. in your CElement.display function. This makes it so that the element variable is undefined, and thus it doesn't append anything to the document.

Chaining function not work when inside another function

I try to create chaining function using vanilla javascript, its work if just chaining, but if inside other function its stop working.
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return doc.getElementById(m[1]);
} else if(m[2]) {
return doc.getElementsByTagName(m[2]);
} else if(m[3]) {
return doc.getElementsByClassName(m[3]);
}
},
$ = function (el) {
this.el = M$(el);
// event function
this.event = function(type,fn) {
this.el.addEventListener(type,fn,false);
return this;
}
// forEach function
this.forEach = function(fn,val) {
for(var i = this.el.length - 1; i >= 0; i--) {
fn.call(val, i, this.el[i]);
}
return this;
}
if(this instanceof $) {
return this.$;
} else {
return new $(el);
}
};
//use
$("button").forEach(function(index, el)
// when i use function event, its not work
el.event("click", function() {
alert("hello");
});
// if i'm using addEventListener its work, but i want use event function
});
My question is, how to be event function working inside forEach function?
Thanks for help!
First off, there is an issue with brackets in your code after $("button").forEach(function(index, el) you are missing {;
Then the problem is that when you try to call click-callback on your elements (buttons), in fact, due to the this issues the elements (buttons) don't have event() property. They are not even defined themselves since this.el = M$(el); goes outside forEach(). I tweaked and cleaned a little your code, check it out. I guess now it does what you want:
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) return doc.getElementById(m[1]); else if(m[2]) return doc.getElementsByTagName(m[2]); else if(m[3]) return doc.getElementsByClassName(m[3]);
},
$ = function(el) {
this.forEach = function(fn,val) {
// assign this.el and this.el[i].event inside forEach(), not outside
this.el = M$(el);
for(var i = this.el.length - 1; i >= 0; i--) {
this.el[i].event = function(type,fn) { this.addEventListener(type,fn,false); };
fn.call(val, i, this.el[i]);
}
}
return this;
};
$("button").forEach(function(index, el) {
el.event("click", function() { alert("hello, " + this.textContent); });
});
<button>btn1</button>
<button>btn2</button>
UPDATE
While the previous solution is fine for the particular purpose of setting click handlers on buttons, I think what you really want is to emulate Jquery and chain function calls. I improved your attempt right in this way:
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) return doc.getElementById(m[1]);else if(m[2]) return doc.getElementsByTagName(m[2]); else if(m[3]) return doc.getElementsByClassName(m[3]);
},
$ = function (el) { //console.log(this);
this.el = M$(el);
this.event = function(type,fn) {
for(var i = this.el.length - 1; i >= 0; i--) this.el[i].addEventListener(type,fn,false);
}
this.forEach = function(fn) {
fn.call(this);
}
return this;
};
$("button").forEach(function() {
this.event("click", function() {
alert("hello, " + this.textContent);
});
});
<button>btn1</button>
<button>btn2</button>
The key to understanding here is that your this object should always be equal to $ {el: HTMLCollection(2), event: function, forEach: function}. So,
calling $("button") you initially set it to $ {el: HTMLCollection(2), event: function, forEach: function} - with HTML Collection and event&forEach functions;
calling $("button").forEach(fn) you keep forEach's context equal to this from previous step;
calling fn.call(this); inside forEach() you call your callback fn and pass the same this to it;
inside the callback fn you call this.event() - it works because your this is always the one from the first step.
in this.event() which is just $.event() we just traverse our HTMLCollection and set handlers for click event on buttons. Inside $.event() this will be equal to a button element because we call it in such a context on click event, so, this.textContent takes the buttons' content.
Thanks, really good question!
First things first.
1.
this.el = M$(el);
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return doc.getElementById(m[1]);
} else if(m[2]) {
return doc.getElementsByTagName(m[2]);
} else if(m[3]) {
return doc.getElementsByClassName(m[3]);
}
}
As you defined M$ you can either have a HtmlCollection if you get elements by tag name or by class name or just one element if you get element by id.
Then you suppose that your el is one when it can be a collection.
this.event = function(type,fn) {
this.el.addEventListener(type,fn,false);
return this;
}
You probably receive a collection if you try to get all buttons.
2.
If you try to run posted code you will receive an Unexpected identifier error because you missed a { after forEach(function(index, el).
3.
If you put that { in there you will receive a el.event is not a function error because you don't have an event function on el, but you have that on $(el).
4.
If you change your code to:
$("button").forEach(function(index, el)
{
// when i use function event, its not work
$(el).event("click", function() {
alert("hello");
});
// if i'm using addEventListener its work, but i want use event function
});
You'll receive an error because you didn't handled multiple elements. See 1 problem.
Have a look at this.
var doc = document,
M$ = function(el) {
var expr = /^(?:#([\w-]+)|(\w+)|\.([\w-]+))$/;
var m = expr.exec(el);
if(m[1]) {
return Array.apply([],[doc.getElementById(m[1])]);
} else if(m[2]) {
return Array.apply([],doc.getElementsByTagName(m[2]));
} else if(m[3]) {
return Array.apply([],doc.getElementsByClassName(m[3]));
}
},
$ = function (el) {
if(! (this instanceof $)) {
return new $(el);
}
this.els = M$(el);
// event function
this.event = function(type,fn) {
this.forEach(function(index, el){
el.addEventListener(type,fn,false);
});
return this;
}
// forEach function
this.forEach = function(fn,val) {
for(var i = this.els.length - 1; i >= 0; i--) {
fn.call(val, i, this.els[i]);
}
return this;
}
return this;
};
//use
$("button").event("click", function() {
alert("hello");
});
Here the M$ function is made to return an array to keep things consistent.
So, the $().event function is changed to iterate through all the elements in this.els.
Hence, you could simply call $("button").event function instead of $("button").forEach function to register event listeners.
Refer: Demo
This one works. But, Is this what you want? I am not sure.

Call method overridden in child class, from the parent class [PHP -> JS]

In my Javascript there is a parent Base class that will be extended by others.
I'd like to:
define in it a method getSubject() that could be common to all children, when it is not overridden.
make getSubject() rely on a Base property, that eventually could be overridden as well.
always call the getSubject() method in the context of the caller (the children classes or the Base class)
To clarify (hopefully) what I want to do..
I wrote (non-valid) PHP code as an example.
<?php
class Base
{
const SUBJ_SELECTOR = 'input';
public function init()
{
$this->wrapper = ....;
$this->subject = $this->getSubj();
if ($this->subject.attr('data-active')) {
// ... do stuff
}
}
public function getSubj() // One definition in parent
{
return $this->wrapper.find(self::SUBJ_SELECTOR);
}
}
class Select extends Base
{
const SUBJ_SELECTOR = 'select' // Override just the selector
}
class Textarea extends Base
{
const SUBJ_SELECTOR = 'textarea[name=foo]';
public function getSubj() // Eventual overriding
{
$subjs = $this->wrapper.find(self::SUBJ_SELECTOR);
foreach ($subjs as $subj) {
if ($subj.attr('multiline')) {
return $subj;
}
}
return $subjs;
}
}
I'd like to achieve the same result with Javascript (and JQuery eventually).
Actually I wrote some code (that I still didn't test) as a sketch:
var Base = function() {
this.options = {};
this.subject_selector = 'input';
this.wrapper = $('.container');
};
Base.prototype.getSubject = function() {
return this.wrapper.find(this.subject_selector);
}
Base.prototype.init = function() {
subj = this.getSubject();
if(subj.attr('data-active')) {
// ... do stuff
}
}
var Select = function() {
this.subject_selector = 'select';
}
Select.prototype = new Base();
Select.prototype.constructor = Select;
var Textarea = function() {
this.subject_selector = 'textarea';
}
Textarea.prototype.getSubject = function() {
subjs = this.wrapper.find(this.subject_selector);
for (var i = subjs.length - 1; i >= 0; i--) {
if(subjs[i].attr('multiline')) {
return subjs[i];
}
};
return subjs;
}
Textarea.prototype = new Base();
Textarea.prototype.constructor = Textarea;
Would it work correctly? Is this a proper use of the inheritance model?
Am I callling the method in the right way and will I get the expected result when executing the init() method?

Categories