Last modified by Raluca Stavro on 2022/05/26

Show last authors
1 {{box cssClass="floatinginfobox" title="**Contents**"}}
2 {{toc/}}
3 {{/box}}
4
5 Explains how to implement adding and removing objects in XWiki when editing application entries.
6
7 We are going to create a simple application built with [[Application Within Minutes>>extensions:Extension.App Within Minutes Application]], called 'Stores'. Since a store can have many headquarters, what we want is to allow the user to add / remove headquarters when editing a Stores entry.
8
9 = Step 1. Create the Stores Application =
10
11 The first step is to create the application with AWM. So, create an app called "Stores" and when designing the form use one Short Text field and one Long Text field, as shown in the following screenshot:
12
13 {{image reference="stores-form.png"/}}
14
15 = Step 2: Modify the Stores Class Sheet =
16
17 Let's now modify ##Stores.Code.StoresSheet## to implement dynamically adding / removing headquarters. Modify the default content to be:
18
19 == For latest versions of XWiki ==
20
21 {{code}}
22 {{velocity}}
23 #macro(cleanClassname $classname)$!{escapetool.xml($classname.substring($classname.indexOf('.')).substring(1))}#end
24
25 {{html clean="false"}}
26 #set($discard = $xwiki.jsfx.use('js/xwiki/editors/dataeditors.js', true))
27 #set($discard = $xwiki.ssfx.use('js/xwiki/editors/dataeditors.css', true))
28 #set($class = 'Stores.Code.StoresClass')
29 #set($discard = $doc.use($class))
30 <div class="xform">
31 <div id="add_xobject" class="add_xobject">
32 <div id="xclass_Stores.Code.StoresClass" class="xclass collapsable">
33 <div id="xclass_${escapetool.xml($class)}_content" class="xclass-content">
34 <div>
35 #foreach($obj in $doc.getObjects($class))
36 #set($objNumber = $obj.number)
37 #set($index = $foreach.index + 1)
38 <div id="xobject_${class}_${objNumber}" class="xobject collapsable">
39 #if($xcontext.action == 'edit')
40 <div class="xobject-title">
41 <h3>#cleanClassname(${class}) <span class="editor-objectNumber">${obj.number}</span>#if ($firstField): $firstFieldSummary#end
42 <a href="$doc.getURL('objectremove', "form_token=$!{services.csrf.token}&amp;classname=${escapetool.url($class)}&amp;classid=${obj.number}&amp;xredirect=${escapetool.url($doc.getURL('edit'))}")" class="xobject-action delete" title="$services.localization.render('core.editors.object.removeObject.tooltip')">$services.localization.render('core.editors.object.removeObject')</a>
43 </h3>
44 </div>
45 #end
46 <div class="xobject-content">
47 <dl>
48 <dt><label#if ($xcontext.action == 'edit') for="${class}_${objNumber}_0_name"#end>$escapetool.xml($doc.displayPrettyName('name', false, false))</label></dt>
49 <dd>{{/html}}$doc.display('name', $obj){{html clean="false"}}</dd>
50 <dt><label#if ($xcontext.action == 'edit') for="${class}_${objNumber}_0_address"#end>$escapetool.xml($doc.displayPrettyName('address', false, false))</label></dt>
51 <dd>{{/html}}$doc.display('address', $obj){{html clean="false"}}</dd>
52 </dl>
53 </div>
54 </div>
55 #end
56 #if($xcontext.action == 'edit')
57 #set($xredirect = $escapetool.url(${doc.getURL('edit', "template=$!{request.template}&title=$!{request.title}&parent=$!{request.parent}")}))
58 <div id="add_xobject_${escapetool.xml($class)}" class="add_xobject">
59 <div id="add_xobject_${escapetool.xml($class)}" class="add_xobject" style="display: block;">
60 <div id="add_xobject_${escapetool.xml($class)}_title" class="add_xobject-title" style="display: block;">
61 <a href="$doc.getURL('edit', "xpage=editobject&amp;xaction=addObject&amp;form_token=$!{services.csrf.token}&amp;classname=$escapetool.url(${class})&amp;xredirect=${xredirect}")" class="xobject-add-control" title="$services.localization.render('core.editors.object.add.label')">$services.localization.render('core.editors.object.add.label')</a>
62 </div>
63 </div>
64 </div>
65 #end
66 </div>
67 </div>
68 </div>
69 </div>
70 </div>
71 {{/html}}
72 {{/velocity}}
73 {{/code}}
74
75 == For older versions up to 12.3 ==
76
77 {{code}}
78 {{velocity}}
79 {{html wiki="true"}}
80 #set($class = 'Stores.Code.StoresClass')
81 #set ($discard = $doc.use($class))
82 (% class="xform" %)
83 (((
84 #foreach($obj in $doc.getObjects($class))
85 #set($objNumber = $obj.number)
86 #set($index = $foreach.index + 1)
87 (% id="xwikiobjects" class="xclass" %)(((
88 (% class="xobject" %)(((
89 #if($xcontext.action == 'edit')
90 (% class="xobject-title" %)(((
91 <h3>Headquarter $index
92 <a href="$doc.getURL('objectremove', "form_token=$!{services.csrf.getToken()}&amp;classname=${escapetool.url($class)}&amp;classid=${objNumber}&amp;xredirect=${escapetool.url($doc.getURL('edit'))}")" class="xobject-action delete" title="$services.localization.render('core.editors.object.removeObject.tooltip')">$services.localization.render('core.editors.object.removeObject')</a>
93 </h3>
94 )))
95 #end
96 (% class="xobject-content" %)(((
97 ; <label#if ($xcontext.action == 'edit') for="${class}_${objNumber}_0_name"#end>$escapetool.xml($doc.displayPrettyName('name', false, false))</label>
98 : $doc.display('name', $obj)
99 ; <label#if ($xcontext.action == 'edit') for="${class}_${objNumber}_0_address"#end>$escapetool.xml($doc.displayPrettyName('address', false, false))</label>
100 : $doc.display('address', $obj)
101 )))
102 )))
103 )))
104 #end
105
106 #if($xcontext.action == 'edit')
107 (% id="add_xobject_${escapetool.xml($class)}" class="add_xobject" style="display: block;" %)(((
108 (% id="add_xobject_${escapetool.xml($class)}_title" class="add_xobject-title" style="display: block;" %)((( ## Have to overwrite a CSS rule from dataeditors.css
109 <a href="$doc.getURL('edit', "xpage=editobject&amp;xaction=addObject&amp;className=$escapetool.url(${class})&amp;xredirect=$escapetool.url(${doc.getURL('edit')})")" class="xobject-add-control" title="$services.localization.render('core.editors.object.add.label')">$services.localization.render('core.editors.object.add.label')</a>
110 )))
111 )))
112 $xwiki.jsfx.use('js/xwiki/editors/dataeditors.js', true)##
113 $xwiki.ssfx.use('js/xwiki/editors/dataeditors.css', true)##
114 #end
115 )))
116 {{/html}}
117 {{/velocity}}
118 {{/code}}
119
120 {{warning}}
121 Note that if you edit the Stores Application after making the changes in ##Stores.Code.StoresSheet##, these changes will be lost. You will have to redo the steps above if you want to set back the add / remove objects feature in the application.
122 {{/warning}}
123
124 You're all set! Let's now try it!
125
126 = Step 3: Create an entry in the Stores Application =
127
128 Navigate back to the Stores Application and Create an entry. You should now have the options to expand the object that was added by default, to remove this object and to add a new object. When loading the edit form, the objects are collapsed because this is the default behavior of the dataeditors application that we reused to make our feature work.
129
130 {{image reference="stores-results1.png"/}}
131
132 This is what the form looks like when you add a new object :
133
134 {{image reference="stores-results2.png"/}}
135
136 And when viewing the page:
137
138 {{image reference="stores-results3.png"/}}
139
140 = A simplified version of edit form =
141
142 {{warning}}
143 This code was tested on versions up to 12.3.
144 {{/warning}}
145
146 You can personalize even more your application by updating translations, by changing the styles, by removing the elements that don't suit your use case (like the expand / collapse feature, for example).
147
148 == The AWM version ==
149
150 This is a simplified UI of an application built with AWM, that doesn't contain icons or the expand/collapse feature, but contains the basic elements that are needed in order to make it possible to add / remove objects in the application entries. This code is meant to be put in the Stores Class Sheet (##Stores.Code.StoresSheet## document).
151
152 {{code}}
153 {{velocity}}
154 {{html wiki="true"}}
155 #set($class = 'Stores.Code.StoresClass')
156 (% class="xform" %)
157 (((
158 #foreach($obj in $doc.getObjects($class))
159 #set($objNumber = $obj.number)
160 (% id="xwikiobjects" class="xclass" %)(((
161 (% class="xobject" %)(((
162 <span class="xobject-title"></span> ## We need this line, because we base our code on dataeditors.js and for now, this class is mandatory
163
164 ; <label#if ($xcontext.action == 'edit') for="${class}_${objNumber}_0_name"#end>$escapetool.xml($doc.displayPrettyName('name', false, false))</label>
165 : $doc.display('name', $obj)
166 ; <label#if ($xcontext.action == 'edit') for="${class}_${objNumber}_0_address"#end>$escapetool.xml($doc.displayPrettyName('address', false, false))</label>
167 : $doc.display('address', $obj)
168
169 #if($xcontext.action == 'edit')
170 <a href="$doc.getURL('objectremove', "form_token=$!{services.csrf.getToken()}&amp;classname=${escapetool.url($class)}&amp;classid=${objNumber}&amp;xredirect=${escapetool.url($doc.getURL('edit'))}")" class="xobject-action delete" title="$services.localization.render('core.editors.object.removeObject.tooltip')">$services.localization.render('core.editors.object.removeObject')</a>
171 #end
172 )))
173 )))
174 #end
175
176 #if($xcontext.action == 'edit')
177 $xwiki.jsfx.use('js/xwiki/editors/dataeditors.js', true)##
178 <a href="$doc.getURL('edit', "xpage=editobject&amp;xaction=addObject&amp;className=$escapetool.url(${class})&amp;xredirect=$escapetool.url(${doc.getURL('edit')})")" class="xobject-add-control button" title="$services.localization.render('core.editors.object.add.label')">$services.localization.render('core.editors.object.add.label')</a>
179 #end
180 )))
181 {{/html}}
182 {{/velocity}}
183 {{/code}}
184
185 == The non-AWM version ==
186
187 Now, let's move outside AWM.
188 If you have an application that was not built with AWM, these are the elements that you need to add in your sheet if you want to enable the option to add / remove objects :
189
190 {{code}}
191 {{velocity}}
192 ## Defining a variable that stores the name of the XClass that is part of MyApp
193 #set($class = 'MyApp.MyClass')
194
195 ## Iterating over all objects of this class
196 #foreach($obj in $doc.getObjects($class))
197
198 ## Container used for detecting objects of a certain class
199 (% id="xwikiobjects" class="xclass" %)(((
200
201 ## Container used for detecting individual objects
202 (% class="xobject" %)(((
203
204 ## We need this element, for now, because it is used by dataeditors.js and you would get JS errors without it
205 (% class="xobject-title" %)
206
207 ## Displaying the properties of the current object
208 $doc.display('my_prop_name', $obj)
209 ...
210
211 ## The DELETE object button, displayed in edit mode
212 #if($xcontext.action == 'edit')
213 {{html}}
214 <a href="$doc.getURL('objectremove', "form_token=$!{services.csrf.getToken()}&amp;classname=${escapetool.url($class)}&amp;classid=${obj.number}&amp;xredirect=${escapetool.url($doc.getURL('edit'))}")" class="xobject-action delete" title="$services.localization.render('core.editors.object.removeObject.tooltip')">$services.localization.render('core.editors.object.removeObject')</a>
215 {{/html}}
216 #end
217 )))
218 )))
219 #end
220
221 #if($xcontext.action == 'edit')
222 ## Adding dataeditors.js to the page
223 $xwiki.jsfx.use('js/xwiki/editors/dataeditors.js', true)##
224
225 ## The ADD object button
226 {{html}}
227 <a href="$doc.getURL('edit', "xpage=editobject&amp;xaction=addObject&amp;className=$escapetool.url(${class})&amp;xredirect=$escapetool.url(${doc.getURL('edit')})")" class="xobject-add-control" title="$services.localization.render('core.editors.object.add.label')">$services.localization.render('core.editors.object.add.label')</a>
228 {{/html}}
229 #end
230 {{/velocity}}
231 {{/code}}

Get Connected