Explanation of how to build a application


Here I've tried to explain how to build a application in XWiki.
It's goal is to explain how to work with data in Objects from Classes.

In depth study of the XWiki User application

In this page I would like to explain how to make a XWiki application that will have a database for fields, forms to maintain data and how to create a new record for that.

XWiki applications are Single-class oriented, but it is possible to create Parent-Class with multiple child-classes. That aspect will be out of scope for this page!
We will focus on single-class applications.

For this case I will take the current project focus; User profiles.

Which classes are involved in our project User-profile?
To find them navigate to XWikiClasses.

In the search box (Location) type USER.

the result will be two classes:
 

Then type PROFILE in search box.
 

then type PERSON in search

We have identified the 4 classes involved.
A quick defenition of these classes.

  1. XWikiUsers Is default XWiki, can be seen as USERACCOUNT. Is automatically created when a user registered. Note that all properties are visible on the internet (public) as a object of this class is required to login. (properties are somewhat secured from public view)
  2. waihonaPediaUserProfileClass only for approved user, holds the more privat data for the user account, specific for WaihonaPedia
  3. personWithSyndromeClass If the user is related to a person with a syndrome/decease, a object of this holds the basic data for such a person
  4. personToSyndromePersonRelationClass is a connecting class to connect a user with a person with a syndrome. It also specifies the role of the connection: Father, Doctor,etcetera.

A bit of information of how a XWiki application is structured:

  1. A class, represented by an object for each user
  2. A template (you can have 1 and more templates)
  3. 1 or more Sheets

All the 3 above elements are basicly normal XWiki pages, each having a way to edit them
When you look at one of the classes of the previous section you experience 2 of these in action.

SHEETS

The class is somewhat hidden in a page; you can normally only see it when you click EDIT-Class-editor. But when you navigate and just want to view the class without editing you would normally not see anything. This is where a 'Sheet' kicks in. A sheet can take objects and 'display' them to the viewer. The page I reviewed (ProfileSheet are in fact a SHEET. Thus a sheet contains CODE (Velocity or Groovy and HTML. They depend on CSS for styling. 

So the relationship between a CLASS and a SHEET is to VIEW objects of that Class.
A default sheet will normally support two MODES; VIEW and EDIT. The one reviewed supports (untill now) only VIEW. When you EDIT a CLASS XWiki also uses another sheet as compared to VIEWING a class: for more complex applications this is quite normal to have different sheets for viewing and editing.
As a example: the out-of-the-box XWiki User profile uses 6 SHEETS of which 5 support both MODES: VIEW and EDIT.

TEMPLATE

A way to VIEW and EDIT objects is nice, but we miss one important aspect! CREATE!
There is not much value if we build a nice Class and some SHEETS but depend on the object EDITOR to ask a novice user to create his own USER-PROFILE.
This is what TEMPLATES do.
It is basicly a page with a object of the Class that you can visit and modify with your Sheets. In fact all pages having a object of that class could be used as a TEMPLATE, but it would be strange to see your new page already populated with data of another record!
So we keep the TEMPLATE's object empty of data, except if we want to set some default data!

So the lifecycle of a record could be described as following;

  1. create the record by making a copy of the TEMPLATE
  2. navigate to the new page and EDIT with a SHEET (note that the edit button has the mode FORM (which basicly tells the wiki, USE A SHEET IF YOU CAN FIND ONE)
  3.  enter your data
  4.  SAVE and VIEW the data with a SHEET (can be the same sheet as when EDITING)
  5.  If other users go to the Page make sure a SHEET is automatically loaded

Automatic SHEET loading

See extended information here

When we do nothing the XWiki server has no knowledge of which SHEETS, TEMPLATES and CLASSES belong together as a application! Sometimes this is GOOD as it increases security and maybe if we want to create a screen for Admin's only that is what we want.

In such cases we need to 'run-time' make a application by using request.parameters
Request Sheets

To apply a sheet on a document upon request, you can use the URL sheet query string parameter as follows:
/xwiki/bin/view/Main/UserPage?sheet=XWiki.XWikiUserSheet. The sheet specified in the request is applied only to the requested document and it doesn't work recursively.
We could create HTML buttons that link to such a URL.
The following URL would edit the page Main.UserPage using the sheet XWikiUserSheet
/xwiki/bin/edit/Main/UserPage?sheet=XWiki.XWikiUserSheet

But most of the time we want a more convenient approach because the URL parameters can be easily typed wrongly.

To see what sheets are linked to a class use this piece of code:
{{velocity}}
#set($className = 'WaihonaCode.waihonaPediaUserProfileClass')
The list of class sheets bound to $className:
#set($classDocument = $xwiki.getDocument($className))
#foreach($sheetReference in $services.sheet.getClassSheets($classDocument))
 * $services.model.serialize($sheetReference)
#end
{{/velocity}}

To see the sheets that will be replaced by this project testSheetService

How is this done?;

  1. On the class page EDIT the page using the OBJECT editor
  2. Notice a object of class: XWiki.DocumentSheetBinding
  3. In that object the sheet is specified, if multiple sheets notice each is represented by a object (see as example: personToSyndromePersonRelationClass
  4. On each sheet page we could add a object for which action (VIEW or EDIT or other action like PRINT) the sheet is intended by a object XWiki.SheetDescriptorClass. This step is optional and only when needed.

How to create a new record (

page with object) using a Template==
create a standard wiki document for your template
see the following HTML to allow the creation of a document from your template:
NOTE: THE CODE IS FOR XWIKI OLDER VERSION, the line with 'webname' needs to be discussed, experimented on

#set ($discard = $xwiki.jsx.use('your page holding a javascript extension that facilitates the JS at he bottom of this code'))
<form action="" id="newdoc">
<input type="hidden" name="parent" value="${doc.fullName}" />
<input type="hidden" name="template" value="XWiki.YourTemplateName" />
<input type="hidden" name="webname" value="Main" size="8" />
Title: <input type="text" name="name" value="Name of your document" />
<br />
<br />
<input type="button" value="Create this document" onclick='if (updateName(this.form.name)) {this.form.action="../../inline/" + this.form.webname.value + "/" + this.form.name.value; this.form.submit(); }'>
</form>

The project context

The 'user profile' project is replacing 4 sheets (+5 sheet module pages). With replacing we normally do not change the original sheets, but making a copy and change it to our new Design.

It is good to discuss the BEST Sheet that we want to replace as it is build by XWiki and thus holds all the best-practises we could need.

XWiki.XWikiUserSheet

When we look at the code with our editor (Best practise is not to EDIT but use the vertical ... button and 'view source')

Styling and Javascript

#########################
## CSS & Javascripts
#########################
#set($discard = $xwiki.ssx.use("XWiki.XWikiUserSheet"))
#set($discard = $xwiki.jsx.use("XWiki.XWikiUserSheet"))

Notice that the page is using specific styling and javascript. I recommend you inspect the CSS and Javascript (as i'm no good at explaining that part anyway ;-)

Categories (or TABS)

Next the sheet defines some categories. In this use-case categories are the TABS on the screen which in XWiki default are below the user Avatar. Notice that this could also be done by using a UIExtension list. The advantage of this way is that the categories are defined in the code and easy to find. A disadvantage of this way is that it is hardly re-use. But the argument here is that the TABS are very specific to this application

 
  7: #########################
  8: ## Setting categories
  9: #########################
 10: #set($categories = [])
 11: #set($discard = $categories.add({'id':'profile', 'sheet':'XWiki.XWikiUserProfileSheet', 'glyphicon': 'user'}))
 12: #set($isMyProfile = ($services.model.resolveDocument($xcontext.user) == $doc.documentReference))
 13: #if($isMyProfile || $hasAdmin)
 14:   #set($discard = $categories.add({'id':'preferences', 'sheet':'XWiki.XWikiUserPreferencesSheet', 'glyphicon': 'wrench'}))
 15: #end
 16: ## TODO: add APIs to be able to display users watchlists to admins
 17: #if($isMyProfile && $hasWatch)
 18:   #set($discard = $categories.add({'id':'watchlist', 'sheet':'XWiki.XWikiUserWatchListSheet', 'glyphicon': 'eye-open'}))
 19: #end
 20: #if($isMyProfile)
 21:   #set($discard = $categories.add({'id':'network', 'sheet':'XWiki.XWikiUserNetworkSheet', 'glyphicon': 'globe'}))
 22: #end
 23: #set($userWikiSheet = 'WikiManager.UserWikiSheet')
 24: #if($xcontext.isMainWiki() && $xwiki.exists($userWikiSheet))
 25:   #set($discard = $categories.add({
26:     'id': 'wikis',
27:     'name': $services.localization.render('platform.wiki.menu.userwikis'),
28:     'sheet': $userWikiSheet,
29:     'glyphicon': 'list'
30:   }))
 31: #end
 32: #if($isMyProfile && $hasDashboard)
 33:   #set($discard = $categories.add({'id':'dashboard', 'sheet':'Dashboard.XWikiUserDashboardSheet', 'glyphicon': 'th'}))
 34: #end
 


 10: this creates a empty array for categories.

 11, 14, 18, 21, 25 and 32 ADD elements to the category array all having a ID:
 

  • profile -> 'glyphicon': 'user'
  • preferences -> 'glyphicon': 'wrench'
  • watchlist -> 'glyphicon': 'eye-open'
  • network -> 'glyphicon': 'globe'
  • wikis -> 'glyphicon': 'list'
  • dashboard -> 'glyphicon': 'th'

Notice that they use a '$services.localization.render('platform.wiki.menu.userwikis') to get a translated label: NICE...

Notice the Sheet part, The code of this page is relatively short as they use SHEET-PARTS organized in different pages. That will be used to render the Right side of the screen for the respective category...
 

In the respective #if clauses we learn nice inspections

12: This line is setting the $isMyProfile variable that will tell us:

  • Is the viewing user ($xcontext.user) the user owning this page or is the page viewed by another user.
    Note that the page-name ($doc.documentReference) is tightly linked to the account; e.g. my account name is XWiki.GerritjanKoekkoek and my data is in the page XWiki.GerritjanKoekkoek. Ergo: I should be made difficult/impossible to change the page-name or move the page. This is not the case in the normal XWiki software.

13: #if($isMyProfile || $hasAdmin)
Use the variable in a OR clause
$hasAdmin is a obvious system variable
Notice that we have a custom variable implemented $isApproved (only in the custom skin)

24: igore this as we have a single WIKI setup
Meaning our solution should drop this functionality

32: igore this as we have a very different DASHBOARD implementation
Meaning our solution should drop this functionality, and replace it with linking to our WaihonaPedia Dashboard.

Current category or TAB

 
    #########################
 36: ## Current category
 37: #########################
 38: #set($currentCategory = "$!request.category")
 39: #if($currentCategory == "")
 40:   #set($currentCategory = $categories[0].get('id'))
 41: #end


38: A request variable is used: This comes from the url after the '?' symbol in the url. Al request variables are seperated by '&'

40: the velocity way of reading the array CATEGORIES.
so url http://some url?category=preferences would get that line from the Array

Creating a vertical Menu

     #########################
 43: ## Creating vertical menu
 44: #########################
 45: #set($userMenu = [{
46:   " classname="XWiki.XWikiUsers" object="$obj.number" property="avatar" savemode="direct" defaultValue="XWiki.XWikiUserSheet@noavatar.png" width="180" alternateText="$xwiki.getUserName($doc.fullName, false)" buttontext="$services.localization.render('platform.core.profile.changePhoto')" displayImage="true" filter="png,jpg,gif"/}}
78:     #end
79:   


 62:
 63:

 ..:

the line closing the 'div' is not in our code above, but will be in our next code block
 This is WIKI syntax (not to be confused by Velocity)!!!
 When the Wiki syntax is transformed to HTML it will result in

 <div id="user-menu-col">
 ...
 </div>
 

Note that the programmer in this sheet does not use the HTML macro and only use WIKI syntax combined with velocity. I think that in our project we could add the HTML macro

67, 68 and 79 will create a div for the avatar

69 if a request parameter xpage == 'edituser'
The first part seems to relate to the state when the UI of the Avatar selection is open. for now we only explain ELSE part.
77: the {{attachmentSelector}} macro is used

read full documentation for this macro here

{{attachmentSelector
 classname="XWiki.XWikiUsers"
 object="$obj.number"
 property="avatar"
  #if ($isMyProfile) savemode="direct" #end
 defaultValue="XWiki.XWikiUserSheet@noavatar.png"
 width="180"
 alternateText="$xwiki.getUserName($doc.fullName, false)"
 buttontext="$services.localization.render('platform.core.profile.changePhoto')"
 displayImage="true"
 filter="png,jpg,gif"/}}


As you can see the macro is a nice starting point for our drag-drop replacement variant.
The code for this macro is here use the EDIT?editor=object

Display the left menu continued

 80:   ##########
 81:   ## Menu
 82:   ##########
 83:   (% id="user-vertical-menu" %)
 84:   (((
 85:     #verticalNavigation($userMenu, {'translationPrefix' : 'platform.core.profile.category.', 'crtItemId' : $currentCategory, 'cssClass' : 'profile-menu'})
 86:   )))
 87: )))

83,84 and 86 create a enclosing div

85: Typical XWiki way of surprising us with a velocity macro that is not easy to find
But i'm sure it will create a menu structure in HTML. Notice that it gets 2 parameters that both are of type Object (e.g. we have seen $userMenu was a object, but the second is enclosed by {}, the velocity notation for a object. Notice it looks very much like a JSON object which certainly is a best parctise in XWiki.

display the right side of the screen

88: #########################
 89: ## Display the page content
 90: #########################
 91: (% id="user-page-content" %)
 92: (((
 93:   #foreach($category in $userMenu)
 94:     #foreach($subcategory in $category.get('children'))
 95:       #set($tabKey = $subcategory.get('id'))
 96:       (% id="${tabKey}Pane" class="user-page-pane#if($tabKey != $currentCategory) hidden#end" %)
 97:       (((
 98:         #set($tabInclude = $subcategory.get('sheet'))
 99:         {{include reference="${tabInclude}" /}}
100:       )))
101:     #end
102:   #end
103: )))

Note that the full page is generated and all categories are there.
95: Each selector needs a unique ID ($tabKey)
96: Div's with this id ${tabKey}Pane are generated.
notice the velocity inside (velocity can be chained making inline statements possible)
In the code below we 'undo' the chaining for better readability 

#if($tabKey != $currentCategory)
  hidden
#end"

it will result in the following two class specifications

  • class="user-page-pane" -> for the selected category
  • class="user-page-pane hidden"-> for all other categories

specific code for EDIT mode, both normal and INLINE

107: {{html clean="false"}}
108:   #if($xcontext.action == 'edit' || $xcontext.action == 'inline')
109:     <input type='hidden' name='category' value="$!{escapetool.xml($currentCategory)}" />
110:   #end
111:   <div class="clearfloats">&nbsp;</div>
112:   #if($request.get('xpage'))
113:     <script type="text/javascript">
114:       document.fire('lightbox:userprofile:loaded');
115:     </script>
116:   #end
117: {{/html}}

107: If your HTML is wrapped inside other HTML it is good practise to use 'clean="false"'
108: This is how you can detect if the MODE is EDIT
109: A hidden input field is added, in this case the current category (profile, preferences, watchlist, network, wikis or dashboard)

112: Think this is related to the UI of the AVATAR selection (the macro)

appendix

Example of link behind the EDIT AVATAR button (the little pencil)
http://cdlsworld.devxwiki.com/xwiki/bin/view/XWiki/AttachmentSelector?
   docname

XWiki.GerritjanKoekkoek
 & classname=XWiki.XWikiUsers
 & property=avatar
 & object=0
 & savemode=direct
 & defaultValue=XWiki.XWikiUserSheet%40noavatar.png
 & filter=png,jpg,gif
 & displayImage=true
 

Example of the button-link that will save the photo already uploaded to the profile

http://cdlsworld.devxwiki.com/xwiki/bin/save/XWiki/GerritjanKoekkoek?
   XWiki.XWikiUsers_0_avatar=Gerritjan.png
 & form_token=iCCW4ymGixW6x2zkMQ7PtA

About the website contents

 

All of the information on this WebSite is for education purposes only. The place to get specific medical advice, diagnoses, and treatment is your doctor. Use of this site is strictly at your own risk. If you find something that you think needs correction or clarification, please let us know at: 

Send a email: wiki@waihonapedia.org