Generating a JSP file
There are a couple Node packages you'll need installed in order to start generating JSP files on your machine. You can get them by cloning this repo and running $ npm install
in the repo directory. Make sure any work you do moving forward is within that directory.
To get familiar with the build process, let's generate some simple HTML. Our end goal will be to produce a simple page like this:
<html>
<head>
<title>My First jsxQuery Component</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
Writing a Component
class
Let's start by creating a Component
class with jsxQuery.createClass()
. You can find a Component
class that's already been started for you in HelloWorld.jsx
.
components/HelloWorld.jsx
var jsxQuery = require('jsxquery');
var HelloWorld = jsxQuery.createClass({
// we will fill this in
})
module.exports = HelloWorld;
We have to add our Component class to exports
, since we want to use it outside this file later.
Filling in the render
method
Before our HelloWorld
component will do anything, we need to give it a render
method. createClass
will expect your render
method to return
some JSX.
That HTML above can also serve as perfectly valid JSX, so let's copy and paste that right into our render
method. (Remember: we can return
a piece of JSX just like any ordinary JavaScript value, since it's really just a function call.)
components/HelloWorld.jsx
...
var HelloWorld = jsxQuery.createClass({
render: function() {
return (
<html>
<head>
<title>My First jsxQuery Component</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
);
},
})
...
Our HelloWorld
component is now ready to be rendered.
Building some JSP files
Try running this command and you'll see a change in the repo directory:
$ npm run jsxquery-build
Now, there's a bunch of new files in the jsp
folder. If you take a look inside, you'll see markup equivalent to what we wrote earlier.
jsp/myFirstJsp.jsp
<html>
<head>
<title>My First jsxQuery Component</title>
</head>
<body>
<h1>Hello, World!</h1>
</body>
</html>
If you're wondering why that ended up in myFirstJsp.jsp
, take a look in filetree.jsx
.
filetree.jsx
var jsxQuery = require('jsxquery');
var HelloWorld = require('./components/HelloWorld.jsx');
module.exports = {
myFirstJsp: <HelloWorld />,
mySecondJsp: <HelloWorld />,
aDirectory: {
aSubDirectoryWithOneFile: {
anotherJsp: <HelloWorld />,
},
yetAnotherJsp: <HelloWorld />
}
};
Our jsxquery-build
task uses this object to fill out a file tree. You'll notice that the contents of the jsp
directory mirror exactly the structure of that object.
Making an instance of a Component
class
You must have noticed how we used our HelloWorld component to create a JSX element. This is how you instantiate a Component
class.
<HelloWorld />
// transforms into:
// jsxQuery.createElement(HelloWorld, null, null)
When the JSX interpreter sees that capitalized tag name, it knows to look for a Component
with that name. Now, the jsxquery-build
task will use that Component's render
method to produce our markup.
As you can see, you can instantiate a Component
as many times as you want. Of course, it's hard to see the point in that if all our instances of HelloWorld
render the exact same thing. Let's make HelloWorld
a lot more useful by turning the page title and <h1>
text into dynamic values.
Introducing dynamic values as props
The first step to making our HelloWorld
component work with dynamic values is to add some special attributes to an existing instanc.
filetree.jsx
module.exports = {
myFirstJsp:
<HelloWorld pageTitle="My First jsxQuery Component"
headerText="Hello, World!" />
// ...
}
We have just defined two props. We can access these within our Component
's render()
function as this.props
. Then, we can inject them into our JSX with curly braces.
components/HelloWorld.jsx
render: function() {
var pageTitle = this.props.pageTitle;
var headerText = this.props.headerText;
return (
<html>
<head>
<title>{pageTitle}</title>
</head>
<body>
<h1>{headerText}</h1>
</body>
</html>
);
},
If you run $ npm run jsxquery-build
again, you should get the same result in myFirstJsp.jsp
. No surprises there. But now, we can create something completely different just by changing the props passed to our HelloWorld
component.
filetree.jsx
module.exports = {
myFirstJsp:
<HelloWorld pageTitle="My First jsxQuery Component"
headerText="Hello, World!" />,
mySecondJsp:
<HelloWorld pageTitle="Reusing our HelloWorld Component"
headerText="Woohoo!" />
// ...
};
Run $ npm run jsxquery-build
again, and you'll find mySecondJsp.jsp
looking like this:
jsp/mySecondJsp.js
<html>
<head>
<title>Reusing our HelloWorld Component</title>
</head>
<body>
<h1>Woohoo!</h1>
</body>
</html>
As you can see, any value you extract into a prop
can be replaced on the fly. The values in this.props
are not set until the moment you instantiate your Component
.
Keeping prop data separate
The props
we used here were just a couple simple strings. However, the real purpose of the props
object is to keep all your Component
's dynamic values in one place. Having them separate from the view logic in your component's render()
method is what makes jsxQuery components testable.
Since any given Component
could potentially have dozens of dynamic values, that list of props could get really long. For that reason, you probably don't want them all typed in right in your filetree.jsx
.
Thanks to JSX spread attributes, you don't have to. Take a look at our first HelloWorld
instance written with spread attributes.
var myFirstJspAttributes = {
pageTitle: 'My First jsxQuery Component',
headerText: 'Hello, World!'
};
module.exports = {
myFirstJsp: <HelloWorld {...myFirstJspAttributes} />,
// ...
};
This way, you can write your props in the nice, readable format of a JavaScript object. Since we're in Node, we can put that object in a separate file and keep your filetree.jsx
nice and short.
Our complete Component
Here's our complete HelloWorld
code.
components/HelloWorld.jsx
var jsxQuery = require('jsxquery');
var HelloWorld = jsxQuery.createClass({
render: function() {
var pageTitle = this.props.pageTitle;
var headerText = this.props.headerText;
return (
<html>
<head>
<title>{pageTitle}</title>
</head>
<body>
<h1>{headerText}</h1>
</body>
</html>
);
},
});
module.exports = HelloWorld;
Aside: Taking advantage of ES6
Feel free to skip this part.
We can make things a bit more concise using ES6 destructuring. Since you'll be referencing properties on that that props
object all the time, this will really come in handy. Another nice piece of syntactic sugar we get is shortened function definitions in object literals. Additionally, we can prevent anyone from accidentally touching values they shouldn't, via the new const
keyword.
Here's what our HelloWorld
component looks like with those changes. At this scale, it's not a very huge difference, but you can imagine the benefits adding up as you write more and more JavaScript.
const jsxQuery = require('jsxquery'); /*** CONST KEYWORD ***/
const HelloWorld = jsxQuery.createClass({
render() { /*** METHOD DEFINITION SHORTHAND ***/
const { pageTitle, headerText } = this.props; /*** DESTRUCTURING ***/
return (
<html>
<head>
<title>{pageTitle}</title>
</head>
<body>
<h1>{headerText}</h1>
</body>
</html>
);
},
});
module.exports = HelloWorld;
Conclusion
I hope you agree that this way of generating JSP files isn't so far removed from the way you're used to writing JSP files. It's not an entirely new way of doing things--it's more like a new way of combining skills you already have as a front-end developer. You've seen this tag syntax in HTML/JSTL before, and you've dealt with data stored in JavaScript objects via JSON, as well. Even if the bits of Node that glue everything together are new to you, don't forget--it's just JavaScript!