Creating A Person Manager Application
This tutorial will show you how to build a Person Manager application. It's very similar to the Manual FAQ tutorial. One difference is that the FAQ tutorial involves a very trivial object: a FAQ object contains just two things: a question and an answer, while this example uses a Person object that has more properties (name, age, sex, etc). This is a very simple application that makes use of XWiki's xclasses, xproperties, and xobjects. It also uses a technique that you may frequently use as the basis for several different kinds of applications.
Also, this tutorial explains things a little differently than the FAQ Tutorial. Here, we gloss over a few things like Templates, and cover other things in more detail, like properties. Also, this page sometimes shows other ways to do things: using SOLR instead of XWQL, using a template instead of a custom form, using custom code rather than a livetable. If something here doesn't make sense to you, try going through the FAQ tutorial and maybe it will make more sense.
Prerequisites for following the tutorial
You should have installed XWiki and have a basic understanding of how to use it.
All through this tutorial you should refer to the XWiki Data Model for information on XWiki's data model. You might also use the XWiki Scripting Guide to get you started with scripting in XWiki and manipulating XWiki objects. In addition, this tutorial will introduce the concepts of Authoring Templates and Page Design Sheets, patterns that you will find particularly useful in creating XWiki applications. Completing this tutorial is a recommended prerequisite for anyone who wants to build custom applications on the XWiki engine. And by "custom application", we really mean any non-trivial, non-static web application. You should be able to build anything from a simple one-page form to accept online orders for your pizza parlor to your own competitor to TurboTax. The focus here, of course, is to build just the "front-end" of the website, and we don't cover "server-side" things like how to get that tax information sent to the IRS.
Application Overview
The Person Manager application will allow users to create a "Person object" by entering data (name, age, sex, etc) into a simple form and then submitting the form. Let's sketch out what roughly what those two pages should look like on a "napkin", using Balsamiq:
A Page For Creating a Person
Our page won't look exactly like that, but the point is that a website user can create a Person by filling out a "form" page like this.
Note all the various types of "widgets" shown here:
- "name" is a single-line text field
- "email" is also a single-line text field, but we'd like it to be validated (e.g. give an error if it doesn't have a "@" in it)
- "address" is a multi-line text field
- "phone" is a single-line text field (perhaps some validation here, too)
- "sex" is a drop-down list
- "married" is a checkbox: the only allowed values are "true" and "false"
- "image" is actually the name of some image file, but we actually display the image itself. Nice!
- "age" is Number field, which looks like a single-line text field, but has validation.
- "related people" is one more more links to other web pages. Very nice!
The Person then appears in a table with all other Person objects that have been previously created. Users can click on the Person in the table to see all the information about the Person. He may also edit that information or delete the Person. The table might look something like this:
A Page With a Table of Person Objects
When the user clicks on a row in the table, he will get a page that shows the information about the Person, which will look similar to the "Create Person" page, but without the ability to change anything.
Objects Overview And Terminology
Next, let's summarize the terminology for "Objects". For full details, see Data Model. There is nothing fancy happening here, but it's important to get our terminology straight.
A Person is an example of some "Object" or "class". We will use XWiki to define the "properties" in a "Person" class. For example, we will say that there's a property called "name" of type "String". There's also a property called "age" of type "Number", and an "address" property that's type "TextArea" (a string that can be multiple lines).
When a user creates a new Person, we call that an "instance" of the "class". So we might say something like "I've created an instance of the Person class, with name 'Joe Smith'". And we would say that our table shows all the instances of the Person class. And instances can not only be created, but also edited or deleted.
We, the creators of the website, define the "Person" class. We define that once, and we're done. Our users use our website to create, edit, and delete instances of our Person class.
Overview Of What We Will Do
In this tutorial, we'll do the following steps:
- Define our Person class, using the XWiki "Data Types" page.
- Specify the properties of our Person class, using the XWiki "Class Editor" page.
- Define how a Person instance should be displayed, by creating a "Person Sheet" page.
- Create a Template and a Template Provider (whatever they are) for our Person class.
- Create a web page that displays a table of Person instances.
- Create several example pages, each containing a Person instance.
Note that we don't need to define a page for creating or editing a Person, just a page for *displaying* a Person. XWiki will automatically do that for us!
Once we are done with these steps, our application will be finished. A user of our website can see a page containing the table of Person objects, view the page for an existing Person, add a new Person, edit an existing Person, or delete a Person.
Go to the Special "Data Types" Page
The "Data Types" page is a special XWiki page that lets us define classes like "Person". This page is actually hidden by default. To "unhide" it, go to your profile page, press the Edit button, select the Preferences tab and select Yes for the DISPLAY HIDDEN PAGES option.
To find the Data Types page, enter XWikiClasses in the search of your wiki. This will find the XWikiClasses page titled "Data Types".
Create the Person Class
- On the "Data Types" page, under the heading "Create a new data type", in the "Title" field, enter Person:
- As you can see in the Breadcrumb below the new page will be created at location XWiki > Person. In practice, the Data Types page will automatically add a postfix Class to the name of the page(you could also enter PersonClass as the page name directly).
- Now it would be nice to have it created in a new location such as PersonSpace > Person Class. Since the PersonSpace parent doesn't exist we cannot use the Tree picker button. Thus click the Pencil button as shown in the following image and replace XWiki by PersonSpace.
- XWiki has now created a "space" called PersonSpace. A "space" is a directory (or "folder") where pages live. All of our pages will go in this space.
- In technical terms you're creating a page named PersonClass (with a title of "Person Class") located in a space called PersonSpace and thus the technical reference for the page is PersonSpace.PersonClass.
- Click the "Create this Class" button. You should then see a page with the following content:{{velocity}}
## Replace the default space with the space where you want your documents to be created.
## Replace the default parent with the one of your choice and save the document.
##
#set($defaultSpace = $doc.space)
#set($defaultParent = $doc.fullName)
{{/velocity}}
In this code, change "$doc.space" to the name of the space where you want your pages to be created: "PersonSpace".
The line of code should look like this:
You can also change the default parent of the new Person documents that are going to be created. To do so, replace the "$defaultParent" variable with the name of your document.
The line of code should look like this:
The ".WebHome" here is the XWiki naming convention for a "non-terminal" page (a page with children).
Click the "Save & View" button. The class is now created and you should be looking at a page titled "Person Class" that looks like this:
Add Properties to the Class
Under the page title, you should see the words "The class does not have any properties yet. You can use the class editor to define them." Click on that link.
Now, we need to specify all the properties of a Person. Let's have the following properties:
Property Name | Property Type |
---|---|
name | String |
address | TextArea |
phone | String |
sex | static list |
married | Boolean |
image | String |
age | Number |
relatedPeople | Page (Multiple) |
Follow these steps to define our properties of the Person class:
- Enter the text name in the "name" field
- Choose "String" for the type of the property and then click on "Add". By using a String type, when a user goes to enter the name of a new Person, he will be prompted with a single-line text field.
- Click on the "+" icon to expand the options for the newly created property
- Change the value of the "Pretty Name" field to "Name"(capital N). With this done, the user sees the label "Name" rather than "name" when prompted for the name of a Person. This doesn't make a huge difference for this property, but when it comes to the property with the name "relatedPeople", it's nice to show the user something a little more friendy like "Related People". Also, you could later decide to change that label without actually renaming the property (and thereby probably breaking something).
- Now repeat this to add each of the properties shown in the table above.
- Note that the "EMail" type is like a String, except that it has a "Validation Expression" (e.g. to make sure it has an "@" character).
- If we wanted to, we could add a "Validation Expression" to the "phone" property to make sure it's in a particular format.
- For the "sex" property, in the "Display Type" field, enter "Select". This will cause the user to see a drop-down menu.
- As we define the "sex" property as type "static list", we specify the values for the field like this:
- Note that there is no "Image" type. That's unfortunate. Let's just define it as a String here, and deal with that later.
- Note that the "size" of our "age" field is 30 digits. People never live longer than 999 years, so feel free to change that to 3.
- For our "relatedPeople" property, we want to allow multiple values, not just one. So find the "Multi Select" checkbox and check it.
- When you are done, you should see all your properties like this:
- When you are done adding the properties, click the "Save & View" button
Create the Page Design Sheet
Next, we will create a "Page Design Sheet" to specify what a Person should look like when displayed on a page.
- After the previous step you are now on the PersonClass page which should look like this:
- Click the first button ("Create the document sheet") to create the document sheet (the Page Design Sheet).
- You should see a warning message with the text "The sheet is not bound to the class so it won't be applied automatically when a page that has an object of this class is displayed". Click the "Bind the sheet to the class" link that appears after the text. Basically, this ties the Person Class to the Person Sheet.
- Now click on "View the sheet document". This takes you to the PersonSpace.PersonSheet page which you can edit in wiki mode and see its default content. This content is Velocity code which simply goes through all the Person properties and displays each one. For example, it will see that our Person class has a "married" property of type "Boolean", and will show a checkbox with a label of "married" (or perhaps "Married" if we specified that as our "pretty name" for the property). See the Creating a FAQ Application (Manual) tutorial for more details about this code. This code is actually very close to working as-is. The only thing that would be ugly is that our "image" property would display as just a String, whereas we probably want to display the image itself, not some URL.
- This code is fine for now, so click "Save & View"
Create the Authoring Template
- Navigate back to the PersonSpace.PersonClass document (you can use the arrows in the breadcrumb to do so).
- Click on the "Create the template" button in the Class Template section of the page. The Authoring Template will be automatically created.
Now we need to associate the prototype object with this document to turn it into a true authoring template:
- If you're on the template page, navigate back to the PersonSpace.PersonClass document.
- At the bottom of the page, look for the following warning message: "The template does not contain an object of type PersonClass. Add a Person object to the template »."
- Click on "Add a Person object to the template »":
Next, we want to remove the title for the newly created template:
- Navigate to the PersonSpace.PersonTemplate document (you can click on the "View the template page (PersonSpace / Person Template)" link for doing that for example.
- Edit this document in Wiki mode
- Inside the Title field you have "Person Template" written -> delete this text
- Save & View
This step is needed so that all of our future entries don't have "Person Template" as their title.
Congratulations: you just created an Authoring Template! You're almost done now.
Create the Template Provider
After the template was created and the object was added, a new section appears with a button to create a template provider to use the existing template. Click that button.
Create a Home Page for the Person Manager application
Whew! Take a deep breath, grab a cup of coffee, and pat yourself on the back. We are now finished defining our Person class and all its properties, and those mysterious Templates. Now it's time to create some web pages. Recall that we only have two pages to create:
- A "main" page that contains a table of Person objects.
- A "Person" page that displays a single Person
- (We don't need to create a "form" page for creating a new Person or edit an existing person - XWiki does that for us)
Our "main" page will be the PersonSpace.WebHome page.
- Click on "PersonSpace" in the breadcrumb to navigate to PersonSpace.WebHome and notice that the page doesn't exist yet:
- Click on the "edit this page" link.
- Type in the title "People". Note that a page's title (what the user sees, e.g. "People") does not have to match the page's name (the unique id of the page, part of the URL e.g. "PersonSpace.WebHome")
Displaying Existing Person Entries In a Table
You have 2 main options when it comes to displaying existing Person entries:
- Use the livetable component
- Write custom code in order to display them in a table
We will only cover the "custom code" option here, because it's actually very easy to write a little code to display the table the way we want to. See the "Using the Livetable component" section of the Creating a FAQ Application (Manual) tutorial page for instructions to use a "livetable" instead.
To create our table, we need to think about Objects, not web pages. As people use our application, they create new instances of our Person class - they create Object instances. Let's say there have been three "Person" instances created so far. That means there will be three rows in our table. But how do we write code for this table? We will need to "query the XWiki database" - essentially tell XWiki "get me all the instances of the PersonClass that exist". XWiki provides several different ways to do this query:
- XWiki Query Language (XWQL)
- Hibernate Query Language (HQL)
- Solr Query Language (SOLR)
The Creating a FAQ Application (Manual) tutorial page tells you how to use XWQL, but here, let's use SOLR. Why is SOLR "better" than XWQL? It's a bit of a standard, it's very well documented, very flexible and fast. It's also pretty simple.
Regardless of which of these techniques we use to query, we will be writing code in the Velocity Template Language.
Using custom code
We will need to write code to do the following:
- Display a header above the table that says "People"
- Perform a SOLR query to get all documents (i.e. pages) containing an instance of PersonClass, with a "name" property of any value.
- Let's limit the number of results to 1000 because the default for SOLR is 10 and that's too low.
- Let's use XWiki syntax to create a table with three columns: Name, Email, and Phone (note that we don't bother to show all properties of a Person as columns in our table, but we could if we wanted to).
- The query will return multiple Documents. Each Document contains all the data on the web page. It's similar to the DOM that many developers know about. Loop through each Document, doing the following:
- find the PersonClass instance on the page. Set a variable called $object to that.
- get the "name" property from the instance and display that in the first column of the table.
- get the "email" property from the instance and display that in the second column of the table.
- get the "phone" property from the instance and display that in the third column of the table.
Here is the resulting code:
= People =
#set ($className = 'PersonSpace.PersonClass')
#set ($attr = 'name')
#set ($queryStatement = "property.$className.$attr:*")
#set ($query = $services.query.createQuery($queryStatement, 'solr'))
#set ($discard = $query.bindValue('rows', '1000'))
#set ($searchResponse = $query.execute()[0])
|=Name|=Email|=Phone
#foreach ($searchResult in $searchResponse.results)
#set ($documentReference = $services.solr.resolveDocument($searchResult))
#set ($d = $xwiki.getDocument($documentReference))
#set ($object = $d.getObject("$className"))
#if ($object.getProperty('name').getValue() != '')
|$object.getProperty('name').getValue()##
|$object.getProperty('email').getValue()##
|$object.getProperty('phone').getValue()
#end
#end
{{/velocity}}
Some things to note about this code:
- The lines starting with "#" are Velocity
- Once all of this is processed by the XWiki engine, the result will be XWiki syntax for a table like this:
= People =
|=Name|=Email|=Phone
|Joe Smith|[email protected]|999-555-1212 - The "##" characters at the end of the lines showing rows in the table are required because XWiki wants all table data for a row to be on a single line.
You may be thinking "How on earth would I possibly know to write that code? What is all that stuff?" Great questions! We'll cover that in the next section, but for now:
- Copy this code and paste it as Wiki content inside PersonSpace.WebHome
- Click "Save & View"
- New Person entries will now be displayed on the page once you create them.
At this point, your PersonSpace.WebHome page will just display as an empty table because no instances of PersonClass have been created yet.
Overview of XWiki Scripting
In this section, we'll get you started on scripting in XWiki by explaining some of the code shown in the previous section. If you don't care about these details and just want to see something on your Person Table, just skip this section.
For details about writing scripts like this, see Scripting API Guide.
Let's look closely at the code in the previous section. This line:
{{velocity}}
is simply telling XWiki that everything here is part of a Velocity script. As such, we can just put in some any text we want (we want XWiki Syntax). But we can also have lines of Velocity that start with "#".
This line...
= People=
...is just XWiki syntax saying "Give show me some "header" text with a big, bold font, that says "People".
These lines...
#set ($attr = 'name')
#set ($queryStatement = "property.$className.$attr:*"
...are just Velocity code setting some variables. Plain text in single quotes, double quotes needed when the text contains references to variables.
Now we come to this line:
We are calling some "createQuery()" method (or "function") on some "$services.query" variable. That's odd, because this script never set any variable called "$services.query". This is a "special variable" that's automatically set for you by XWiki. The Scripting API Reference section on the XWiki Scripting API Reference page contains a list of all these "special variables" that XWiki has set for us. Scroll down in that list and find "$services.query", and click on it. You'll come to a "javadoc" page that shows information about "Class QueryManagerScriptService", including a method called "createQuery()". This method takes two parameters: a "statement" and a "language". We passed in 'solr' for the "language", and that "you just have to know" that that's how XWiki has set things up.
Where can you learn about how to create a "SOLR statement"? I haven't found any good documentation. The SOLR Tutorial is way too complicated.
The XWiki-specific SOLR Schema page is very much for XWiki developers, not users, but I found it useful. The main thing here is to understand that we can use this SOLR statement...
..to query for all the instances of PersonClass in the PersonSpace space that have a "name" property set to anything.
With this line...
...we add to our SOLR query the fact that we only want to get a maximum of 1000 results. We save the value returned in a variable called "$discard", but never use that value. Then why save it? Because Velocity syntax does not have a way to simply "execute a statement", you have to "set a variable". Annoying, I know.
Next, we execute the SOLR query, get a list of returned values, but only care about the first returned value, and save that in a variable called "$searchResponse":
...but now what? The javadoc page for Query shows the execute() method returns a List of "<T>", but what are the details of T? What fields does this object have? Again, the SOLR documentation is not helpful, and the XWiki documentation on it is sparse. But this page at least shows us that there is a "results" field, which we can loop through. So we do:
#set ($documentReference = $services.solr.resolveDocument($searchResult))
#set ($d = $xwiki.getDocument($documentReference))
We call "resolveDocument()" on a "special" #services.solr variable to get a DocumentReference object, and call getDocument() on that to get aDocument object.
We did some hand-waving here, but at least now we know we're looping through the Document for each web page. We have this massive Document object that has what seems like hundreds of methods to choose from. For example, we can call the getTitle() method to get the title of the page. So this is very powerful. But in our case, all we care about is the Person instance on the page. We store that in a variable:
And then we get the "name", "email", and "phone" properties of that object, and display them as columns in our table:
|$object.getProperty('email').getValue()
|$object.getProperty('phone').getValue()
In summary, our page uses Velocity code to create the XWiki syntax for a table. We do a SOLR query for all pages that have a Person instance that has a "name" property with any value. We execute the query and get back something that contains a list of results. We loop through those results, extracting first a DocumentReference (whatever that is), get a Document (like a DOM) from that, and then get the instance of PersonClass from that. We then output XWiki table syntax to display the "name", "email", and "phone" properties of that Person instance as the columns in a table.
We've learned a little XWiki syntax, Velocity syntax, SOLR syntax, learned to navigate through some XWiki javadoc pages. It wasn't easy or pretty, but we did it.
Finally, let's create a few Person instances so we can see them in our table.
Creating new Person instances
There are 2 ways for you to let your users create new instances of our PersonClass:
- Declare the Person as a template
- Add a custom creation form
The Creating a FAQ Application (Manual) tutorial page describes how to create a custom form if you want to do that. But the simpler way is to use a template, and so that's what we'll do. Remember earlier, on the Person Class page, we clicked on a button to create a template, and clicked on another button to create a "Template Provider"? That's all we needed to do!
With that done, let's go ahead and create an instance of PersonClass. To be specific, we'll create a new page and add an instance of PersonClass to that page. Go to your PersonSpace.WebHome page (the one with the table), and click the "Create" button to create a child page. You will be prompted for a page title and page type. In the list of Types, under "Template" you should see "Person":
Fill in the person's name as the page title, choose the "Person" type, and click "Create."
You will then be prompted for all the information about your the Person instance that you want to create:
Fill in all the values and press "Save & View".
Now go back to the PersonSpace.WebHome page, refresh your browser, and you should see your new instance as a row in the table.
Create a few more pages, each with a Person instance, and see them in the table, like this:
Finishing Up the Table
Had we chosen to use the "livetable" feature, we'd be done. Clicking on a row in the table would take us to that user's page, and the table would also allow us to edit or delete a Person. But we chose to write "custom code" using Velocity and XWiki syntax to show our table. So now we need to make it so that clicking on the user's name in the table takes us to that user's page.
Go back to the PersonSpace.WebHome page and click "Edit" to modify it. Change the line that shows the Person's name:
to this:
All we've done here is change the first column in our table to display a link rather than simple text. Press "Save & View" and verify that the links work.
Finishing Up the Sheet
Currently, our page showing a Person looks pretty bland:
Recall that we defined a "Sheet" page for our PersonClass, which defined how any Person instance should be displayed. Also recall that we just decided to use the default code that XWiki provided, which simply loops through every Person property and displays it.
There are two problems left to solve:
- It sure would be nice to see the image itself, not just the URL to the image.
- We're not seeing anything for the "Related People" property yet. We should see multiple links to other pages.
Additionally, let's customize this page in a few ways:
- Let's put it all into a visual "box"
- Let's put the image at the top
- Let's rearrange properties, putting "phone" above "email", so that the order of properties we display does not match the order that we defined them in the Class Editor.
After these changes (and after changing Joe Smith's image to be the URL for an elephant and specifying multiple "relatedPeople" for him), it should look something like this:
Go to the Person Sheet page, edit it, and paste the following:
#set($person = $doc.getObject('PersonSpace.PersonClass'))
(% id="infobox" style="flex: 0 0 300px" %)
(((
{{box}}
(% style="float:right; clear:right;" %)
|(% colspan="2" %) [[image:$person.display('image', 'view')||width="100"]]
|Name:|$person.display('name', 'view')
|Phone:|$person.display('phone', 'view')
|Email:|$person.display('email', 'view')
|Address:|$person.display('address', 'view')
|Sex:|$person.display('sex', 'view')
|Married:|$person.display('married', 'view')
|Related:|##
#foreach ($other in $person.getProperty('relatedPeople').getValue())
|[[PersonSpace.$other]]##
#end
{{/box}}
)))
{{/velocity}}
Press "Save & View", go to the "Joe Smith" page, and it should now look the way we want.
Some things to note about this code:
- We called the "getObject()" method on the "special XWiki variable" $doc to get the Person instance on this page.
- We used the XWiki Box to put everything in a box.
- We included some CSS here that didn't do much, but could have been any arbitrary CSS:
(% style="float:right; clear:right;" %) - We wanted the CSS line to apply to what could potentially be many lines of following code. We used the XWiki Groups syntax of ((( ... ))) for that.
- To make the image span two columns of our table, we used an XWiki Parameterized Table
- For the "relatedPeople" property, we didn't want to just display the (multiple) Person names, we want them to be links. So we loop through them, creating a link for each.
- We are careful to put "##" at the end of each line when we want to put multiple things in a table column, so as not to confuse XWiki.
- By using this code rather than blindly looping through every property, we're able to easily:
- Not display the "age" property at all
- reorder the properties in any way we like
- use any labels we want, not be stuck with the "Pretty name" for each property (though this is more of a drawback than an advantage)
As you can see, this code to customize how we display a Person is extremely straightforward:
- We easily got the value for the Person instance on the page.
- The Velocity syntax is simple and doesn't get in our way. We could someday choose Groovy if Velocity is not enough.
- We're able to inject CSS as needed. We could also have injected raw HTML.
- We chose to use XWiki syntax, which is even simpler than HTML.
Conclusion
You've done something very special here, taking advantage of the Power Of XWiki by using data-driven web pages.
If we had "gone cheap" and naively created a static HTML (or Wiki) page for each Person, we would have had to "hard-code" the table, containing links to them all. And maintaining that table would be a nightmare because it would need to be updated each time a new Person is added or each time any of the data that it displays is changed on the other page. With XWiki, whenever we add a new Person instance, it will automatically show up in our table because it's automatically generated. In addition, if we hadn't done it this way and we wanted to change the look of the pages, we would likely have to change every one of them (even if they share common CSS).
At the other end of the spectrum, suppose we had been willing to spend a lot of time and money and decided on a typical three-tier high-end architecture. We would have to hire a whole team or even several teams. A "client" team would build pages using Javascript-plus-some-framework. And a "server" team would handle requests from the client to the server to store and retrieve the data. We'd have a database guy (or whole team) store the data in some relational database. Lots of time and energy would go into defining the (perhaps REST) API that the server provides and the client would use. And lots of effort would go into defining the database schema, database creation, and code (perhaps Hibernate or some other ORM tool) to get the data from the database into the server code and back again. All of this is perhaps 100x the amount of work needed compared to doing the same thing with XWiki.
Using XWiki in this way, the pages *are* the database, because they don't contain static HTML - they contain Objects - data. And those pages can be queried easily and flexibly, just like a relational database. The client side here is written in XWiki syntax, which is simpler than HTML and far simpler than Javascript. But we could also use HTML and CSS (and even Javascript) as needed, if we need more flexibility. The client also contains some simple Velocity code to do things like looping to create the rows in a table. There is no comparison between the simple Velocity code we've just seen and the equivalent Javascript-plus-the-latest-JS-framework code. And even this Velocity code can often be replaced by something even simpler (like "livetable").
It should be obvious here that what we are building is a full, web-based application, not just a wiki. So don't be fooled by the name "XWiki". We are using it as a platform to build applications, like CUBA but far simpler. Our client side can get nearly as complex as we want. If we do, someday, want some shiny bells and whistles offered by the latest Javascript+framework, we can incorporate that later without too much pain.
In short, by using XWiki to build a data-driven application, we get nearly the flexibility and maintainability of a full three-tier architecture, while only investing nearly the same small effort needed to build just the client in static HTML.