How to replace style property with proxy - javascript

I have code like this in my unit tests for jQuery Terminal:
// https://github.com/tmpvar/jsdom/issues/135
Object.defineProperties(window.HTMLElement.prototype, {
offsetLeft: {
get: function() { return parseFloat(window.getComputedStyle(this).marginLeft) || 0; }
},
offsetTop: {
get: function() { return parseFloat(window.getComputedStyle(this).marginTop) || 0; }
},
offsetHeight: {
get: function() { return parseFloat(window.getComputedStyle(this).height) || 0; }
},
offsetWidth: {
get: function() { return parseFloat(window.getComputedStyle(this).width) || 0; }
},
// this will test if setting 1ch change value to 1ch which don't work in jsdom used by jest
style: {
get: function() {
if (this.__style) {
return this.__style;
}
var self = this;
var attr = {};
function set_style_attr() {
var str = Object.keys(attr).map((key) => `${key}: ${attr[key]}`).join(';') + ';';
self.setAttribute('style', str);
}
var mapping = {
backgroundClip: 'background-clip',
className: 'class'
};
var reversed_mapping = {};
Object.keys(mapping).forEach(key => {
reversed_mapping[mapping[key]] = key;
});
return this.__style = new Proxy({}, {
set: function(target, name, value) {
name = mapping[name] || name;
if (!value) {
delete target[name];
delete attr[name];
} else {
attr[name] = target[name] = value;
}
set_style_attr();
return true;
},
get: function(target, name) {
if (name === 'setProperty') {
return function(name, value) {
attr[name] = target[name] = value;
set_style_attr();
};
} else {
return target[name];
}
},
deleteProperty: function(target, name) {
name = reversed_mapping[name] || name;
delete target[name];
delete attr[name];
set_style_attr();
}
});
}
}
});
It works for 1ch attribute in my tests that look like this:
it('should handle wider characters without formatting', function() {
var input = 'ターミナルウィンドウは黒[[;;]です]';
var string = $.terminal.format(input, {char_width: 7});
expect(string).toEqual('<span style="width: 24ch"><span style="widt'+
'h: 24ch">ターミナルウィンドウは黒</span></span'+
'><span style="width: 4ch" data-text="です">'+
'<span style="width: 4ch">です</span></span>');
});
If I don't use my Proxy I got width in pixels, because I have code like this to check if ch is supported in my code:
var agent = window.navigator.userAgent;
var is_IE = /MSIE|Trident/.test(agent) || /rv:11.0/i.test(agent);
var is_IEMobile = /IEMobile/.test(agent);
// -------------------------------------------------------------------------
var is_ch_unit_supported = (function() {
if (is_IE && !is_IEMobile) {
return false;
}
var div = document.createElement('div');
div.style.width = '1ch';
return div.style.width === '1ch';
})();
The problem is when I want to check the style property to get some value, like this test:
it('should find inside formatting', function() {
term.less(big_text.concat(['[[;red;]foo bar baz]']));
search('bar');
var spans = term.find('[data-index="0"] > div:first-child span');
['foo ', 'bar', ' baz'].forEach(function(string, i) {
expect(a0(spans.eq(i).text())).toEqual(string);
});
[true, false, true].forEach(function(check, i) {
console.log(spans.get(i).style.getPropertyValue('color'));
expect([i, !!spans.get(i).attr('style').match(/color:\s*red/)]).toEqual([i, check]);
});
});
I've tried:
spans.get(i).style.getPropertyValue('color')
This return error that's not a function and
spans.get(i).attr('style')
is undefined. This also don't work
spans.get(i).getAttribute('style')
which should be the same and the one before.
Is there a way to have ch unit support check work but in same way getting values from style attribute as well?
I'm using jest framework that use jsDom I running my tests from Node.
I've tried to create getPropertyValue function in get trap for the proxy but I don't know how to get original function so I can call it.
So to sum up, I need solution in jsDOM that allow to set width to 1ch and get that value back (so my code don't change), and that should work when creating new HTMLElement in DOM and get it's value out of, It don't need to be style object with props it can be style property as string. Alternative solution is to test if ch unit is supported that will work in jsDOM.

I've solved the issue by temporarily disabling getter when accessing properties:
(function() {
var style_descriptor = Object.getOwnPropertyDescriptor(window.HTMLElement.prototype, 'style');
Object.defineProperties(window.HTMLElement.prototype, {
offsetLeft: {
get: function() { return parseFloat(window.getComputedStyle(this).marginLeft) || 0; }
},
offsetTop: {
get: function() { return parseFloat(window.getComputedStyle(this).marginTop) || 0; }
},
offsetHeight: {
get: function() { return parseFloat(window.getComputedStyle(this).height) || 0; }
},
offsetWidth: {
get: function() { return parseFloat(window.getComputedStyle(this).width) || 0; }
},
// this will test if setting 1ch change value to 1ch which don't work in jsdom used by jest
style: {
get: function getter() {
if (this.__style) {
return this.__style;
}
var self = this;
var attr = {};
function set_style_attr() {
var str = Object.keys(attr).map((key) => `${key}: ${attr[key]}`).join(';') + ';';
self.setAttribute('style', str);
}
var mapping = {
backgroundClip: 'background-clip',
className: 'class'
};
var reversed_mapping = {};
Object.keys(mapping).forEach(key => {
reversed_mapping[mapping[key]] = key;
});
function disable(fn) {
// temporary disable proxy
Object.defineProperty(window.HTMLElement.prototype, "style", style_descriptor);
var ret = fn();
Object.defineProperty(window.HTMLElement.prototype, "style", {
get: getter
});
return ret;
}
return this.__style = new Proxy({}, {
set: function(target, name, value) {
name = mapping[name] || name;
if (!value) {
delete target[name];
delete attr[name];
} else {
attr[name] = target[name] = value;
}
set_style_attr();
disable(function() {
self.style[name] = name;
});
return true;
},
get: function(target, name) {
if (name === 'setProperty') {
return function(name, value) {
attr[name] = target[name] = value;
set_style_attr();
};
} else if (target[name]) {
return target[name];
} else {
return disable(function() {
return self.style[name];
});
}
},
deleteProperty: function(target, name) {
name = reversed_mapping[name] || name;
delete target[name];
delete attr[name];
disable(function() {
delete self.style[name];
});
set_style_attr();
}
});
}
}
});
})();

Related

Method chaining with Javascript - Plus Minus Question

I got a code challenge that create a plus-minus functions using method chaining. i have created the code as follows but it eventually failed when it comes to the output rendering
like plus(3).minus(2).value() and minus(3).minus(3).value() kind of method invoking
code as follows
function plus(valuez)
{
this.valuez = this.valuez+ valuez;
function value{
return valuez
}
plus.minus = minus;
plus.value = value;
plus.plus = this;
return this;
}
function minus(valuez)
{
this.valuez = this.v+ valuez;
function value(){
return valuez
}
minus.plus = plus;
minus.minus = this
minus.value = value;
return this;
}
expected output is
1 and 6 but I only get the printed last number entered. how can I resolve this?
class Box {
constructor(v) { this._value = v }
plus(v) { this._value += v; return this; }
minus(v) { this._value -= v; return this; }
value() { return this._value; }
}
function plus(v) { return new Box(v) }
function minus(v) { return new Box(-v) }
console.log("plus(3).minus(2).value()", plus(3).minus(2).value());
console.log("minus(3).minus(3).value()", minus(3).minus(3).value());
function plus (x) { return { _value: x, plus(y){ return plus(this._value+y) }, minus(y){ return plus(this._value-y) }, value(){ return this._value } } }
function minus(x) { return plus(-x) }
console.log("plus(3).minus(2).value()", plus(3).minus(2).value());
console.log("minus(3).minus(3).value()", minus(3).minus(3).value());
Using closure
function plus (x) { return { plus(y){ return plus(x+y) }, minus(y){ return plus(x-y) }, value(){ return x } } }
function minus(x) { return plus(-x) }
console.log("plus(3).minus(2).value()", plus(3).minus(2).value());
console.log("minus(3).minus(3).value()", minus(3).minus(3).value());
Without a constructor or class-sugar you can return an object using closed over values from the addition/subtraction method. Something like:
const { plus, val, reset } = PM();
console.log(plus().plus(3).minus(4).plus(25).plus(4).trace());
console.log(reset().minus(2).plus(33).val());
function PM() {
let values = [];
let traced = [];
const reset = () => {
values = [];
traced = [];
return ret;
};
const add = value => {
const calc = +(values[values.length-1] || 0) + value;
traced.push(`${values[values.length-1] || 0}${
!value || value >= 0 ? "+" : ""}${value || 0}=${calc || 0}`);
values.push(calc || 0);
return ret;
};
const ret = {
plus: value => add(value),
minus: value => add(-value),
val: () => values,
trace: () => traced,
reset,
};
return ret;
}
Or combine this with an embedded constructor

Get element of chaining in jquery to use in $.fn.{FunctionName}

I am creating a pluging based in Jquery and Bootrap (Laste Versions), that is call by this method/code:
$(document).find('.calendar-plug-bs').each(function(index, el) {
//$(this).plgCalendar("destroy"); Testing for destroy if exist...
$(this).plgCalendar();
})
it work with and element like this:
<div class="form-group row">
<div class="calendar-plug-bs"></div>
</div>
and this script/plugin starts as follows ...
(function($) {
$.fn.plgCalendar = function(param) {
return window.plgCalendar(param);
};
}(jQuery));
But the problem is that i need retrive node that call the initial function...
|------|
$(this).plgCalendar();
to add a table inside it ...
function plgCalendar(param = null) {
var r = null;
if (param !== null) {
if (typeof param.func !== 'undefined') {
if (param.func === "destroy") {
} else if (param.func === "getValue") {
}
} else {
console.log('%cMSG: (func) not Set', 'color: #bada55');
}
} else {
/*****WORKING ON THIS***/
var target = $(this);
console.log(target);
if (target.is("div")) {
var id = window.BuildRandID();
var $tableObject = $('<table/>', {
'class': 'bigger',
'id': id
});
$(target).append($tableObject);
return id;
} else {
window.alert("calendar-plug-bs must be a div");
}
/*****WORKING ON THIS***/
}
return r;
}
the fail is that var taget not is a div... and i dont know how to retrive...
resolve by this way:
(function($) {
$.fn.plgCalendar = function(param = null) {
param = window.plgCalendarParam(param);
param['this'] = $(this);
return window.plgCalendar(param);
};
}(jQuery));
function plgCalendarParam(param) {
if (param == null) {
param = [];
}
return param;
}
function plgCalendar(param) {
var r = null;
if (typeof param.func !== 'undefined') {
if (param.func === "destroy") {
} else if (param.func === "getValue") {
}
} else {
/*****WORKING ON THIS***/
var target = param['this'];
if (target.is("div")) {
var id = 'plgCalendar_' + window.BuildRandID();
var $tableObject = $('<table/>', {
'class': 'bigger',
'id': id
});
$(target).append($tableObject);
r = { 'id': id, 'init': true };
} else {
window.alert("calendar-plug-bs must be a div");
}
/*****WORKING ON THIS***/
}
return r;
}

Extending Object prototype by adding a function in a "namespace"

The below code (also in Plunker) is adapted from this SO post.
I am trying to implement the same logic, only add the uniqueId function in a special property of the Object.prototype to keep things clear.
The below code works when run using nodejs (also the HTML-ized Plunker example works as well). I.e. the console prints three unique object identifiers: 0, 1 and 2.
However, when the test switch variable is set to 1, console prints 0, 0 and 0.
What should I do to add the uniqueId function in the foo namespace?
function addUniqueId1(){
if (Object.prototype.foo===undefined) {
Object.prototype.foo = {};
console.log('foo "namespace" added');
}
if (Object.prototype.foo.uniqueId===undefined) {
var i = 0;
Object.prototype.foo.uniqueId = function() {
console.log('uniqueId function called');
if (this.___uniqueId === undefined) {
this.___uniqueId = i++;
}
return this.___uniqueId;
};
console.log('function defined');
}
}
function addUniqueId2() {
if (Object.prototype.uniqueId === undefined) {
var i = 0;
Object.prototype.uniqueId = function() {
if (this.___uniqueId === undefined) {
this.___uniqueId = i++;
}
return this.___uniqueId;
};
};
};
var test=2; // if you set this to 1 it stops working
if (test==1)
addUniqueId1();
else
addUniqueId2();
var xs = [{}, {}, {}];
for (var i = 0 ; i < xs.length ; i++) {
if (test==1)
console.log('object id is: ['+xs[i].foo.uniqueId()+']');
else
console.log('object id is: ['+xs[i].uniqueId()+']');
}
You need to define foo with a getter, to allow it to access this for use within the hash of sub-method(s):
function defineFoo() {
if (Object.prototype.foo) return;
var i = 0;
Object.defineProperty(Object.prototype, 'foo', {
get: function() {
var self = this;
return {
uniqueId: function() {
return self.__uniqueId = self.uniqueId || ++i;
}
};
}
});
}
Now
defineFoo();
obj.foo.uniqueId()
If you prefer, an alternative to using self:
Object.defineProperty(Object.prototype, 'foo', {
get: function() {
return {
uniqueId: function() {
return this.__uniqueId = this.uniqueId || ++i;
}.bind(this)
};
}
});

How to inherit Common object with other objects in javascript prototype

I am working with javascript. I would like to inherit Common object with other objects in javascript. Is it possible to do it? I mean to convert this js to a prototype model.
function setEvent(evt, ele, callback) {
var ele = getEle(ele);
if (ele.length === undefined) {
ele.addEventListener(evt, callback, false);
} else if (ele.length > 0) {
for (var i = 0; i < ele.length; i++) {
ele[i].addEventListener(evt, callback, false);
}
}
return false;
}
function getEle(ele) {
var _idf = ele.charAt(0);
if (_idf == "#") {
var _ele = document.getElementById(ele.substr(1));
return _ele;
} else if (_idf == ".") {
var _els = document.getElementsByClassName(ele.substr(1));
return _els;
}
return ele;
}
function getInnerHtml(id) {
return document.getElementById(id).innerHTML;
}
function getValue(elem) {
return elem.value;
}
setEvent("click", ".input", function() {
console.log(this.value);
var _num = getInnerHtml("math");
_num = _num + "" + getValue(this);
document.getElementById("math").innerHTML = _num;
});
It is a bit hard to understand what type of object model you're trying to build since you did not describe any requirements or desires in that regard. Here's one idea that kind of works like jQuery does:
function core(selector) {
if (!(this instanceof core)) {
return new core(selector);
}
var items = document.querySelectorAll(selector);
if (items) {
this.items = Array.prototype.slice.call(items);
} else {
this.items = [];
}
this.length = this.items.length;
return this;
}
core.prototype = {
each: function(fn) {
for (var i = 0; i < this.length; i++) {
fn.call(this.items[i]);
}
return this;
},
firstProp: function(propName, defaultVal) {
if (this.length > 0) {
var val = this.items[0][propName];
return val !== undefined ? val : defaultVal;
} else {
return defaultVal;
}
},
getHTML: function() {
return this.firstProp("innerHTML", "");
},
setHTML: function(html) {
return this.each(function() {
this.innerHTML = html;
});
},
on: function(event, fn) {
return this.each(function() {
this.addEventListener(event, fn);
});
},
getValue: function() {
return this.firstProp("value", "");
}
};
And, then a usage example like yours:
core(".input").on("click", function(e) {
console.log(this.value);
var math = core("#math");
math.setHTML(math.getHTML() + this.value);
});
Working demo: http://jsfiddle.net/jfriend00/t56L4p5f/

Can't enumerate getters/setters properties

I am working on some reflections code to try to scrape out properties and functions, but I can't seem to get the getters/setters at all.
The reflection code I have for properties is:
Reflector = function() { };
Reflector.getProperties = function(obj) {
var properties = [];
var proto = obj;
while (proto != Object.prototype) {
console.log('Scrapping proto: ', proto);
for (var prop in proto) {
console.log('typeof ' + prop + ": ", typeof obj[prop]);
if (typeof obj[prop] != 'function') {
properties.push(prop);
}
}
proto = Object.getPrototypeOf(proto);
}
return properties;
};
And a sample of it running (with my debug messages) is:
var SimpleTestObject = function() {
this.value = "Test1";
this._hiddenVal = "Test2";
this._readOnlyVal = "Test3";
this._rwVal = "Test4";
};
SimpleTestObject.prototype = {
get readOnlyVal() {
return this._readOnlyVal;
},
get rwVal() {
return this._rwVal;
},
set rwVal(value) {
this._rwVal = value;
},
func1: function() {
// Test
}
};
SimpleTestObject.func2 = function(test) { /* Test */ };
SimpleTestObject.outsideVal = "Test5";
var props = Reflector.getProperties(SimpleTestObject);
console.log('props: ', props);
console.log('Object.getOwnPropertyNames: ', Object.getOwnPropertyNames(SimpleTestObject));
console.log('rwVal property descriptor: ', Object.getOwnPropertyDescriptor(SimpleTestObject, 'rwVal'));
console.log('rwVal (2) property descriptor: ', Object.getOwnPropertyDescriptor(Object.getPrototypeOf(SimpleTestObject), 'rwVal'));
What I expect to see as output to my Reflection.getProperties(SimpleTestObject) is ['readOnlyVal', 'rwVal', 'outsideVal'], but instead I am only seeing outsideVal. Further, when I tried to using getOwnPropertyDescriptor() to see if the rwVal was enumerable, it came back as undefined. So, thinking maybe it somehow got showed into the prototype above, I tried going up a level and still got undefined.
For enumerate the getters please use Object.keys or Object.getOwnPropertiesNames on prototype instead of constructor or/and instance:
function readGetters(obj) {
var result = [];
Object.keys(obj).forEach((property) => {
var descriptor = Object.getOwnPropertyDescriptor(obj, property);
if (typeof descriptor.get === 'function') {
result.push(property);
}
});
return result;
}
var SimpleTestObject = function() {
this.value = "Test1";
this._hiddenVal = "Test2";
this._readOnlyVal = "Test3";
this._rwVal = "Test4";
};
SimpleTestObject.prototype = {
get readOnlyVal() {
return this._readOnlyVal;
},
get rwVal() {
return this._rwVal;
},
set rwVal(value) {
this._rwVal = value;
},
func1: function() {
}
};
SimpleTestObject.func2 = function(test) { /* Test */ };
SimpleTestObject.outsideVal = "Test5";
// For constructor
console.log(readGetters(SimpleTestObject.prototype));
// For instance
var instance = new SimpleTestObject();
console.log(readGetters(Object.getPrototypeOf(instance)));
you can enumerate setter/getter properties by Object.getOwnPropertyNames if you use getter and setter with Object.defineProperty or Object.defineProperties
const _name = Symbol();
const _age = Symbol();
class Dog {
constructor(name, age) {
Object.defineProperties(this, {
name: {
// you can set enumerable true explicitly if you want
//enumerable:true ,
set(value) {
this[_name] = name;
},
get() {
return this[_name];
}
},
age: {
set(value) {
this[_age] = age;
},
get() {
return this[_age];
}
},
book: {
get() {
return "Book"
}
}
});
this.name = name;
this.age = age;
}
}
const dog = new Dog("spike", 3);
console.log(Object.getOwnPropertyNames(dog));

Categories

Resources