Does react have a markup binding syntax? - reactjs

I'm learning React and this is the way my tutorial says to bind, say, a table to a javascript array of Article objects:
<table>
<tbody>
{
articles.map(a =>
{
return <tr>
<td>
{a.property1}
</td>
<td>
{a.property2}
</td>
</tr>
})
}
</tbody>
</table>
I had been using knockout js, which supports a syntax that seems much more natural for this, in that it doesn't have all the curly braces and parentheses, which makes it much easier to see the structure of the HTML that you're trying to produce:
<table>
<tbody>
<tr data-bind="foreach: a in articles">
<td>
{a.property1}
</td>
<td>
{a.property2}
</td>
</tr>
</tbody>
</table>
Does React have a similar declarative style syntax?

JSX is just syntax sugar for JavaScript. This:
<tbody>
{
articles.map(a =>
{
return <tr> ...
gets transpiled to:
React.createElement('tbody', null,
articles.map(a => {
return React.createElement('tr', null,
// <td> elements
)
})
)
The brackets {} are needed in the JSX when interpolating a JavaScript expression into JSX, to indicate that what followed should be parsed as JavaScript rather than as JSX's HTML-like markup - that is, there's no way around the { in
<tbody>
{
articles.map(
in order to interpolate the articles into the <tbody>.
The best you'll be able to do is remove the { and return and use the arrow function's concise return in the .map callback:
<table>
<tbody>
{
articles.map(a => <tr>
<td>
{a.property1}
</td>
<td>
{a.property2}
</td>
</tr>
)
}
</tbody>
</table>
One of the arguable main benefits of React (for new learners who already know JS) is that it's just JavaScript, other than a few additional syntax rules about JSX. So there's no special syntax like foreach: a in articles - instead, the ordinary JavaScript logic for looping over an array is used, articles.map(a =>.

Related

angular smart-table plugin/directive wont work

I am trying to use smart-tables, Select row plugin, I have added 'smart-table' to my application, like so var myApp = angular.module('myApp', ['ui.bootstrap','smart-table']);
I have also changed the first line of the directive (since it didn't work to just copy-paste it), I changed this:
ng.module('smart-table').directive('stSelectRow', ['stConfig', function (stConfig)
To this: myApp.directive('stSelectRow', ['stConfig', function (stConfig) {
In my html I have this
<table st-table="displayedCollection" st-safe-src="safeCollection" class="table">
<thead>
<tr>
<th>Title</th>
<th>FieldOne</th>
<th>FieldTwo</th>
<th>FieldThree</th>
</tr>
</thead>
<tbody>
<tr st-select-row="row" st-select-mode="multiple" ng-repeat="row in displayedCollection">
<td> {{row.Title}} </td>
<td> {{row.FieldOne}} </td>
<td> {{row.FieldTwo}} </td>
<td> {{row.FieldThree}} </td>
</tr>
</tbody>
If I remove the st-select-row="row" st-select-mode="multiple" the table works, but obviously not the row selection
I am guessing I'm missing some dependency or something, I have only added smart-table.min.js to my application, but I think that should be enough, the directive (select row plugin) is added inside my app.js file. What could it be?
It is because the documentation on the site isn't exactly clear. But it does say
...and the class attribute st-selected is set on the tr element.
You have to define the CSS for st-selected for it to "work":
.st-selected{
background: #216eff !important;
color: white !important;
}

Typo3 Fluid Loop Through Properties

I have a working Fluid template file that looks like this:
<table class="my-extension" >
<tr>
<td>
<f:translate key="my-extension_domain_model_appointment.translator" />
</td>
<td>
{appointment.translator}
</td>
</tr>
<tr>
<td>
<f:translate key="my-extension_model_appointment.bringtranslator" />
</td>
<td>
{appointment.bringtranslator}
</td>
</tr>
</table>
In my model I got the class appointment with the two properties translator and bringtranslator.
I want to iterate through all properties so in case I add another one I don't need to change anything in my html file.
So I'm searching for something like this:
<f:for each="{appointmentproperty}" as="property">
<tr>
<td>
<f:translate key="my-extension_domain_model_appointment."+property />
</td>
<td>
{appointment.property}
</td>
</tr>
</f:for>
Can someone tell me how to do this with fluid? (btw. I'm still using the old 4.7.7 Version)
You cannot do this out of the box. However if you access a property of your model in fluid ({model.property}), under the hood the getProperty() method is called.
So if you want some magic functionality that automatically expands your view if you model grows, you have to implement it.
I'll give you one example, but there are other ways to do this: You could add a method to your model:
/**
* #return array
*/
public function getPropertiesForTableView() {
return array(
'property1' => $this->getProperty1(),
'property2' => $this->getProperty2()
);
}
In your view, you can now access this new Getter Method:
<f:for each="{appointment.propertiesForTableView}" as="propertyValue" key='propertyName'>
<tr>
<td>
<f:translate key="my-extension_domain_model_appointment.{propertyName}" />
</td>
<td>
{propertyValue}
</td>
</tr>
</f:for>
You still have to "do" something if you add a property that should show in your view (adding the property to your array). But you dont need to adjust your template.
<f:for each="{appointments}" as="appointment">
<tr>
<td>
<f:translate key="my-extension_domain_model_appointment."{appointment.translator.uid} />
</td>
<td>
{appointment.bringtranslator}
</td>
</tr>
</f:for>
If your appointment object also has other objects, you can iterate them again in your iteration:
<f:for each="{appointments}" as="appointment">
{appointment.title}
<f:for each="{appointment.translators}" as="translator">
{translator.name}
</f:for>
</f:for>
I think you search for something like this:
Typo3 Extbase Guide
Here you can see, that you can give an array to the alias and then you can cycle through. Maybe you can come to the answer by this? Please comment, if you found the correct way.
<f:alias map="{employees: {0: {first_name: 'Stefan', city: 'Lindlar'},1: {first_name: 'Petra', city: 'Lindlar'},2: {first_name: 'Sascha', city: 'Remscheid'},3: {first_name: 'Patrick', city: 'Bonn'},4: {first_name: 'Sven', city: 'Gummersbach'},5: {first_name: 'Andrea', city: 'Wuppertal'}}}">
<table cellpadding="5" cellspacing="0" border="2">
<f:groupedFor each="{employees}" as="employeesByCity" groupBy="city" groupKey="city">
<tr>
<th colspan="2">{city}</th>
</tr>
<f:for each="{employeesByCity}" as="employee">
<tr>
<td>{employee.first_name}</td>
<td>{employee.city}</td>
</tr>
</f:for>
</f:groupedFor>
</table>
</f:alias>

Evaluating expression inside angular directive

I want my table to conditionally render its row based on whether the value is null or not. The rows have different custom entries and labels, that's why I can't just use ng-repeat. Here's the code:
<table>
<thead>
</thead>
<tbody>
<tr ng-show = "{{data.entry_1}} !== null">
<td>Custom Label 1</td>
<td>{{data.entry_1}}</td>
</tr>
<tr ng-show = "{{data.entry_2}} !== null">
<td>Custom Label 2</td>
<td>{{data.entry_2}}</td>
</tr>
.
.
.
<tr ng-show = "{{data.entry_n}} !== null">
<td>Custom Label n</td>
<td>{{data.entry_n}}</td>
</tr>
</tbody>
</table>
However, it seems that this way is not right. It's either javascript (compiler) is complaining at {{}} in the ng-show or at '!== null' or maybe both. How to evaluate an angular expression (in {{}}) inside an ng- directive?
I know that I could also evaluate this instead in the js file, but since I don't want to add further scope variables (to make my code cleaner), I chose to evaluate if it is null in the ng-show directive. Could someone tell me how to do it?
Thanks.
You were close. The curly braces are only needed to echo/print/render the value of the variable. In an expression you should never use the curly braces.
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<table ng-app ng-init="data = {entry_1: 'notnull', entry_2: null, entry_n: 'againNotNull'}">
<thead>
</thead>
<tbody>
<tr ng-show="data.entry_1">
<td>Custom Label 1</td>
<td>{{data.entry_1}}</td>
</tr>
<tr ng-show="data.entry_2">
<td>Custom Label 2</td>
<td>{{data.entry_2}}</td>
</tr>
.
.
.
<tr ng-show="data.entry_n">
<td>Custom Label n</td>
<td>{{data.entry_n}}</td>
</tr>
<tr ng-show="data.device">
<td>Custom Device</td>
<td>{{data.device}}</td>
</tr>
</tbody>
</table>
Use $compile service in the context of the scope inside your directive.
See https://docs.angularjs.org/api/ng/service/$compile
Edit: I agree with Martin's answer.

ng-init miscalculates aliased index after array.splice

I have encountered a strange behavior related to ng-init, any help would be appreciated.
I have a model object which has a flats property that is an array of flat objects. Each flat object has rooms property which is an array of room objects.
I'm trying to display flat and rooms as follows;
<table ng-repeat="flat in model.flats" ng-init="flatIndex = $index">
<thead>
<tr>
<td>{{flatIndex+1}}. {{flat.name}}</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="room in flat.rooms" ng-init="roomIndex = $index">
<td>{{roomIndex+1}}. {{room.name}}</td>
</tr>
</tbody>
</table>
If i delete a flat or room by using array.splice flatIndex and roomIndex variables doesn't seem to update properly even though $index and ui updates properly.
You can see the problem here in action.
Try to delete 1st, 2nd or 3rd flat or room object by clicking the delete link. Deleting last object from the array doesn't really expose the problem.
Any workarounds would also be appreciated.
This is a known behavior when you use ng-init, the scope property values set by ng-init are not watched and they don't update when you remove items from array to reflect the refreshed index position. So don't use ng-init, instead just use $index (deleteFlat($index)) and flat object reference (to get hold of rooms deleteRoom(flat,$index)).
<table ng-repeat="flat in model.flats track by flat.id">
<thead>
<tr>
<td colspan="2">{{$index+1}}. {{flat.name}}</td>
<td>DELETE FLAT</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="room in flat.rooms track by room.id">
<td> </td>
<td>{{$index+1}}. {{room.name}}</td>
<td>DELETE ROOM</td>
</tr>
</tbody>
</table>
and
$scope.deleteFlat = function(flatIndex){
$scope.model.flats.splice(flatIndex,1);
};
$scope.deleteRoom = function(flat,roomIndex){
flat.rooms.splice(roomIndex,1);
};
Plnkr
Or better off use the ids itself, deleteFlat(flat.id) and deleteRoom(room.id, flat).
<table ng-repeat="flat in model.flats track by flat.id">
<thead>
<tr>
<td colspan="2">{{$index + 1}}. {{flat.name}}</td>
<td>DELETE FLAT</td>
</tr>
</thead>
<tbody>
<tr ng-repeat="room in flat.rooms track by room.id">
<td> </td>
<td>{{$index+1}}. {{room.name}}</td>
<td>DELETE ROOM</td>
</tr>
</tbody>
</table>
and
$scope.deleteFlat = function(flatId){
$scope.model.flats.splice(_getItemIndex(flatId, $scope.model.flats), 1);
};
$scope.deleteRoom = function(roomId, flat){
flat.rooms.splice(_getItemIndex(roomId, flat.rooms), 1);
};
function _getItemIndex(imtId, itms){
var id ;
itms.some(function(itm, idx){
return (itm.id === imtId) && (id = idx)
});
return id;
}
Plnkr2

How to correctly wrap few TD tags for JSXTransformer?

I have an array with items and I want to make something like this:
<tr>
(until have items in array
<td></td><td></td>)
</tr>
But if I do that, I get an JSXTransformer error :
Adjacent XJS elements must be wrapped in an enclosing tag
Working version:
{rows.map(function (rowElement){
return (<tr key={trKey++}>
<td className='info' key={td1stKey++}>{rowElement.row[0].value}</td><td key={td2ndKey++}>{rowElement.row[0].count}</td>
<td className='info' key={td1stKey++}>{rowElement.row[1].value}</td><td key={td2ndKey++}>{rowElement.row[1].count}</td>
<td className='info' key={td1stKey++}>{rowElement.row[2].value}</td><td key={td2ndKey++}>{rowElement.row[2].count}</td>
<td className='info' key={td1stKey++}>{rowElement.row[3].value}</td><td key={td2ndKey++}>{rowElement.row[3].count}</td>
<td className='info' key={td1stKey++}>{rowElement.row[4].value}</td><td key={td2ndKey++}>{rowElement.row[4].count}</td>
.......
</tr>);
})}
I tried this one. But with <div> enclosing tag it doesn't work fine.
Answer here:
Uncaught Error: Invariant Violation: findComponentRoot(..., ...$110): Unable to find element. This probably means the DOM was unexpectedly mutated
<tbody>
{rows.map(function (rowElement){
return (<tr key={trKey++}>
{rowElement.row.map(function(ball){
console.log('trKey:'+trKey+' td1stKey'+td1stKey+' ball.value:'+ball.value+' td2ndKey:'+td2ndKey+' ball.count:'+ball.count);
return(<div key={divKey++}>
<td className='info' key={td1stKey++}>{ball.value}</td><td key={td2ndKey++}>{ball.count}</td>
</div>);
})}
</tr>);
})}
</tbody>
Please, advise me how to properly wrap few TD tags!
I tried use a guide Dynamic Children, but JSXTransformer doesn't allow me do that.
The following error usually occurs when you are returning multiple elements without a wrapping element
Adjacent XJS elements must be wrapped in an enclosing tag
Like
return (
<li></li>
<li></li>
);
This doesn't work because you are effectively returning two results, you need to only ever be returning one DOM node (with or without children) like
return (
<ul>
<li></li>
<li></li>
</ul>
);
// or
return (<ul>
{items.map(function (item) {
return [<li></li>, <li></li>];
})}
</ul>);
For me to properly answer your question could you please provide a JSFiddle? I tried to guess what you're trying to do and heres a JSFiddle of it working.
When using the div as a wrapper its actually never rendered to the DOM (not sure why).
<tr data-reactid=".0.0.$1">
<td class="info" data-reactid=".0.0.$1.$0.0">1</td>
<td data-reactid=".0.0.$1.$0.1">2</td>
<td class="info" data-reactid=".0.0.$1.$1.0">1</td>
<td data-reactid=".0.0.$1.$1.1">2</td>
<td class="info" data-reactid=".0.0.$1.$2.0">1</td>
<td data-reactid=".0.0.$1.$2.1">2</td>
<td class="info" data-reactid=".0.0.$1.$3.0">1</td>
<td data-reactid=".0.0.$1.$3.1">2</td>
</tr>
EDIT: React 16+
Since React 16 you can now use fragments.
You would do it like this now
return <>
<li></li>
<li></li>
<>;
Or you can use <React.Fragment>, <> is shorthand for a HTML fragment, which basically is just a temporary parent element that acts as a container, once its appended to the document it no longer exists.
https://reactjs.org/docs/fragments.html
https://developer.mozilla.org/en-US/docs/Web/API/DocumentFragment
So you have pairs of <td> elements which you want to return from a .map. The easiest way to do this is to just wrap them in an array.
<tr>
{data.map(function(x, i){
return [
<td>{x[0]}</td>,
<td>{x[1]}</td>
];
})}
</tr>
Don't forget the comma after the first </td>.
With the release of React 16, there is a new component called Fragment.
If are would like to return a collection of elements/components without having to wrap them in an enclosing element, you can use a Fragment like so:
import { Component, Fragment } from 'react';
class Foo extends Component {
render() {
return (
<Fragment>
<div>Hello</div
<div>Stack</div>
<div>Overflow</div>
</Fragment>
);
}
}
Here is how you will do it
<tbody>
{this.props.rows.map((row, i) =>
<tr key={i}>
{row.map((col, j) =>
<td key={j}>{col}</td>
)}
</tr>
)}
</tbody>

Resources