I'm constructing a Builder in JavaScript, and I'm not sure how builders normally handle undefined values for optionals. I would like to think that the Builder doesn't append the optional field to the object if the field is undefined. Is that acceptable for a Builder? If not, what would be alternatives?
Here is a sample of the first implementation where the builder doesn't append an undefined optional:
Builder:
function Thing(required1, required2, required3) {
//check required params
var fields = {
required1: required1,
required2: required2,
required3: required3
};
var _withOptionalParam = function(param) {
if(!param) { return this; } //exit function early if param is undefined
fields.param = param;
return this;
};
var _build = function() {
var result = fields;
return result;
};
var builder = {
withOptionalParam: _withOptionalParam,
build: _build
};
return builder;
}
In action:
var thing = new Thing("foo","bar","baz").withOptionalParam(undefined).build();
//'thing' should be
// {
// required1:"foo",
// required2:"bar",
// required3:"baz"
// };
//
Thanks in advance!
I think you are losing the context of this in your _withOptinalParam function. You could bind you fields object to it as the this context.
function Thing(required1, required2, required3) {
//check required params
var fields = {
required1: required1,
required2: required2,
required3: required3
};
var _withOptionalParam = function(param) {
if(!param) { return this; } //exit function early if param is undefined
fields.param = param;
return this;
};
var _build = function() {
var result = fields;
return result;
};
var builder = {
withOptionalParam: _withOptionalParam.bind(fields),
build: _build
};
return builder;
}
var thing = new Thing("foo","bar","baz").withOptionalParam(undefined);
console.log( thing );
//'thing' should be
// {
// required1:"foo",
// required2:"bar",
// required3:"baz"
// };
//
<script src="http://codepen.io/synthet1c/pen/WrQapG.js"></script>
Related
I'm trying to implement the Builder Pattern to generate a JSON string of options that are passed into a library to generate widgets. I can't understand why at console.log below that this.options is undefined.
let Options = function(options) {
this.options = options;
}
let OptionsObjectBuilder = function () {
let options;
return {
addConstantLineToValueAxis: function (lineValue) {
console.log(this.options); // EQUALS UNDEFINED SO CAN'T ADD TO THIS OBJECT
this.options.valueAxis.constantLine.value = lineValue;
return this;
},
build: function () {
return new Options(this.options);
}
};
};
let option = new OptionsObjectBuilder().addConstantLineToValueAxis(1000000000).build();
There are two different ways for the builder to store the temporary states:
In the builder object itself (by setting this.options =)
In a closure (by setting options =)
The closure example has the benefit that the temporary builder state is not accessible to the outside.
You can use either way, as long as the builder uses them from the correct place. I will fix the broken example from the post you mentioned. I think they started using closures, and it didn't work because the param name was shadowing the closure variable and they ended up getting confused switching to using this instead. They forgot to update their build() function to read from the correct place.
Using builder object state - Exposes internal state
let Task = function(name, description, finished, dueDate) {
this.name = name;
this.description = description;
this.finished = finished;
this.dueDate = dueDate;
}
let TaskBuilder = function () {
return {
setName: function (name) {
this.name = name;
return this;
},
setDescription: function (description) {
this.description = description;
return this;
},
setFinished: function (finished) {
this.finished = finished;
return this;
},
setDueDate: function (dueDate) {
this.dueDate = dueDate;
return this;
},
build: function () {
return new Task(this.name, this.description, this.isFinished, this.dueDate);
}
};
};
let builder = new TaskBuilder().setName('Task A').setDescription('finish book')
.setDueDate(new Date(2019, 5, 12));
let task = builder.build();
// Notice the builder does expose the name/description... properties
console.log({builder, task});
Using closure variables - Hides internal state
let Task = function(name, description, finished, dueDate) {
this.name = name;
this.description = description;
this.finished = finished;
this.dueDate = dueDate;
}
let TaskBuilder = function () {
let name;
let description;
let isFinished = false;
let dueDate;
return {
setName: function (pName) {
name = pName;
return this;
},
setDescription: function (pDescription) {
description = pDescription;
return this;
},
setFinished: function (pFinished) {
finished = pFinished;
return this;
},
setDueDate: function (pDueDate) {
dueDate = pDueDate;
return this;
},
build: function () {
return new Task(name, description, isFinished, dueDate);
}
};
};
let builder = new TaskBuilder().setName('Task A').setDescription('finish book')
.setDueDate(new Date(2019, 5, 12));
let task = builder.build();
// Can't see the name/description... properties on the builder, just the methods
console.log({builder, task});
I believe I should only ever use this when returning at the end of the add functions in the builder pattern. I'm still not sure why in the example (zetcode.com/javascript/builderpattern) I was basing my code off of.. they set values with this.name, but passed name in their build function.
// #Steven de Salas: expando function: https://stackoverflow.com/a/44014709/1432612
function buildObjectDepth(obj, base) {
return Object.keys(obj)
.reduce((clone, key) => {
key.split('.').reduce((innerObj, innerKey, i, arr) =>
innerObj[innerKey] = (i+1 === arr.length) ? obj[key] : innerObj[innerKey] || {}, clone)
return clone;
}, Object.assign({}, base));
}
let Options = function(options) {
this.options = options;
}
let OptionsObjectBuilder = function () {
let options = {};
return {
addConstantLineToValueAxis: function (lineValue) {
options = buildObjectDepth({"valueAxis.constantLine.value": lineValue}, options);
return this;
},
build: function () {
return new Options(options);
}
};
};
let obj = new OptionsObjectBuilder().addConstantLineToValueAxis(1000000000).build();
str = JSON.stringify(obj);
str = JSON.stringify(obj, null, 4); // indented output.
alert(str);
This doesn't work.
var genericClickHandler = function () {
this.handlers = [];
if (console && console.log) {
console.log("this:", this);
console.log("event:", event);
}
};
genericClickHandler.addHandler = function (handlerSpec) {
this.handlers.push(handlerSpec);
return this;
};
genericClickHandler.executeHandler = function (handlerName) {
for (var i = 0; i < this.handlers.length; i++) {
if (handlerName === this.handlers[i][0]) {
this.handlers[i][1]();
}
}
return this;
};
It doesn't work because the addHandler can't see the this.handlers in genericClickHandler.
Anyway what I'm after is function that gets defined once, but has methods and properties. I want to be able to use the function with Google Maps like this:
heatmap.addListener("click", genericClickHandler)
circle.addListener("click", genericClickHandler)
polygons.addListener("click", genericClickHandler)
So in the first instance, it only reports the this and event object. However, I then want to write code which extends the genericClickHandler dynamically so that it can implement map-object-specific behaviour.
Here's an example of what I meant using an object rather than a function.
var genericClickHandler = {
handlers: []
};
genericClickHandler.addHandler = function (name, fn) {
this.handlers.push([name, fn]);
return this;
};
genericClickHandler.executeHandler = function (name) {
for (var i = 0, l = this.handlers.length; i < l; i++) {
if (this.handlers[i][0] === name) this.handlers[i][1]();
}
};
genericClickHandler.addHandler('click', function () {
console.log('hi');
});
genericClickHandler.addHandler('click', function () {
console.log('hallo again');
});
genericClickHandler.executeHandler('click'); // hi... hallo again
DEMO
if you want to create an object, here you can see 2 ways to do the same thing, javascript got multiple way to write the same things.
var genericClickHandler = function()
{
this.handlers = [];
this.addHandler = function (handlerSpec)
{
this.handlers.push(handlerSpec);
return this;
},
this.executeHandler = function (handlerName)
{
this.handlers[handlerName]();
return this;
}
};
//sample:
var tmp = new genericClickHandler();
console.log(tmp.handlers);
console.log(tmp.addHandler("TEST"));
Another way to write the same object, but more optimised : prototype will be stored once for each object
var genericClickHandler = function(){}
genericClickHandler.prototype =
{
handlers:[],
addHandler : function (handlerSpec)
{
this.handlers.push(handlerSpec);
return this;
},
executeHandler : function (handlerName)
{
this.handlers[handlerName]();
return this;
}
}
//sample:
var tmp = new genericClickHandler();
console.log(tmp.handlers);
console.log(tmp.addHandler("TEST"));
I have a sealed object with an array member on which I want to prevent direct pushes.
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d; }
});
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // pushed = sadness
How can I prevent the push?
UPDATE:
Thanks for all the thoughts. I eventually need the JSON to send to the server. It looks like I might need to use an object for the array then figure out a way to generate and return the JSON needed, or change _something to use .slice(). Will play and report.
you could override the push method:
var _d = [];
_d.__proto__.push = function() { return this.length; }
and when you need to use it in your module, call Array.prototype.push:
_b.addD = function (newD) {
Array.prototype.push.call(_d, newD);
};
I haven't done any performance tests on this, but this certainly helps to protect your array.
(function(undefined) {
var protectedArrays = [];
protectArray = function protectArray(arr) {
protectedArrays.push(arr);
return getPrivateUpdater(arr);
}
var isProtected = function(arr) {
return protectedArrays.indexOf(arr)>-1;
}
var getPrivateUpdater = function(arr) {
var ret = {};
Object.keys(funcBackups).forEach(function(funcName) {
ret[funcName] = funcBackups[funcName].bind(arr);
});
return ret;
}
var returnsNewArray = ['Array.prototype.splice'];
var returnsOriginalArray = ['Array.prototype.fill','Array.prototype.reverse','Array.prototype.copyWithin','Array.prototype.sort'];
var returnsLength = ['Array.prototype.push','Array.prototype.unshift'];
var returnsValue = ['Array.prototype.shift','Array.prototype.pop'];
var funcBackups = {};
overwriteFuncs(returnsNewArray, function() { return []; });
overwriteFuncs(returnsOriginalArray, function() { return this; });
overwriteFuncs(returnsLength, function() { return this.length; });
overwriteFuncs(returnsValue, function() { return undefined; });
function overwriteFuncs(funcs, ret) {
for(var i=0,c=funcs.length;i<c;i++)
{
var func = funcs[i];
var funcParts = func.split('.');
var obj = window;
for(var j=0,l=funcParts.length;j<l;j++)
{
(function() {
var part = funcParts[j];
if(j!=l-1) obj = obj[part];
else if(typeof obj[part] === "function")
{
var funcBk = obj[part];
funcBackups[funcBk.name] = funcBk;
obj[part] = renameFunction(funcBk.name, function() {
if(isProtected(this)) return ret.apply(this, arguments);
else return funcBk.apply(this,arguments);
});
}
})();
}
}
}
function renameFunction(name, fn) {
return (new Function("return function (call) { return function " + name +
" () { return call(this, arguments) }; };")())(Function.apply.bind(fn));
};
})();
You would use it like so:
var myArr = [];
var myArrInterface = protectArray(myArr);
myArr.push(5); //Doesn't work, but returns length as expected
myArrInterface.push(5); //Works as normal
This way, you can internally keep a copy of the interface that isn't made global to allow your helper funcs to modify the array as normal, but any attempt to use .push .splice etc will fail, either directly, or using the .bind(myArr,arg) method.
It's still not completely watertight, but a pretty good protector. You could potentially use the Object.defineProperty method to generate protected properties for the first 900 indexes, but I'm not sure of the implications of this. There is also the method Object.preventExtensions() but I'm unaware of a way to undo this effect when you need to change it yourself
Thank you, dandavis!
I used the slice method:
var myModule = (function () {
"use strict";
var a = (function () {
var _b = {},
_c = _c = "",
_d = [];
Object.defineProperty(_b, "c", {
get: function () { return _c; }
});
Object.defineProperty(_b, "d", {
get { return _d.slice(); } // UPDATED
});
_b.updateC = function (newValue) {
_c = newValue;
};
_b.addD = function (newD) {
_d.push(newD);
};
Object.seal(_b);
return _b;
}());
var _something = { B: _b };
return {
Something: _something,
AddD: _b.addD
};
}());
myModule.Something.c = "blah"; // doesn't update = WIN!!
myModule.AddD({}); // pushed = WIN!
myModule.Something.d.push({}); // no more update = happiness
This allows me to protect from direct push calls enforcing some logic.
I want to be able to call simultaneously something like this in javascript:
classInstance.room.get('criteria');
classInstance.room('criteria').remove('criteria');
classInstance.room().update('criteria');
I have seen implemented something similar at shouldjs
should(10).be.a.Number();
(10).should.be.a.Number();
Updated
I have the following code:
function connectToDatabase() {
var server = orientDB(dbConfig.server);
var db = server.use(dbConfig.database);
db.on("endQuery", function onDbEndQuery() {
db.server.close();
});
return db;
}
var DbSet = function DbSet(name) {
return {
list: function list(where, select, order) {
where = where || true;
select = _.isString(select) || _.isArray(select) ? select : '*';
order = _.isString(order) || _.isArray(order) ? order : 'rid';
return connectToDatabase()
.select(select)
.from(name)
.where(where)
.order(order)
.all();
},
get: function get(where, select) {
where = where || true;
select = _.isString(select) || _.isArray(select) ? select : '*';
return connectToDatabase()
.select(select)
.from(name)
.where(where)
.all()
.then(function onResults(results) {
if (results.length > 1) {
throw new Error('multiple results');
}
return results[0];
});
},
create: function create(record) {
return connectToDatabase()
.insert()
.into(name)
.set(record)
.one();
},
update: function update(where, changes) {
where = where || true;
return connectToDatabase()
.update(name)
.set(changes)
.where(where)
.scalar();
},
remove: function remove(where) {
where = where || true;
return connectToDatabase()
.delete('VERTEX', name)
.where(where)
.scalar();
}
};
};
var db = function getDb() {
return {
room: new DbSet('Room'),
invitation: new DbSet('Invitation'),
participant: new DbSet('Participant'),
};
};
module.exports = db();
And I want to change the code be able to execute the following code:
var db=require('path/to/database');
var room = db.room.get({name:'room 1'});
var sameRoom = db.room({name:'room 1'}).get();
db.room.create({name:'second room'});
db.room({name:'second room'}).create();
//same for methods list and delete
var room = db.room.list({status:'active'});
var sameRooms = db.room({status:'active'}).list();
db.room.update({name:'second room'},{status:'inactive'});
db.room({name:'second room'}).update({status:'inactive'});
I want to be able to execute the same code for Invitation and Participant too.
We need more information as to what those functions do, but this code presents those features.
Klass = function () {};
Klass.prototype.room = function () {
....
return {
get: function () {...},
remove: function () {...},
update: function () {...}
}
};
Klass.prototype.room.get = function () {...};
classInstance = new Klass();
I'm developing a small framework (in JS) and for esthetic reasons and simplicity I was wondering if there could be a way to implement something like PHP "__invoke".
For example:
var myClass = function(config) {
this.config = config;
this.method = function(){};
this.execute = function() {
return this.method.apply(this, arguments);
}
}
var execCustom = new myClass({ wait: 100 });
execCustom.method = function() {
console.log("called method with "+arguments.length+" argument(s):");
for(var a in arguments) console.log(arguments[a]);
return true;
};
execCustom.execute("someval","other");
Desired way to execute:
execCustom("someval","other");
Any ideas? Thanks.
if you are ready to use JS pattern, you can do this in following way:
var myClass = function(opts) {
return function(){
this.config = opts.config;
this.method = opts.method;
return this.method.apply(this, arguments);
};
};
var execCustom = new myClass({
config:{ wait: 100 },
method:function() {
console.log("called method with "+arguments.length+" argument(s):");
for(var a in arguments) console.log(arguments[a]);
return true;
}});
execCustom("someval","other");
jsbin
this is the best way I can think of
UPDATED VERSION (by op)
var myClass = function(opts) {
var x = function(){
return x.method.apply(x, arguments);
};
x.config = opts.config;
x.method = opts.method;
return x;
};
var execCustom = new myClass({
config:{ wait: 100 },
method:function() {
console.log("called method with "+arguments.length+" argument(s):");
for(var a in arguments) console.log(arguments[a]);
return true;
}});
execCustom("someval","other");
jsbin
Just return a function that will form the public interface:
function myClass(config)
{
var pubif = function() {
return pubif.method.apply(pubif, arguments);
};
pubif.config = config;
pubif.method = function() { };
return pubif;
}
The rest of the code remains the same.