Chain delayed function using vanilla Javascript ES6 - javascript

How do you implement delay like the jQuery Library? - I know this question has been asked so many times, but haven't seen anyone implement it using async/await or ES6 stylistics. let me know if you have ideas
//create a jquery like library
class DOM {
constructor(selector){
this.elements = [];
if(!selector){
return;
} else if(selector === 'document' || selector === 'window'){
this.elements = [selector];
} else {
this.elements = Array.from(document.querySelectorAll(selector));
}
}
on(){
console.log('on');
return this;
}
get(){
console.log('get');
return this;
}
delay(ms, callback){
//how to implement this? how to chain result of callback onto next chain?
console.log('delay');
const promise = Promise.resolve();
return promise.then(function(resolve) {
setTimeout(function(){
resolve(this);
}, ms);
});
}
}
const $ = function(selector) {
return new DOM(selector);
}
$('document').on().delay(10000).get()

You probably don't need promises or async/await at all, I think you can create a Proxy object that intercepts subsequent call.
The idea is that, when .delay(duration) is called, it'll return a proxy object instead of the class instance. This proxy object will intercept a method call, set time out for duration, then call the method with the original class instance.
class J {
constructor(selector) {
this.$element = document.querySelector(selector)
}
delay(duration) {
const proxy = new Proxy(this, {
get: (target, prop) => {
const f = target[prop]
return (...args) => {
setTimeout(() => {
return f.apply(target, [...args])
}, duration)
// return the class instance again, so subsequent call & delay still works
return this
}
}
})
return proxy
}
text(content) {
this.$element.textContent = content
return this
}
}
const $ = selector => new J(selector)
$('#test').text('hello').delay(1000).text('world')
<div id="test"></div>

You could maintain a queue of functions still to execute on the selected element(s). That way you can allow multiple delays in the chain and also allow the client to stop the action.
A proxy can be used to "decorate" the methods which make sense to be delayed, so that they can be put in the queue instead of executed whenever a timer is still active.
Here is how that could look:
class DOM {
constructor(selector) {
this.elements = typeof selector === "object" ? [selector]
: selector === 'document' || selector === 'window' ? [document]
: Array.from(document.querySelectorAll(selector));
this.delayed = false;
this.queue = [];
const proxy = new Proxy(this, {
get(obj, prop) {
return !["css","show","hide","delay"].includes(prop) || !obj.delayed ? obj[prop]
: function (...args) {
obj.queue.push(() => proxy[prop](...args));
return this;
}
}
});
return proxy;
}
each(cb) {
this.elements.forEach(cb);
return this;
}
css(name, value) {
return this.each(elem => elem.style[name] = value);
}
show() {
return this.css("display", "");
}
hide() {
return this.css("display", "none");
}
on(eventType, cb) {
return this.each(elem => elem.addEventListener(eventType, cb.bind(elem)));
}
delay(ms) {
this.delayed = true;
setTimeout(() => {
this.delayed = false;
while (this.queue.length && !this.delayed) this.queue.shift()();
}, ms);
return this;
}
stop() {
this.queue.length = 0;
return this;
}
}
const $ = selector => new DOM(selector);
const $span = $('#demo').hide();
for (let i = 0; i < 100; i++) {
$span.delay(500).show()
.delay(500).css("color", "red")
.delay(500).css("color", "blue")
.delay(500).hide();
}
$("#stop").on("click", function () {
$span.stop();
$(this).hide();
});
<div>This is a <span id="demo">colorful </span>demo</div>
<button id="stop">Stop</button>

Related

Promise ignoring simple syncronous operation

In a promise, I want to assign a value to the property of several objects created from a class (in a loop), but when executing the function and doing the .then(() => console.log(r)) thing, the r does was not modified to what the promise promised me it would.
Here:
function assignSentenceImageDescription () {
return new Promise((resolve, reject) =>
{
assigningWordsPartOFSpeech().then((r) => {
JSON.parse(r).sentences.forEach((sentence) => {
let adjectiveBeforeNoun = [];
let sentenceImageDescription = [];
sentence.words.forEach((wordInSentence) => {
try {
if (wordInSentence.partOfSpeech[0].wordtype === "n.") {
let imageDescription = adjectiveBeforeNoun.join('') + wordInSentence.partOfSpeech[0].word;
sentenceImageDescription.push(imageDescription)
adjectiveBeforeNoun = [];
} else if (wordInSentence.partOfSpeech[0].wordtype === "superl.") {
adjectiveBeforeNoun.push(wordInSentence.partOfSpeech[0].word + " ")
}
} catch (e) {
console.log("===NOT IN DICTIONARY===")
}
})
sentence.imageDescription = sentenceImageDescription;
}
)
resolve(r);
}
);
}
);
}
On the line
sentence.imageDescription = sentenceImageDescription;
I try to assign the image description of each of the sentences iterated over, but when I do
assignSentenceImageDescription().then(r => console.log(r));
the r object still does not have each of its sentences's imageDescription property modified to the value the array sentenceImageDescription has, which is what the assignSentenceImageDescription() function is intended to do.
Refactor your code as follows:
Note: you don't need a Promise constructor since assigningWordsPartOFSpeech returns a Promise that you can work with (and return)
Set sentences = JSON.parse(r).sentences;
Now you can iterate through sentences as you already do, then simply return sentences in the .then - and you're done
function assignSentenceImageDescription() {
return assigningWordsPartOFSpeech().then((r) => {
const data = JSON.parse(r);
data.sentences.forEach((sentence) => {
let adjectiveBeforeNoun = [];
let sentenceImageDescription = [];
sentence.words.forEach((wordInSentence) => {
try {
if (wordInSentence.partOfSpeech[0].wordtype === "n.") {
let imageDescription = adjectiveBeforeNoun.join('') + wordInSentence.partOfSpeech[0].word;
sentenceImageDescription.push(imageDescription)
adjectiveBeforeNoun = [];
} else if (wordInSentence.partOfSpeech[0].wordtype === "superl.") {
adjectiveBeforeNoun.push(wordInSentence.partOfSpeech[0].word + " ")
}
} catch (e) {
console.log("===NOT IN DICTIONARY===")
}
})
sentence.imageDescription = sentenceImageDescription;
});
return data;
});
}

Uexpected Function Behavior. JS Classes

I've created HTML builder (finally!)
It just has methods: create div, span, p, br.
As you can see in console.logs it has nesting and chaining behavior.
And for detecting nesting and chaining I have item instanceof Class
But it doesn't show me correct return when I have nesting condition.
Need help to find the mistake and get output in first console.log =
<div><p>Hello</p><p>World</p></div>
class Templater {
constructor() {
this.arr = [];
this.nesting = false;
}
transform(tags) {
return tags.join("");
}
div(...tags) {
tags.forEach(item => {
this.nesting = (item instanceof Templater);
});
this.arr.push(`<div>${this.transform(tags)}</div>`)
return this;
}
span(...tags) {
tags.forEach(item => {
this.nesting = (item instanceof Templater);
});
this.arr.push(`<span>${this.transform(tags)}</span>`);
return this
}
br(argument) {
tags.forEach(item => {
this.nesting = (item instanceof Templater);
});
if (argument) {
throw new Error('Nested content is not allowed');
} else {
this.arr.push(`<br>`);
return this;
}
}
p(...tags) {
tags.forEach(item => {
this.nesting = (item instanceof Templater);
});
this.arr.push(`<p>${this.transform(tags)}</p>`);
return this
}
toString() {
if (this.nesting) {
this.nesting = false;
let qwe = [...this.arr];
this.arr = [];
return qwe[qwe.length-1];
} else {
let qwe = [...this.arr];
this.arr = [];
return qwe.join('');
}
}
}
const template = new Templater();
console.log(template.div(
template.p('Hello'),
template.p('World')
).toString());
console.log(template.div().toString());
console.log(template.div('Hello').p('fix').toString());
Worked for me with :
console.log(template.div(
template.p('Hello').p('World').toString()
).toString());

Is it possible to detect at instantiation time all of a given type's callable methods and then dynamically create a forwarding process for each method

I needed a sort of aggregate class that forwards it's method calls to all of it's constituents. This is what I wrote:
class CompositeSpritesheet {
sprites = []; // collect Spritesheet objects
draw() {
this.sprites.forEach(s => (s.draw.apply(s, arguments)))
}
init() {
this.sprites.forEach(s => (s.init.apply(s, arguments)))
}
nextStep() {
this.sprites.forEach(s => (s.nextStep.apply(s, arguments)))
}
// ... for all possible functions
}
let board = new Spritesheet("a");
let text = new Spritesheet("b");
let composite = new CompositeSpritesheet();
composite.sprites.push(wood,text);
composite.draw(20,30);
This code is really repetitive, not exhaustive, might fail for unexpected function calls, and most importantly just really icky. Is there a way I can generalize this and maybe write a higher-order class that generates such compositions at whim?
In other words, is there a mechanism in JavaScript that lets me replicate the following pseudocode's behavior?
class CompositeSpritesheet{
sprites = [];
*("fn", args){
this.sprites.forEach(s => (s[fn]?.apply(s, arguments)))
}
}
This could be a dumb question, but I find JavaScript flexible in many unexpected ways that I haven't been able to completely wrap my head around. I feel like it could possible in some form or another.
If you have ideas that don't completely answer the question but are still related to the solution, feel free to post them as comments.
... something like this ..?
class CompositeSpritesheet {
constructor(...sprites) {
let spriteList = [].concat(...sprites);
this.addSprites = (...sprites) => {
spriteList = spriteList.concat(...sprites);
};
this.execute = (actionName, ...args) => {
spriteList.forEach(sprite => {
const action = sprite[actionName];
if (typeof action === 'function') {
action.apply(sprite, args);
}
});
};
}
}
let board = new Spritesheet("a");
let text = new Spritesheet("b");
let composite = new CompositeSpritesheet();
composite.addSprites(board, text);
composite.execute('draw', 20, 30);
Edit / Second Iteration
The next provided approach is a generic one. Knowing that at any time every internally listed Spritesheet type does feature the same set of methods, a newly introduced CompositeSpritesheet type does now detect all accessible keys of its first listed Spritesheet element, either at construction time or as soon as such (an) element(s) will be concatenated to the composite type's internally managed spritesheet-list. Then, while iterating this key-list, for every found spritesheet-method a corresponding forwarder method gets created dynamically.
... working example code ...
function isBooleanValue(type) {
return (typeof type === 'boolean');
}
function isNumberValue(type) {
return (typeof type === 'number');
}
function isStringValue(type) {
return (typeof type === 'string');
}
function isSymbol(type) {
return (typeof type === 'symbol');
}
function isPrimitive(type) {
return (
isBooleanValue(type)
|| isNumberValue(type)
|| isStringValue(type)
|| isSymbol(type)
);
}
function isObject(type) {
return (!!type && (typeof type === 'object'));
}
function isFunction(type) {
const functionType = 'function';
return (
(typeof type === functionType)
&& (typeof type.call === functionType)
&& (typeof type.apply === functionType)
);
}
/**
* - recursively collect a list of a type’s accessible keys
* that also might be inherited, but that are neither keys
* of `Object.prototype` nor keys of `Function.prototype`.
*/
function getAllAccessiblePropertyNames(type, keyList) {
// default return value.
keyList = (keyList || []);
// accept primitive data types as well.
if (isPrimitive(type)) {
type = Object(type);
}
// undefined and null value are kept out.
if (isObject(type)) {
keyList = keyList.concat(
Object.keys(type)
).concat(
Object.getOwnPropertyNames(type)
);
const protoType = (isFunction(type.constructor) && type.constructor.prototype);
if (protoType && (protoType !== Object.prototype)) {
if (protoType === protoType.constructor.prototype) {
keyList = keyList.concat(
Object.keys(protoType)
).concat(
Object.getOwnPropertyNames(protoType)
);
} else {
keyList = getAllAccessiblePropertyNames(protoType, keyList);
}
}
const proto = type.__proto__;
if ((isObject(proto) || isFunction(proto)) && (proto !== Object.prototype)) {
if (proto === proto.__proto__) {
keyList = keyList.concat(
Object.keys(proto)
).concat(
Object.getOwnPropertyNames(proto)
);
} else {
keyList = getAllAccessiblePropertyNames(proto, keyList);
}
}
}
return [...(new Set(keyList))].filter(key => !(/^\d+$/).test(key));
}
function isEmptyList(type) {
return ((type = Object(type)) && ('length' in type) && (Array.from(type).length === 0));
}
function withSpritesheetForwarderMethods(sharedState) {
// guard.
if (sharedState.hasForwarderMethods || isEmptyList(sharedState.spritesheetList)) {
// either does already feature all methods or there is still nothing to work with.
return;
}
const compositeType = this;
const spritesheetType = sharedState.spritesheetList[0];
getAllAccessiblePropertyNames(spritesheetType).forEach((key) => {
if (isFunction(spritesheetType[key])) {
// apply spritesheet forwarder method.
compositeType[key] = function (...args) {
sharedState.spritesheetList.forEach(
(spritesheet) => spritesheet[key].apply(spritesheet, args)
);
}
}
});
sharedState.hasForwarderMethods = true;
}
function withSpritesheetListManagement(sharedState) {
const compositeType = this;
compositeType.addSpritesheets = (...spritesheets) => {
sharedState.spritesheetList = sharedState.spritesheetList.concat(...spritesheets);
// ... either applicable latest at concat time ...
withSpritesheetForwarderMethods.call(compositeType, sharedState);
};
// ... or (preferably) already applicable at construction time.
withSpritesheetForwarderMethods.call(compositeType, sharedState);
}
class CompositeSpritesheet {
constructor(...spritesheets) {
const compositeType = this;
const sharedState = {
hasForwarderMethods: false,
spritesheetList: [].concat(...spritesheets)
};
withSpritesheetListManagement.call(compositeType, sharedState);
}
}
class Spritesheet {
constructor(name) {
this.name = name;
}
draw(...args) {
console.log(`"${ this.name }" :: draw :: args : `, args);
}
}
// test 1 :: apply forwarder methods at construction time.
const board = new Spritesheet("board");
const text = new Spritesheet("text");
const forwardingAtConstructionTime = new CompositeSpritesheet([board, text]);
console.log('forwardingAtConstructionTime.draw : ', forwardingAtConstructionTime.draw);
forwardingAtConstructionTime.draw(20, 30);
// test 2 :: apply forwarder methods at concat time.
const wood = new Spritesheet("wood");
const paper = new Spritesheet("paper");
const forwardingAtConcatTime = new CompositeSpritesheet();
console.log('forwardingAtConcatTime.draw : ', forwardingAtConcatTime.draw);
forwardingAtConcatTime.addSpritesheets(wood, paper);
forwardingAtConcatTime.draw(50, 10);
console.log('forwardingAtConcatTime.draw : ', forwardingAtConcatTime.draw);
.as-console-wrapper { min-height: 100%!important; top: 0; }
Edit / Third Iteration
The design of the 3rd iteration is much cleaner. The class implementation is straight forward. The glue code work of dynamically creating the forwarding actions is done by a factory named from with this factory also being the only method of its CompositeSpritesheet namespace. Thus, this factory needs any randomly chosen or ad hoc created Spritesheet type in order to then create an according CompositeSpritesheet type.
... working example code ...
function isBooleanValue(type) {
return (typeof type === 'boolean');
}
function isNumberValue(type) {
return (typeof type === 'number');
}
function isStringValue(type) {
return (typeof type === 'string');
}
function isSymbol(type) {
return (typeof type === 'symbol');
}
function isPrimitive(type) {
return (
isBooleanValue(type)
|| isNumberValue(type)
|| isStringValue(type)
|| isSymbol(type)
);
}
function isObject(type) {
return (!!type && (typeof type === 'object'));
}
function isFunction(type) {
const functionType = 'function';
return (
(typeof type === functionType)
&& (typeof type.call === functionType)
&& (typeof type.apply === functionType)
);
}
/**
* - recursively collect a list of a type’s accessible keys
* that also might be inherited, but that are neither keys
* of `Object.prototype` nor keys of `Function.prototype`.
*/
function getAllAccessiblePropertyNames(type, keyList) {
// default return value.
keyList = (keyList || []);
// accept primitive data types as well.
if (isPrimitive(type)) {
type = Object(type);
}
// undefined and null value are kept out.
if (isObject(type)) {
keyList = keyList.concat(
Object.keys(type)
).concat(
Object.getOwnPropertyNames(type)
);
const protoType = (isFunction(type.constructor) && type.constructor.prototype);
if (protoType && (protoType !== Object.prototype)) {
if (protoType === protoType.constructor.prototype) {
keyList = keyList.concat(
Object.keys(protoType)
).concat(
Object.getOwnPropertyNames(protoType)
);
} else {
keyList = getAllAccessiblePropertyNames(protoType, keyList);
}
}
const proto = type.__proto__;
if ((isObject(proto) || isFunction(proto)) && (proto !== Object.prototype)) {
if (proto === proto.__proto__) {
keyList = keyList.concat(
Object.keys(proto)
).concat(
Object.getOwnPropertyNames(proto)
);
} else {
keyList = getAllAccessiblePropertyNames(proto, keyList);
}
}
}
return [...(new Set(keyList))].filter(key => !(/^\d+$/).test(key));
}
const CompositeSpritesheet = (function () {
// module scope.
// lean class.
class CompositeSpritesheet {
// declare private instance field.
#list
constructor() {
// initialize private instance field.
this.#list = [];
}
// prototypal instance methods with private instance field access.
getSpritesheets() {
return [...this.#list];
}
addSpritesheets(...spritesheets) {
return [...(this.#list = this.#list.concat(...spritesheets))];
}
}
// creation helper.
function createForwardingAction(composite, key) {
composite[key] = function (...args) {
composite.getSpritesheets().forEach(
(spritesheet) => spritesheet[key].apply(spritesheet, args)
);
}
}
// factory.
function createCompositeFromSpritesheetType(dummySpritesheet) {
const composite = new CompositeSpritesheet();
getAllAccessiblePropertyNames(dummySpritesheet).forEach((key) => {
if (isFunction(dummySpritesheet[key])) {
// apply spritesheet forwarder method.
createForwardingAction(composite, key);
}
});
return composite;
}
// module export.
return {
from: createCompositeFromSpritesheetType
};
}());
class Spritesheet {
constructor(name) {
this.name = name;
}
draw(...args) {
console.log(`"${ this.name }" :: draw :: args : `, args);
}
}
// test
const composite = CompositeSpritesheet.from(new Spritesheet);
console.log('composite : ', composite);
console.log('get current spritesheet list : ', composite.getSpritesheets());
const board = new Spritesheet("board");
const text = new Spritesheet("text");
const wood = new Spritesheet("wood");
const paper = new Spritesheet("paper");
console.log('add [board, text] to list : ', composite.addSpritesheets(board, text));
composite.draw(20, 30);
composite.addSpritesheets([wood, paper]); // add via array is possible too.
console.log('get current spritesheet list : ', composite.getSpritesheets());
composite.draw(50, 10);
.as-console-wrapper { min-height: 100%!important; top: 0; }
EDIT:
Given the comments in the other answer, what you could do is to create your class and then add the functions to the prototype:
class CompositeSpritesheet {
sprites = [];
};
var functionsToCreate = ["draw", "init", "nextStep"];
for (let f of functionsToCreate) {
CompositeSpritesheet.prototype[f] = function() {
this.sprites.forEach(s => (s[f].apply(s, arguments)));
}
}
And so CompositeSpritesheet will have all the functions you want in its prototype and so you will be able to call composite.draw(20,30);.
The only thing you have to do is to fill the functionsToCreate array.
=====================
A solution I can see could be to call only one method with different arguments:
class CompositeSpritesheet {
function doSomething(action, args) {
this.sprites.forEach(s => (s[action].apply(s, args)));
}
So you can call composite.doSomething("draw", [20,30]);.

Javascript workaround for firefox iframe getComputedStyle bug

I'm trying to use the following workaround for the hidden iframe/getComputedSytle firefox bug 548397.
if (/firefox/i.test(navigator.userAgent)){
window.oldGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = function (element, pseudoElt) {
var t = window.oldGetComputedStyle(element, pseudoElt);
if (t === null) {
return {};
} else{
return t;
}
};
}
However in my case I also need getComputedSytle.getPropertyValue i.e. I get the following error:
TypeError: my_window.getComputedStyle(...).getPropertyValue is not a function
How can I add getPropertyValue to the above workaround?
You can just create an empty function:
if (/firefox/i.test(navigator.userAgent)){
window.oldGetComputedStyle = window.getComputedStyle;
window.getComputedStyle = function (element, pseudoElt) {
var t = window.oldGetComputedStyle(element, pseudoElt);
if (t === null) {
return {
getPropertyValue: function(){}
};
} else{
return t;
}
};
}
I think a better solution would be this one
function setFirefoxPolyfill() {
if (/firefox/i.test(navigator.userAgent)){
window.oldGetComputedStyle = window .getComputedStyle;
window.getComputedStyle = function (element, pseudoElt) {
var t = window.oldGetComputedStyle(element, pseudoElt);
if (t === null) {
return element.style;
} else{
return t;
}
};
}
}
in case of null response you just return element styles with all prototyped methods and fields

JavaScript extend Array and add methods to child protype?

I want to create a js class that resembles simple music playlist (Array). I want to instantiate this playlist with IDs, each ID being a track ID in my database. I have interface like this:
function Playlist() {
Playlist.prototype.current = 0;
Playlist.prototype.prev = function() {
if (this.current-1 < 0) {
return null;
}
return this[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.length) { // length is index + 1
return null;
}
return this[++this.current];
};
Playlist.prototype.seek = function(id) {
for (i in this) {
if (this[i] == id) {
this.current = parseInt(i);
return i;
}
}
return false;
};
Playlist.prototype.getCurrent() {
return this.current;
};
};
The code above DOES NOT do what I want, because I imagine it as class that has it's method defined, that can be instantiated like this:
var newPlaylist = Playlist(2,3,5,10/* those are my ids */);
And currently the only way I've found is something like:
Playlist.prototype = new Array(2, 3, 5, 10/* those are my ids */);
Which does not make any sense since it can be instantiated as different objects. Any ideas are very welcome!
Best way - nested array;
function Playlist() {
this.current = 0;
this.list = Array.prototype.slice.call(arguments);;
};
Playlist.prototype.prev = function() {
if (this.current-1 < 0) {
return null;
}
return this.list[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.list.length) { // length is index + 1
return null;
}
return this.list[++this.current];
};
Playlist.prototype.getCurrent = function() {
return this.current;
};
var newPlaylist = new Playlist(2,3,5,10/* those are my ids */);
But you can't use list[i] to get element by index, but you just need add at() method to your class that provide similar functionality
PlayList.prototype.at(i) {
return this.list[i];
}
Since you cannot subclass Array, you should build wrapper objects with your Playlist constructor:
Playlist = (function() {
function Playlist(list) {
this.list = list || [];
}
Playlist.prototype.current = 0;
Playlist.prototype.prev = function() {
if (this.current <= 0)
return null;
return this.list[--this.current];
};
Playlist.prototype.next = function() {
if (this.current+1 >= this.length)
return null;
return this.list[++this.current];
};
Playlist.prototype.seek = function(id) {
return this.list.indexOf(id);
};
return Playlist;
})();
Usage:
var newPlaylist = new Playlist([2,3,5,10]);

Categories