Note You can view the source files for this project in the intermine/intermine-apps-c repo.
The app will have the following functionalities:
- Work with cancer related publications from PubMed.
- Ask user for an input text and get back a list of publications.
- Click on any of the results to see a detailed view.
- Detailed search for publications like this one from the document.
- Autocomplete and provide suggestions for user's input.
Among the important libraries we will be using:
- canJS is a framework for client-side development handling routing, events etc.
- D3 is used to manipulate documents based on data.
- ElasticSearch is a search server with a RESTful web service peddling JSON documents.
- Foundation is a CSS framework of reusable UI components.
- Grunt to build/transpile our source files.
- jQuery is a DOM manipulation library (and more).
- Moment is a date library for parsing, manipulating and formatting dates.
- Mustache is a multi-platform templating language allowing us to embed dynamic objects in HTML.
- Stylus allows us to be more expressive and dynamic with CSS.
- Underscore is a utility toolbelt making actions such as iterating over items easier.
Warning Some of the code block examples on this page feature line numbers. Please view the page in a widescreen mode.
The first step will be to setup our directory structure.
Will be the directory where our final app package will live. We will develop in languages like Stylus or CoffeeScript and need a way to package all these resources into one whole directory. This is where all these files will live.
This directory will be automatically created and will contain libraries we have requested through the Bower system.
Is a directory where we can keep data files that we will load to ES later.
Contains an example of our app in use.
Source files that our code will consist of.
Will contain a listing of libraries we want to download using Bower.
Lists libraries we will need to compile and build our app.
Once Node is installed, edit the
package.json file like so:
This file tells Node which libraries will be used to build our app. These are not client-side libraries, but server-side if you will.
The top bit of the
devDependencies lists a bunch of Grunt and Bower related libraries while the bottom one (line 17 onward) lists some libraries used to load ES with data.
In order to install all of these, execute the following:
Now we want to fetch libraries that our app, when running, will depend on.
bower.json file like so:
The file has a bunch of key-value pairs.
Name of our application in the Bower ecosystem, required.
Version number in the Bower ecosystem, required.
Lists the actual libraries and their versions to fetch. You can populate this list by executing
$ bower install jquery --save for example. This will download the latest version of the
jquery component into the
bower_components/ directory. You can search for available components using
$ bower search jquery. To actually trigger a search, execute
$ bower install. The different libraries will be introduced as we code along.
Grunt is used to munge files together and execute commands on them. Create a file called
This file is written in CoffeeScript and lists the tasks to run when we want to build our app. From the top:
Take a Stylus file and turn it into CSS.
Take our vendor files (installed using Bower) and, together with our app, make them into a bundle. If someone else wants to use our app, they need our app and its deps too, so this one file will do it for them. Do the same to CSS too.
A task that copies fonts from FontAwesome into our build directory.
The same as
uglify, but for CSS
Lines 76 and 83 have two calls to
grunt.registerTask which bundle a bunch of tasks together. For example, running
$ grunt minify will run the
While developing, it is quite useful to watch the source files and re-run the build task:
This will run the default Grunt task every 2s.
ES will hold our index of publications, fetch it and then unpack it somewhere.
To start it:
Check that it is up by visiting port
9200. If you see a JSON message, it is up.
data/ dir in
elastic-med on GitHub, you will be able to see one way that documents can be indexed. In that example:
That will index (after a few seconds) 1000 cancer publications found in
convert.coffee file was used to convert source XML to JSON.
Check that documents got indexed by visiting the document URL in the browser.
You should get back a JSON document, provided you are using index
publication and you have a document under the id
One needs an access point where our app will get loaded with particular configuration. This is where the
example/index.html comes in:
This file does not do anything else other then load our built CSS and JS files (lines 7 and 9) and starts our app. In our example, we are pointing to a
build directory relative to the
example directory. So let’s make a symbolic link to the actual
Such links get preserved when version controlling using Git. We are linking to our bundled builds that contain vendor dependencies too.
Then we are waiting for the page to load and call our (future) app with some config.
em is being configured in the
Gruntfile.coffee file in the
As for the config:
Selector where our app should be displayed.
Points to the ES endpoint. By default it starts on port
Refers to the ES index we are using.
Refers to the type of ES documents we are storing in our index.
Is a default query we will want to show when our app loads.
window) object and implements a way of relating between different files.
We have asked to load an app in our
example/index.html page, now we are going to write the backing code.
apps-c task (in
Gruntfile.coffee) contains the following two options:
How do we call our app for CommonJS
Contains a path (an index) that will be called when we actually call the
We have specified that our app index lives in
src/app.coffee, so let's create this file:
Each module (file) in our app needs to export some functionality. When we call
require we will be getting this functionality.
We are going to be using canJS which consists of objects that can be observed. What this means is that when their values change, others listening to this changes will be notified. When we want to change their value, we call
attr function on them. One such example is on line 7 where we change the value of
client as passed in by the user from
Is a call to a future canControl component which will setup our routing. We need a way to change between an index page that does search and a detail page that shows a detail.
Actually tells canJS to start listening to changes in the browser address.
On line 14, we see an example of checking whether we are looking at the index page when the app loads. If so, we are changing a
current attribute on a (future) canMap component which will correspond to the query, meaning user query input. Our
example/index.html page contains an example query to use in this case.
Now we need to write the actual router component. It will be a type of canControl and lives in the
src/app.coffee file too. Since we do not want/need to export this functionality, it will be placed above the current
We are loading some components that we are using in this app into the memory and then rendering our app layout. This layout will setup the structure for our whole app.
Is a function that will be called when we are on the index page of the app. It renders the index page template.
Matches when we are looking at a detail of a document/publication. So, if someone manually types in the address
#!doc/438 or it changes as a result of user interaction, this function gets called. We are either retrieving the document from a results cache or we are explicitly calling for a document from ElasticSearch. Consider that when we search for documents, we get their content too so we do not need to fetch them again when looking at their detail. In contrast, someone could type in a random document address and we need to be ready for that. In either case we are calling the
fin function on line 19 to render the results.
Serves as a helper we have created that injects a template into the DOM and updates the page title.
When discussing the router, we talked about different page templates. Let us define them now. In
This is the index template with three custom tags corresponding to different components:
the search form
the results when our search is successful
Now for the template that gets rendered on a detail page, in
We see that
app-state is present, it will tell us when a doc is not found. If it is (we have a document
oid) we show the rest of the page.
Is the view of one document. We are passing extra parameters (options) into the context saying we don't want to link to the detail page (we are on detail page) but we want to show keywords (which will not be shown on the index results set).
Is a results set similar to
app-results which corresponds to a component that will automatically search for and display documents that are similar like this one.
This template will be rendered for the
app-search component defined on the index page. In
We are splitting the DOM into two parts. These parts have a
row class on them representing the grid of the Foundation framework.
The first part is split into two
columns, one for the input field and the other for a button triggering search.
We will want to get caret position from the input field. To do that, we are going to get all of the text from the input field up to the caret position and then copy it over to a div that has the same CSS styling as us, but is invisible. Then we are going to get the width of
The place where input goes. We can see Mustache syntax here that outputs the value of the current query.
Shows up when a list of suggestions has some items. Represents suggestions for the current word, hence the need to get the caret position. If some suggestions are "active" (we hover on them etc.) then we toggle their CSS class.
A query history. Only shows up when it has items in it.