I need to inherit DataView object to create my own type and add additional methods etc. But I'm a bit confused how to do this in a right way. I tried to do like this:
var CFDataView = function() {
this.offset = 0;
};
CFDataView.prototype.__proto__ = DataView.prototype;
CFDataView.prototype.readU8 = function() {
if (this.byteLength >= this.offset+1) {
return this.getUint8(this.offset++);
} else {
return null;
}
};
But got an error:
DataView.prototype.byteLength called on incompatible receiver CFDataView
From the proposals, I tried to do like this:
var CFDataView = function CFDataView(buffer, byteOffset, byteLength) {
DataView.call(this, buffer, byteOffset, byteLength);
this.offset = 0;
};
CFDataView.prototype = Object.create(DataView.prototype);
CFDataView.prototype.constructor = CFDataView;
But receive an error:
TypeError: Constructor DataView requires 'new'
You need to use ES6 class to extend the native classes such as DataView. As the error messages say, you can only use the methods on real dataviews ("compatible receivers"), and to create such you need to use the DataView constructor (with new - or with super or Reflect.construct). So
class CFDataView {
constructor(...args) {
super(...args)
this.offset = 0;
}
readU8() {
if (this.byteLength >= this.offset+1) {
return this.getUint8(this.offset++);
} else {
return null;
}
}
}
Related
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.
TypeError: this.pizzaPlaceEditService.currentPizzaPlace.getPoint is not a function
I found TypeError: is not a function typescript class but I don't think it applies to this situation. Found several more that didn't seem to apply. Here's my redacted class:
export class PizzaPlace {
deliveryArea = [];
getPoint(seq: number): Point {
let result: Point = null;
this.deliveryArea.forEach(function(point:Point) {
if(seq == point.sequence) {
result = point;
}
});
return result;
}
}
Here is my working unit test:
it('should retrieve a point by sequence', () => {
var point1 = new Point();
point1.sequence = 0;
point1.latitude = 5.12;
point1.longitude = 5.12;
var point2 = new Point();
point2.sequence = 1;
point2.latitude = 6.13;
point2.longitude = 6.13;
var point3 = new Point();
point3.sequence = 2;
point3.latitude = 5.25;
point3.longitude = 5.25;
pizzaPlace.deliveryArea = [point1, point2, point3];
expect(pizzaPlace.getPoint(0)).toBe(point1);
expect(pizzaPlace.getPoint(1)).toBe(point2);
expect(pizzaPlace.getPoint(2)).toBe(point3);
expect(pizzaPlace.getPoint(3)).toBe(null);
});
Here is the code generating the error:
onDownClick(): void {
if(this.selectedPoint) {
let currSeq = this.selectedPoint.sequence;
let nextSeq = currSeq + 1;
console.log("Current pizza place:" + JSON.stringify(this.pizzaPlaceEditService.currentPizzaPlace));
console.log("nextSeq:" + nextSeq);
let next = this.pizzaPlaceEditService.currentPizzaPlace.getPoint(nextSeq);
if(next) {
this.pizzaPlaceEditService.currentPizzaPlace.swapPoints(this.selectedPoint, next);
}
}
}
And here is the error:
Current pizza place:{"contactName":"Zaphod Beeblebrox","customerId":"TPGup2lt","deliveryArea":[{"errorMsg":"","sequence":0,"latitude":34.552,"longitude":-84.556},{"errorMsg":"","sequence":1,"latitude":34.711,"longitude":-84.665}],"deliveryZips":[],"paypalAccount":"HB17","settings":null,"shopName":"Donato's Hartland","shopStreet":"1531 Kenesaw Dr","shopCity":"Lexington","shopState":"KY","shopZip":"40515","shopPhone":"(859)555-2637","billStreet":"1531 Kenesaw Dr","billCity":"Lexington","billState":"KY","billZip":"40515","billPhone":"(859)555-2637"}
pizza-place-delivery.component.ts:63:6
nextSeq:1
pizza-place-delivery.component.ts:64:6
TypeError: this.pizzaPlaceEditService.currentPizzaPlace.getPoint is not a function
I have run out of things to try. Any advice appreciated!
Thanks to Artem's excellent input, I was able to figure out that the problem was caused by the fact that I was creating an object from the JSON instead of the object that I wanted and so it was a different type. Evidently, Classes in Typescript are compile time only and are discarded at runtime.
So, based on: How do I initialize a TypeScript object with a JSON object option 4 of the selected answer, I created a serializable.ts.
export interface Serializable<T> {
deserialize(input: Object): T;
}
And then modified my class with the implements:
export class PizzaPlace implements Serializable<PizzaPlace> {
and then added:
deserialize(input): PizzaPlace {
this.paypalAccount = input.paypalAccount;
this.customerId = input.customerId;
...
this.settings = input.settings;
return this;
}
Then, since my web service returns an array of these, I changed my service call:
.subscribe(places => this.deserializePlaces(places));
and added a new function:
deserializePlaces(places: Object[]){
this.pizzaPlaces = [];
places.forEach(obj=> {
let place = new PizzaPlace().deserialize(obj);
this.pizzaPlaces.push(place);
});
}
And this seems to work just fine. Thanks for the input.
I'm trying to implement some basic operations to the Set object like says here
This is the code
export class Conjunto extends Set<any>{
constructor(initialValues?) {
super();
return new Conjunto(initialValues);
}
isSuperset(subset) {
//some code
}
}
Do you guys have any idea to make it work? or am I doing something wrong?
For the moment I'm using the hack this guy found here
if you are trying to add functions to the Set prototype, or add polyfills to Set, you can do the following:
declare global {
interface Set<T> {
// polyfill
isSuperset(subset: Set<T>) : boolean;
// new function
someNewFunc(): boolean;
}
}
// add polyfill to the Set prototype as mentioned in the doc you provided: https://developer.mozilla.org/es/docs/Web/JavaScript/Referencia/Objetos_globales/Set
Set.prototype.isSuperset = function(subset) {
for (var elem of subset) {
if (!this.has(elem)) {
return false;
}
}
return true;
}
//add the new someNewFunc;
Set.prototype.someNewFunc = function() {
// some logic here...
return true;
}
to use:
stringSet = new Set<string>()
stringSet.isSuperset(someOtherSet);
stringSet.someNewFunc(); // returns true
trying to wrap my head around javascript composition using assign. The property on my base object is unexpectedly being shared between instances. What am I doing wrong? I have...
Stat.js:
import assign from 'object-assign';
var Stat = assign({}, {
_value: undefined,
get: function() {
return this._value;
},
set: function(n) {
var max = this.getMax();
if(n > max) {
this._value = max;
} else {
this._value = n;
}
},
getMax: function() {
return 1;
}
});
module.exports = Stat;
HitPoints.js:
import assign from 'object-assign'
import Stat from './Stat.js';
var HitPoints = assign({}, Stat, {
getMax: function() {
return 100;
}
});
module.exports = HitPoints;
Unit.js:
import assign from 'object-assign';
import HitPoints from 'HitPoints.js';
var Unit = assign({}, {
hp: Object.create(HitPoints)
}
);
module.exports = Unit;
Usage:
import Unit from 'Unit.js';
var u1 = Object.create(Unit);
console.log( u1.hp.get() ); // undefined - ok
u1.hp.set(11);
console.log( u1.hp.get() ); // 11 - ok
var u2 = Object.create(Unit);
console.log( u2.hp.get() ); // 11 - ???
u2.hp.set(22);
console.log( u1.hp.get() ); // 22 - ???
console.log( u2.hp.get() ); // 22
Thanks for your help...
For starters, a quick example of why people don't want you using class.
I don't necessarily hate class, but 90% of the reason to use class is to get inheritance, and while it's occasionally helpful, it can frequently be very painful.
class Person { }
class ArmedPerson extends Person {
constructor (details) {
super(details);
const gun = new Gun();
this.equipment = { gun };
}
attack (target) {
this.equipment.gun.fireAt(target);
}
}
class Civilian extends Person { }
class ArmedCivilian extends ArmedPerson {
/* ... shouldn't it be extending Civilian?
Maybe all Civilians should be armed?
Is this why most games take place in the US?
*/
}
class Soldier extends ArmedPerson {
constructor (personDetails) {
super(personDetails);
}
}
class UnarmedSoldier extends Soldier {
/* HOW DO I TAKE HIS GUN AWAY? */
constructor (personDetails) {
super(personDetails);
}
attack () {
/* I know he has a gun, and anybody hacking the game can use it, but what do I do here? */
}
}
class inheritance has shown itself to be one of those things that people have misused terribly, for the past 30+ years (just like every other useful tool out there).
Rather than inheritance, we can look at composition (via Dependency Inversion).
class Soldier {
constructor (personDetails, gun) {
/*...setup...*/
this.equipment = { gun };
this.selectedWeapon = gun;
}
attack (target) {
this.selectedWeapon.fireAt(target);
}
}
const soldier = new Soldier({ /*...details... */ }, new Gun());
Not a lot has changed, in terms of the end-result we wanted... we've been able to simplify really a lot, and now we can even give him a method to swap guns if we want, all because rather than bolting the gun into something that he inherits from, we're handing him a gun when we first meet him.
It could be any type of gun we want, as long as it can still be fired in a similar fashion.
The question arises:
are there better ways of making things reusable, then, if inheritance is completely off the table?
To that I say: inheritance shouldn't be completely off the table... ...it should just be so far off to the side that it should be an "aha" moment, when you discover that it really is the best and cleanest way to accomplish something (rather than attempting to inherit from .......something, anything, right now!).
Various languages have a concept referred to as Traits or Mix-Ins.
In something like Java, a close-ish approximation is Interfaces.
I'm not a huge fan of Interfaces (the structure, not the concept - love the concept).
In Java, Interfaces make you do more work, because they have you define the function, what the function takes, what it returns...
...but you can't give it any default behaviour (traditionally), so if you have 14 objects which implement the same interface, that's the same method you write out 14 times (plus the signature for the interface). Sometimes, those methods are going to be completely different in the specifics of the implementation; that's fine... ...sometimes, they'll be the exact same as what you intended when you wrote the interface to begin with.
That's less okay. Queue Traits; these are things which you define the interface of, define the behaviour for, and then copy onto your object.
In JS, we can even have some closure safety around them, by injecting context that they get to work from, rather than letting them assume they get to mess around with the entirety of this.
const Movable = (pos) => ({
up (distance) { pos.y += distance; },
down (distance) { pos.y -= distance; },
left (distance) { pos.x -= distance; },
right (distance) { pos.x += distance; }
});
class Point {
constructor (x, y) {
Object.assign(this, { x, y });
}
}
class Person {
constructor (position) {
Object.assign(this, { position }, Movable(position));
}
}
const person = new Person( new Point(0, 0) );
person.up( 20 );
person.position.y; // 20
If you'll note, Movable is returning a new instance of an object, with methods which change values on position. That object is having its methods copied onto the instance of person.
I can now create as many of these Traits as I'd like, and copy them onto as many objects as I'd like, and get reuse that way.
Well, this worked...
Stat.js:
var Stat = {
get: function() {
return this._value;
},
set: function(n) {
var max = this.getMax();
if(n > max) {
this._value = max;
} else {
this._value = n;
}
},
getMax: function() {
return 1;
}
}
HitPoints.js:
var HitPoints = function() {
return assign(Object.create(Stat), {
getMax: function() {
return 100;
}
});
}
Unit.js:
var Unit = function() {
return assign({},
Object.create(XYPiece),
Object.create(TeamMember),
{
hp: HitPoints()
}
);
}
Usage:
var u1 = Unit();
console.log( u1.hp.get() ); // undefined
u1.hp.set(11);
console.log( u1.hp.get() ); // 11
var u2 = Unit();
console.log( u2.hp.get() ); // undefined
u2.hp.set(22);
console.log( u1.hp.get() ); // 11
console.log( u2.hp.get() ); // 22
This article helped. Hooray!!!
Still, tho, if this is fundamentally an idiotic way to go about it, tell me...
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?