How to rebuild a existing Dom in reactjs? - javascript

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

Related

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

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"

DOM element creator

I'm trying to create a function using vanilla JS that will:
Create a new DOM element
Assign it a Class Name
Place it in the DOM either appending to an existing div or inserting it specifically into the DOM if required using "insertBefore()"
I have come up with the somewhat inelegant solution below:
function createDomElem(elem, className, parent, refElement, type) {
var a = document.createElement(elem);
if (type == "append") {
document.querySelector(parent).appendChild(a);
} else if (type == "insert") {
document.querySelector(parent).parentNode.insertBefore(a, refElement)
}
a.className = className;
};
My problems with this solution are
Too many arguments to be passed
If not passing "insert" then you don't require refElement and to avoid "type" being mistaken for "refElement" you'd have to pass "refElement" as "null" and then define type as "append"
So my question is where can I streamline this function to become more useful within my program?
I'm also dreaming of the ability to be able to push child divs into the newly created div right within this function, defining how many child divs you would want and then using a for loop to append or insert these. Would this be better placed in a new function though?
I would split the code into two parts, as they have to separate concerns. I use something similar to the following for creating DOM elements:
var DomFactory = (function (document) {
var api = {
element: function (name, attributes) {
var el = document.createElement(name);
if (attributes) {
for (var key in attributes) {
if (attributes.hasOwnProperty(key)) {
el.setAttribute(key, attributes[key]);
}
}
}
return el;
},
div: function (attributes) {
return api.element('div', attributes);
}
};
return api;
}(window.document));
Usage:
var div = DomFactory.div({ 'class': 'hero' });
var table = DomFactory.element('table', { 'class': 'table table-bordered' });
Then for positioning, you could have a generalised position function:
function attach(source, target, position) {
switch (position) {
case 'before': {
target.parentNode.insertBefore(source, target);
break;
}
case 'after': {
if (target.nextSibling) {
target.parentNode.insertBefore(source, target.nextSibling);
} else {
target.parentNode.appendChild(source);
}
}
}
}
Usage:
attach(table, div, 'before');

how to render multiple children without JSX

How to write this without using JSX?
var CommentBox = React.createClass({
render: function() {
return (
<div className="commentBox">
<h1>Comments</h1>
<CommentList />
<CommentForm />
</div>
);
}
});
This comes from the react.js tutorial: http://facebook.github.io/react/docs/tutorial.html
I know I can do the following:
return (
React.createElement('div', { className: "commentBox" },
React.createElement('h1', {}, "Comments")
)
But this only adds one element. How can I add more next to one another.
You can use the online Babel REPL (https://babeljs.io/repl/) as a quick way to convert little chunks of JSX to the equivalent JavaScript.
var CommentBox = React.createClass({displayName: 'CommentBox',
render: function() {
return (
React.createElement("div", {className: "commentBox"},
React.createElement("h1", null, "Comments"),
React.createElement(CommentList, null),
React.createElement(CommentForm, null)
)
);
}
});
It's also handy for checking what the transpiler outputs for the ES6 transforms it supports.
insin's answer is the direct translation, however you may prefer to use factories.
var div = React.createFactory('div'), h1 = React.createFactory('h1');
var CommentBox = React.createClass({displayName: 'CommentBox',
render: function() {
return (
div({className: "commentBox"},
h1(null, "Comments"),
React.createElement(CommentList, null),
React.createElement(CommentForm, null)
)
);
}
});
createFactory essentially partially applies createElement. So the following are equivalent:
React.createElement(c, props, child1, child2);
React.createFactory(c)(props, child1, child2);
If you're just using es6 but aren't fond of JSX you can make it less verbose with destructuring assignment. See this jsbin for an interactive example using 6to5 instead of jsx.
var [div, h1, commentForm, commentList] = [
'div', 'h1', CommentForm, CommentList
].map(React.createFactory);
if you have a variable number of children then you can use that:
Using apply function which take an array of parameters.
React.createElement.apply(this, ['tbody', {your setting}].concat(this.renderLineList()))
where renderLineList is for instance:
renderLineList: function() {
var data=this.props.data;
var lineList=[];
data.map(function(line) {
lineList.push(React.createElement('tr', {your setting}));
});
return lineList;
}
You just add them one after another as children to your parent component,
return React.createElement("div", null,
React.createElement(CommentList, null),
React.createElement(CommentForm, null)
);
I had this problem, it took a while to solve by stepping through the interpreter source code:
var arrayOfData = [];
var buildArray = (function () {
var id;
var name;
return{
buildProc(index, oneName){
id = index;
name = oneName;
arrayOfData[index] = (React.createElement('Option', {id},name));
}
}
})();
// then
this.state.items = result;
var response = parseJson.parseStart(this.state.items);
var serverDims = response.split(":");
for (var i = 1; i < serverDims.length; i++) {
buildArray.buildProc(i, serverDims[i] )
}
// then
render(){
const data = this.props.arrayOfData;
return (
React.createElement("select", {},
data
)
// {data} Failed with "object not a valid React child, data with no curly's worked
)
}

React js and Laravel Localization strings?

I recently started to give a go to React js and im starting to like it.
There is one logic what i am stuck with.
My site is multi-language and i have problems rendering the strings.
So what i thought is to place a data-translate attribute to the id's or classes but still does not fit right.
This is just a basic example of my logic
React js
var counter = document.getElementById('counter').getAttribute('data-translate');
var Timer = React.createClass({
getInitialState: function() {
return {secondsElapsed: 0};
},
tick: function() {
this.setState({secondsElapsed: this.state.secondsElapsed + 1});
},
componentDidMount: function() {
this.interval = setInterval(this.tick, 1000);
},
componentWillUnmount: function() {
clearInterval(this.interval);
},
render: function() {
return (
<div className={this.translate}>{counter} {this.state.secondsElapsed}</div>
);
}
});
React.renderComponent(
<Timer />,
document.getElementById('counter')
);
HTML
<div id="counter" data-translate="{{ trans('stream.counter') }}"></div>
So not the best idea.
Could someone give me a hint?
You want to convert your translation files into JSON and use a client side translation function.
For example, image you generated something like this:
var translations = {"en":{"stream":{"counter":"counter"}}};
You could then write trans like this:
function trans(key){
var keys = key.split(".");
var lang = navigator.language.split("-");
return lang.concat(keys).reduce(function(o, k){
var next = o[k];
if (!next) {
console.error('No translation found for ' + key, new Error().stack);
return {};
}
else {
return next;
}
}, translations);
}
And in your components, just use
<div>{trans('stream.counter')}</div>
An API like gettext will do what you need. During application initialisation, you'd set up a dictionary with source keys being the "fallback language" text, and the values in the destination language.
// Data source initialised once, could be embedded in the source from the server.
var TranslationDictionary = {
"the ticks": "les tiques",
...
};
// In the component:
return <div>{gettext("the ticks")}</div>;
// Then the gettext utility to join them together:
function gettext(lookup) {
var translation = TranslationDictionary[lookup];
if (translation) {
return translation;
}
else {
console.log("Warning: missing translation for: " + lookup);
return lookup;
}
}
The gettext function is then very simple, and since the keys are the full text in the fallback language, the view code is still easy to read. Bonus points if you write a code analyser which looks for missing translations.

Is there a way to only show parent nodes in a extjs tree

I want to show only parent nodes of a tree in extjs. In my datastore there are leaf nodes as well.
The output should be like -
Folder 1
Folder 1.1
Folder 2
Folder 3
Create a filter object that gets only parent nodes and add it to the store config:
E.g. filter for parent nodes only:
var nodeFilter = new Ext.util.Filter({
property: 'leaf',
value : false
});
Putting it on the treestore config:
var yourTreeStore = Ext.create('Ext.data.TreeStore', {
// other configs ...
filters: [nodeFilter]
});
EDIT:
incutonez is right, I submitted according to the API properties but did not notice the missing functions. They are easy enough to override though to apply filtering for a treestore though. This is working for me in 4.1b2:
Ext.override(Ext.data.TreeStore, {
hasFilter: false,
filter: function(filters, value) {
if (Ext.isString(filters)) {
filters = {
property: filters,
value: value
};
}
var me = this,
decoded = me.decodeFilters(filters),
i = 0,
length = decoded.length;
for (; i < length; i++) {
me.filters.replace(decoded[i]);
}
Ext.Array.each(me.filters.items, function(filter) {
Ext.Object.each(me.tree.nodeHash, function(key, node) {
if (filter.filterFn) {
if (!filter.filterFn(node)) node.remove();
} else {
if (node.data[filter.property] != filter.value) node.remove();
}
});
});
me.hasFilter = true;
},
clearFilter: function() {
var me = this;
me.filters.clear();
me.hasFilter = false;
me.load();
},
isFiltered: function() {
return this.hasFilter;
}
});
With this overrride in your code, you could create a "leaf only" filter as a function or a property/value pair as per the Ext.util.Filter API:
// leaf only filter as a property/value pair
var nodeFilter = new Ext.util.Filter({
property: 'leaf',
value : false
});
// leaf only filter as a function
var nodeFilter = Ext.create('Ext.util.Filter', {
filterFn: function(item) {
return !item.data.leaf;
}
});
You could then just call the filter function whenever to take out the leaf nodes:
myTreeStore.filter(nodeFilter);
TreeStores do not inherit filtering (because they're abstract stores), so Geronimo's answer did not work for me. I wish it did because it would've made my life a whole lot easier.
Anyway, I have a thread over on the Sencha forums that provides a working filtering solution. In my example, filtering is called by the filterBy function, so I'm sure you could tweak it to work your way.

Categories