How does React's JSX breakout an array through interpolation? - html

I'm following the Thinking in React Tutorial and on Part 2 below see that the var rows is interpolated into the markup through JSX. How does JSX know to break out each markup item inside the array? Because to me, the interpolation of {rows} will simply return an array of markup rather than each markup row laid out one by one. So I see it returning [rowA, rowB, ...] instead of <rowA /> <rowB /> ...
var ProductTable = React.createClass({
render: function () {
var rows = [];
var lastCategory = null;
this.props.products.forEach(function (product) {
if (product.category !== lastCategory) {
rows.push(
<ProductCategoryRow
category={product.category}
key={product.category}
/>
);
}
rows.push(<ProductRow product={product} key={product.name} />);
lastCategory = product.category;
});
return (
<table>
<thead>
<tr>
<th>Name</th>
<th>Price</th>
</tr>
</thead>
<tbody>{rows}</tbody>
</table>
);
},
});

Curly braces are a way to execute arbitrary JavaScript expressions inline (including a simple object) and have React evaluate that and render the result.
When React sees {rows}, this is what it thinks:
"oh cool, I have a variable to render. Oh look, it's an array! What's in the array? Oh I see that the first item is a React element called ProductCategoryRow, I'll go render that. NEXT! I see the next one is a ProductRow, I'll go render that"
and so on.
Since you're the curious type :) here's the source that seems to check to see if the item is an Array, if so it renders each item as it would any single item.

This is what the former JSXTransformer, now Babel, does. It traverses the JSX syntax and generates valid JavaScript. When it finds a {} construct...
If it's a literal, return it.
If it's JavaScript expression, evaluate it and return it (in case the result is a React Element, transforms it)
If it's a React Element, transform it.
If it's an Array or React Element, transform each one individually and return each of them.
The above list is probably not complete but you get the picture.

Related

How can I render a tr with soy templates?

I've got this soy template
{template .myRowTemplate}
<tr><td>Hello</td></tr>
{/template}
and I want to do something like
var myTable = goog.dom.createElement("table");
goog.dom.appendChild(myTable, goog.soy.renderAsFragment(mytemplates.myRowTemplate));
goog.dom.appendChild(myTable, goog.soy.renderAsFragment(mytemplates.myRowTemplate));
But that causes
Uncaught goog.asserts.AssertionError
Assertion failed: This template starts with a <tr>,
which cannot be a child of a <div>, as required by soy internals.
Consider using goog.soy.renderElement instead.
Template output: <tr><td>Hello</td></tr>
What's the best way to do this?
Why it fails
Right, the documentation of renderAsFragment is a bit confusing; it reads:
Renders a Soy template into a single node or a document fragment. If the rendered HTML string represents a single node, then that node is returned
However, the (simplified) implementation of renderAsFragment is:
var output = template(opt_templateData);
var html = goog.soy.ensureTemplateOutputHtml_(output);
goog.soy.assertFirstTagValid_(html); // This is your failure
var safeHtml = output.toSafeHtml();
return dom.safeHtmlToNode(safeHtml);
So why do the closure author assert that the first tag is not <tr>?
That's because, internally, safeHtmlToNode places safeHtml in a temporary div, before deciding if it should return the div wrappper (general case) or the only child (if the rendered HTML represents only one Node). Once again simplified, the code of safeHtmlToNode is:
var tempDiv = goog.dom.createElement_(doc, goog.dom.TagName.DIV);
goog.dom.safe.setInnerHtml(tempDiv, html);
if (tempDiv.childNodes.length == 1) {
return tempDiv.removeChild(tempDiv.firstChild);
} else {
var fragment = doc.createDocumentFragment();
while (tempDiv.firstChild) {
fragment.appendChild(tempDiv.firstChild);
}
return fragment;
}
renderAsElement won't work either
And I'm unsure what you are asking for fragments, but unfortunately goog.soy.renderAsElement() will behave the same because it also uses a temporary div to render the DOM.
renderElement cannot loop
The error message suggests goog.soy.renderElement, but that will only work if your table has one row, since it replaces content, and doesn't append children nodes.
Recommended approach
So usually, we do the for loop in the template:
{template .myTable}
<table>
{foreach $person in $data.persons}
<tr><td>Hello {$person.name}</td></tr>
{/foreach}
</table>
{/template}
Of course, we can keep the simple template you have for one row and call it from the larger template.

How do I format my AngularJS data model?

Hi I am just beginning with angular and I am struggling to find the answer to what I'm sure is quite a simple thing to do.
I am currently getting the values of some input boxes and pushing them into my scope. This is creating one long 'array' eg:
['data-1','data-2','data-3']
I would like to format my data in the following way instead
$scope.data = [
{
'header1': 'data1-1',
'header1': 'data1-2',
'header1': 'data1-3'
},
{
'header1': 'data2-1',
'header1': 'data2-2',
'header1': 'data2-3'
}
]
This is my function as it currently is.
$scope.createRow = function(){
angular.forEach(angular.element("input"), function(value, key){
$scope.td.push($(value).val());
});
}
Any help or pointers would be greatly appreciated as I am just getting my head round the angular way
Doing this isn't hard... but before I give you a gun to shoot yourself in the foot, just to say that I think it would be beneficial to explain WHY you want structure in that other format you are mentioning. You seem to have lots of data repetition and that's always a red flag.
Now for the code, you just need to create object before pushing it to the array like:
$scope.createRow = function(){
angular.forEach(angular.element("input"), function(value, key){
var obj = {
"header1": val + "-1",
"header2": val + "-2"
};
$scope.td.push(obj);
});
}
EDIT:
OK, so you are trying to add new row to the table. First of all, you shouldn't be doing angular.forEach, but rather those input elements in HTML should bind to existing scope object, like:
// obviously use better names than Input1Value
// I am here just giving you example
$scope.bindData = {
Input1Value: null,
Input2Value: null
};
// in HTML you will do
// <input ng-model="bindData.Input1Value" />
// <input ng-model="bindData.Input2Value" />
Now that you've eliminated that nasty angular.forEach you need to have some kind of event handler, for example when user clicks the button you want to add this object to the array to which table is data bound. Just be sure to clone the $scope.bindData object when you add it to array.
$scope.createRow = function(){
var newRowData = $scope.cloneObject($scope.bindData);
$scope.td.push(newRowData);
}
// http://heyjavascript.com/4-creative-ways-to-clone-objects/
// https://stackoverflow.com/questions/728360/most-elegant-way-to-clone-a-javascript-object
$scope.cloneObject = function(objToClone) {
var newObj = (JSON.parse(JSON.stringify(objToClone)));
}
To close this answer off - keep in mind, if you ever find yourself directly referencing HTML DOM elements in Javascript with AngularJS - you are doing something wrong. It's a nasty habit to eliminate, especially if you are coming from jQuery background (and how doesn't?), where everything is $("#OhHiThere_ElementWithThisId).
Obviously the main thread on this topic on StackOverflow is this one:
“Thinking in AngularJS” if I have a jQuery background?
However I find that it's too theoretical, so Google around and you may find better overviews like:
jQuery vs. AngularJS: A Comparison and Migration Walkthrough

In what contexts is interpolation legal in Angular, and why?

I know that Angular's string interpolation normally operates on expressions inside Handlebars-style {{ double curly braces }}, and by observation I know that I can use it in contexts like
text outside HTML tags: <span>{{ 'string literal expression ' }}</span>
attribute values inside HTML tags: link
and not to generate attributes themselves, i.e.
<a {{ 'href="/link/to/elsewhere"' }}>link</a>
does not get interpolated.
What I'm curious about is why: what are the rules on where interpolation does and doesn't happen, where this is documented, and what the design considerations or constraints are that led to this.
I guess this is because the document is parsed as HTML by the browser before Angular sees it, so the structure is dictated by HTML and the {{ stuff }} has to appear in places that are well-formed according to HTML even before interpolation happens. But I'd appreciate knowing the whole story.
What are the rules on where interpolation does and doesn't happen ?
Angular.js uses $compile service to compile a piece of DOM. The docs says:
The compilation is a process of walking the DOM tree and matching DOM elements to directives.
In the source code of compile.js there is a function collectDirectives, I trimmed it to show only the relevant code:
function collectDirectives(node, directives, attrs, maxPriority, ignoreDirective) {
var nodeType = node.nodeType;
// ....
switch(nodeType) {
case 1: /* Element */
// ....
// iterate over the attributes
for (var attr, name, nName, ngAttrName, value, nAttrs = node.attributes,
j = 0, jj = nAttrs && nAttrs.length; j < jj; j++) {
// ....
addAttrInterpolateDirective(node, directives, value, nName);
// ....
}
// ....
break;
case 3: /* Text Node */
addTextInterpolateDirective(directives, node.nodeValue);
break;
case 8: /* Comment */
// ....
break;
}
directives.sort(byPriority);
return directives;
}
As you can see, $compile search for interpolated content only inside attributes and text nodes when it iterates a piece of DOM.
Those functions, addTextInterpolateDirective and addAttrInterpolateDirective "translate" the interpolated expression into directives that $watch interpolated expressions and update the DOM element.
Where this is documented?
The compilation phase is documented here: http://docs.angularjs.org/guide/compiler
It's getting better every day but still some in-depth stuff are not clear until you read the source code itself. I guess some things are just too compilcated to explain without showing the code.
What the design considerations or constraints are that led to this?
I guess there are two reasons:
Angular operates on DOM nodes rather than strings, If angular needed to interpolate attributes or elements then It should operate on html strings which is probably bad for performance.
There is no major use case for such things.
If you still want to interpolate everything, do that kind of magic inside a directive:
An example:
app.directive('myAnchor',function(){
return {
restrict: "E",
transclude: true,
link: function(scope,element,attrs,ctrl,$transclude) {
attrs.$observe('interpolate', function(val){
var e = angular.element("<a " + val + "></a>");
$transclude(scope,function(clone){
e.append(clone);
});
element.replaceWith(e);
});
}
};
});
Be sure to read this:
What is the difference between the $parse, $interpolate and $compile services?

JQuery selectors - using html snippets as "context" in filter and find

A quick question about using context with Jquery selectors:
I'm trying to grab the text from a div element that has id="time". Can a HTML snippet be used as context in the following:
// An AJAX request here returns a HTML snippet "response":
var myTime = $("#time", response).text();
The reason I'm doing this is that I want the time variable from within the html held in response, but don't want the overhead of loading all of the html into the DOM first. (it's a large amount of html).
From the comments what I understand is the response is <span id="time">blah blah</span> which means the element time is the root variable itself, that is why the child lookup is not working.
var response = '<span id="time">blah blah</span>';
var myTime = $(response).text(); // Or $(response).filter("#time").text();
alert(myTime)
Demo: Fiddle
This method uses filter() rather than find(), the difference being:
filter() – search through the passed element set
find() – search through all the child elements only.
Did you try it?
$("#time", "<div><span id=time></span></div>")[0].id //returns 'time'
From the jQuery source code:
// HANDLE: $(expr, context)
// (which is just equivalent to: $(context).find(expr)
} else {
return this.constructor( context ).find( selector );
}
so valid selectors should work in the context parameter. Personally, I prefer using find to begin with because it keeps all the selectors in the same order instead of $("second > third", "first");

JQuery weird syntax

I'm new to JQuery and trying to use it to dynamically build HTML based on results of a query for JSON objects. Anyways on the JQuery API site (http://api.jquery.com/jQuery.getJSON/) I found this example where I don't understand the syntax and I can't seem to find any explanation of why this syntax is legal or how to use it.
$.getJSON('ajax/test.json', function(data) {
var items = [];
$.each(data, function(key, val) {
items.push('<li id="' + key + '">' + val + '</li>');
});
// *** THIS IS THE PART THAT IS WEIRD ***
$('<ul/>', {
'class': 'my-new-list',
html: items.join('')
}).appendTo('body');
});
Can someone refer me to documentation that explains the syntax with the comment above it?
$('<ul/>')
Creates a new UL element not attached to the DOM
$('<ul/>', {'class':'my-new-list'})
Sets DOM variables for that element using a key value pair. So now you have a UL element with a class of my-new-list.
$('<ul/>', {'class':'my-new-list', 'html': items.join('')})
This is taking the array of LI elements created above joining the elements together in a string (with no delimiter in this case, .join('-') would have put a hyphen between each LI element) and assigning to the inner html of the UL.
$('<ul/>', {'class':'my-new-list', 'html': items.join('')}).appendTo('body');
Last but not least, it appends the newly created UL element with this child LI elements to the BODY element making it visible on the page.
$('<ul/>', {
'class': 'my-new-list',
html: items.join('')
}).appendTo('body');
This simply creates a new UL element with a class of 'my-new-list' and the contents of items. It will create a structure like:
<ul class='my-new-list'>
<li id="key1">val1</li><li id="key2">val2</li><li id="key3">val3</li>
</ul>
This is simply short hand for:
$('<ul></ul>').attr('class', 'my-new-list').attr('html', items.join('')).appendTo('body');
Documentation: http://api.jquery.com/jQuery/#jQuery2
Check out the documentation for jQuery ($). Specifically, look at the part dealing with the overload jQuery( html, props ):
html: A string defining a single, standalone, HTML element (e.g. or ).
props: An map of attributes, events, and methods to call on the newly-created element.
Further down there is more information:
As of jQuery 1.4, the second argument to jQuery() can accept a map
consisting of a superset of the properties that can be passed to the
.attr() method. Furthermore, any event type can be passed in, and the
following jQuery methods can be called: val, css, html, text, data,
width, height, or offset. The name "class" must be quoted in the map
since it is a JavaScript reserved word, and "className" cannot be used
since it is not the correct attribute name.
See the jQuery documentation here. Specifically the section entitled:
Creating New Elements
This section contains details on the use of the jQuery( html, props ) overload, added in version 1.4. This is the overload used in your example. It takes an html string argument which is used to create a new DOM element and then adds the attributes contained in the props object argument.
That means you're appending a <ul> element with the my-new-list class to the body. The <li> items you've constructed in the items array are then added to the <ul>, so you'll end up with something like:
<ul class="my-new-list">
<li id="key1">val1</li><li id="key2">val2</li>...
</ul>
$('<ul/>', {
'class': 'my-new-list',
html: items.join('')
}).appendTo('body');
In jQuery, besides searching for DOM elements, you can also create them.
$('<ul/>') // or $('<ul></ul>')
This makes a new <ul> elements and returns it as a jQuery object.
The object passed as the 2nd parameter sets its attributes. Thus making this:
<ul class="my-new-list"><li id="one">O Hai</li></ul>
This <ul> is then appended to the body.
The part you don't understand is a completely different usage of the $ function.
In most cases you use $ function as a selector which returns a set of jquery elements (dom nodes wrapped as jquery objects) which you can perform some kind of operation on.
In this case you are instead using the $ function to build DOM nodes (actually, jquery objects) and then those objects you can perform functions on, such as appendTo in the example shown.
It is pretty much equavalent to writing something like
var node = document.createElement("ul");
node.setAttribute("class", "my-new-list");
node.innerHTML = items.join('');
document.body.appendChild(node);