This tutorial will show you how to create a simple blog site with a live updating comment feed and email notifications when a comment is received.
You'll need node.js 6.0 or greater installed.
-
Create a new folder and initialise a new node.js npm project by using
npm init
and following the instructions. -
Install Iris.
npm install --save https://github.com/FilipNest/Iris
- Create a launch file with your basic site configuration. Let's call it
mysite.js
- Inside
mysite.js
put the following:
var config = {
"sitePath": "/mysite",
"port": 4000,
"dbEngine": "nedb",
"siteEmail": "you@yoursite.org",
"max_file_size": 10
}
require("irisjs")(config);
(Change the sitePath, port and site email address as needed.)
- Run the site using
node mysite.js
Visit localhost:4000
in your web browser and you should be greeted with a screen asking you to fill out an administrator email address and password.
Select the standard
installation type, type in your preferred admin account access details and press install.
Before we get started creating a blog content type, head over to the content
section in the admin menu and create a new entity of type page
. There is nothing special about this type, it just comes packaged into the standard installation so you might as well use it.
Give it a path of /
. This will make it your site's home page.
Don't worry about content too much, we'll add more to this page later.
Add another page for your blog homepage and give it the path of /blog
You can visit these pages at their relevant paths but it won't look great until we get onto theming a bit later. Go to /admin
to go back to the administration system.
Head over to the structure -> entities tab in the menu.
You should already have a page and user type but we're going to create two new ones. First of all, a blog post type.
- Select
Create new entity type
- Name the entity type
blog
and optionally give it a description.
You'll now need to add some fields to your entity type.
For now let's add a title field and a body field.
- Press add new field and add a
textfield
with the labelTitle
- On the next screen you can give it a field description if needed to help on the edit page but most importantly, set the field view permissions to allow anoymous and authenticated users to view the field.
- Optionally mark the field as unique (you won't want two blogs to have the same title)
- Hit save and then add a body field of type
longtext
. Similar settings apply to this field. - Hit save on the body field.
- The longtext field has an additional option of a widget. This is a setting that allows you to add a WYSIWYG editor to the field. You might want to write in Markdown or plain text/html and leave this blank, but if you want to use CKEditor (as many will) select edit on the body field and head over to the widget tab. Hit save on the CKEditor widget.
You want other people to be able to view your blog entries so head over to the permissions
screen in the users
section and select can view any blog
for anonymous and authenticated in the entity
section. Do the same for the page
entity type while there.
You might want to type in unique urls for each blog, but it helps to have a default.
The autopath
module is there to help with this.
Go to structure -> autopath
and in the blog post section you should be able to create a pattern for making URLS.
Type in /blog/[title]
and it'll automatically use a url friendly version of the title in the URL.
Head over to the content section of the administration interface and you'll see your blog entity type. Select it and use the interface to create a new blog. Call it hello world
for example.
Leave the path
field blank and it will automatically get filled in by the autopath settings as mentioned above. You can override it if you want.
When done, head over to the URL for the blog. Something like - http://localhost:4000/blog/hello-world
. You can go directly to the blog path by clicking view content and then the blog title in the list.
As you'll notice, the blog's template is just a list of fields. We'll now make a theme to style it.
-
Create a folder in your site directory called themes. (Your site directory was specified with the sitePath configuration parameter.
mysite
in this case). It should already have folders likeconfiguration
in it. -
In this new
themes
directory, create a subdirectory with your theme name.mytheme
for example. -
In the
mytheme
directory, add a file calledmytheme.iris.theme
(change the mytheme part to whatever you named your theme). -
Add two directories to your
mytheme
foldertemplates
andstatic
. We're going to be using these a bit later. -
Add the following JSON configuration to your theme's
.iris.theme
file:
{
"name":"My theme",
"regions":["header","sidebar"]
}
- Visit the
themes
tab in the Iris administration menu and select and enable your theme.
Every template in Iris is an HTML file that is parsed through the Handlebars-powered template engine. When Iris searches for templates it looks for them in module and theme template folders passing in a few search arguments. These arguments are split by a __
in the actual file name. Here's an example to help explain what that means:
If looking for a template to display a page
entity, Iris looks for templates named page.html
. It also passes in the entity ID so you could easily override a template for a page with page__1.html
. General entity pages (if no other template is found, can be stored at entity.html
). The default entity.html
is what you're currently seeing when you visit your page. It simply lists all the fields on an entity and their labels.
Error pages take the name of their status code, so use 404.html
for a 404 page.
Any system templates provided by modules can also be overriden in your theme or custom modules (you could even replace the whole admin theme if you wanted to).
A special template file called html.html
is used for the wrapper of the page (everything that goes around it). This can also be overriden for more specific templates, html__page__1.html
for example.
For the wrapper template to work it needs to include a special tag of [[[MAINCONTENT]]]
. Put this wherever you want the inner template markup to display.
It is also a good idea to include the following markup for any messages that dispaly to a user:
{{{iris embed="messages"}}}
And the following in the <head></head>
to include any clientside scripts or stylesheets loaded by an Iris module. We'll use some of these later.
{{{iris embed="tags" name="headTags"}}}
So let's start with creating an HTML wrapper template.
- Create an
html.html
file and place it in thetemplates
folder of your theme. - In that file, put something like the following:
<html>
<head>
<title>{{current.title}}</title>
{{{iris embed="tags" name="headTags"}}}
</head>
<body>
{{{iris embed="messages"}}}
[[[MAINCONTENT]]]
</body>
</html>
Notice the current
Handlebars variable. You'll use this to access the entity currently being viewed and its fields.
For now, let's keep the blog page template very short. Use the following in a blog.html
file (named after your entity type).
<article>
<h1>{{current.title}}</h1>
{{{current.body}}}
</article>
The {{{current.body}}}
Handlebars tag has triple brackets so that it allows the HTML through. Double curly brackets would leave it as plain text.
Create another template for page.html
in a similar way. entity.html
could be used for both but we're eventually going to put different content in them so it makes sense to separate the two.
You'll probably want to include a stylesheet in your template. Simply add it to the static
folder in your theme directory and include it in your html.html
file like this:
<link rel="stylesheet" href="/themes/mytheme/style.css" />
Static files can also be saved in the whole site root under a /static
folder. They will be available via the root url rather than /themes/themename/...
. This is useful for non theme specific static assets.
Refresh your page once your stylesheet file is in and it should be used.
Although this site will be small, you might want to put in a simple menu to navigate between the home page and the blog page. And if you keep this site you'll probably want to expand it in the future by adding further menu pages.
- Head over to the structure -> menu section of the admin section (you'll need the menu UI module enabled but this comes with the standard installation).
- Select
create new menu
and give it a title ofmain
. - Add some links using the form filling in the paths
/
&/blog
for our two pages and giving them menu titles. They won't need any child items for now. - We could embed this directly in our theme by adding the following tag to our
html.html
template:
{{{iris embed="menu" menu="main"}}}
- However, we can also use the menu block module and the Iris region system to add this menu to a region in the theme. Remember the regions we added in the
.iris.theme
file? Let's add them to our template. - Edit
html.html
and put in the following somewhere near the top. Before the[[[MAINCONTENT]]]
tag:
{{{iris embed="region" region="header"}}}
- Go to the
modules
admin page and enable the menu block module if it is not enabled already. - Now head back to the structure section of the admin system and create a menu block. Head to the blocks section and add a new block of type menu. Call it
main menu
and select the main menu. - Now in the structure -> region section add the
main menu
block to the header region. - Hit save and the menu should appear in your template allowing you to move between the two pages. Use CSS to style it or, if you want to overwrite the whole handlebars template (found in
/node_modules/irisjs/modules/core/menu/templates/menu.html
) create amenu.html
or amenu__main.html
file in your template.
Once the blog site has more blogs you're going to want to have an easy to access list of the latest ones. As this will be placed both on the main blog page and each individual blog page we can place it in a single sidebar template that will be visible on both templates. This can be done directly with templates but we're going to use the lists
module to show how it can be done through the user interface. We're going to use the direct in template version for comments later so you will learn both in this tutorial.
- Go to the modules page in the administration interface and enable the
lists
module. - Go to structure/blocks and create a new block of type
list of blog
. - Name it
blogs
and skip the query section (we want to show all blogs so you don't need to filter them). - Put in a limit and a sort if you want.
- The important part is the template. As the field description says you can use the
list
variable which is an array of the fetched entities. Put in a template such as:
<ul>
{{#each list}}
<li> <a href="{{this.path}}">{{this.title}}</a></li>
{{/each}}
</ul>
- Put this block in your sidebar in the regions page and then put the sidebar in your theme with:
{{{iris embed="region" region="sidebar"}}}
Tick the live update
box if you want the list to update without a page refresh (more on this later).
You should now have a list of blogs on your site that you can style however you want.
We're now going to allow comments on blogs and list them along with a comment form.
- Create a new entity type called
comment
. - Add a body
longtext
field and an authortextfield
(eventually you can allow comments from only logged in users and reference their user names but we're going to go for a simple text field for now). - Add an
entity reference
field calledparent
and in the settings select entity typeblog
andfield to search on
astitle
. - Make sure all these fields are available for anonymous users to view and edit.
- Also make sure the comment entity type is available for anonymous users to view via the users -> permissions page.
- While you're on the permissions page allow the
can create comment
permission for anonymous users. We'll need this later.
We're now going to use the entity embed system to show any comments for a blog post underneath it.
- In your
blog.html
template, put in the following snippet. We'll then move onto what each part means.
<section>
{{#iris embed="entity" entities='["comment"]' queries='[{"field":"parent.eid", "operator":"IS", "value":$current.eid}]' sort='{"eid":1}' as |comments|}}
<ul>
{{#each comments}}
<li>
{{{this.body}}}
<b>{{this.author}}</b>
</li>
{{/each}}
</ul>
{{/iris}}
</section>
Unlike the previous Iris embeds we've used, the entity
embed is a block embed. It starts with a {{#iris
and ends with a {{/iris}}
tag. Entity queries take the following parameters:
- Entities - An array of strings stating which entities you want to query for.
- Queries - An array of JSON objects with the following keys:
- Field - the field to check. Here we're checking the parent.eid (a subfield on the parent field that says which entity ID we're fetching. The entity reference field also has an
entityType
subfield) - Operator - is, includes, contains
- Value - The field to check the value against. Here we're using the current entity ID and preceding it with a dollar sign so that it gets loaded in as a variable from the current context.
- Field - the field to check. Here we're checking the parent.eid (a subfield on the parent field that says which entity ID we're fetching. The entity reference field also has an
- Sort - An object with a key of the field you want to sort by and either
1
for ascending or-1
for descending. We're sorting by ascending eids as we want the latest comment to appear at the bottom. - Limit - We can limit the results if required.
- Skip - We can skip a few results if needed. Not necessary here.
Add liveupdate=true
to the entity embed and in a new browser tab add a new comment through the administration interface (type the name of the blog you want to add it to in the autocomplete bit of the parent
field). It should automatically update on the list on the relevant blog page without you having to refresh the page. This will be handled by JavaScript automatically loaded into every visitor's page if you put the headTags tags in your header in the html.html
template.
To create a comment form you're going to need to create a custom Iris module. It's not much work and will be a good introduction to how to forms, hooks and module system works.
- Create a folder in your site folder called
modules
like you did withthemes
. - In it put a new folder called
comments
. - Create a file in this folder called
comments.iris.module
and put in the following information:
{
"name": "comments",
"dependencies": {
"entityReference": "1.0.0"
},
"description": "Adds a comment form for blog entities",
"weight": 2
}
The weight is given so that it loads after the entity reference module which it depends on.
- Create a file called
comments.js
in the same directory. This will be where your module's code will go. - Go to the administration interface and enable the
comments
module on themodules
page. - It won't do anything yet so let's go to the comments.js file and start creating the form.
(You can bundle in the comment entity type into this module so that it's automatically made available when the comments module is enabled. This isn't necessary here but if you want to you can copy the comment.json
from your configurations/entity
folder in your site folder into a /schema
subfolder in your module.)
Theme changes can be made without having to restart the system. Things like hooks and modules need to be initialised by the system so everytime you make a change to a module's code you'll need to restart the Iris system. This can be done through the restart
form in the administration system. Your session should persist between a restart.
You'll need some JavaScript knowledge for this bit. You should be able to get through without too much by copying and pasting but it helps to understand how functions and objects work.
The comments module we've created will automatically have been registered under the iris.modules.comments
object. We can now use this object to register a hook into the form system that responds when we request a comment form. Let's try this with the following code:
iris.modules.comments.registerHook("hook_form_render__comments", 0, function (thisHook, data) {
data.schema.author = {
"title": "author",
type: "text"
};
data.schema.body = {
"title": "comment",
"type": "textarea"
};
data.schema.eid = {
"type": "hidden",
default: thisHook.context.params.eid
};
thisHook.pass(data);
});
The parent hook is called hook_form_render
and in this case is being called for the specific form comments
. This hook will be called any time someone tries to view a form with the formID comments
.
The number that comes after the hookname is the weight of the hook. This is fine to set at 0 but you can set it higher to take place after another hook has run and overwrite the form (there aren't any other hooks for this comment form but there could be in the future). You can overwrite system forms in this way. All Iris hooks work like this allowing you to hook into any core funtionality and extend it.
Then we have a function which takes two parameters.
- thisHook - information about the hook that has been called such as who has called it (details of the client) and if it has any special parameters included in it (under
thisHook.context
). It also carries two special methodspass
andfail
which need to be run at the end of the hook to tell the system that the hook has finished. It is very important to always pass or fail a hook. - data - the form object being generated which this hook will extend.
The form system Iris uses is based on a library called JSONForm. It is best to read the documentation directly at JSONForm at https://github.com/joshfire/jsonform/wiki for information.
You can see we have extended the form with three fields, first an author text field and a textarea field for the comment body then a hidden field which contains the entity ID of the entity you're adding a comment to. This will be passed in as a parameter in the embed (note this being used in the default value).
We've then finished the hook off by passing it and sending through the data object we have added to.
Restart Iris using the administration system to get the form to initialise.
To embed the form, go to your blog.html
template and add in the snippet:
{{{iris embed="form" formID="comments" eid=current.eid}}}
The eid=current.eid
parameter will be passed into the form render hook to be used to reference the current blog entity.
If you refresh the page you should see your new form.
Feel free to submit it but it won't do anything. We need to build a submit handler.
Here's the code for the submit handler:
iris.modules.comments.registerHook("hook_form_submit__comments", 0, function (thisHook, data) {
var entity = {
author: thisHook.context.params.author,
body: thisHook.context.params.body,
entityType: "comment",
parent: {
"entityType": "comment",
eid: thisHook.context.params.eid
}
};
iris.invokeHook("hook_entity_create", thisHook.authPass, entity).then(function (entity) {
thisHook.pass(data);
});
});
It starts similarly to the render handler, this time calling the form's submit hook. Its thisHook object gets the form data in the thisHook.context.params
object. First we create an entity with it, filling out all the fields we created on the comment type. The entity reference field is a two part field so we need to fill in the entityType part as well as the entity ID we have been passed through.
Once our entity is created we invoke another hook inside the submit handler, hook_entity_create
.
The first parameter to the invokeHook
function is the name of the hook, the second is the authPass of who the hook will be called as (the person submitting the form in this case) and the final parameter we're using here is the entity object.
We want to wait until the entity has been saved into the database to finish the submit handler so we're finishing the hook by chaining onto the result of hook_entity_create
. To understand more about how this works, look up JavaScript Promises, this is an example of one.
We're done! Restart the server again to make those changes go live and try to submit the form. The comments should show up. If you've enabled liveupdating on the comment entity embed as in this tutorial new comment should show up for all users without them needing to refresh the page.
Now to send an email when a comment is received.
- Enable the
triggers
module. - Create a new trigger at config -> triggers
- Call the action
email comment
- Set the event type to
comment created
- Leave the conditions blank as we want to recieve emails for all comments, you can change this to something like only receiving emails for certain blog post comments or by certain authors later.
- Under actions select
email
- Fill in the information you want to send in the email.
- You can use tokens from the comment itself such as
[author]
and[body]
. These are listed in the conditions section of the form.New comment by [author]
could be your subject for example. - We're done. Add a new comment and the email should send.
The textfilters module allows you to strip out HTML and tags you don't want to appear in a textarea if they are submitted by a user. Filters can be useful in the case of comments by allowing bold and italic tags for instance but nothing else.
-
Go to config > content authoring > text filters and make a new text filter. Call it
safe comments
and add the tags you want to support in the form.b,i,strong,em
for example. Ignore the attributes section as you'll probably want to strip out all attributes from HTML tags. -
Save the filter and go to structure -> entities -> comment and the manage fields section of the comment entity type.
-
Edit the comment body field and select the text filter you have just created.
-
All comments should now be filtered before they are viewed. Edit the filter and it will update automatically.
Iris has been built with version control in mind so it's easy to export your configuration and import it onto a live server. To do so, head over to the config tab of the administration menu and export config. The config page will show you all the configuration files that differ between your live and staging configuration directories. If you hit export your configuration will be moved to the staging
folder in your site's folder. That can then be moved to your live server and imported where necessary.
If you don't want to use regions or simply want to split your template files into smaller, reusable chunks you can embed any template using the template
iris embed.
Such as:
{{{iris embed="template" template="footer__$current.eid"}}}
The same template lookup system works as with the normal file naming. Use __
to seperate words in a template that will be looked for. This embed will start looking for footer__1.html
for example followed by footer.html
if it doesn't find it.
The whole node.js req
request object is passed through to the template. This gives you access to everything including the page's query string, req.query
, the url itself req.url
, the authPass of the user visiting the site (userid etc) req.authPass
and more. Look at the node.js and Express documentation for more details.
Iris ships with the whole very powerful Assemble Handlebars helpers library. Visit https://github.com/assemble/handlebars-helpers for more information. You can also easily extend Iris Handlebars helpers and more with hooks such as hook_frontend_handlebars_extend
.