JSTL to jsxQuery
What you've seen so far is hardly different than ordinary HTML and JSTL. We've basically just wrapped a thin layer of JavaScript round our markup.
If you wanted, you could write jsxQuery Component
s just as if they were containers for regular old HTML and JSTL. Actually, this is good news in case you need time to get used to doing things differently.
Still, remember that the point of this whole exercise is to keep our view logic separated from all back-end logic. Once we direct some effort towards that goal, we will really see the advantages to having this build process.
Three points of difference
There are three areas jsxQuery diverges markedly from JSTL in order to maintain this separation of concerns:
- displaying dynamic values
- control flow
- iterating over collections
This means your jsxQuery Component
code will ideally be free of these JSTL features:
- JSTL Expression Language (
${likeThis}
), and<c:out>
tags <c:if>
or<c:choose>
tags<c:forEach>
tags
If you have any of these in a jsxQuery Component
, it will still render fine. You will just have to use the jsxQuery equivalent if you want to write unit tests for that part of your code. But remember--JSX is just JavaScript. All the jsxQuery equivalents to JSTL syntax are just ordinary language features of JavaScript. Let's them in action now.
1. Displaying dynamic values
Since JSX uses {}
brackets to enclose dynamic values, you won't want to type an expression like this right in your JSX.
// within a Component's render() method
// WRONG!!!!!
<span>${order.totalItemCount}</span>
That would try to render the dollar sign, plus the JavaScript value order.totalItemCount
, which probably doesn't exist. The solution looks like this:
// within a Component's render() method
<span>{cartCount}</span>
And then, whenever you insantiate that component:
<Cart cartCount="${order.totalItemCount}" />
In short, whenever you want to render text using JSTL Expression Language, make the JSTL into a string and pass it in as a prop. You'll want to take the whole expression, including the ${}
dollar sign and brackets and give it a good name.
(Note: one of the few differences between JSX in jsxQuery and JSX in React is that anything within ${}
is always unescaped. This is so that no operators like <
will get broken within your JSTL expressions.)
<c:out>
tags
A <c:out>
tag will render fine in jsxQuery, without any complications like with JSTL Expression Language. However, that doesn't mean you should keep it in your Component
's render()
method. Put it in your props just like you would a JSTL Expression.
<Cart cartCount="${order.totalItemCount}" linkUrl={<c:out val="${someUrl}" />} />
Clearly, this is a situation where you would benefit from spread attribute syntax.
var cartProps = {
cartCount: '${order.totalItemCount}',
linkUrl: <c:out val="${someUrl}" />
// ...
};
// ...
<Cart {...cartProps} />
With our cartProps
object defined as above, that {...cartProps}
syntax is equivalent to the HTML-style syntax.
2. Control flow
In JSTL, there are a few ways to handle control flow, but jsxQuery uses just one. Anywhere you would use <c:if>
or <c:choose>
tags, as well as ${ x ? y : z }
conditional expressions, just use a JavaScript conditional expression, or several of them, if necessary.
Whether your JSX condition expression becomes <c:if>
, <c:choose>
, or a JSTL conditional expression is decided automatically.
var ControlFlow = jsxQuery.createClass({
render() {
var testCondition = this.props.testCondition;
return (<div>
<div>{testCondition ? <span>Two elements</span> : <em>trigger c:choose.</em>}</div>
<div>{testCondition ? 'Strings favor' : 'conditional expressions.'}</div>
<div>{testCondition ? <span>Only one possible outcome</span> : null}</div>
<div>{testCondition ? null : <span>means a c:if tag</span>}</div>
<div>{testCondition ? 'unless you\'re just dealing with a string.' : null }</div>
</div>);
},
});
<ControlFlow testCondition="condition == true" />.markup();
// <div>
// <div><c:choose><c:when test="${condition == true}"><span>Two elements</span></c:when><c:otherwise><em>trigger c:choose</em></c:otherwise></c:choose></div>
// <div>${condition == true ? "Strings favor" : "conditional expressions"}</div>
// <div><c:if test="${condition == true}"><span>Only one possible outcome</span></c:if></div>
// <div><c:if test="${!(condition == true)}"><span>means a c:if tag</span></c:if></div>
// <div>${condition == true ? "unless you're just dealing with a string" : ''}</div>
// </div>
There are two things to watch out for:
- Omit the
${}
brackets, as they will be added for you where necessary - The test condition (left of
?
question mark) must be a single prop.
That second part means no complex expressions can go left of the ?
question mark. So none of these will work:
prop < 3 NOPE!
!prop NOPE!
prop == 0 NOPE!
prop > otherValue NOPE!
...and so on. Remember, all the fetching and manipulation of back-end data belongs outside render()
, in your props.
(Note: This ternary expression magic actually works only within JSX expressions. If you want to name a conditional value beforehand and then insert it into a JSX expression afterwards, you can use jsxQuery.ternary(test, consequent, alternate)
.)
3. Iterating over collections
Instead of <c:forEach>
tags, jsxQuery uses map()
. You can call map()
on a prop
that represents a collection just like it were a JavaScript array.
function productLi(productData, i) {
return (<li class="product">
<span class="index">{i}</span>
<span class="product-name">{productData.name}</span>
<span class="product-price">{productData.price}</span>
</li>);
}
var Iteration = jsxQuery.createClass({
render() {
return (<ul>
{this.props.productList.map(productLi)}
</ul>);
},
});
<Iteration productList="${catalog.allProducts}" />.markup();
// <ul>
// <c:forEach var="productListItem" items="${catalog.allProducts}" varStatus="productListIndex">
// <li class="product">
// <span class="index">${productListIndex.loop}</span>
// <span class="product-name">${productListItem.name}</span>
// <span class="product-price">${productListItem.price}</span>
// </li>
// </c:forEach>
// </ul>
This map()
function takes a single function as its argument. In this case, the return value of that function you give map
will serve the same purpose as the body of your <c:forEach>
loop.
Testing different page states
If you change your JSTL habits in just these three areas, testing different page states is as easy as switching out your props. Look in the examples
folder of the repo to see working examples.
Swapping out dynamic text
Obviously, you can use a plain string in the place of a prop whose actual value is a JSTL expresson or a <c:out>
tag.
Use Boolean values to test
Swap out your JSTL test conditions you defined as strings for JavaScript true
or false
.
Arrays
Swap out your collection values you defined as strings for a JavaScript array. If you accessed properties on your collection items, mock them out using JavaScript objects.