How to traverse VDOM when using m.component() in Mithril? - javascript

I´m new to Mithril, but very pleased about the way it enforces good coding patterns and the separation of concerns. Referring to this I started coding, making intensive use of m.component().
Later I read the Mithril-Article "When CSS lets you down" (http://lhorie.github.io/mithril-blog/when-css-lets-you-down.html), which explains how to write Transformer-Functions to travers and manipulate the virtual DOM-Tree. A brilliant concept to write several kinds of cross-cutting-concerns.
But when I tried to use this pattern with a VDOM containing components, it doesn´t work because m.component is returning a component and not a VDOM-Object. Detecting the component doesn´t help, because the embedded view is not constructed at this point.
Now I´m asking myself, how to handle this issue or if I missunderstood something fundamentally wrong...
Here a few lines of code showing the Problem:
...
someComponent.view = function() {
return m('html', [
m('body', [
m('div', [
m.component(anotherComponent)
])
])
};
...
// and now the traversal function from the mithril side
var highlightNegatives = function (root, parent) {
if (!root) return root;
else if (root instanceof Array) {
for (var i = 0; i < root.length; i++) {
highlightNegatives(root[i], parent);
}
} else if (root.children) {
highlightNegatives(root.children, root);
} else if (typeof child == "string" && child.indexOf("($") === 0) {
parent.attrs.class = "text-danger";
}
return root;
};
highlightNegatives(someComponent.view()); // will not find the relevant elements in "anotherComponent"
How do others handle that issue?

Components were added after the blog post was written.
You need to convert a component into a vDOM object before you can include it in a view. Notice I've done this 2 times, once inside the highlightNegatives() function to catch components while we traverse the vDOM tree, and also in the function parameters when we call it in the view.
You can just include the component identifier without the call to m.component(), unless you want to send attributes or other options to the component:
myComponent
vs.
m.component( myComponent, {extraStuff: [1,2,3]}, otherStuff )
You'd need to consider this when calling highlightNegatives().
There's no need to include html and body in your templates. The browser does this for you (or you can include them in index.html, of course)
Here's a working example of the code below:
http://jsbin.com/poqemi/3/edit?js,output
var data = ['$10.00', '($20.00)', '$30.00', '($40.00)', '($50.00)']
var App = {
view: function(ctrl, attrs) {
return m("div.app", [
m('h1', "My App"),
someComponent
])
}
}
var anotherComponent = {
controller: function (){
this.data = data
},
view: function (ctrl){
return m('div.anotherComponent', [
m('h3', 'anotherComponent'),
ctrl.data.map( function(d) {
return m('li', d)
})
])
}
}
var someComponent = {}
someComponent.controller = function (){ }
someComponent.view = function (ctrl) {
return m('div.someComponent', [
m('h2', 'someComponent'),
highlightNegatives(anotherComponent.view( new anotherComponent.controller() ))
])
};
// and now the traversal function from the mithril side
var highlightNegatives = function (root, parent) {
if (!root) return root;
else if (root instanceof Array) {
for (var i = 0; i < root.length; i++) {
// when you encounter a component, convert to VDOM object
if (root[i].view) {
highlightNegatives(root[i].view( new root[i].controller() ))
}else{
highlightNegatives(root[i], parent);
}
}
} else if (root.children) {
highlightNegatives(root.children, root);
} else if (typeof root == "string" && root.indexOf("($") === 0) {
parent.attrs.class = "text-danger";
}
return root;
};
m.mount(document.body, App)
//Calling this function after the App is mounted does nothing.
//It returns a vDOM object that must be injected into the someComponent.view
//highlightNegatives(someComponent.view()); // will not find the relevant elements in "anotherComponent"

Related

Is this "DI" structure impossible, or should I look for a bug?

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.

Prototypal Inheritance in Module Pattern JavaScript - Base Class not inheriting anything from parent

I just started to get into JS lately and I am using module pattern a lot and I really don't know if I am doing it right when it comes to doing inheritance from the module I wrote.
Here are the following .js files I am working on:
define(["src/Inhabitant"], function(Inhabitant)
{
console.log("Coin.js loaded");
return (function()
{
function Coin(stage , position)
{
Inhabitant.call(this, stage, position, "coinGold.png");
}
Coin.prototype =
{
prototype : Object.create(Inhabitant.prototype)
, constructor : Coin
, update : update
}
function update(elapsed)
{
}
return Coin;
})();
});
I have a JS class named as Coin and its parent is Inhabitant:
define([], function()
{
console.log("Inhabitant loaded.");
return (function()
{
var mStage = null;
var mSprite = null;
var mID = -1;
function Inhabitant(stage, position , resource)
{
mStage = stage;
mSprite = new PIXI.Sprite.fromFrame(resource);
mSprite.position = position;
}
Inhabitant.prototype =
{
constructor : Inhabitant
, get position(){ return mSprite.position; } , set position(position){ mSprite.position = position; }
, get x(){ return mSprite.x; } , set x(x){ mSprite.x = x; }
, get y(){ return mSprite.y; } , set y(y){ mSprite.y = y; }
, get id(){ return mID; } , set id(id){ return mID; }
, get sprite(){ return mSprite; }
, update : update
}
function update(elapsed)
{
console.log("Calling update from Inhabitant");
}
return Inhabitant;
})();
});
I am stuck on this one because I can't even call the methods I am supposed to inherit. Even the update function isn't provided by the parent. If I remove the update from Coin it will not call the parent version (I don't know if I have the correct assumption on this one).
Also most of the time I write my classes this way
define([] , function()
{
return function()
{
var o = {};
return o
}
});
This works most of the time since I am creating objects without the need of inheritance that much. But now I need to it in prototypal way so I can reduce code duplication.
What is the proper way of doing Module Pattern with prototypal inheritance given on what I currently have?
This have been asked many times via this link and this link but its not helping my situation.
Any ideas?

lodash where method to consider nested array elements [duplicate]

I have an object of folders/files that looks like this:
{
about.html : {
path : './about.html'
},
about2.html : {
path : './about2.html'
},
about3.html : {
path : './about3.html'
},
folderName : {
path : './folderName',
children : {
sub-child.html : {
path : 'folderName/sub-child.html'
}
}
}
}
And it can go 6-7 levels deep of folders having children.
I want to find the object where path is equal to a string that I provide. Regardless of how deep it is.
I'm using underscore which only does top level:
_.findWhere(files,{path:'./about2.html'}
How can I do a deep, nested search. Does underscore have something for this or do I need to build a mixin with recursion?
This isn't the prettiest code, but I tested it out and it seems to work the way you are asking. It's setup as a lodash/underscore mixin, but can be used however. Usage would be like this:
_.findDeep(testItem, { 'path': 'folderName/sub-child.html' })
Implementation:
findDeep: function(items, attrs) {
function match(value) {
for (var key in attrs) {
if(!_.isUndefined(value)) {
if (attrs[key] !== value[key]) {
return false;
}
}
}
return true;
}
function traverse(value) {
var result;
_.forEach(value, function (val) {
if (match(val)) {
result = val;
return false;
}
if (_.isObject(val) || _.isArray(val)) {
result = traverse(val);
}
if (result) {
return false;
}
});
return result;
}
return traverse(items);
}
Instead of findWhere, use filter, which takes a function as the predicate rather than a key-value map. Use a recursive function to check the current node and possible children. Something like this:
var searchText = './about2.html';
var recursiveFilter = function(x) {
return x.path == searchText ||
( typeof x.children != 'undefined' && recursiveFilter(x.children['sub-child.html']) );
};
_.filter(files, recursiveFilter);
Edit
Assuming this works, you'll probably want to make a function getRecursiveFilter(searchText). Here's how that would look:
function getRecursiveFilter(searchText) {
var recursiveFilter = function(x) {
return x.path == searchText ||
(typeof x.children != 'undefined'
&& arguments.callee(x.children['sub-child.html']) );
};
return recursiveFilter;
}
Note that here, recursiveFilter uses arguments.callee to call itself recursively.
Here's a working demo.
This already has an accepted answer, but this other answer was very clean and perfect for my similar situation: https://stackoverflow.com/a/21600748/1913975
_.filter +_.where
Though accepted answer works, it's too generic - it searches all the properties of an object to find children. I am proposing introducing an extra parameter, called 'recursProperty' which will be considered to go deep in the object. This solution is also setup to be used as lodash/underscore mixin and extends loadash/underscore capabilities.
_.findDeep = function(collection, predicate, recursProperty){
let items = [];
_.each(collection, each => items.push(each));
return _.find(items, function(value, key, coll){
if (predicate(value, key, coll)){
return true;
} else {
_.each(value[recursProperty], each => items.push(each));
}
});
};
It can be used as any other underscore function. e.g,
_.findDeep(self.baseEntities, baseEntity => baseEntity.id === 71, 'entity');
Not providing proper value for 'recursProperty' argument or providing null/undefined will simply make the search only on first level (no going deep).

How to rebuild a existing Dom in reactjs?

I am currently trying to get reactJS working with the following Setup:
I am getting a xml-DOM from the Server. Its a server side rendered "custom" DOM, which normaly gets consumed by another client (not html, but java) which renders components.
<row id="a">
<label id="aa" width="140" text="Title" />
<field id="ab" bgpaint="" width="200" text="" enabled="true" />
</row>
I defined some ReactComponents with React.createClass(). So I have a Label, a Field and a Row (div).
Currently I cannot work out a way to render the Components of the xml-DOM with react.
I am going trough the xml-DOM and got the snippet from above. If I find a "row" shell I call React.createElement(Row, {}, ?)? But I i dont know what children will be to fill the '?' before I go deeper into the xml-DOM.
If I walk through the xml-DOM until I got a "endNode" with no children, how can i create the react elements from bottom to top? Do I have to create a super String and somehow call it as javascript.
Is there a way to create a Row, and afterwads add the children? Do I have to use the components props?
Something like:
//<row> found
var newRow = React.createElement(Row, {}, ?);
...
//found the label in the row
var newLabel = React.createElement(Label, {}, "Title");
newRow.add(newLabel);
I can hardly come to a solution - is it possible to do with react? Or am I trying to do something which react is not made for? Probably I do not understand the child concept totally.
Or maybe its not really a react-Problem itself...
Any hints for me?
greetz
Create the children array before you call createElement. For example (jsfiddle):
function pascalize(str) {
return str.split("-").map(function(name) {
return name.charAt(0).toUpperCase() + name.slice(1).toLowerCase();
}).join("");
}
function attrsToProps(attrs) {
var obj = {};
for (var i = 0; i < attrs.length; ++i) {
obj[attrs[i].nodeName] = attrs[i].value;
}
return obj;
}
function toReact(node, components) {
if (node.nodeType === 1) {
var children = [].map.call(node.childNodes, function(child) {
return toReact(child, components);
});
return React.createElement(components[pascalize(node.nodeName)],
attrsToProps(node.attributes),
children);
} else if (node.nodeType === 3) {
return (node.nodeValue + "").trim();
}
return "";
}
function xmlToReactComponents(xmlDom, components) {
if (xmlDom && xmlDom.documentElement) {
return React.createElement("div", [], [].map.call(xmlDom.documentElement.childNodes, function(child) {
return toReact(child, components);
}));
}
}
The usage in jsfiddle is:
var xml = document.querySelector("#xml").innerHTML;
var parser = new DOMParser();
var dom = parser.parseFromString(xml, "text/xml");
var defaultClass = {
render: function() {
return React.createElement("div", {}, "id: " + this.props.id, this.props.children);
}
};
var components = {
Row: React.createClass(defaultClass),
Label: React.createClass(defaultClass),
Field: React.createClass(defaultClass)
}
var result = xmlToReactComponents(dom, components);
React.render(result, document.body);
You need to pass all the components the xml could create in the object otherwise whole thing fails

What's best practice to ensure JavaScript objects are set and not undefined when handling multiple leveled objects?

I'm a js developer and work in an environment where we do API calls and get some data returned. The structure of the data returned is HIGHLY inconsistent so therefor we can't assume anything about the data returned.
Picture the following scenario:
$.ajax({
success: function(data){
// Now I want to access a property 4 levels in
var length = data.results.users.length;
doSomeStuffWithLength(length);
}
})
What's the correct way to ensure data.results.users.length is not undefined? Because of the inconsistencies from the API, each level of the returned object could be broken/undefined. Do I really have to do the following:
if (data && data.results && data.results.users && data.results.users.length){
var length = data.results.users.length;
doSomeStuffWithLength(length);
}
Aren't there more elegant solutions?
You can create helper function like this.
Expect object with structure like this :
var someObj = {
some: {
other: {
third: 'bingo',
qwe: 'test'
}
}
};
Would be great to have something like
getPropByPath(someObj, 'some.other.qwe');
So the implementation of getPropByPath may looks like following:
function getPropByPath(obj, path){
var parts = path.split('.'),
root = obj;
for(var i=0; i<parts.length; i++) {
if(root[parts[i]] !== 'undefined') {
root = root[parts[i]]
} else {
return false;
}
}
return root;
}
If at all levels there may be something undefined, you should check all levels, something like:
var length = data &&
data.results &&
data.results.users &&
data.results.users.length || 0;
You can also use some helper function. Here's one:
function getPropValue ( path, defaultIfNotExisting ) {
defaultIfNotExisting = defaultIfNotExisting || 0;
path = path.split('.'), test = this;
while (path.length) {
test = test[path.shift()];
if (!test) {
return defaultIfNotExisting;
}
}
return test;
}
// usage in your case:
if ( getPropValue.call(data, 'results.users', []).length) { /* do stuff */}

Categories