I have created a JS class. Here is following code:
export default class Service {
constructor(
serviceId,
serviceName,
serviceDescription,
serviceImageName,
categoryId,
servicePrice,
currencyCode,
acceptPayment,
serviceDuration,
multipleBookingPerSlot,
mode,
tzSupport,
minOptionCount
) {
try{
this.id = serviceId;
this.title = serviceName;
this.subTitle = serviceDescription;
this.imageUrl = serviceImageName;
this.categoryId = categoryId;
this.price = servicePrice;
this.currencyCode = currencyCode;
this.acceptPayment = acceptPayment;
this.meetingDuration = serviceDuration;
this.multipleBookingPerSlot = multipleBookingPerSlot;
this.serviceName = serviceName;
this.mode = mode;
this.tzSupport = tzSupport;
this.session = minOptionCount
} catch(e){
if(e instanceof ReferenceError){
console.error("Service data missing.")
}
}
}
}
My goal is whenever new object of Service creates like new Service('1') if any of key is missing code should throw error and stop execution. How can i achieve this?
You won't get a ReferenceError if the caller doesn't supply enough arguments, you'll just see undefined in the parameters.
You have 13 parameters (which is far, far too many). You could do the brute-force thing:
if (arguments.length < 13) {
throw new Error("Missing arguments");
}
Instead, though, I suggest using the builder pattern or an options object instead of 13 discrete parameters. More than three parameters is fairly hard to manage.
For instance, with an options object:
export default class Service {
constructor(
options
) {
["id", "title", "subTitle", "imageUrl", "categoryId", "price", "currencyCode",
"acceptPayment", "meetingDuration", "multipleBookingPerSlot", "serviceName",
"mode", "tzSupport", "session"].forEach(name => {
if (!options.hasOwnProperty(name)) {
throw new Error(name + " is a required option");
}
});
Object.assign(this, options);
}
}
Usage:
let s = new Service({id: 1, title: "foo", /*...etc...*/});
That way, the caller isn't lost in a sea of parameters.
However, if it's important to validate the parameter values are present, isn't it important to validate their values, too? Nothing's to stop me from calling new Service with 13 completely-invalid arguments (undefined repeated 13 times, for instance).
So I would probably use an options object (because it's much easier for the caller) combined with parameter destructuring, and then individual validation, e.g.:
export default class Service {
constructor({ // <== Notice the {
id,
name,
decription,
imageUrl,
categoryId,
price,
currencyCode,
acceptPayment,
meetingDuration,
multipleBookingPerSlot,
mode,
tzSupport,
minOptionCount
}) { // <== And the }
this.id = validate.positiveNumber(id);
this.title = validate.nonBlank(name);
this.subTitle = validate.nonBlank(description);
this.imageUrl = validate.URL(imageUrl);
this.categoryId = validate.positiveNumber(categoryId);
this.price = validate.price(price);
this.currencyCode = validate.currencyCode(currencyCode);
this.acceptPayment = validate.boolean(acceptPayment);
this.meetingDuration = validate.duration(meetingDuration);
this.multipleBookingPerSlot = validate.boolean(multipleBookingPerSlot);
this.serviceName = this.title; // Already validated
this.mode = validate.mode(mode);
this.tzSupport = validate.tzSupport(tzSupport);
this.session = validate.whateverThisIs(minOptionCount);
}
}
...where validate is a set of reusable validations. Usage is the same as above:
let s = new Service({id: 1, title: "foo", /*...etc...*/});
As i already commented assigning undefined to an objects property is completely valid. The solution might be to check for the values of the arguments Arraylike against undefined:
constructor(a,b,c){
if(arguments.length!=3){//check length
return;
}
for(var a=0;a<arguments.length;a++){
if(arguments[a]===undefined){//check against undefined
return;
}
}
//your code
}
http://jsbin.com/vugepakama/edit?console
Related
Let's say I have a Thing class which I want to be both Hideable and Openable.
Using a similar approach to Douglas Crockford's object creation through composition, I have been able to "inherit" from multiple classes.
This approach does not work with accessors (getter/setters).
I need to use classes as it's a requirement. I'm also finding that I am duplicating functionality from class to class, but I don't want these to inherit from a base class.
Any ideas?
The progress I have made so far is in the below snippet:
class Openable {
constructor(isOpen = false) {
this._isOpen = isOpen;
}
get isOpen() {
return this._isOpen + ' is stupid.';
}
set isOpen(value) {
this._isOpen = value;
}
}
class Hideable {
constructor(isHidden = false) {
this._isHidden = isHidden;
}
get isHidden() {
return this._isHidden + ' is stupid.';
}
set isHidden(value) {
this._isHidden = value;
}
}
class Thing {
constructor(config) {
let { isOpen, isHidden } = config;
let openable = new Openable(isOpen);
this.isOpen = openable.isOpen;
let hideable = new Hideable(isHidden);
this.isHidden = openable.isHidden;
}
}
let thing = new Thing({
isOpen: true,
isHidden: false
});
Because isOpen and isHidden are accessors, you can't just grab a copy of them, you have to access them when you want them.
Still, you can create your own isOpen, isHidden which use the underlying ones:
let openable = new Openable(isOpen);
Object.defineProperty(this, "isOpen", {
get: () => openable.isOpen,
set: value => {
openable.isOpen = value;
}
});
let hideable = new Hideable(isHidden);
Object.defineProperty(this, "isHidden", {
get: () => hideable.isHidden,
set: value => {
hideable.isHidden = value;
}
});
Live example on Babel's REPL
Naturally, if you do this a lot, you'd want to have a worker function to set that up rather than retyping it all the time:
function wrapProperty(dest, src, name) {
Object.defineProperty(dest, name, {
get: () => src[name],
set: value => { src[name] = value; }
});
}
(or do it by grabbing the property descriptor and updating it)
then:
wrapProperty(this, openable, "isOpen");
wrapProperty(this, hideable, "isHidden");
I'd question the requirement that you must use class for Openable and Hideable. They look much more like mixins to me.
Besides that the OP's accessor approach via "pseudo private property" notation and prototypal getters/setters for Openable/Hideable classes already is questionable, traits would come closest to the also doubtable requirement of using classes as mixin surrogates just for the sake of meeting documentation requirements.
As long as JavaScript does not provide traits natively, one has to stick to either more advanced class based mixin patterns ore one remembers Angus Croll's Flight Mixins.
A mixin's function body one has to write is close enough to the constructor body of a class. Nevertheless function based mixins never will be instantiated but always have to be applied to an object/type via either call or apply.
A possible solution, featuring this kind of mixin approach, that already reliably fulfills the OP's requirements then might look like the next provided example code ...
let
Openable = (function openableMixinFactory () {
let
defineProperty = Object.defineProperty,
isBoolean = (type => (typeof type == 'boolean'));
return function openableMixinApplicator (isOpen = false) {
let
openableCompositeType = this,
getIsOpen = (() => isOpen),
setIsOpen = (value => ((isBoolean(value) && (isOpen = value)) || (void 0)));
defineProperty(openableCompositeType, 'isOpen', {
get: getIsOpen,
set: setIsOpen,
enumerable: true
});
return openableCompositeType;
};
}()),
Hideable = (function hideableMixinFactory () {
let
defineProperty = Object.defineProperty,
isBoolean = (type => (typeof type == 'boolean'));
return function hideableMixinApplicator (isHidden = false) {
let
hideableCompositeType = this,
//getIsHidden = (() => isHidden),
getIsHidden = (() => [isHidden, 'is stupid.'].join(' ')),
setIsHidden = (value => ((isBoolean(value) && (isHidden = value)) || (void 0)));
defineProperty(hideableCompositeType, 'isHidden', {
get: getIsHidden,
set: setIsHidden,
enumerable: true
});
return hideableCompositeType
};
}());
class Thing {
constructor(config) {
let
{isOpen, isHidden} = config;
Openable.call(this, isOpen);
Hideable.call(this, isHidden);
}
}
var
thing = new Thing({ isOpen: true/*, isHidden: false*/ });
console.log('thing : ', thing);
console.log('thing.isOpen : ', thing.isOpen);
console.log('thing.isHidden : ', thing.isHidden);
console.log('(thing.isOpen = "xyz") : ', (thing.isOpen = "abc"));
console.log('(thing.isHidden = "xyz") : ', (thing.isHidden = "xyz"));
console.log('thing.isOpen : ', thing.isOpen);
console.log('thing.isHidden : ', thing.isHidden);
console.log('(thing.isOpen = false) : ', (thing.isOpen = false));
console.log('(thing.isHidden = true) : ', (thing.isHidden = true));
console.log('thing.isOpen : ', thing.isOpen);
console.log('thing.isHidden : ', thing.isHidden);
.as-console-wrapper { max-height: 100%!important; top: 0; }
Other answers of mine at SO, that provide similar solutions to related questions, featuring the same approach are ...
How do I organize data by common traits?
Composition/Inheritance/Factory - Best Pattern For This Case
Multiple inheritance using classes
Class inheritance between projects
The DOMTokenList and DOMSettableTokenList interfaces (MDN, WHATWG) provide methods for manipulating ordered sets of string tokens represented by space-delimited strings. They are most commonly used in the form of the Element.prototype.classList property, a DOMTokenList which reflects the class attribute of an associated element.
var div = document.createElement('div');
div.setAttribute('class', 'hello world goodnight moon');
var list = div.classList;
console.assert(list.length === 4);
console.assert(list[0] === 'hello');
console.assert(list.item(1) === 'world');
console.assert(list.contains('moon') === true);
console.assert(list.contains('mars') === false);
list.remove('world', 'earth', 'dirt', 'sand');
list.add('hello', 'mars');
list.toggle('goodnight');
console.assert(div.getAttribute('class') === 'hello moon mars');
I'm working on a custom element (HTML5Rocks, W3C Draft) which displays a real-time feed of the activity of specified Stack Overflow users. This list of users is specified in an ids attribute, and may be updated at any time.
<so-users ids="1114 22656 106224"></so-users>
document.querySelector('so-users').setAttribute('ids', '23354 115866');
Instead of requiring users to manipulate this attribute directly, I would like to have an .ids property providing a DOMTokenList that they can use instead. Ideally this would be directly associated with the attribute, but an unbound DOMSettableTokenList instance that I have to manually bind would also be fine.
document.querySelector('so-users').ids.add('17174');
Unfortunately, I have been unable to find any way to create a DOMTokenList instance. The definition is not a constructor, and directly creating an object using its prototype results in errors when I call any associated methods:
new DOMTokenList; // TypeError: Illegal constructor
new DOMSettableTokenList; // TypeError: Illegal constructor
var list = Object.create(DOMSettableTokenList.prototype, {
value: { value: 'hello world' }
});
console.assert(list instanceof DOMTokenList);
console.assert(list instanceof DOMSettableTokenList);
list.item(0); // TypeError: Illegal invocation
function TokenListConstructor() {
this.value = 'hello world';
}
TokenListConstructor.prototype = DOMSettableTokenList.prototype;
var list = new TokenListConstructor;
console.assert(list instanceof DOMTokenList);
console.assert(list instanceof DOMSettableTokenList);
list.add('moon'); // TypeError: Illegal invocation
How can I construct a new DOMTokenList or DOMSettableTokenList instance?
You cannot create an DOMTokenList or an DOMSettableTokenList directly. Instead you should use the class attribute to store and retrieve your data and perhaps map an ids attribute of your DOM element to the classList property.
var element = document.querySelector('so-users');
element.ids = element.classList;
You can use relList according to the documentation but classList is more supported, the only drawback is that you might run into issues if one of your ids matches a class name so set an inline style to hide the element just in case.
For a custom component compatibility should be a concern (classList is present in IE>=10, Firefox 3.6, Chrome 8, Opera 11.5 and Safari 5.1, see http://caniuse.com/#feat=classlist) so if compatibility is in your requirements use the another solution posted below.
If you cannot use clases or classList and/or must use the ids attribute you should implement a custom function according to the spec with the following properties as functions.
item()
contains()
add()
remove()
toggle()
This is an example implementation of such functionality.
var TokenList = function (ids) {
'use strict';
var idsArray = [],
self = this,
parse = function (id, functionName, cb) {
var search = id.toString();
if (search.split(' ').length > 1) {
throw new Error("Failed to execute '" + functionName + "' on 'TokenList': The token provided ('" + search + "') contains HTML space characters, which are not valid in tokens.');");
} else {
cb(search);
}
};
function triggerAttributeChange() {
if (self.tokenChanged && typeof self.tokenChanged === 'function') {
self.tokenChanged(idsArray.toString());
}
}
if (ids && typeof ids === 'string') {
idsArray = ids.split(' ');
}
self.item = function (index) {
return idsArray[index];
};
self.contains = function (id) {
parse(id, 'contains', function (search) {
return idsArray.indexOf(search) !== -1;
});
};
self.add = function (id) {
parse(id, 'add', function (search) {
if (idsArray.indexOf(search) === -1) {
idsArray.push(search);
}
triggerAttributeChange();
});
};
self.remove = function (id) {
parse(id, 'remove', function (search) {
idsArray = idsArray.filter(function (item) {
return item !== id;
});
triggerAttributeChange();
});
};
self.toggle = function (id) {
parse(id, 'toggle', function (search) {
if (!self.contains(search)) {
self.add(search);
} else {
self.remove(search);
}
});
};
self.tokenChanged = null;
self.toString = function () {
var tokens = '',
i;
if (idsArray.length > 0) {
for (i = 0; i < idsArray.length; i = i + 1) {
tokens = tokens + idsArray[i] + ' ';
}
tokens = tokens.slice(0, tokens.length - 1);
}
return tokens;
};
};
Set an 'ids' property in your element with a new instance of this function and finally you must bound the targeted attribute to the property listening to changes to the element and updating the property o viceversa. You can do that with a mutation observer.
See firing event on DOM attribute change and https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
var attachTokenList = function (element, prop, initialValues) {
'use strict';
var initValues = initialValues || element.getAttribute(prop),
MutationObserver = window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
observer,
config,
cancelMutation = false;
function createTokenList(values) {
var tList = new TokenList(values);
tList.tokenChanged = function () {
element.setAttribute(prop, element[prop].toString());
cancelMutation = true;
};
element[prop] = tList;
}
createTokenList(initValues);
observer = new MutationObserver(function (mutation) {
var i,
mutationrec,
newAttr;
if (mutation.length > 0 && !cancelMutation) {
for (i = 0; i < mutation.length; i = i + 1) {
mutationrec = mutation[i];
if (mutationrec.attributeName === prop && element[prop]) {
newAttr = element.getAttribute(prop);
createTokenList(newAttr);
}
}
}
cancelMutation = false;
});
config = {
attributes: true
};
observer.observe(element, config);
};
Testing to see if it works
<so-users ids="1234 5678"></so-users>
<button onclick="clickButton1()">Add 7890</button>
<button onclick="clickButton2()">Set to 3456</button>
<button onclick="clickButton3()">Add 9876</button>
Inside a script tag
var elem = document.querySelector('so-users');
attachTokenList(elem, 'ids')
function clickButton1 () {
elem.ids.add('7890');
}
function clickButton2 () {
elem.setAttribute('ids', '3456');
}
function clickButton3 () {
elem.ids.add('9876');
}
Clicking the buttons in sequence set the ids attribute to '3456 9876'
You can get an instance of DOMTokenList with this function:
function newDOMTokenList(initialTokens) {
const tmp = document.createElement(`div`);
const classList = tmp.classList;
if (initialTokens) {
initialTokens.forEach(token => {
classList.add(token);
});
}
return classList;
}
We can 'steal' the DOMTokenList from a div, since it does not affect the current document until you choose to insert the element (for example by using insertAdjacentElement) and it will be garbage collected since we do not keep any references to the variable tmp.
Then you can use your list:
var list = newDOMTokenList(['a', 'b']);
list.add('c');
list.contains('d'); // false
list.contains('b'); // true
list.item(1) // 'b'
list instanceof DOMTokenList // true
// etc...
// render it to a string
var soUsers = document.querySelector('so-users');
soUsers.setAttribute('ids', list.toString());
You can even add a MutationObserver to the tmp element and get callbacks whenever the classList changes:
function newDOMTokenList(initialTokens, changed) {
const tmp = document.createElement('div');
const classList = tmp.classList;
if (initialTokens) {
initialTokens.forEach(token => {
classList.add(token);
});
}
if (changed) {
const observer = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.attributeName === 'class') {
changed();
}
}
});
observer.observe(tmp, {attributes: true});
}
return classList;
}
This, however, will cause the tmp div to never be garbage collected, since the MutationObserver needs to keep a reference to it.
Utilizing Custom Elements - Adding JS properties and methods initialization approach , HTMLElement.dataset
, try
var XFooProto = Object.create(HTMLElement.prototype);
// 1. Give x-foo a foo() method.
XFooProto.contains = function(id) {
var data = JSON.parse(this.dataset.ids);
return data.some(function(_id) {
return id == _id
})
};
XFooProto.add = function(id) {
var data = JSON.parse(this.dataset.ids);
if (!this.contains(id)) {
data.push(id);
};
return data
};
XFooProto.remove = function(id) {
var data = JSON.parse(this.dataset.ids);
if (this.contains(id)) {
for (var _id in data) {
if (data[_id] === id) {
data.splice(_id, 1)
}
};
};
return data
};
XFooProto.ids = function() {
return this.dataset.ids
};
// 2. Define a property read-only "bar".
// Object.defineProperty(XFooProto, "ids", {value: this});
// 3. Register x-foo's definition.
var XFoo = document.registerElement('x-foo', {prototype: XFooProto});
// 4. Instantiate an x-foo.
var xfoo = document.createElement('x-foo');
xfoo.dataset.ids = '["23354", "115866"]';
// 5. Add it to the page.
document.body.appendChild(xfoo);
console.log(xfoo.add("123")); // `["23354", "115866", "123"]`
console.log(xfoo.remove("123")); // `["23354", "115866"]`
console.log(xfoo.contains("123")); // `false`
console.log(xfoo.contains("23354")); // `true`
console.log(xfoo.ids()); // `["23354", "115866"]` , type : `String`
var pre = document.getElementsByTagName("pre")[0]
pre.innerText = JSON.stringify(JSON.parse(xfoo.dataset.ids), null, 4);
<pre></pre>
The DOMTokenList and DOMSettableTokenList interfaces (MDN, WHATWG) provide methods for manipulating ordered sets of string tokens represented by space-delimited strings. They are most commonly used in the form of the Element.prototype.classList property, a DOMTokenList which reflects the class attribute of an associated element.
var div = document.createElement('div');
div.setAttribute('class', 'hello world goodnight moon');
var list = div.classList;
console.assert(list.length === 4);
console.assert(list[0] === 'hello');
console.assert(list.item(1) === 'world');
console.assert(list.contains('moon') === true);
console.assert(list.contains('mars') === false);
list.remove('world', 'earth', 'dirt', 'sand');
list.add('hello', 'mars');
list.toggle('goodnight');
console.assert(div.getAttribute('class') === 'hello moon mars');
I'm working on a custom element (HTML5Rocks, W3C Draft) which displays a real-time feed of the activity of specified Stack Overflow users. This list of users is specified in an ids attribute, and may be updated at any time.
<so-users ids="1114 22656 106224"></so-users>
document.querySelector('so-users').setAttribute('ids', '23354 115866');
Instead of requiring users to manipulate this attribute directly, I would like to have an .ids property providing a DOMTokenList that they can use instead. Ideally this would be directly associated with the attribute, but an unbound DOMSettableTokenList instance that I have to manually bind would also be fine.
document.querySelector('so-users').ids.add('17174');
Unfortunately, I have been unable to find any way to create a DOMTokenList instance. The definition is not a constructor, and directly creating an object using its prototype results in errors when I call any associated methods:
new DOMTokenList; // TypeError: Illegal constructor
new DOMSettableTokenList; // TypeError: Illegal constructor
var list = Object.create(DOMSettableTokenList.prototype, {
value: { value: 'hello world' }
});
console.assert(list instanceof DOMTokenList);
console.assert(list instanceof DOMSettableTokenList);
list.item(0); // TypeError: Illegal invocation
function TokenListConstructor() {
this.value = 'hello world';
}
TokenListConstructor.prototype = DOMSettableTokenList.prototype;
var list = new TokenListConstructor;
console.assert(list instanceof DOMTokenList);
console.assert(list instanceof DOMSettableTokenList);
list.add('moon'); // TypeError: Illegal invocation
How can I construct a new DOMTokenList or DOMSettableTokenList instance?
You cannot create an DOMTokenList or an DOMSettableTokenList directly. Instead you should use the class attribute to store and retrieve your data and perhaps map an ids attribute of your DOM element to the classList property.
var element = document.querySelector('so-users');
element.ids = element.classList;
You can use relList according to the documentation but classList is more supported, the only drawback is that you might run into issues if one of your ids matches a class name so set an inline style to hide the element just in case.
For a custom component compatibility should be a concern (classList is present in IE>=10, Firefox 3.6, Chrome 8, Opera 11.5 and Safari 5.1, see http://caniuse.com/#feat=classlist) so if compatibility is in your requirements use the another solution posted below.
If you cannot use clases or classList and/or must use the ids attribute you should implement a custom function according to the spec with the following properties as functions.
item()
contains()
add()
remove()
toggle()
This is an example implementation of such functionality.
var TokenList = function (ids) {
'use strict';
var idsArray = [],
self = this,
parse = function (id, functionName, cb) {
var search = id.toString();
if (search.split(' ').length > 1) {
throw new Error("Failed to execute '" + functionName + "' on 'TokenList': The token provided ('" + search + "') contains HTML space characters, which are not valid in tokens.');");
} else {
cb(search);
}
};
function triggerAttributeChange() {
if (self.tokenChanged && typeof self.tokenChanged === 'function') {
self.tokenChanged(idsArray.toString());
}
}
if (ids && typeof ids === 'string') {
idsArray = ids.split(' ');
}
self.item = function (index) {
return idsArray[index];
};
self.contains = function (id) {
parse(id, 'contains', function (search) {
return idsArray.indexOf(search) !== -1;
});
};
self.add = function (id) {
parse(id, 'add', function (search) {
if (idsArray.indexOf(search) === -1) {
idsArray.push(search);
}
triggerAttributeChange();
});
};
self.remove = function (id) {
parse(id, 'remove', function (search) {
idsArray = idsArray.filter(function (item) {
return item !== id;
});
triggerAttributeChange();
});
};
self.toggle = function (id) {
parse(id, 'toggle', function (search) {
if (!self.contains(search)) {
self.add(search);
} else {
self.remove(search);
}
});
};
self.tokenChanged = null;
self.toString = function () {
var tokens = '',
i;
if (idsArray.length > 0) {
for (i = 0; i < idsArray.length; i = i + 1) {
tokens = tokens + idsArray[i] + ' ';
}
tokens = tokens.slice(0, tokens.length - 1);
}
return tokens;
};
};
Set an 'ids' property in your element with a new instance of this function and finally you must bound the targeted attribute to the property listening to changes to the element and updating the property o viceversa. You can do that with a mutation observer.
See firing event on DOM attribute change and https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
var attachTokenList = function (element, prop, initialValues) {
'use strict';
var initValues = initialValues || element.getAttribute(prop),
MutationObserver = window.MutationObserver = window.MutationObserver || window.WebKitMutationObserver || window.MozMutationObserver,
observer,
config,
cancelMutation = false;
function createTokenList(values) {
var tList = new TokenList(values);
tList.tokenChanged = function () {
element.setAttribute(prop, element[prop].toString());
cancelMutation = true;
};
element[prop] = tList;
}
createTokenList(initValues);
observer = new MutationObserver(function (mutation) {
var i,
mutationrec,
newAttr;
if (mutation.length > 0 && !cancelMutation) {
for (i = 0; i < mutation.length; i = i + 1) {
mutationrec = mutation[i];
if (mutationrec.attributeName === prop && element[prop]) {
newAttr = element.getAttribute(prop);
createTokenList(newAttr);
}
}
}
cancelMutation = false;
});
config = {
attributes: true
};
observer.observe(element, config);
};
Testing to see if it works
<so-users ids="1234 5678"></so-users>
<button onclick="clickButton1()">Add 7890</button>
<button onclick="clickButton2()">Set to 3456</button>
<button onclick="clickButton3()">Add 9876</button>
Inside a script tag
var elem = document.querySelector('so-users');
attachTokenList(elem, 'ids')
function clickButton1 () {
elem.ids.add('7890');
}
function clickButton2 () {
elem.setAttribute('ids', '3456');
}
function clickButton3 () {
elem.ids.add('9876');
}
Clicking the buttons in sequence set the ids attribute to '3456 9876'
You can get an instance of DOMTokenList with this function:
function newDOMTokenList(initialTokens) {
const tmp = document.createElement(`div`);
const classList = tmp.classList;
if (initialTokens) {
initialTokens.forEach(token => {
classList.add(token);
});
}
return classList;
}
We can 'steal' the DOMTokenList from a div, since it does not affect the current document until you choose to insert the element (for example by using insertAdjacentElement) and it will be garbage collected since we do not keep any references to the variable tmp.
Then you can use your list:
var list = newDOMTokenList(['a', 'b']);
list.add('c');
list.contains('d'); // false
list.contains('b'); // true
list.item(1) // 'b'
list instanceof DOMTokenList // true
// etc...
// render it to a string
var soUsers = document.querySelector('so-users');
soUsers.setAttribute('ids', list.toString());
You can even add a MutationObserver to the tmp element and get callbacks whenever the classList changes:
function newDOMTokenList(initialTokens, changed) {
const tmp = document.createElement('div');
const classList = tmp.classList;
if (initialTokens) {
initialTokens.forEach(token => {
classList.add(token);
});
}
if (changed) {
const observer = new MutationObserver((mutationList, observer) => {
for (const mutation of mutationList) {
if (mutation.attributeName === 'class') {
changed();
}
}
});
observer.observe(tmp, {attributes: true});
}
return classList;
}
This, however, will cause the tmp div to never be garbage collected, since the MutationObserver needs to keep a reference to it.
Utilizing Custom Elements - Adding JS properties and methods initialization approach , HTMLElement.dataset
, try
var XFooProto = Object.create(HTMLElement.prototype);
// 1. Give x-foo a foo() method.
XFooProto.contains = function(id) {
var data = JSON.parse(this.dataset.ids);
return data.some(function(_id) {
return id == _id
})
};
XFooProto.add = function(id) {
var data = JSON.parse(this.dataset.ids);
if (!this.contains(id)) {
data.push(id);
};
return data
};
XFooProto.remove = function(id) {
var data = JSON.parse(this.dataset.ids);
if (this.contains(id)) {
for (var _id in data) {
if (data[_id] === id) {
data.splice(_id, 1)
}
};
};
return data
};
XFooProto.ids = function() {
return this.dataset.ids
};
// 2. Define a property read-only "bar".
// Object.defineProperty(XFooProto, "ids", {value: this});
// 3. Register x-foo's definition.
var XFoo = document.registerElement('x-foo', {prototype: XFooProto});
// 4. Instantiate an x-foo.
var xfoo = document.createElement('x-foo');
xfoo.dataset.ids = '["23354", "115866"]';
// 5. Add it to the page.
document.body.appendChild(xfoo);
console.log(xfoo.add("123")); // `["23354", "115866", "123"]`
console.log(xfoo.remove("123")); // `["23354", "115866"]`
console.log(xfoo.contains("123")); // `false`
console.log(xfoo.contains("23354")); // `true`
console.log(xfoo.ids()); // `["23354", "115866"]` , type : `String`
var pre = document.getElementsByTagName("pre")[0]
pre.innerText = JSON.stringify(JSON.parse(xfoo.dataset.ids), null, 4);
<pre></pre>
When you want to validate breeze-entity you write:
this.entityAspect.validateEntity()
But what about if I want to fire validations only for complex-type, without fire the entire-entity validations?
complexType.complexAspect not have method validateEntity.
So, what should I do?
Edit after I saw Jay answer:
I tried to use method validateProperty.
But the result was that it always returns true, becouse it not check each one of the properties.
So, I tried to call method validateProperty several-times, each time for other field of the complexType. It give me boolian-result of valid/not valid, but not update the validation-errors.
Here is the code that I tried after I saw Jay answer, but it is not help:
validateSingleField(myComplexProertyName);
first version of validateSingleField function: (the result was that it always returns true, becouse it not check each one of the properties)
function validateSingleField(object, fieldName) {
var entityAspect = object.entityAspect;
var objectType = object.entityType;
var prop = objectType.getProperty(fieldName);
var value = object.getProperty(fieldName);
if (prop.validators.length > 0) {
var context = { entity: entityAspect.entity, property: prop, propertyName: fieldName };
if (entityAspect._validateProperty(value, context)) {
return true;
}
return false;
}
}
second version:(It give me boolian-result of valid/not valid, but not update the validation-errors.)
function validateSingleField(object, fieldName) {
var aspect = object.entityAspect || object.complexAspect;
var entityAspect = object.entityAspect || object.complexAspect.getEntityAspect();
var objectType = object.entityType || object.complexType;
var prop = objectType.getProperty(fieldName);
if (prop.isComplexProperty) {
var isOk;
objectType.getProperties().forEach(function (p) {
isOk = isOk && validateSingleField(object[fieldName](), p.name)//writing 'object[fieldName]()' - is for send the entire complexType of the entity
});
return isOk;
}
else {
{
var value = object.getProperty(fieldName);
if (prop.validators.length > 0) {
var context = { entity: entityAspect.entity, property: prop, propertyName: fieldName };
if (entityAspect._validateProperty(value, context)) {
return true;
}
return false;
}
}
}
}
There is no separate method to validate a complex type because the validation results are all part of the 'parent' entity. Complex type properties are considered part of the entity, not independent entities.
What you can do is call validateProperty on the 'complex' property of the parent entity.
I have this post method
$.post("/abc/export.action",
{
sessiontoken: sessiontoken,
exportType: "xls",
param1:compareData,
param2:comparePatchData},
function(resdata)
{
alert(resdata);
}
);
I wanted some best practice so that I can have enumeration or array which stores all my post parameters in it and use it while post, A way in which I can avoid hardcoding of sessiontoken, param1 etc.. what could be the solution?
EDIT
Sometimes it might happen that I need to change the names of params so i have to edit everywhere I have post method, instead of that if all the params where there in some enum or array it would be much easier to just change at one place.
If you want the labels in a object to be based on some other variable you can do it with
var paramLabels = {"session": "sessionToken", "type": "exportType", ...}
var paramValues = {};
paramValues[paramLabels.session] = sessionToken;
paramValues[paramLabels.type] = "xls"
...
$.post(/"abc/export.action", paramValues, function(resdata) { alert(resdata);});
I can't really see the benefit of this approach expect when the developers of the backend like to change the names of the parameters every five minutes.
Another way of handling this would be to create a factory method or a builder
function createParams(session, type, ...) {
return { "sessionToken": session, "exportType": type, ...) }
}
var params = createParams(sessionToken, "xls", ...);
or
var Parameters = function() {
this.session = function(session) { this.session = session; return this;}
this.type = function(type) { this.type = type; return this;}
...
this.build = function() {
var params = {}
!this.session || params.sessionToken = this.session;
!this.type || params.exportType = this.type;
...
return params;
}
}
var params = new Parameters().session(sessionToken).type("xls")...build();
Both of these approaches let you define the concreate name of the parameters only once. The latter may be easier to reuse when different set of parameters are needed.
Instead of passing an object literal to $.post() you can pass an object that was defined elsewhere in your code, and thus re-use that same object for multiple posts. Or put in a function call that returns an object with the appropriate parameters set up.
var postParams = {
sessiontoken: sessiontoken,
exportType: "xls",
param1:compareData,
param2:comparePatchData
};
$.post("/abc/export.action",
postParams,
function(resdata) { alert(resdata); });
$.post("/abc/import.action",
postParams,
function(resdata) { alert(resdata); });
// or use a function that can return a different object
// depending on some conditions (or parameters passed to
// the function though I haven't shown that)
function getPostParams() {
if (someCondition)
return postParams;
else
return {
sessionToken : something,
// etc.
};
}
$.post("/abc/someother.action",
getPostParams(),
function(resdata) { alert(resdata); });