Focus on data, the part to understand


We will go in depth into the XWiki User Application

This might be a lot to you, to comfort you scroll to end of this page and read the summary!!

One of the first sheets we need to build is for managing the account data
Then after that we can worry about:

  • preferences
  • watchlist
  • network
  • wikis
  • dashboard

As a example we look at the existing code for doing just that. The management of the AVATAR foto was touched already

XWikiUserProfileSheet

1: {{velocity}}
2: #set ($discard = $xwiki.ssx.use('XWiki.XWikiUserSheet'))
3: ##
4: #set ($inEditMode = $xcontext.action == 'edit' || $xcontext.action == 'inline')
5: #set ($xwikiUsersClassName = 'XWiki.XWikiUsers')
6: #set ($obj = $doc.getObject($xwikiUsersClassName))
7: ##

2: use the same stylesheet as the Wrapping sheet, obviously XWiki will take care that the sheets are not downloaded multiple times to the client

4: A velocity way of setting the $inEditMode based on a OR clause : it will result in TRUE or FALSE

5: create a variable to set the class for which we want to manage objects, better compared to repeating the class-string-name many times

6: A bit dirty way to set the $obj (I would rather use a more explaining variable name like $accountObject)
Notice the very much used system variable $doc

$doc is ALWAYS the current document.
NEVER SET $doc !!!

Notice the use of getObject() instead of getObjects().
This is why XWiki preaches that we should use 1 Object on 1 Page. In this way we always sure the right object is maintained.
If the page would contain multiple objects getObject will get the first is sees, it is not predictable which of the objects it will pick.

  8: #if (!$obj)
  9: = $services.localization.render('xe.admin.users.sheet') =
 10:
 11: {{info}}$services.localization.render('xe.admin.users.applyonusers'){{/info}}
 12:
 13: #else
 14: ## Make sure we only care about properties of the user object and don't get polluted by other objects that might be in the user's profile page.
 15: #set ($discard = $doc.use($obj))
 16: ##
 17: #set ($sheetDocumentReference = $services.model.createDocumentReference($xcontext.database, 'XWiki', 'AdminUserProfileSheet'))
 18: #set ($sheetDocument = $xwiki.getDocument($sheetDocumentReference))
 19: #set ($xwikiUsersClass = $xwiki.getClass($xwikiUsersClassName))
 20: ##
 21: #set ($sectionsObjectClassName = 'XWiki.UserProfileSectionsClass')
 22: #set ($sectionObjectClassName = 'XWiki.UserProfileSectionClass')
 23: ##
...
118: #end

8: A nice way to see if we are on a page holding a object of the class.
Note that when you edit the sheet and save the #If will evaluate to TRUE and show you the message $services.localization.render('xe.admin.users.applyonusers')

14 and 15: This statement will make sure that we will only modify properties of the intended object
Note that if the page would hold other class objects where a property has a non-unique name ('name' is a good candidate) that these are not changed.

17 and 18: The OFFICIAL way to set a document variable: first create a document reference that can accept all user input (like Chinese characters). I think we should try to use this two step approach! I did not use this way always and directly used 18

19: The getClass() method will give you all class definitions in a object; like all properties, the type of the property etc.

21 and 22; Since the account class has a huge amount of properties this Sections will be used to group properties. 

 24: {{html clean='false' wiki='true'}}
 25: #if (!$inEditMode)
 26:   <div class="vcard">
 27:   <span class="fn hidden">$xwiki.getUserName($doc.fullName, false)</span>
 28: #end

25..28: A way to create extra <div>'s when NOT IN EDIT MODE: !$inEditMode short notation ($inEditMode is not true)

 29:   <div class="#if($xcontext.action == 'view')half #{else}full #{end}column xform">
 30:     <div class='userInfo'>
 31:     #if($xcontext.action == 'view' && $hasEdit)
 32:       <div class='editProfileCategory'><a href="$doc.getURL('edit', 'editor=inline&amp;category=profile')"><span class='hidden'>$services.localization.render('platform.core.profile.category.profile.edit')</span></a></div>
 33:     #end
 34:     ## Please do not insert extra empty lines here (as it affects the validity of the rendered xhtml)
 35:     #set ($sectionsObject = $sheetDocument.getObject($sectionsObjectClassName))
 36:     #set ($sectionsToDisplayString = $sectionsObject.getProperty('sections').value)
 37:     #set ($sectionsToDisplay = $sectionsToDisplayString.split('\s+'))
 38:     #foreach ($sectionId in $sectionsToDisplay)

29 In this case they define a div with class 'xform' 

  • class="half xform" -> in view mode
  • class="full xform" -> not in view mode

32 href=... In view mode a url for the pencil icon is generated

  • $doc.getURL('edit', 'editor=inline&amp;category=profile')

the getURL() can be used in a few way's when executed in document context ($doc)

  • getURL() will give a url for document in normal view mode
  • getURL('MODE') will give a url in the specified mode
  • getURL('MODE', "request params") will give a url in specified mode and adding request parameters

on XWiki.GerritjanKoekkoek it will give :
http://cdlsworld.devxwiki.com/xwiki/bin/edit/XWiki/GerritjanKoekkoek?editor=inline&category=profile

notice localization is not specifically using a translation document: this means they depend on de translation documents specified in the XWiki ADMIN section.

35,36 and 37 : The sheetDocument is read for a object of class $sectionsObject... Then a label property is read
Then the string holding section id's is Split 

  • $sectionsToDisplayString.split('\s+')
    This is a nice velocity string tool that will give you a Array easy to use in a FOREACH. The string mist have seperator characters: example: "word1,word2,word3" the separator character = ',' (I assume '\s+' escaped = ',' )

38: FOREACH section

 39:       #set ($sectionObject = $sheetDocument.getObject($sectionObjectClassName, 'id', $sectionId))
 40:       #set ($sectionName = "$!sectionObject.getProperty('name').value")
 41:       ## The section name will be evaluated. The admin can specify a static string or a call to $msg(...) to provide internationalization support.
 42:       #set ($sectionName = "#evaluate($!sectionName)")
 43:       ## If there is no section name specified, use the default translations prefix for the user profile, maybe we get lucky.
 44:       #if ("$!sectionName" == '')
 45:         #set ($sectionName = $services.localization.render("platform.core.profile.section.${sectionId}"))
 46:       #end
 47:       ## If that does not work either, just display the sectionID.
 48:       #if ("$!sectionName" == "platform.core.profile.section.${sectionId}")
 49:         #set ($sectionName = $sectionId)
 50:       #end
 51:       #set ($sectionPropertiesString = $sectionObject.getProperty('properties').value)
 52:       #set ($sectionProperties = $sectionPropertiesString.split('\s+'))
 53:       #if ($sectionProperties && $sectionProperties.size() > 0)

39: For each section get the 'section' object (Note that sections are a way to group properties in sections)
42: I do not exactly know what #evaluate will do? But I assume it will look for a translationkey
43, 44, 45, 46, 47, 48, 49 and 50 : if evaluate did do nothing fall back on property translation key's

51 and 52: get the properties in this section and Split them into an Array

53: check if the array holds a element: notice the .size() method

 53:       #if ($sectionProperties && $sectionProperties.size() > 0)
 54:         <h1>$sectionName</h1>
 55:         <dl>
 56:         #foreach ($sectionProperty in $sectionProperties)
 57:           #set ($vCardData = $sectionProperty.split(':'))
 58:           #set ($vCardProperty = '')
 59:           #if ($vCardData.size() == 2)
 60:             #set ($vCardProperty = $vCardData[0])
 61:             #set ($sectionProperty = $vCardData[1])
 62:           #end
 63:           #if ("$!sectionProperty" != '' && $xwikiUsersClass.get($sectionProperty))
 64:           <dt class='label'>
 65:             <label #if($inEditMode)for="${xwikiUsersClassName}_${obj.number}_${sectionProperty}"#{end}>$doc.displayPrettyName("${sectionProperty}")</label>
 66:           </dt>
 67:           <dd #if("$!vCardProperty" != '' && !$inEditMode)class="$vCardProperty"#{end}>$doc.display($sectionProperty)</dd>
 68:           #end
 69:         #end
 70:         </dl>
 71:       #end
 

53: we explained already in previous code block

55: Notice the usage of the <dl>: it is the XWiki way of creating forms with field, replace with your own preferred way

56: Foreach property

57..62 The section stuff makes it a bit elaborate as the sectional defined by pair of strings 'string':'string' hence the split function.

Line 63 to 68 contain the most interesting part of this instruction

63 and 68: Evaluate if the property defined in the section is a existing class property of $xwikiUsersClass

64,66 and 67: Again the XWiki way of creating a form; each field is wrapped in a <dl>; the label as a <dt> and the datafield as a <dd>

65:  and The label will be in two versions (In this example I will assume the property name = 'name' and assume we have 1 object on the page so $obj.number = 0. Notice that when, by accident there are multiple objects, the html will still have valid labels

  • <label for="XWiki.XWikiUsers_0_name>$doc.displayPrettyName("name")</label> -> in edit mode
  • <label>$doc.displayPrettyName("name")</label> -> in view mode

67: The actual data binding through the $doc.display() method
$doc.display($sectionProperty)

It is as simple as this...

Message stream on the right

As you can see XWiki shows a message stream in the user profile page.
I do not prefer this as I believe this deserves a separate GUI and does not have much VIEW/EDIT characteristics

 75:   #if (!$inEditMode)
 76:     <div class='half column'>
 77:       #set ($isMessageStreamActive = $services.messageStream.isActive())
 78:       #if ($isMessageStreamActive && !$isGuest)
 79:       <div class='userMessage profile-section'>
 80:         <h1>$services.localization.render('platform.core.profile.section.sendMessage')</h1>
 81:
 82:           {{messageSender /}}
 83:
 84:       </div>
 85:       #end
 86:       <div class='userRecentChanges'>
 87:       #if ($xcontext.user == $doc.fullName)
 88:         <h1>$services.localization.render('platform.core.profile.section.activity')</h1>
 89:       #else
 90:         <h1>$services.localization.render('platform.core.profile.section.activityof', [$xwiki.getUserName($doc.fullName, false)])</h1>
 91:         #if ($hasWatch)
 92:         $xwiki.ssx.use('XWiki.XWikiUserProfileSheet')##
 93:         <div class='activity-follow'>
 94:           #set ($xredirect = $doc.getURL($xcontext.action, $request.queryString))
 95:           #if ($services.watchlist.isWatched(${doc.prefixedFullName}, "USER"))
 96:           <span class='following'>$services.localization.render('xe.activity.messages.following')</span>
 97:           <a class='action unfollow' href="$doc.getURL('view', "xpage=watch&amp;do=removeuser&amp;xredirect=${escapetool.url($xredirect)}")">$services.localization.render('xe.activity.messages.unfollow')</a>
 98:           #else
 99:           <a class='action follow' href="$doc.getURL('view', "xpage=watch&amp;do=adduser&amp;xredirect=${escapetool.url($xredirect)}")">$services.localization.render('xe.activity.messages.follow')</a>
100:           #end
101:         </div>
102:         #end
103:       #end
104: {{/html}}
105:
106: {{activity authors="${doc.prefixedFullName}" /}}
107:
108: {{html clean='false'}}
109:       </div>
110:     </div>
111:   #end
112:   <div class='clearfloats'>&nbsp;</div>

75, 76, 110, 111 and 112: If in VIEW mode fill the right half of the screen with a <div class="half column"> and clear the float

104, 105, 106, 107, 108: Note this bit strange code: the {{HTML}} macro is interrupted at 104 without closing div's and #if. This allows the use of a macro that may not be enclosed inside a HTML macro; in this case 106: {{activity}}

I propose you look at the code for learning yourself, but will not further 

113: #if(!$inEditMode)
114:   ## Close the vcard
115:   </div>
116: #end
117: {{/html}}##
118: #end
119: {{/velocity}}

113..116: Close the card that was opened in 26
117: close the html-macro
118 the end statement of the #if($obj)

Summary

The XWiki platform makes creating a Form based application very simple.
It is building HTML forms for you, you only need to set the definitions !

When the goal of the application is a Form (Sheet) for a single record: e.g. a user-profile, a person with a syndrome you can use the following framework:

## Define the class you are going to use in the sheet
#set($classForThisForm = 'WaihonaCode.personWithSyndromeClass')
## On your current document ('$doc') make sure the right object is used when using API
#set($discard = $doc.use($classForThisForm) )

## Set the extended styling (note that styling of the skin/colortheme is already doing a lot)
#set($discard = $xwiki.ssx.use('yourSheetName')) ## Note that we need a full document reference notation of 'yourSheetName'
## Set, if needed, your javascript
#set($discard = $xwiki.jsx.use('yourSheetName'))## Note that this assumes your sheet is holding the velocity AND the JavaScript
## set the translation file to be used; so your form will be in any language
##   Note that a translation file has a very strong relationship with your class, hence the naming convention
#set($discard = $services.localization.use('document', 'WaihonaCode.personWithSyndromeClassTranslations'))

#if($doc.getObject())
 ## In this part put the code of the below code examples to define which data attributes you want to make input controls for    
 ## It will automatically bind to the backend
  ...
#else
 {{info}}
    The page you are looking at does not have a object of $classForThisForm.
    This page might be the sheet holding the code, if you are a programmer check by editing this page
 {{/info}}
#end

Notice that we do not need to define any HTML in a sheet; no form, no input, nothing.
When a user clicks the EDIT button the platform will automatically generate all of this!
So when you EDIT a PAGE in the right mode (INLINE) (which will happen automatically if the sheet is connected to a class, see the class setting, binding a sheet to a class).

Where is the SAVE button?? Notice that these buttons (SAVE & VIEW, SAVE & CONTINUE, PREVIEW) are not in the sheet.
They are generated by the server for us.

Note that we talk about documentReferences; 


$services.model.createDocumentReference(String wiki,String space, String page)

Create a Document Reference from a passed wiki, space and page names, which can be empty strings or null in which case they are resolved using the resolver.
Parameters:
wiki - the wiki reference name to use (can be empty or null)
space - the space reference name to use (can be empty or null)
page - the page reference name to use (can be empty or null)
So when using single quotes (') it means you must provide a space (or nested parent page) and the target page; e.g. 'XWiki.testNoesteClass'
Note that we do not need to specify the wiki, as we are working in single wiki mode. Do not get confused by the space (= nested page) called 'XWiki'

Thus the most simple sheet we can have would look like:

1: #set($class = $doc.getObject('XWiki.testNoesteClass').xWikiClass)
2: #foreach($prop in $class.properties)
3: $doc.displayPrettyName($prop.prettyName, false, false)
4: $doc.display($prop.getName())
5: #end

1: Get a object that has the property info by using the .xwikiClass method

2 and 5: The foreach gives us very little control about order of the properties (this is why XWiki used the sections) but you could also hard code it:

1: #set($thisClassName = 'XWiki.testNoesteClass')
2: #set($thisClass = $xwiki.getClass($thisClassName))
3: ## First we want 'name' property
4: #set($property = $thisClass.get('name')
5: $doc.displayPrettyName($property.prettyName, false, false)
6: $doc.display($property.getName())
7: ## Second we want 'descrition' property, the typo is my fault ;-)
8: #set($property = $thisClass.get('descrition')
9: $property.prettyName
10: $doc.display($property.getName())

2: This will give you a object of the class defenition: the object has 'name', 'prettyName' but also information about the type of the property (text, number, date/time, list (static or database), users, groups etcetera)
4 and 8: these will give you control about which property you want to show

As you can see each property is linked to the user interface by 3 lines of code: (4,5 and 6) and (8,9 and 10)
Also very important is the use of the $doc object. This object ensures that the the form knows where the data resides.
10: $doc.display($property.getName()) the property is stored on the current page the user is viewing (look at the url)

Last we need to improve the styling of the form:
That is why we add a few html tags;

1 <div class="form-group">
2  <label for="XWiki.testNoesteClass_$property.getName()">$escapetool.xml($doc.displayPrettyName($property.getName(), false, false))</label>
3  $doc.display($property.getName())
4 </div>

 

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