The title is pretty self-explanatory..
Is there a way to read whatever's been output to the console.log up until the moment you decide to read it, using Javascript?
You can make a proxy around it, such as :
(function(win){
var ncon = win.console;
var con = win.console = {
backlog: []
};
for(var k in ncon) {
if(typeof ncon[k] === 'function') {
con[k] = (function(fn) {
return function() {
con.backlog.push([new Date(), fn, arguments]);
ncon[fn].apply(ncon, arguments);
};
})(k);
}
}
})(window);
Related
This question already has answers here:
How to set object property (of object property of..) given its string name in JavaScript?
(16 answers)
Closed 6 years ago.
I am trying to deeply assign a value in an object. For example:
const errors = {}
if(errorOnSpecificField) {
// TypeError: Cannot read property 'subSubCategory' of undefined(…)
errors.subCategory.subSubCategory.fieldWithError = 'Error Message'
}
Right now, without lodash, I can do:
const errors = {}
if(errorOnSpecificField) {
errors.subCategory = errors.SubCategory || {}
errors.subCategory.subSubCategory = errors.SubCategory.subSubCategory || {}
errors.subCategory.subSubCategory.fieldWithError = 'Error Message'
}
With lodash, I can do this:
const errors = {}
if(errorOnSpecificField) {
_.set(errors, 'subCategory.subSubCategory.fieldWithError', 'Error Message');
}
I am trying to avoid using a third party library. Is there a more elegant solution, especially now that es2015 has object destructuring. The inverse operation is easy:
let {subCategory : {subSubCategory: {fieldWithError}}} = errors
What is an elegant solution to deep object assignment? Thanks!
Here's a fairly readable way to safely assign to the deep object:
(((errors||{}).subCategory||{}).subSubCategory||{}).fieldWithError = 'Error Message'
That doesn't create errors.subCategory.subSubCategory if it doesn't already exist, though.
Short answer, no there is no clean way of doing this without writing a method for it (tbh you could just use the method from lodash without importing the whole library)
... however ...
WARNING This is for fun only. Do not try this in production (req es6).
Object.prototype.chainSet = function() {
let handler = {
get (target, name) {
if (!(name in target)) {
target[name] = new Proxy({}, handler)
}
return target[name]
}
}
return new Proxy(this, handler)
}
use:
let a = {}
a.chainSet().foo.bar.baz = 1
a.foo.bar.baz // => 1
Object.assign() will work just fine for what you're asking.
let errors = { otherField: "value" };
let newobj = {subCategory : {subSubCategory: {fieldWithError: "Error goes here"}}};
Object.assign(errors, newobj);
This yields:
{
otherField:'value',
subCategory: {
subSubCategory: {
fieldWithError:'Error goes here'
}
}
}
You could try something like below:
function ErrorRegistry(obj)
{
this.errors = obj || {};
this.addError = function(k, msg)
{
var keys = k.split('.');
var o = this.errors;
for(var i = 0, l = keys.length, last = l-1; i<l; i++)
{
if(typeof o[keys[i]] === 'undefined')
o[keys[i]] = {};
if(i == last)
o[keys[i]] = msg;
else
o = o[keys[i]];
}
};
}
var errors = {'subCategory1':{'fieldWithError1':'Error1'}};
var errorRegistry = new ErrorRegistry(errors);
errorRegistry.addError('subCategory1.fieldWithError2', "Error2");
errorRegistry.addError('subCategory1.subSubCategory1.fieldWithError3', "Error3");
errorRegistry.addError('subCategory1.subSubCategory2.fieldWithError4', "Error4");
errors = errorRegistry.errors;
console.log(errors);
Explanation:
As a personal project, I'm trying to create my own lightweight version of Dependency Injection for JavaScript - Some would probably disagree with calling this DI because it has no interfaces, but I arrived at the conclusion that interfaces were overkill in JS since we can so easily type check. I have looked at the source of Angular, but I just feel like the complexity there may be overkill for my projects, and I'm interested in attempting my own for a learning experience anyway.
Question:
My question is, fundamentally, is the syntax I'm trying to implement impossible or not?
I'll explain my goal for the syntax, then provide the error and code snippet, and below that I'll post the full code.
Goal for Syntax:
I'd like the creation of a component, and injection of dependencies to work like this, where everything is a component, and anything can be a dependency. I created scope with a string path, using "/scopeName/subScopeName:componentName" to select a scope, so that code users can select the scope while defining the component in a simple way, using a ":" to select a component from the scope.
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
/* ...snip - see full code for the process component - snip ... */
JHTML.addComponent('/generate:init', function (jsonInput, process) {
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
The inject function just takes an array of component paths in the order the component's arguments are expected. null can be used to skip, allowing direct argument input instead, as shown above.
I also have something I call hooks, which are components stored in a certain place, and then there's a function returnUserHandle which will return an object consisting of just the hooks, so all of the functions are hidden in closures, and you can feed the code user just the usable methods, clean and easy, and can produce the final product as a library without the wiring, no need for my DI framework as a dependency. Hopefully that makes sense.
Error:
Right now, running the code (which is a very simple library to generate HTML by parsing a JSON structure) I get the error that process is undefined in the line var html = process(jsonInput);. I was having trouble understanding whether this is a fundamental design problem, or just a bug. Maybe this syntax is not possible, I'm hoping you can tell me.
Code:
Here's the code, and a link to the JS Bin.
/* Dependency Injection Framework - viziion.js */
function Viziion(appName) {
if (typeof appName == 'string') {
var that = this;
this.name = appName;
this.focus = null;
this.scope = {
'/': {
'subScopes': {},
'components': {}
}
};
this.hooks = {};
this.addScope = function(scopeName) {
if (typeof scopeName == 'string') {
var scopeArray = scopeName.split('/');
var scope = that.scope['/'];
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
scope.subScopes[scopeArray[i]] = {
'subScopes': {},
'components': {}
};
}
}
}
} else {
throw 'Scope path must be a string.';
}
return that;
};
this.addComponent = function(componentName, func) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
scope.components[scopeName] = func;
that.focus = scope.components[scopeName];
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string1.';
}
return that;
};
this.returnComponent = function(componentName, callback) {
if (typeof componentName == 'string') {
var scopeArray = componentName.split(':');
if (scopeArray.length == 2) {
var scope = that.scope['/'];
var scopeName = scopeArray[1];
scopeArray = scopeArray[0].split('/');
for (var i = 0; i < scopeArray.length; i++) {
if (scopeArray[i] !== "") {
if ((i + 1) === scopeArray.length) {
//console.log('yep1');
//console.log(scope.components[scopeName]);
callback(scope.components[scopeName]);
} else if (scope.subScopes[scopeArray[i]]) {
scope = scope.subScopes[scopeArray[i]];
} else {
throw 'Scope path is invalid.';
}
}
}
} else {
throw 'Path does not include a component.';
}
} else {
throw 'Component path must be a string2.';
}
};
this.addHook = function(hookName, func) {
if (typeof hookName == 'string') {
that.hooks[hookName] = func;
that.focus = that.hooks[hookName];
} else {
throw 'Hook name must be a string.';
}
return that;
};
this.inject = function(dependencyArray) {
if (dependencyArray) {
var args = [];
for (var i = 0; i < dependencyArray.length; i++) {
if (dependencyArray[i] !== null) {
that.returnComponent(dependencyArray[i], function(dependency) {
args.push(dependency);
});
}
}
console.log(that.focus);
that.focus.apply(null, args);
return that;
}
};
this.returnUserHandle = function() {
return that.hooks;
};
} else {
throw 'Viziion name must be a string.';
}
}
/* JSON HTML Generator - A Simple Library Using Viziion */
var JHTML = new Viziion('JHTML');
JHTML.addScope('/generate');
JHTML.addComponent('/generate:process', function(children) {
var html = [];
var loop = function() {
for (var i = 0; i < children.length; i++) {
if (children[i].tag) {
html.push('<' + tag + '>');
if (children[i].children) {
loop();
}
html.push('</' + tag + '>');
return html;
} else {
throw '[JHTML] Bad syntax: Tag type is not defined on node.';
}
}
};
}).inject();
JHTML.addComponent('/generate:init', function(jsonInput, process) {
console.log(process);
var html = process(jsonInput);
return html;
}).inject([null, '/generate:process']);
JHTML.addHook('generate', function(jsonInput, init) {
var html = init(jsonInput);
return html;
}).inject([null, '/generate:init']);
handle = JHTML.returnUserHandle();
/* HTML Generator Syntax - Client */
var htmlChunk = [{
tag: '!DOCTYPEHTML'
}, {
tag: 'html',
children: [{
tag: 'head',
children: []
}, {
tag: 'body',
children: []
}]
}];
console.log(handle.generate(htmlChunk));
is the syntax I'm trying to implement impossible or not?
It's absolutely possible, and I'm sure with a bit of bugfixing it'd work just fine.
What you're describing is essentially the same as Asynchronous Module Definition (AMD) which is used extensively for handling code dependencies.
Rather than continuing to pursue your own version of the same concept, I recommend that you give requirejs a try and follow the existing standards with your projects.
What I am interested to know is if there is a shorter way of achieving the following:
App.Plugins = App.Plugins || {};
App.Plugins.SomePlugin = App.Plugins.SomePlugin || {};
App.Plugins.SomePlugin.Models = App.Plugins.SomePlugin.Models || {};
App.Plugins.SomePlugin.Views = App.Plugins.SomePlugin.Views || {};
App.Plugins.SomePlugin.Collections = App.Plugins.SomePlugin.Collections || {};
As far as I know, this format is fine, please someone let me know if I'm mistaken, I'm just wondering if there is some nicer way of doing this initial setup on my singleton.
Thanks in advance...
You could do the following:
function defaults(obj, prop) {
return obj[prop] = obj[prop] || {};
}
defaults(App, 'Plugins');
defaults(App.Plugins, 'SomePlugin');
defaults(App.Plugins.SomePlugin, 'Models');
defaults(App.Plugins.SomePlugin, 'Views');
defaults(App.Plugins.SomePlugin, 'Collections');
I don't understand exactly why you do this
Anyway a shorter way to write that is:
App={
Plugins:{
SomePlugin:{
Models:{},
Views:{},
Collections:{}
}
}
}
then considering the function
function defaults(obj, prop) {
return obj[prop] = obj[prop] || {};
}
would return an error using
defaults(App.Plugins.AnotherPlugin,'Models')
checking this is a pain:
var x={};
if(App&&
App.Plugins&&
App.Plugins.AnotherPlugin&&
App.Plugins.AnotherPlugin.Models
){
x=App.Plugins.AnotherPlugin.Models
}
console.log(x);
A solution is
var x={};
try{x=App.Plugins.AnotherPlugin.Models}catch(e){}
console.log(x)
this gives you no errors
but you can't set it the easy way.
EDIT
comment answer
Then you should start checking at the point where nothing is certain. In your case you just need to check if anotherPlugin exists.you probably already have App & App.Plugins.so you don't need App=App||{}, but only App.Plugins.AnotherPlugin
!App.Plugins.AnotherPlugin||App.Plugins.AnotherPlugin=NewPlugin
or a function
function addPlugin(name,newPlugin){
!App.Plugins[name]||App.Plugins[name]=newPlugin
}
An define your own standards... I mean why return an object if it does not exist?
if it does not exist you can't do anything anyway.
and again the biggest problem is always to check if it exists... and like i already described above it is try catch.
EDIT2
check this function.
function def(a,b,c,d){
c=b.split('.');
d=c.shift();
a[d]||(a[d]={});
!(c.length>0)||def(a[d],c.join('.'));
}
usage:
var A={};
def(A,'B.C.D.E.F')
//this transforms your {}
//to
A:{
B:{
C:{
D:{
E:{
F:{
}
}
}
}
}
}
http://jsfiddle.net/5NgWL/
to create your plugin:
var App={}
def(App,'Plugins.SomePlugin.Models')
def(App.Plugins.SomePlugin,'View')
// &/or
def(App,'Plugins.SomePlugin.Collections')
May be use it
function namespace(path) {
var segments = path.split('.'),
result = {};
function define(name, obj) {
if (name === '.') {
return obj;
}
!obj[name] && (obj[name] = {});
return define(segments.shift(), obj[name]);
}
segments.push('.'); //add stop symbol;
define(segments.shift(), result);
return result;
}
namespace('App.Plugins.SomePlugin.Collections').controller = function() {}
I am trying to set a custom error handler for 3rd party plugins/modules in my core library, but somehow, myHandler does not alert the e.message.
Can somebody help me please? thank you
Function.prototype.setErrorHandler = function(f) {
if (!f) {
throw new Error('No function provided.');
}
var that = this;
var g = function() {
try {
var a = [];
for(var i=0; i<arguments.length; i++) {
a.push(arguments[i]);
}
that.apply(null,a);
}
catch(e) {
return f(e);
}
};
g.old = this;
return g;
};
function myHandler(e) {
alert(e.message)
};
// my Core library object
(function(){
if (typeof window.Core === 'undefined') {
var Core = window.Core = function() {
this.addPlugin = function(namespace, obj){
if (typeof this[namespace] === 'undefined') {
if (typeof obj === 'function') {
obj.setErrorHandler(myHandler);
} else if (!!obj && typeof obj === 'object') {
for (var o in obj) {
if (obj.hasOwnProperty(o) && typeof obj[o] === 'function') {
obj[o].setErrorHandler(myHandler);
}
}
}
this[namespace] = obj;
return true;
} else {
alert("The namespace '" + namespace + "' is already taken...");
//return false;
}
};
};
window.Core = new Core();
}
})();
// test plugin
(function(){
var myPlugin = {
init: function() {},
conf: function() {
return this.foo.x; // error here
}
};
Core.addPlugin("myPlugin", myPlugin);
})();
// test
Core.myPlugin.conf(); // supposed to alert(e.message) from myHandler()
setErrorHandler in the above code doesn't set an error handler on a Function, as such. JavaScript does not give you the ability to change the called code inside a Function object.
Instead it makes a wrapped version of the function it's called on, and returns it.
obj.setErrorHandler(myHandler);
Can't work as the returned wrapper function is thrown away, not assigned to anything.
You could say:
obj[o]= obj[o].setErrorHandler(myHandler);
though I'm a bit worried about the consequences of swapping out functions with different, wrapped versions. That won't necessarily work for all cases and could certainly confuse third-party code. At the least, you'd want to ensure you don't wrap functions twice, and also retain the call-time this value in the wrapper:
that.apply(this, a);
(Note: you don't need the manual conversion of arguments to an Array. It's valid to pass the arguments object directly to apply.)
How to write this JavaScript code without eval?
var typeOfString = eval("typeof " + that.modules[modName].varName);
if (typeOfString !== "undefined") {
doSomething();
}
The point is that the name of the var that I want to check for is in a string.
Maybe it is simple but I don't know how.
Edit: Thank you for the very interesting answers so far. I will follow your suggestions and integrate this into my code and do some testing and report. Could take a while.
Edit2: I had another look at the could and maybe itis better I show you a bigger picture. I am greatful for the experts to explain so beautiful, it is better with more code:
MYNAMESPACE.Loader = ( function() {
function C() {
this.modules = {};
this.required = {};
this.waitCount = 0;
this.appendUrl = '';
this.docHead = document.getElementsByTagName('head')[0];
}
function insert() {
var that = this;
//insert all script tags to the head now!
//loop over all modules:
for (var modName in this.required) {
if(this.required.hasOwnProperty(modName)){
if (this.required[modName] === 'required') {
this.required[modName] = 'loading';
this.waitCount = this.waitCount + 1;
this.insertModule(modName);
}
}
}
//now poll until everything is loaded or
//until timout
this.intervalId = 0;
var checkFunction = function() {
if (that.waitCount === 0) {
clearInterval(that.intervalId);
that.onSuccess();
return;
}
for (var modName in that.required) {
if(that.required.hasOwnProperty(modName)){
if (that.required[modName] === 'loading') {
var typeOfString = eval("typeof " + that.modules[modName].varName);
if (typeOfString !== "undefined") {
//module is loaded!
that.required[modName] = 'ok';
that.waitCount = that.waitCount - 1;
if (that.waitCount === 0) {
clearInterval(that.intervalId);
that.onSuccess();
return;
}
}
}
}
}
};
//execute the function twice a second to check if all is loaded:
this.intervalId = setInterval(checkFunction, 500);
//further execution will be in checkFunction,
//so nothing left to do here
}
C.prototype.insert = insert;
//there are more functions here...
return C;
}());
var myLoader = new MYNAMESPACE.Loader();
//some more lines here...
myLoader.insert();
Edit3:
I am planning to put this in the global namespace in variable MYNAMESPACE.loadCheck, for simplicity, so the result would be, combining from the different answers and comments:
if (MYNAMESPACE.loadCheck.modules[modName].varName in window) {
doSomething();
}
Of course I will have to update the Loader class where ever "varName" is mentioned.
in JS every variable is a property, if you have no idea whose property it is, it's a window property, so I suppose, in your case, this could work:
var typeOFString = typeof window[that.modules[modName].varName]
if (typeOFString !== "undefined") {
doSomething();
}
Since you are only testing for the existence of the item, you can use in rather than typeof.
So for global variables as per ZJR's answer, you can look for them on the window object:
if (that.modules[modName].varName in window) {
...
}
If you need to look for local variables there's no way to do that without eval. But this would be a sign of a serious misdesign further up the line.