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

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())

Related

I want to modify all elements with the same query

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

Fabric JS - UNDO & REDO optimization using JSON diff

Currently, I have implemented quite standard UNDO and REDO by using listeners to trigger canvas.getObjects() whose JSON output I store in a stack.
// Canvas modified listeners
canvas?.on('object:modified', onCanvasModifiedHandler)
canvas?.on('object:removed', onCanvasModifiedHandler)
canvas?.on('object:changed', onCanvasModifiedHandler)
When the user clicks undo and redo, we fetch JSON representation of the canvas from the stack and loads it using canvas?.loadFromJSON(json, () => { ... })
My problem is that it is quite inefficient to store the entire JSON representation of the canvas when the actual change is quite small. As a result, this approach causes my application to freeze for 500 milliseconds when the user clicks UNDO and REDO.
My proposed solution is to store only the JSON diff by using for example this package, although it is quite an undertaking. https://www.npmjs.com/package/jsondiffpatch
My question is if anyone has had this problem before, and how did you solve it in that case? Or if someone has any other ideas.
Inspired by this thread: https://bountify.co/undo-redo-with-2-canvases-in-fabric-js
I think you need to use the command pattern for this. It will be more efficient than using all JSON data. For that, you need to implement the next approach:
Create a class for storing History. It maybe looks like this
class CommandHistory {
commands = [];
index = 0;
getIndex() {
return this.index;
}
back() {
if (this.index > 0) {
let command = this.commands[--this.index];
command.undo();
}
return this;
}
forward() {
if (this.index < this.commands.length) {
let command = this.commands[this.index++];
command.execute();
}
return this;
}
add(command) {
if (this.commands.length) {
this.commands.splice(this.index, this.commands.length - this.index);
}
this.commands.push(command);
this.index++;
return this;
}
clear() {
this.commands.length = 0;
this.index = 0;
return this;
}
}
// use when you init your Canvas, like this.history = new CommandHistory();
Then you must implement the command classes for your commands.
For adding object
class AddCommand {
constructor(receiver, controller) {
this.receiver = receiver;
this.controller = controller;
}
execute() {
this.controller.addObject(this.receiver);
}
undo() {
this.controller.removeObject(this.receiver);
}
}
// When you will add object on your canvas invoke also this.history.add(new AddCommand(object, controller))
For removing object
class RemoveCommand {
constructor(receiver, controller) {
this.receiver = receiver;
this.controller = controller;
}
execute() {
this.controller.removeObject(this.receiver);
}
undo() {
this.controller.addObject(this.receiver);
}
}
The fabric.js has the saveState method for every object http://fabricjs.com/docs/fabric.Object.html#saveState. And you can use it for implementing the transform command, which will be added to the history object when you will modify your fabric object on the canvas.
class TransformCommand {
constructor(receiver, options = {}) {
this.receiver = receiver;
this._initStateProperties(options);
this.state = {};
this.prevState = {};
this._saveState();
this._savePrevState();
}
execute() {
this._restoreState();
this.receiver.setCoords();
}
undo() {
this._restorePrevState();
this.receiver.setCoords();
}
// private
_initStateProperties(options) {
this.stateProperties = this.receiver.stateProperties;
if (options.stateProperties && options.stateProperties.length) {
this.stateProperties.push(...options.stateProperties);
}
}
_restoreState() {
this._restore(this.state);
}
_restorePrevState() {
this._restore(this.prevState);
}
_restore(state) {
this.stateProperties.forEach((prop) => {
this.receiver.set(prop, state[prop]);
});
}
_saveState() {
this.stateProperties.forEach((prop) => {
this.state[prop] = this.receiver.get(prop);
});
}
_savePrevState() {
if (this.receiver._stateProperties) {
this.stateProperties.forEach((prop) => {
this.prevState[prop] = this.receiver._stateProperties[prop];
});
}
}
}
Now you can add your commands to your history and execute or undo them.

JavaScript/TypeScript Array interface[] , group by type to send 1 of many functions

I have an array of Question (interface) that I need to send to 1 of many functions based on Question type. I think my series of if statements is very ugly and am hoping there is a way of doing this that adheres to SOLID. I believe I am violating O (Open for extension, closed for modification).
renderQuestionList(contents: Question[]): HTMLElement {
return yo`
<div>${contents.map(q => {
if (q.type == 'passfailna') { return this.renderQuestionPassfailna(q) };
if (q.type == 'yesno') { return this.renderQuestionYesno(q) };
if (q.type == 'numeric') { return this.renderQustionNumeric(q) };
})}
</div>`;
}
Then,
renderQuestionPassfailna(q: Question): any {
return yo`<div>Stuff</div>`;
}
renderQuestionYesno(q: Question): any {
return yo`<div>Other Stuff</div>`;
}
renderQustionNumeric(q: Question): any {
return yo`<div>I'm Helping!</div>`;
}
it is ugly. How about building a map of functions? Perhaps something like
constructor() {
this.questions = {
passfailna: q => this.renderQuestionPassfailna(q),
yesno: q => this.renderQuestionYesno(q),
numeric: q => return this.renderQustionNumeric(q)
};
}
renderQuestionList(contents: Question[]): HTMLElement {
return yo`<div>${contents.map(q => this.questions[q.type](q))}</div>`;
}
If the logic inside the template is too large, then it can be moved to a function, such as
renderQuestionList(contents: Question[]): HTMLElement {
return yo`
<div>${contents.map(q => renderQuestion(q))}
</div>`;
}
renderQuestion(q):HTMLElement {
if (q.type == 'passfailna') { return this.renderQuestionPassfailna(q) };
if (q.type == 'yesno') { return this.renderQuestionYesno(q) };
if (q.type == 'numeric') { return this.renderQustionNumeric(q) };
}
However, I would question the wisdom of generating such a large tree all at once. When I use YO I prefer to generate small items, and insert them using appendChild. For example,
renderQuestionList(contents: Question[]): HTMLElement {
let div = yo`<div> </div>`;
contents.forEach(q => {
div.appendChild(renderQuestion(q));
});
return div;
}

How to implement more than one method in JS es6 class?

I have a class of validations that I have created in JS:
let test = new Validator(req.body);
Now I want to test something, maybe that a specific key in this object is 2-5 char length, I would do it like this:
let myBoolean = test.selector("firstName").minLength(2).maxLength(5);
// firstName is like: req.body.firstName
And how this could be done in the class?
EDIT
I made something like this:
audit.isLength({selector: "from", gte: 2, lte: 35})
class Validator {
constructor(obj) {
this.obj = obj;
this.isValid = true;
}
isExists(sel) {
if (typeof this.obj[sel] === "undefined") return false;
return true;
}
isLength(info) {
let sel = this.obj[info.selector];
if (typeof sel === "undefined") return false;
if (info.gte) {
if (sel.length<info.gte) return false;
}
if (info.lte) {
if (sel.length>info.lte) return false;
}
if (info.gt) {
if (sel.length<=info.gt) return false;
}
if (info.lt) {
if (sel.length>=info.lt) return false;
}
return true;
}
}
Try something like this - assign the object to validate to a property on the instantiation, return this from each validating call, and when validating, assign to an isValid property on the object (if it isn't already false). Note that you need to access the isValid property finally in order to retrieve the boolean.
class Validator {
constructor(obj) {
this.obj = obj;
this.isValid = true;
}
selector(sel) {
this.sel = sel;
return this;
}
minLength(min) {
if (this.isValid) this.isValid = this.obj[this.sel].length >= min;
return this;
}
maxLength(max) {
if (this.isValid) this.isValid = this.obj[this.sel].length <= max;
return this;
}
}
const test = new Validator({firstName: 'foobar'}); // 6 chars: invalid
console.log(test.selector("firstName").minLength(2).maxLength(5).isValid);
const test2 = new Validator({firstName: 'fooba'}); // 5 chars: valid
console.log(test2.selector("firstName").minLength(2).maxLength(5).isValid);
const test3 = new Validator({firstName: 'f'}); // 1 char: invalid
console.log(test3.selector("firstName").minLength(2).maxLength(5).isValid);
Create a class with fluent methods/chainable methods, that return this, which is an instance of the class itself and when you finally run validation according to the rules, call .validate(), which will act as a final method to return the result:
class Validator {
constructor (body) {
this._body = body;
}
selector(str) {
this._selector = str;
return this;
}
minLength(num) {
this._minLength = num;
return this;
}
maxLength(num) {
this._maxLength = num;
return this;
}
validate() {
// run your validation logic here and return true or false accordingly
return true
}
}
const req = { body: 'body' };
const test = new Validator(req.body);
const myBoolean = test
.selector('firstName')
.minLength(2)
.maxLength(5)
.validate();
console.log('rules:');
console.log(test);
console.log(`result: ${myBoolean}`);
This is the builder pattern (sort of). You'll probably want to define a separate class that has a minLength and maxLength function. Those functions will set some state on the builder, and return either this (the builder its self), or a new builder that's a copy of this. Then you'd have some finalize function on the builder, which looks at the state, handles all the logic based on the min/max, and returns a boolean.

Javascript, tree structure datamodel

I have this to imitate a tree structure:
var MODULESYSTEM =
{
modules:
{
a : function() { return 'modules.a'; }
b : function() { return 'modules.b'; }
c :
{
d : function() { return 'modules.c.d'; }
}
}
}
so MODULESYSTEM.modules.a(); is valid, so MODULESYSTEM.modules.c.d(); too. But what if I want something like MODULESYSTEM.modules.c(); ? It should return 'modules.c'
You won't be able to declare that sort of data structure in one line. You will need to build it up procedurally:
var MODULESYSTEM = {
modules: {
// Other top-level namespace objects
c: function() {
return 'modules.c';
}
}
};
// Later:
MODULESYSTEM.modules.c.d = function() { return 'modules.c.d'; };
There might be a better solution to this problem if you could provide more background about the problem you're looking to solve.

Categories