Welcome to the Callimachus Developers Tutorial! You will soon know how to build Linked Data applications using Callimachus.

This tutorial walks you through the creation of an address book application on the Callimachus platform. By the time you complete it you will have a completely functional Linked Data address book.

You will need to have an account on a Callimachus Open Source instance. Your account will need to have appropriate permissions to create folders and other content. To get an account:

Download Callimachus Open Source from CallimachusProject.org and a Callimachus Archive (CAR) file of the developers tutorial. Set up an initial account in accordance with the documentation. Install the tutorial materials by making a new Callimachus folder and selecting "Import folder contents" from the main menu. Upload the tutorial CAR file at the prompt to install the tutorial materials.

You might also want to review the developer prerequisites in the Callimachus documentation. The prerequisites provide a handy summary of technologies that Callimachus supports, including those we will use in this tutorial.

Chapter 1. Callimachus - The Basics

This chapter will lead you through the creation of a basic Linked Data address book. By the time you have completed it, you will understand Callimachus templates and how they are used to create, edit and view RDF data via HTML pages. Callimachus templates are used to create new address book entries that represent people, to display them to the Web and to edit them after they have been created.

Next, you will learn how to query your address book entries using the SPARQL query language, how to save a SPARQL query in Callimachus and how to display the results via a Callimachus template. That process is used to create the address book itself.

Finally, you will expand your basic address book by adding fields to the address book entries, including photos and chooseable lists of concepts. Your address book will start to look like a useful application by the time you have completed this chapter.

1.1. File Structure

This tutorial contains two copies of the address book application.  

  • The version contained in the folder finished app is the entire completed application.  You may use it to see what the completed application looks like or to compare what you have done to the way that we built it.
  • The version contained in the folder initial app is where you will build your own address book application as you progress through the tutorial.  If you follow the directions exactly, you will end up with the same application as the one in the finished app folder.  Naturally, feel free to express yourself!  You can build out the application any way you want to.

You might want to recover the state of the initial app folder at some time in the future, perhaps to allow someone else to work through the tutorial.  You can do that exporting the state of the initial app folder as a Callimachus ARchive (CAR) file via the main menu.  Then you can later create a new folder and import the saved CAR file into it for others to use.  The file paths are relative so you can avoid stepping on each other.

The image below shows you the file structure at the top level of the tutorial files.

Tutorial file structure

The finished app and initial app folders have the same subfolders within them.  Each set of files is contained in its own folder to make life a little bit easier.

Tutorial application file structure

1.2. Classes and Templates

1.2.1. Classes, Instances and What They are Good For

A "class" is "a set or category of things having some property or attribute in common and differentiated from others by kind, type, or quality".  So, a class of cars includes my car and your car.  A class of people includes you and me.  A class of software includes Callimachus, Microsoft Sharepoint, Oracle's database and the game Angry Birds.

Callimachus allows you to define classes of information objects using the Web Ontology Language (OWL).  OWL classes may be defined in RDF data or you can make them yourself using the Callimachus user interface.  See the documentation for creating a new class if you want to make your own.

Let's start by exploring the classes and templates for the finished address book application.  Later, we will walk through the creation of them from scratch.

This tutorial already supplies a class that describes a person (see the Person class in the tutorial's initial app/classes folder or the Person class in the finished app/classes folder) so you don't need to create your own class right now.  We will explore what that class is, how to make instances of it (that represent real people) and how to use it to generate HTML pages to create, edit and view them.

Start by navigating to the finished app's Person class and selecting "Create a new Person" from the main menu.  Note that the "Create a new Person" menu item only appears when you are looking at the Person class.  The main menu is contextual and shows you relevant information depending on what you are currently doing.

Create a new Person

How does Callimachus know how to create an instance of a Person class?  The secret is that each class has up to three templates associated with it:

  • Create templates create a new instance of an OWL class
  • Edit templates are used to edit instances of an OWL class
  • View templates are used to view instances of an OWL class (or other things, like a SPARQL named query or XProc pipeline - more on that later)

The "Create a new Person" menu item tells Callimachus to serve the create template for the Person class.  The create template is just an HTML form that, when submitted, tells Callimachus to create a new instance of that class.  Since a create template is just XHTML, you can make it look like anything you want, can use CSS and JavaScript and can cause it to create instances of very complex classes.  RDFa markup is used to provide the hints that Callimachus needs to create compliant RDF data when you submit the form.

A new person

Fill out the create template with some information about yourself or another person.  It would be good if you included a profile picture (see the lower right of the screen) because it will look impressive later :)  Select the "Create" button when you are finished.

You will be asked to save the data for the new person via a dialog box.  Navigate to the folder Tutorial/finished-app/people/ and select the "Save" button as shown below.  You should try to save all your instances in a folder to keep your information organized, but should also know that Callimachus doesn't care where your data is stored.  Your people folder will probably only contain one example entry for "Joe Bloggs" the first time you use it unless you are sharing your Callimachus instance with others.

Save a person

Callimachus will save your new Person instance and redirect you to the view template for a Person, which is filled in with that person's information.  Whew!  In other words, Callimachus uses the view template for the Person class to make a Web page for each instance of the class. These pages are created dynamically when they are requested, so you won't be able to find them anywhere in the file system. The view for a Person instance is shown below.

A person view

You can change the view template to control exactly how you want a Person instance to look, just like you can change the create and edit templates to collect the data you want.  We generally make the create and edit templates look very similar to each other.

Let's look at the data that was created.  Select the "Describe" tab to the upper right.  You will see a stylized presentation of the RDF data that you just made about Joe Bloggs:

Person describe

You can use the hyperlinks in the upper right to download the raw data about your new Person instance if you like.

Instances of a class may be seen via the class view page.  You can use Callimachus to create your own custom views of any data you have!  The default instance list is accessible from the "Person resources" menu item in the main menu when you are on the Person class page:

Person resources menu item

Person instances

Review of the tutorial steps for this section:

  1. Go to the finished app's Person class
  2. Create an instance of the Person class
  3. View the instance of the Person class
  4. Review the data in the Describe tab
  5. Find any other instances of the Person class

1.2.2. How Templates Work: The Create Template

Now let's look at how the templates actually work.  We will explore the simplest possible create, view and edit templates used for the Person class in the initial app folder.

Start by opening the Person class in the initial app folder and looking at the templates associated with that class.

Edit the create template for the Person class in initial app folder to see its contents.

The create template is located in the initial app/templates/ folder.  You can edit the create template by selecting the file and then its Edit tab (or by selecting this link to the same URL).

The create template's <form> tag needs to create a Person resource on the Callimachus server.  The tag definition is a bit complicated and uses some built-in JavaScript functions.  You will not need to understand this right now because it is always the same or very similar.  For now, you can always include the same syntax when you make create templates.  Look for the <form> tag (line 15 in the template):


<form role="form" method="POST" action="" enctype="application/rdf+xml" typeof="" onsubmit="return calli.saveResourceAs(event,calli.slugify($('#label').val()))">

Edit the create template and look for the following lines (lines 18-21 in the template):


<div class="form-group">
     <label for="label">Name (Required)</label>
     <input type="text" class="form-control" id="label" value="{rdfs:label}" required="required" autofocus="autofocus" />
</div>

These lines provide the label and text input box for the new Person's name.  The <label> element provides the label (just marked as "Label" in the default create template) and the following <input> element provides the text field.  Note that the value for the <input> element is set to the term {rdfs:label}.  This means that the value that you type in the text field will be associated with the URI of the newly created Person in an RDF triple, like this:


<joe+bloggs> rdfs:label "Joe Bloggs"

All of the fields in the create template work in a similar way.  You can make any statement about the resource you are creating by providing the appropriate HTML form element and assigning its value to the RDF predicate that you wish to use for that statement.

Naturally, some parts of templates are a bit harder such as select widgets (pull downs), uploading images, etc.  Those will be covered later.  If you can't wait, please see the template language documentation.

The last thing you should notice in the create template is that the form needs to be submitted to Callimachus, so the form must include a Create button.  You could call it "Save" or any other name if you like.  See line 27 in the template.


<button type="submit" class="btn btn-success">Create</button>

Review of the tutorial steps for this section:

  1. Find the create template for the Person class
  2. Understand the create template's HTML and RDFa markup
  3. Map the markup to the generated page

1.2.3. How Templates Work: The View Template

A view template defines how an instance of a class is to be shown to the public.  It is the primary way that end users will see Callimachus resources.  A view template presents specific data as a friendly HTML page.

The view template for the Person class is, like all Callimachus templates, just an XHTML page with some RDFa markup.  The means that it can be styled and given interaction using CSS and JavaScript as you see fit.  The default view template doesn't use any CSS or JavaScript but is styled using the site-wide Callimachus layout file.  We will add more functionality and styling to the view template later.

A view template is easier to write than a create because it is just an HTML page, not an HTML form.  The magic in a view template is the resource attribute on the <body> tag:


<body resource="?this">

That attribute tells Callimachus to replace ?this with the URI of the resource that was requested.  So, if you ask Callimachus for the URI for the Person instance called <joe+bloggs>, ?this becomes the fully qualified URI for Joe.  That might be something like http://localhost:8080/Tutorial/initial-app/people/joe+bloggs (but your path might vary).  All the other information on the view template will be found by locating that URI in Callimachus' RDF database and "walking the graph" from there.  A create template doesn't use this attribute because it isn't created until the create template is submitted.

All of the information in the Person view template is written in the HTML in a manner similar to the create template's syntax, but without the form elements.  For example, the Person's name is shown with (line 16 in the template):


<h1 property="rdfs:label" />

This could also be written as:


<h1>{rdfs:label}</h1>

The two syntax forms yield the same result.  The curly-bracket notation is not legal RDFa but is provided as a handy shortcut form.  It is particularly useful when you want an HTML element to contain information from more than one RDF property and/or to mix RDF and defined text, like this:


<h1>Person's name: {rdfs:label}</h1>

If you understand the RDF data that was created for a Person, it is easy to write a view template!  If you don't know the data well, the Describe tab can show it to you.

Review of the tutorial steps for this section:

  1. Edit the Person class
  2. Understand the view template's HTML and RDFa markup

1.2.4. How Templates Work: The Edit Template

The Edit tab on any resource in Callimachus allows you to edit that resource.  There is only one trick:  You need to have defined an edit template for that resource type.

Fortunately, an edit template is a lot like a create template.  They are nearly identical except for three small differences.  They are so small that we generally cut-paste-and-modify a create template to make an edit template.

1.  An edit template needs to refer to a resource that already has a URI.  So, you will need to add a resource attribute to the <body> tag, like you did for a view template:


<body resource="?this">

2.  Next, an edit template's form generates a SPARQL UPDATE request to the server instead of a TODO.  So, the <form> tag needs to look like this:


<form role="form" method="POST" action="" enctype="application/sparql-update" resource="?this">

3.  Finally, an edit template may be used to delete a resource.  The edit operation can also be canceled (which has no effect other than to redirect the user back to the view template).  These features are provided by adding some additional buttons to the bottom of the edit template.  The buttons called built-in JavaScript functions:


<div class="row">
    <div class="col-sm-12 form-group">
        <button type="submit" class="btn btn-primary">Save</button>
        <button type="button" onclick="window.location.replace('?view')" class="btn btn-default">Cancel</button>
        <button type="button" onclick="calli.deleteResource(event)" class="btn btn-danger">Delete</button>
    </div>
</div>

You have complete creative control over the contents of an edit template.  You can protect some fields from being edited by simply not exposing them on the form!

Try going to an instance of the Person class and editing its values.  You are changing values in the RDF database by submitting an HTML form. This ability to round-trip RDF data is one of Callimachus' most useful features.  It is made more powerful by allowing developers complete control over which fields get updated and how the pages appear. 

Review of the tutorial steps for this section:

  1. Create an edit template for the Person class
  2. Understand the edit template's HTML and RDFa markup, including how it differs from a create template
  3. Choose which fields to expose for editing
  4. Test by editing an instance of the Person class

1.2.5. Wrap-Up & Review

There are 3 kinds of templates - Create, View and Edit. They're all associated with a Callimachus class and define how class instances are created, viewed and edited.

1.3. Named SPARQL Queries

Callimachus allows you to save a SPARQL query so that you can execute it every time to resolve its URL.  We call this a "named query".

Named queries have two features that make them quite useful:  They can accept parameters and their results can be rendered to HTML pages using Callimachus view templates.  The full documentation is available.

1.3.1. Writing a Named Query

The tutorial comes with two versions of a named query that returns all instances of the class Person.  Look in the folder /initial-app/queries to see the query.  Look first at the query called simple-person-listing.rq.  The query definition is:


#
# @Cache-Control: 
# @infer true
# @view
#
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?person WHERE {
    ?person a <../classes/Person>
} ORDER BY ?person

You are presented with a page that shows the results of the query whenever you look at a named query in a browser.  Callimachus provides that functionality by default using a Callimachus view template.  Callimachus is mostly built using Callimachus. 

If you want the raw SPARQL results (e.g. to use from JavaScript or some other code external to Callimachus), you can resolve the query's URL with the query string "?results" (instead of "?view").  You can try that for the simple-person-listing.rq query by selecting this link.  The results of the query should be downloaded by your browser.  They are in SPARQL's XML results format.

Selecting an RDF Query from a Folder's Create menuYou can create your own named queries by selecting the menu option "RDF query" from the Create menu on any Callimachus folder.  Note that the .rq file extension is commonly used for SPARQL queries.  You should use that extension when creating your own queries.

1.3.2. Displaying Named Query Results using a View Template

You can also customize the display of named query results by assigning a view template to the query.  Query results are then rendered by your view template instead of the default template.  Two steps are necessary to do this:

  1. Create a view template to render the results of your query
  2. Add a comment (technically, a pragma) to your named query listing the template URL

That's it!  It is very easy to use view templates with named queries.

The initial app folder contains a simple view template to render the results of the Person listing query.  Look in the initial app/templates folder for the template person-list-view.xhtml.  That 

  1. Create simple address book listing page
  2. Use the variable returned in your named query to set the scope
  3. Write this template the same way you would the view template for a Callimachus class

1.3.3. Associating a Named Query with a View Template

  1. Click the Edit tab of the Named Query you created earlier.
  2. Change the @view pragma at the top of the query to point to the location of the view template you just created. 
  3. When you click Save you will be redirected to the View template of the Named Query. Notice now, however, the View template of the Named Query renders the results in the context of the file specified in the @view pragma (../templates/person-listing-view.xhmtl), rather than the default table layout.

# @Cache-Control: 
# @infer true
# @view ../templates/person-listing-view.xhtml
#
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?person WHERE {
    ?person a <../classes/Person>
} ORDER BY ?person
Default Query View Named Query with View Template

1.3.4. Wrap Up & Review

Named Query view templates are very similar to view templates for Callimachus classes. The main difference is that instead of using the implict ?this to set the context for the template, you use ?{variable-returned} to connect the template to the query. From this point you can operate using the same syntax and structure, expanding as necessary. These can also be used with parameters as any normal named query would be.

1.4. Revisiting the Person Create Template

This section will lead you through the migration of the address book application from the basic one in the initial app folder to the complete one in the finished app folder.

We will need to do some data modeling in order to update the address book application.  You will be faced with new RDF terms such as vcard:locality to identify a person's city and foaf:phone to identify their phone number.  These terms may look strange at first, but they are very useful because they constitute a social agreement that exists across the Web.  The terms expand to full URIs (e.g. foaf: expands to http://xmlns.com/foaf/0.1/) so you can look them up in their specifications.  Try it :)

Callimachus keeps track of the vocabularies that are used in any RDF data that is uploaded to it.  You can find the list of stored vocabularies in /callimachus/profile?view.

Of course, you don't need to limit yourself to these terms; you can make up your own if you want to.  However, using common vocabularies is a way to help Linked Data applications interlink more easily.  It also helps people new to your application to understand your intent.

If you haven't already, this would be a great time to read the About RDF section in the Callimachus documentation.  That section provides a list of some commonly used RDF vocabularies and discusses what they are good for.  Another good source of commonly used vocabularies is chapter 2 in the book Linked Data: Structured Data on the Web.  That chapter also covers creating your own vocabularies.

1.4.1. Adding More Fields

We will start by updating the Person class's create template.  This will allow us to collect more information about a person when one is created.

The first step is to change the labels for the person's name and description to be more meaningful.  We will add some layout and a birthday field while we are at it.  The table below shows the difference:

First changes to the Person create template
initial app finished app
Initial app - Person create template Finished application - Person create template (partial)

The HTML changes look like this:

initial app finished app

<fieldset class="col-sm-4">
  <div class="form-group">
      <label for="label">Label</label>
      <input type="text" class="form-control" id="label" value="{rdfs:label}" required="required" autofocus="autofocus" />
  </div>
  <div class="form-group">
      <label for="comment">Comment</label>
      <textarea id="comment" class="form-control">{rdfs:comment}</textarea>
  </div>
...
</fieldset>

<div class="row">
  <fieldset class="col-sm-4">
    <legend>Personal</legend>
    <div class="form-group">
      <label for="label">Name (Required)</label>
      <input type="text" class="form-control" id="label" value="{rdfs:label}" required="required" autofocus="autofocus" />
    </div>
    <div class="form-group">
      <label for="comment">Description</label>
      <textarea id="comment" class="form-control">{rdfs:comment}</textarea>
    </div>
    <div class="form-group">
      <label for="bday">Birthday</label>
      <input type="date" class="form-control" id="bday" value="{foaf:birthday}" />
    </div>
  </fieldset>
...
</div>

There is not much magic there. Only three things changed:

  • A <legend> was added to the HTML <fieldset>
  • Two labels were changed:  "Label"->"Name (Required)" and "Comment"->"Description"
  • A birthday field was added.  The birthday field uses the foaf:birthday RDF term.

Most of the new Contact and Social sections in the create template are added in a similar way.  The RDF terms used were:

Field RDF term
Phone foaf:phone
Email foaf:mbox
Street address vcard:street-address
City vcard:locality
State/Region vcard:region
Postal code vcard:postal-code
Country vcard:country-name
Callimachus account foaf:account
FOAF profile page (a URL) foaf:homepage

The most complicated part of the create template is associated with the Person's image:


<div class="form-group">
     <div id="thumbnail" dropzone="link string:text/uri-list" ondrop="return calli.insertResource(event)">
         <label for="thumbnail">Profile Picture <a href="../images/?view" title="Select or upload profile picture" onclick="return calli.selectResource(event)" class="glyphicon glyphicon-picture" /></label>
         <div rel="foaf:depiction">
             <span resource="?thumbnail" typeof="foaf:Image">
                 <img src="{?thumbnail}" style="max-width:100%" />
                 <a href="{?thumbnail}" title="Remove" onclick="return calli.removeResource(event)" class="glyphicon glyphicon-remove" />
             </span>
         </div>
     </div>
</div>

There are a number of things going on in this block of code, but you don't need to master them all now.  For now, notice that there is a JavaScript onclick() handler for the image upload link.  The JavaScript for that is provided by Callimachus, so this is just a pattern you can reuse.  There is another handler that allows you to remove an image after it is added.

1.4.2. Creating Concepts 

There is only one part of the finished app's create template that we haven't covered yet:  The select (pulldown) menu indicating a person's online status.  That select widget is dynamically populated with a list of items.  We commonly create lists of items in Callimachus using concepts.

Formally, concepts in Callimachus are SKOS Concepts.  You don't need to know that for the purposes of this tutorial, but you might want to know it later.

To create the concepts you will need for the status select widget:

  1. Go to the initial app directory.
  2. Create a folder called "concepts" with a subfolder called "status" using the Create menu on the folder view. The status folder will contain all concepts related to a person's status, or availability.
  3. Create three separate concepts within the status folder (you will only need to fill out the "Label" field in the New Concept form, although you can experiment with the others):
    1. Available
    2. Busy
    3. Away
  4. Note the file path in which these concepts are saved as they will be needed in the next step.

Remember that you can always look in the finished app folder tree to see what we intended if you get lost.

1.4.3. Linking Concepts to Templates via HTML Form Controls

The status component defines a directly connected property to the new Person (foaf:status) and a nested property (calli:hasComponent) that must come from a pre-defined list of values.  The list of acceptable values in located in the app/concepts/status/ folder.  Each acceptable status is a SKOS Concept created in Callimachus using the Create menu option called Concept.  Try making your own status types by adding new ones to that folder!  They will show up in the create template's status pull down menu.

Now that the concepts have been created they can be included in create and edit templates in order to create an association between an instance of the Person class and the concepts. To create this connection:

  1. Insert a <select> element into both the create and edit template of the Person class. Make sure to include the appropriate RDFa as detailed below in order to establish the relationship.
HTML Code HTML Rendered

<div class="form-group">
    <label for="status">Status</label>
    <select id="status" rel="foaf:status" class="form-control">
        <option selected="selected" about="?status" rev="calli:hasComponent" 
                resource="../concepts/status/" property="skos:prefLabel" />
    </select>
</div>

For more information see the full documentation on selection patterns

1.4.4. Wrap Up & Review

Whether you're adding a simple text field or or a custom controlled vocbulary selection the process is the same. The RDFa must create the appropriate relationship between the two resources via particular markup. It is important to note here that this markup can live in either the create template, the edit template, or both. Some workflows may call for the ability to create a piece of data the first time that is no longer editable, or for more options to be available only after a resource is first created. This is easily achieved with the separation of the create and edit templates. In the same vein, not all data has to be surfaced in the view template. Some data can be used for other queries or analytics and never shown to the end user.

1.5. Revisiting the Person View Template

Now we need to actually use all the data that was made with the create template.  We want to turn our boring view template into a cool-looking Web page.  The following table shows the transformation:

initial app finished app
Initial app - person view Finished app - person view

This process isn't nearly as hard as it might look.  You already have the list of RDF terms that you used in the create template, so you just need to fill them into the HTML of the view template.  The rest is all CSS and Callimachus even helps you with that by including helpful libraries such as Bootstrap and Font Awesome.

If you were developing a Callimachus application from scratch, you might want to look at the RDF generated for a Person instance when developing a view template.  This can be done by selecting the "Describe" tab on an instance.  This tutorial comes with a sample Person called Joe Bloggs in the finished app: The Describe tab view is available here. You can use that information to determine which RDF terms are available for your use.

We can get a good idea of what information is exposed in the finished app's view template by looking at the view of the template itself.  In Callimachus, even templates are rendered using templates.

Finished app - person view template

The photo looks like a broken image because the src attribute of the <img> tag has no value yet.  However, you can see at a glance how most of the layout works.  Font Awesome icons are used to decorate several of the items.  Some items are hyperlinked and will need to have their href attributes completed.

The status indication in the upper right displays the skos:prefLabel term of the chosen concept.  More on this in a while.

Let's start with an easy section: The name, country and description located under the photo.  The before and after views are shown in the table below, along with the HTML to create them:

initial app finished app

<div class="container">
  <hgroup class="page-header">
    <h1 property="rdfs:label" />
  </hgroup>
  <pre class="wiki" property="rdfs:comment" />
</div>

<div class="container">
  <div class="row">
    <div class="col-sm-6 col-sm-offset-3 text-center">
      <h1>{rdfs:label} <small rel="vcard:hasAddress" resource="?addressTitle">{vcard:country-name}</small></h1>
      <p class="lead">{rdfs:comment}</p>
      <hr/>
    </div>
  </div>
...
</div>
Initial app - person view

Finished app - person name view

Impressive difference, right?  The important bit is to see how two items were put into the <h1> element. The rdfs:label containing the person's name was referenced using an RDFa attribute in the initial app version and switched to using the Callimachus template language's curly bracket syntax in the finished app version.  Also, the country name was added to the <h1> element by using the rel/resource pattern.  The rel ("relationship") attribute walks the graph from the person's URI to their address by following the vcard:hasAddress term.  The address URI is put into the variable called ?addressTitle in case you want to use it in a nested HTML tag such as a <span> or <div>.  When the rel/resource pattern is found, the graph context shifts so that any nested elements now refer to the URI of the address.  So, the vcard:country-name is found relative to the address URI, not the URI of the person.  We can "walk the graph" in the Callimachus template language by using nested <span> or <div> tags to refer to one element after another until we reach the items we want.  The following figure illustrates the process graphically:

Walking the graph

Adding the image is as easy as referring to it and using some Bootstrap CSS:


<div class="col-sm-4 col-sm-offset-4 text-center">
  <img src="{foaf:depiction}" class="img-circle" />
</div>

Now that you know how to walk the graph, getting from the person's URI to the concept that is used for their status and then finding the concept's label shouldn't be hard.  If it is confusing, just go look at the RDF in the Describe tab and follow it through.


<div class="col-sm-1">
  <p class="lead">Currently <span id="status" rel="foaf:status" resource="?status" class="label label-info">{skos:prefLabel}</span></p>
</div>

The color of the status indication is changed with a bit of JavaScript at the top of the template that changes CSS classes:


<script>
    // <![CDATA[
        $(document).ready(function() {
            // Change Bootstrap label based on status
            if ($('#status').text() == "Available") {
                $("#status").attr('class', 'label label-success');
            } else if ($('#status').text() == "Busy") {
                $("#status").attr('class', 'label label-danger');
            } else if ($('#status').text() == "Away") {
                $("#status").attr('class', 'label label-warning');
            }
            
            var profile = $("#foaf-profile").text();
            
        });
    // ]]>
</script>

The items that use Font Awesome icons are easy to mark up.  Just look through the Font Awesome icon list until you find an icon you like and then use the matching class name.  The glyphicon-calendar class is used to show an calendar icon.  Be sure to include the generic glyphicon class, too:


<span class="glyphicon glyphicon-calendar" /> <time class="date" property="foaf:birthday" />

You should be able to complete the view template by using the patterns already described.  If you have any trouble, don't forget to look at the finished app view template.

1.6. Revisiting the Person Edit Template

Don't forget to update your edit template so you can edit all the new fields.  You can either:

  • Copy and paste your create template and make the three modifications needed, as described in section 1.2.4. How Templates Work: The Edit Template; or
  • Copy and paste the sections from your create template that aren't different.  Just avoid the <body> and <form> tags and the buttons at the bottom.

You might wonder why we don't just automate this step.  The reason is that many real-world use cases pull data from read-only sources.  End users aren't always allowed or encouraged to edit them.  Callimachus attempts to provide ultimate flexibility in which fields you can create, view and edit.  The choice is yours.

1.7. Revisiting the Query View Template

There is only one more thing to do to get a fully functional address book. We would like to change the simplistic address book view:

Initial app - query view

To this more useful view that includes more fields and a search capability:

Finished app - query view

You might think that we need to change the named query that drives this page, but we don't.  You would need to do that if you were using a relational database and a traditional development environment, but that's not how Callimachus works.  The query looks like this:


#
# @Cache-Control: 
# @infer true
# @view ../templates/person-listing-view.xhtml
#
PREFIX xsd: <http://www.w3.org/2001/XMLSchema#>
PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#>
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>

SELECT ?person WHERE {
    ?person a <../classes/Person>
} ORDER BY ?person

Note that the query only returns a single bound variable called ?person.  That variable will be bound for every instance of the Person class in the database, so we will get some number of them.  The finished app people folder has three of them.  Those variables are filled into the query's associated view template one at a time to become bound to the ?this variable (referenced in the body tag).  From there, the template language "walks the graph" to find the data it needs.  So, you can safely add anything to the view template you like, as long as there is a path in the graph from the ?this variable to what you need.

Do you remember how to use the rel/resource pattern as described in section 1.5. Revisiting the Person View Template?  You might be interested to know that there is an equivalent to rel called rev that walks a relationship backward.  That can help you find data when the arrows are pointing the wrong way.  The documentation has more information on the use of rev.

So all we need to add to the query's view template are the calls to the data:


<table class="table table-striped table-hover lead">
  <thead>
    <th>Name</th>
    <th>Email</th>
    <th>Phone</th>
    <th>Status</th>
  </thead>
  <tbody>
    <tr resource="?person"> 
      <td><a href="?person">{rdfs:label}</a></td>
      <td><a href="mailto:{foaf:mbox}">{foaf:mbox}</a></td>
      <td><a class="phone" href="tel:{foaf:phone}">{foaf:phone}</a></td>
      <td><span rel="foaf:status">{skos:prefLabel}</span></td>
    </tr>
  </tbody>
</table>

Look carefully at how the hyperlinks were made.  The name of a person was linked to the URI of its instance (?person).  We can do that because those URIs are resolvable in Callimachus (which makes them into URLs - they are "locators").  The string pointed to by the foaf:mbox predicate is an email address, and that can be made into a URL by prepending it with "mailto:". Similarly, the telephone number in foaf:phone can be turned into a URL by prepending it with "tel:".  Naturally, the phone number cannot have any white space in it when it is entered!  If you want to check the formats of things like a phone number or email address, you can do that using JavaScript on the create and edit templates.

The last thing we are going to do on this page is to add a basic search capability.  That is covered in the next chapter in section 2.1. SPARQL Endpoint.

1.7.1. Wrap up & Review

What additional changes could you think to make to the query view template?  For example, it might be nice to color-code the status indication on this view.  You already have the JavaScript to do something very similar on the Person view template.


Chapter 2. Callimachus - Getting Fancy

You already have a Linked Data address book but we can make it better! This chapter will show you how to call back to Callimachus from your Web pages by using the SPARQL query language from JavaScript. You will also learn how to use the XProc pipelining language to perform complicated operations such as including content from remote data sources. By the time you are finished, your address book will look rather impressive.

Lastly, you will learn how to save your work by creating a Callimachus ARchive (CAR) file that contains both your application and any data you have created. You can use CAR files to import your application into other Callimachus instances.

2.1. SPARQL Endpoint

The address book will scale better if there is a search capability.  This is a practical necessity if there are hundreds or thousands of entries. That requires some JavaScript to call back to the Callimachus server to perform the search.  You could do that with a named query that takes a parameter, or you could issue against the SPARQL endpoint directly. We will show you how to do the latter since you already know how to set up a named query.

2.1.1. Modify the Named Query View Template

  1. Create an <input type="text"> tag somewhere on the view template.
HTML Code HTML Rendered

<form id="search" class="input-group">
    <input id="searchInput" type="text" class="form-control" 
        placeholder="Search..." />
    <span class="input-group-btn">
        <button type="submit" class="btn btn-default">
            <span class="glyphicon glyphicon-search" />
        </button>
    </span>
</form>

2.1.2. Write JavaScript Against SPARQL Endpoint

Now that there is an HTML element that JavaScript can be bound to, we can write the JavaScript that uses that input and feeds it into the SPARQL query.

Step Instructions
1.

Write a JavaScript function that retrieves and stores the value of the <input> element when a user submits the form. 


$("#search").bind("submit", function(event) {
        event.preventDefault();
        var term = $("#searchInput").val();
});
2.

Write a SPARQL query to return only instances of the Person class that contain the string searched by the user. It's often easiest to test these against the SPARQL endpoint directly before converting it into JavaScript.


SELECT ?name WHERE {
  ?person a <{your-path-to-person-class}/Person>
    ; rdfs:label ?name .
  FILTER regex(?name, '.*{your-search-term}.*', 'i')
}
3.

Encode the query as a string in JavaScript and pass in the value gathered in step 1 to replace {your-search-term} in step 2.


var query = "SELECT ?name WHERE {" +
    "?person a <{your-path-to-person-class}/Person>" +
    " ; rdfs:label ?name ." +
    "FILTER regex(?name, '.*" + term + ".*', 'i')" +
    "} LIMIT 10";
4.

Execute the query via an AJAX call to the SPARQL endpoint for your local Callimachus instance.


$.ajax({
    type: 'GET',
    url: '/sparql?query=' + encodeURIComponent(query),
    headers: {
        Accept: 'application/sparql-results+xml'
    },
    success: function(data, textStatus, jqXHR) {
        console.log(data);
    }
});

Now that we've got all the pieces we can put it together into a single cohesive JavaScript code block.


$("#search").bind("submit", function(event) {
   event.preventDefault();
   var term = $("#searchInput").val();
   var query = "SELECT ?name WHERE {" +
        "?person a </Development/Projects/Tutorial/finished-app/classes/Person>" +
        " ; rdfs:label ?name ." +
        "FILTER regex(?name, '.*" + term + ".*', 'i')" + 
        "} LIMIT 10";
        
    $.ajax({
        type: 'GET',
        url: '/sparql?query=' + encodeURIComponent(query),
        headers: {
            Accept: 'application/sparql-results+xml'
        },
        success: function(data, textStatus, jqXHR) {
           console.log(data);
        }
    });
});

2.1.3. Filter View Template Based on Results

Now we have data being returned based on a user's input, but all we're doing is logging it to the console. Not very useful! Now it's time to write the JavaScript (via jQuery) that will actually take action based on those results and execute our search filter.

Step Instructions
1.

Store the data you will be using for comparison in an array for future use. In this case we'll be using the person's name to filter. You could easily use another field if you altered the query, or use multiple fields to make a more robust searching solution.


var people = [];
$(data).find("literal").each(function() {
    people.push($(this).text());  
});
2.

Now you want to loop through the existing people in your address book and remove any rows that contains a person who is not present in your search results. jQuery makes this easy with :selectors, $.each(), and other similar functions.


$("tbody > tr > td:first-child").each(function() {
    var name = $(this).find("a").text();
    if ($.inArray(name, people) == -1) {
        $(this).closest('tr').hide();
    }
});

We can plug that into the function we created earlier to get our final JavaScript function which looks like this:


$("#search").bind("submit", function(event) {
	event.preventDefault();
	var term = $("#searchInput").val();
	var query = "SELECT ?name WHERE {" +
		"?person a </Development/Projects/Tutorial/finished-app/classes/Person>" +
		" ; rdfs:label ?name ." +
		"FILTER regex(?name, '.*" + term + ".*', 'i')" + 
		"} LIMIT 10";
	
	$.ajax({
		type: 'GET',
		url: '/sparql?query=' + encodeURIComponent(query),
		headers: {
			Accept: 'application/sparql-results+xml'
		},
		success: function(data, textStatus, jqXHR) {
			var people = [];
			$(data).find("literal").each(function() {
				people.push($(this).text());  
			});
			
			$("tbody > tr > td:first-child").each(function() {
				var name = $(this).find("a").text();
				if ($.inArray(name, people) == -1) {
					$(this).closest('tr').hide();
				}
			});
		}
	});
});

Now when a user types something into the search box and submits the form, our JavaScript retrieves that value, uses it to search through Callimachus, parses the results, and filters the table on our page based on those results. Pretty neat, huh? There's a few more things you'll notice on our page that make the user experience a bit more pleasant. For example, users need to be able to revert to the entire address book so they can keep searching. Here's how we decided to accomplish this:

HTML Code JavaScript Code HMTL Rendered

<span id="reset" class="close pull-right">Reset</span>

function reset() {
    $('tbody tr').each(function() {
        $(this).show();
    });
    $("#searchInput").val('');
}

$("#reset").click(reset);

$("#searchInput").keyup(function() {
    if ($(this).val() === "") {
        reset();
    } 
});

All of the code related to search has been spruced up by placing it inside a Bootstrap 3 Panel. This is by no means tied to the functionality and could easily be changed to fit whatever design or layout was desired.

2.1.4. A Note About SPARQL Endpoint Permissions

The use of the Callimachus SPARQL endpoint is limited by default to those users in the 'admin' and 'power' groups.  Administrators may change this behavior by giving 'Reader'  permissions to other users or groups by selecting 'Permissions' from the main menu when viewing the SPARQL endpoint.  'Reader' permission on the SPARQL endpoint allows HTTP GET (read) operations via that interface.

Administrators are cautioned not to provide members of the public with permissions to issue SPARQL queries.  Doing so provides substantial opportunities for denial of service (DoS) attacks.

2.1.5. Wrap Up & Review

This tutorial demonstrates a couple important points about Callimachus. One is that every Callimachus instance comes with a default SPARQL endpoint available at /sparql. This allows you to execute any valid SPARQL query across your entire database. These queries can be executed directly via the SPARQL interface or via AJAX and JavaScript, as we did here. The second important point is that jQuery comes by default in Callimachus so there's no need to explicitly include it on any given page. This offers the ability to quickly and easily write JavaScript to extend the functionality of a Callimachus page.

2.2. Using XProc

XProc is the XML Processing pipelining language.  It is "a language designed for describing operations to be performed on XML documents." Callimachus uses XProc for a variety of functions; XProc is used when an operation needs to tranform XML data or fetch data from remote sources.  XProc pipelines can, like Unix pipelines, consist of multiple steps and can also branch into multiple streams.

Our goal will be to allow individuals to represent their interests in an XML file, publish it somewhere and then provide the URL to the file in their address book entries.  The Person View template will be modified to show a list of that person's interests.

The final result will look like this:

Person with interests

We will show you how to retrieve an XML file from a remote server, transform the file into an HTML fragment and use JavaScript to insert the fragment into the Person View template.  The Create and Edit templates will be updated to allow the collection of a URL to an XML document.

2.2.1. Overview of the Process

The following figure shows what we will do:

XProc application data flow summary

The steps are:

  1. Retrieve an XML document
  2. Use XSLT to transform the XML contents into an HTML unordered list
  3. Call the XProc pipeline from JavaScript to get the unordered list
  4. Insert the list into the Person View template for a given Person

2.2.2. Define an XML Document Representing a Person's Interests

We want to represent a person's interests in XML format, so we will use a simple XML structure like this:


<person>
  <name>
    <firstname>...</firstname>
    <lastname>...</lastname>
  </name>
  <interest>
    <label>...</label>
    <url>...</url>
  </interest>
</person>

Further, we want a person to be able to list as many interests as they like, so we will allow the <interest> tag to be used multiple times.

Those of you who are already familiar with RDF and Linked Data might be wondering why we didn't use Friend-of-a-Friend (FOAF) files for this since they are in RDF, many people already have them published and the FOAF specification includes an interest element.  We could have done that (in fact, you can view David Wood's FOAF file that includes interests), but our one of our goals was to demonstrate how Callimachus can easily make use of XML data since it is so commonly available from enterprise software.

We have provided a sample XML interests file that is served live on the Web for you to use until you publish your own.  It is located at http://3roundstones.com/dave/interests.xml.  The file's contents are:


<?xml version="1.0"?>
<person>
 <name>
   <firstname>David</firstname>
   <lastname>Wood</lastname>
 </name>
 <interest>
	<label>Linked Data</label>
	<url>https://en.wikipedia.org/wiki/Linked_Data</url>
 </interest>
 <interest>
	<label>Resource Description Framework (RDF)</label>
	<url>https://en.wikipedia.org/wiki/Resource_Description_Framework</url>
 </interest>
 <interest>
	<label>Metadata</label>
	<url>https://en.wikipedia.org/wiki/Metadata</url>
 </interest>
 <interest>
	<label>Semantic Web</label>
	<url>https://en.wikipedia.org/wiki/Semantic_Web</url>
 </interest>
 <interest>
	<label>Origins of agriculture</label>
	<url>https://en.wikipedia.org/wiki/History_of_agriculture</url>
 </interest>
 <interest>
	<label>Theory of Memes</label>
	<url>https://en.wikipedia.org/wiki/Meme</url>
 </interest>
 <interest>
	<label>Egyptian hieroglyphs</label>
	<url>https://en.wikipedia.org/wiki/Hieroglyphics</url>
 </interest>
 <interest>
	<label>Software maintenance</label>
	<url>https://en.wikipedia.org/wiki/Software_maintenance</url>
 </interest>
 <interest>
	<label>Cognition</label>
	<url>https://en.wikipedia.org/wiki/Cognition</url>
 </interest>
</person>

2.2.3. Write an XProc Pipeline to Retrieve, Transform and Render XML data

The first step is to create the XProc pipeline.  XProc has its own unique syntax that takes some getting used to.  Don't be too concerned about trying to learn XProc at the same time as you are learning Callimachus.  Rather, try to follow how Callimachus is using XProc for now and go on to learn more about XProc later.

Navigate to the initial-app/pipelines folder and select "Pipeline" from the Create menu:

Create an XProc pipeline

Callimachus will give you a dialog box showing a simple (non-functional) XProc pipeline.  You will need to define some meaningful XProc steps and save your pipeline.  We want to define an XProc pipeline that will:

  1. Return an XML document, but omit the XML header declaration;
  2. Require a parameter that represents the URL to a remote XML document;
  3. Retrieve the XML document from that URL;
  4. Pass the document through an XSLT transformation to turn it into an HTML unordered list.

The top of any XProc pipeline in Callimachus defines some handy namespaces:


<?xml version="1.0" encoding="UTF-8" ?>
<p:pipeline version="1.0"
        xmlns:p="http://www.w3.org/ns/xproc"
        xmlns:c="http://www.w3.org/ns/xproc-step"
        xmlns:l="http://xproc.org/library"
        xmlns:calli="http://callimachusproject.org/rdf/2009/framework#">

Next, we want to tell the pipeline to return an HTML document without the XML declaration.  The <p:serialization> step is used for that:


<p:serialization port="result" media-type="text/html" method="xml" omit-xml-declaration="true" />

If you wanted to return a different media type such as text/plain, you could do that here.

Our pipeline will require a parameter that is a URL.  We will call that parameter "href", but you can name it anything you like.  The <p:option> step is used to define parameters:


<p:option name="href" required="true" />

 In order for Callimachus to process our pipeline, it will need to load a library.  You need to load libraries after providing namespaces, serialization information and parameter definitions:


<p:import href="/callimachus/library.xpl" />

Now we need to fetch the URL that we have been given.  The <p:load> step will fetch the URL and return its results.  The URL to fetch was put into the $href variable earlier:


<p:load> 
    <p:with-option 
        name="href" 
        select="$href" 
    /> 
</p:load>

At this point, the pipeline has the contents of the remote XML document.  Now we need to pass it through an XML Stylesheet Language Transformation, or XSLT file, to turn it into an HTML list:


<p:xslt name="find-interests">
    <p:input port="stylesheet">
        <p:document href="../transforms/find-interests.xsl" />
    </p:input>
</p:xslt>

Finally, we end the pipeline definition:


</p:pipeline>

The entire pipeline looks like this:


<?xml version="1.0" encoding="UTF-8" ?>
<p:pipeline version="1.0"
        xmlns:p="http://www.w3.org/ns/xproc"
        xmlns:c="http://www.w3.org/ns/xproc-step"
        xmlns:l="http://xproc.org/library"
        xmlns:calli="http://callimachusproject.org/rdf/2009/framework#">

<p:serialization port="result" media-type="text/html" method="xml" omit-xml-declaration="true" />

<p:option name="href" required="true" /> 

<p:import href="/callimachus/library.xpl" />

<p:load> 
    <p:with-option 
        name="href" 
        select="$href" 
    /> 
</p:load> 

<p:xslt name="find-interests">
    <p:input port="stylesheet">
        <p:document href="../transforms/find-interests.xsl" />
    </p:input>
</p:xslt>

</p:pipeline>

You can save your pipeline now in the /initial-app/pipelines/ folder.  Give it the name find-interests.xpl so it matches the name we used in the finished application.

But what about that XSLT file we snuck into the last step?  Navigate to the /initial-app/transforms/ folder and create an XSLT transform file:

Create an XSLT transformation file

XSLT is yet another language in the XML tool chain.  You are probably familiar with it if you work with XML documents.  If not, you might want to start by reading the XSLT page on Wikipedia.

Our XSLT file will:

  1. Find any <person> tags in the file that it is operating on;
  2. Print an HTML unordered list tag (<ul>);
  3. Find any <interest> tags and for each one:
    1. Print an HTML list item tag (<li>);
    2. Print an HTML anchor tag (<a>) with an href attribute consisting of the value of the XML <url> tag;
    3. Print the value of the XML <label> tag;
    4. Close the anchor (</a>);
    5. Close the list item (</li>).

The contents of the XSLT file should look like this:


<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:strip-space elements="*" />

  <xsl:template match="person">
    <div id="interestlist">
      <p class="lead">Interests</p>
      <ul>
        <xsl:for-each select="interest">
          <li>
            <a>
              <xsl:attribute name="href">
                <xsl:value-of select="descendant::url" />
              </xsl:attribute>
              <xsl:value-of select="descendant::label" />
            </a>
          </li>
        </xsl:for-each>
      </ul>
    </div>
  </xsl:template>

</xsl:stylesheet>

Save your XSLT file in the /initial-app/transforms/ folder using the name find-interests.xsl.

2.2.4. Test the Pipeline

You can test the pipeline by calling its URL with the ?results pragma.  Adding ?results to the end of the URL will execute the pipeline and return its results.  Try it on the finished application's pipeline first:  ../finished-app/pipelines/find-interests.xpl?results

You should see an error that looks like this:

Pipeline error

Oops!  What happened?  Did you remember that the href parameter was defined as mandatory?  The pipeline won't execute without that parameter being present.

Try it again, this time by adding the parameter to the query string of the URL:  ../finished-app/pipelines/find-interests.xpl?results&href=http://3roundstones.com/dave/interests.xml

Now you should see the results, an HTML unordered list, as in the following screenshot:

Interests list

You can also call the pipeline from the command line using a utility such as curl, which is available for most operating systems.  The curl command line will need provide the entire URL and use authentication for your user account.  For example, the Callimachus user david who signs in using the HTTP Digest authentication method could access the pipeline results like this:


curl --digest --user david 'https://wiki.3roundstones.com/Development/Projects/Tutorial/finished-app/pipelines/find-interests.xpl?results&href=http://3roundstones.com/dave/interests.xml'

The curl utility would then ask for david's password and present the results:


<ul><li><a href="https://en.wikipedia.org/wiki/Linked_Data">Linked Data</a></li><li><a href="https://en.wikipedia.org/wiki/Resource_Description_Framework">Resource Description Framework (RDF)</a></li><li><a href="https://en.wikipedia.org/wiki/Metadata">Metadata</a></li><li><a href="https://en.wikipedia.org/wiki/Semantic_Web">Semantic Web</a></li><li><a href="https://en.wikipedia.org/wiki/History_of_agriculture">Origins of agriculture</a></li><li><a href="https://en.wikipedia.org/wiki/Meme">Theory of Memes</a></li><li><a href="https://en.wikipedia.org/wiki/Hieroglyphics">Egyptian hieroglyphs</a></li><li><a href="https://en.wikipedia.org/wiki/Software_maintenance">Software maintenance</a></li><li><a href="https://en.wikipedia.org/wiki/Cognition">Cognition</a></li></ul>

The white space was stripped from the results by using this command in the XSLT file:


<xsl:strip-space elements="*" />

2.2.5. Modify the Create, Edit and View Templates

Now that we have a working XProc pipeline, we will need to add the ability to collect URLs to XML interests files to our address book application.  The Create and Edit templates will need to provide the new URL field and the View template will need to make use of it.

Our steps will be:

  1. Add a field for entering URL to XML document to Create and Edit templates
  2. Add the property to the View template

On the Person Create and Edit templates, add this code between the "FOAF Profile Page" text field and the "Status" pulldown:


<div class="form-group">
  <label for="interests">XML interests document</label>
  <input type="url" class="form-control" id="interests" value="{tute:interests}" placeholder="http://3roundstones.com/dave/interests.xml" />
</div>

That will create (or edit) a new property using the RDF predicate tute:interests.  We just made that up; you can call it anything you like.  Of course, you will also need to add the definition of the tute namespace to the top of both templates.  We just assigned a unique URI to that namespace:


xmlns:tute="http://callimachusproject.org/2014/03/tutorial#"

As always, have a look at the finished app's Create and Edit templates if you need to see exactly where this information goes.

The Person View template will also need to have the tute namespace defined at the top of the file.  Callimachus will complain if you attempt to use a namespace that is not defined.

Add a reference to the tute:interests property in the View template.  Callimachus templates will only render properties that have been defined in their HTML, so we must provide a reference if we want the interests to appear.  We could hide the HTML division if we were worried about it, but we intend for JavaScript to operate on the URL on page load, so we didn't hide it for this tutorial.  The reference should look like this:


<div id="interests" class="col-sm-2">{tute:interests}</div>

We gave the division an id attribute so the JavaScript could easily locate it.  The CSS class tells Bootstrap how to lay out the division.  The first row of the View template now looks like:


<div class="row">
  <div class="col-sm-4 col-sm-offset-4 text-center">
    <img src="{foaf:depiction}" class="img-circle" />
  </div>
  <div class="col-sm-2">
    <p class="lead">Currently <span id="status" rel="foaf:status" resource="?status" class="label label-info">{skos:prefLabel}</span></p>
  </div>
  <div id="interests" class="col-sm-2">{tute:interests}</div>
</div>

You may want to play around with the Bootstrap column numbers to allocate more or less space (or change the layout entirely if you prefer).

You might also want to adjust the CSS at the top of the View template so the interests list lays out differently from the lists below the person's name. Something like this will help:

Assign a class called em on the existing unordered lists:


<ul class="em">

...and update the CSS to:


<style>
        ul.em {
            list-style: none;
            padding-left: 0;
        }
        ul {
            padding-left: 20px;
        }
        .em {
            font-size: 18px;    
        }
</style>

That ensures that the existing lists do not get bullet points added to them, but the new interests list will.

Remember, you can always compare your work to the finished application.

2.2.6. Add JavaScript to Call the Pipeline

There is already a JavaScript file associated with the Person View template called scripts/person-view.js.  We will want to modify that file to execute the pipeline and insert the new list into place if the template includes the tute:interests property.  Specifically, our steps will be to write some JavaScript that:

  1. Retrieves the XML URL if present
  2. Passes the XML URL as a parameter to the XProc Pipeline
  3. Places the results into a container on the View template

Our code looks like this:


// Find interests, if any
var url = $("#interests").html();
$( "#interests" ).load( "../pipelines/find-interests.xpl?results&href=" + url );

Pretty simple, right? The code will only do anything if the tute:interests property is present on the page returned by the view template. If so, the pipeline is called and the results placed into the HTML division we added to the Person View template.

Finally, we place this code so it will execute when the document is ready:


$(document).ready(function() {
    //...
    
    // Find interests, if any
    var url = $("#interests").html();
    $( "#interests" ).load( "../pipelines/find-interests.xpl?results&href=" + url );
});

That's it!  You should now be able to make your own XML files, publish them and get Callimachus to render them in your address book when they are associated with Person entries.

2.2.7. A Word About Serving XML files from Callimachus

Some of you might want to publish XML interest files from your Callimachus instance and will be disappointed to note that Callimachus won't accept them.  Why not?  Because Callimachus is not a general-purpose Web server like Apache or even Tomcat.  There are almost 1500 media types and Callimachus has to date only allowed the ones that are necessary for it to function.  We are naturally open to feedback about this decision on the Callimachus-discuss mailing list or the #callimachus IRC channel on Freenode.

One way to publish XML files to Callimachus is to trick Callimachus into thinking that they are just plain text by giving them a .txt file extension when you upload them.  They will serve to the Web with a media type of text/plain, which will be acceptable to JavaScript and other forgiving client applications.

2.2.8. Wrap Up & Review

Even though it can be a steep learning curve, you can do a lot of powerful things with XProc. If your application can not be implememented using Classes or Named Queries, it can be done via XProc. Learning XProc is well worth the effort.

A good source of XProc examples is Callimachus itself:  Look in the /webapp/pipelines directory in the source code and the Callimachus XProc examples document.  There is also an XProc tutorial and the official XProc specification has all the details.