Changes for page Create Application

Last modified by Ludovic Dubost on 2024/07/22 15:51

From version 3.1
edited by Ludovic Dubost
on 2014/11/18 12:31
Change comment: Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui-6.3]
To version 8.1
edited by Ludovic Dubost
on 2024/07/22 15:51
Change comment: Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui/16.5.0]

Summary

Details

Page properties
Syntax
... ... @@ -1,1 +1,1 @@
1 -XWiki 2.0
1 +XWiki 2.1
Content
... ... @@ -1,102 +1,158 @@
1 -{{include reference="AppWithinMinutes.WizardStep"/}}
1 +{{include reference="AppWithinMinutes.VelocityMacros"/}}
2 2  
3 +{{velocity}}
4 +#if ($request.wizard == 'true')
5 + {{include reference="AppWithinMinutes.WizardStep"/}}
6 +#end
7 +{{/velocity}}
8 +
9 +{{template name="locationPicker_macros.vm" /}}
10 +
3 3  {{velocity output="false"}}
4 -#macro(showStep)
5 - {{html wiki="true"}}
6 - #appWizardHeader(1)
7 - (% class="wizard-help" %)
8 - (((
9 - **$services.localization.render('platform.appwithinminutes.wizardStepHelpTitle')** $services.localization.render('platform.appwithinminutes.wizardStepHelpDescription')
10 - (% class="steps vertical" %)
11 - #foreach($index in [1, 2, 3])
12 - * (% class="number" %)$index(%%) (% class="name" %)$services.localization.render("platform.appwithinminutes.wizardStep${index}Name")(%%)
13 - (% class="description" %)$services.localization.render("platform.appwithinminutes.wizardStep${index}Description")
14 - #end
15 - )))
16 - <form action="" method="post" class="xform wizard-body">
17 - ; <label for="appName">$services.localization.render('platform.appwithinminutes.appNameLabel')</label>
18 - (% class="xHint" %)$services.localization.render('platform.appwithinminutes.appNameHint')
19 - : <input type="text" id="appName" name="appName" />
20 - #appWizardFooter(1)
12 +#macro (showStep)
13 + #appWizardHeader('name')
14 + <div class="wizard-help">
15 + <p>
16 + <strong>$services.localization.render('platform.appwithinminutes.wizardStepHelpTitle')</strong>
17 + $services.localization.render('platform.appwithinminutes.wizardStepHelpDescription')
18 + </p>
19 + <ul class="steps vertical">
20 + #foreach($step in $awmSteps)
21 + <li>
22 + <span class="btn btn-xs number">$mathtool.add($foreach.index, 1)</span>
23 + <span class="name">$services.localization.render("appWithinMinutes.wizardStep.${step}.name")</span>
24 + <span class="description">$services.localization.render("appWithinMinutes.wizardStep.${step}.description")</span>
25 + </li>
26 + #end
27 + </ul>
28 + </div>
29 + <form action="$doc.getURL()" method="post" class="xform wizard-body">
30 + <fieldset>
31 + #locationPicker({
32 + 'id': 'app',
33 + 'title': {
34 + 'label': 'platform.appwithinminutes.appNameLabel',
35 + 'hint': 'platform.appwithinminutes.appNameHint',
36 + 'name': 'appName'
37 + },
38 + 'preview': {
39 + 'label': 'appWithinMinutes.createApp.location.label',
40 + 'hint': 'appWithinMinutes.createApp.location.hint'
41 + },
42 + 'parent': {
43 + 'label': 'appWithinMinutes.createApp.parent.label',
44 + 'hint': 'appWithinMinutes.createApp.parent.hint',
45 + 'name': 'appParentReference',
46 + 'reference': $doc.documentReference.wikiReference,
47 + 'placeholder': 'appWithinMinutes.createApp.parent.placeholder'
48 + }
49 + })
50 + <div class="appName-preview"></div>
51 + #appWizardFooter(1)
52 + </fieldset>
21 21   </form>
22 - {{/html}}
23 23  #end
24 24  
25 -#macro(getAppDescriptor $appName)
26 - #set($appDescriptorClassName = 'AppWithinMinutes.LiveTableClass')
27 - #set($appDescriptorStatement = "from doc.object($appDescriptorClassName) as obj where doc.space = :space")
28 - #set($appDescriptors = $services.query.xwql($appDescriptorStatement).bindValue('space', $appName).execute())
29 - #if($appDescriptors.size() > 0)
30 - #set($appDescriptor = $xwiki.getDocument($appDescriptors.get(0)))
31 - #end
32 -#end
33 -
34 -#macro(processStep)
56 +#macro (processStep)
35 35   ## Check if the application already exists.
36 - #set($appName = $request.appName)
37 - #getAppDescriptor($appName)
38 - #if($appDescriptor)
39 - ## Edit an existing application. Use the configured class name.
40 - #set($classStringRef = $appDescriptor.getObject($appDescriptorClassName).getProperty('class').value)
41 - ## The class string reference is relative to the document holding the application descriptor.
42 - #set($classRef = $services.model.resolveDocument($classStringRef, 'explicit', $appDescriptor.documentReference))
58 + #getAppReference
59 + #getAppDescriptor($appReference)
60 + #if ($appDescriptor)
61 + ## Edit an existing application.
62 + #getAppClassReference($appDescriptor)
63 + #set ($appClassRef = $classReference)
43 43   #else
44 44   ## Create a new application. Use the default class name.
45 - #set($className = "#toXMLName($appName)")
46 - #set($classRef = $services.model.createDocumentReference($doc.wiki, "${className}Code", "${className}Class"))
66 + #set ($appCodeRef = $services.model.createSpaceReference('Code', $appReference))
67 + #set ($appClassRef = $services.model.createDocumentReference("$!{appReference.name}Class", $appCodeRef))
47 47   #end
48 - #set($queryString = 'wizard=true')
49 - #if(!$xwiki.exists($classRef))
50 - #set($classTitle = "$appName Class")
51 - #set($appHomeRef = $services.model.createDocumentReference($doc.wiki, $appName, 'WebHome'))
52 - #set($classParent = $services.model.serialize($appHomeRef))
53 - #set($queryString = "$queryString&editor=inline&template=AppWithinMinutes.ClassTemplate&parent=$escapetool.url($classParent)&title=$escapetool.url($classTitle)&AppWithinMinutes.MetadataClass_0_dataSpaceName=$escapetool.url($appName)")
69 + #set ($queryString = {'wizard': true})
70 + #if (!$xwiki.exists($appClassRef))
71 + #set ($appHomeRef = $services.model.resolveDocument('', 'default', $appReference))
72 + #set ($discard = $queryString.putAll({
73 + 'form_token': $services.csrf.getToken(),
74 + 'template': 'AppWithinMinutes.ClassTemplate',
75 + 'parent': $services.model.serialize($appHomeRef),
76 + 'title': "$appReference.name Class"
77 + }))
54 54   #end
55 - $response.sendRedirect($xwiki.getURL($classRef, 'edit', $queryString))
79 + $response.sendRedirect($xwiki.getURL($appClassRef, 'edit', $escapetool.url($queryString)))
56 56  #end
57 57  
58 -#macro(validateAppName $appName)
59 - #getAppDescriptor("$!appName")
60 - #if($appDescriptor)
61 - ## Edit an existing application.
62 - #set($appHomeRef = $appDescriptor.documentReference)
63 - #set($classStringRef = $appDescriptor.getObject($appDescriptorClassName).getProperty('class').value)
64 - ## The class string reference is relative to the document holding the application descriptor.
65 - #set($appClassRef = $services.model.resolveDocument($classStringRef, 'explicit', $appDescriptor.documentReference))
82 +#macro (validateAppName)
83 + #getAppReference
84 + #if (!$appReference)
85 + <span class="xErrorMsg">$services.localization.render('platform.appwithinminutes.appNameEmptyError')</span>
66 66   #else
67 - ## Create a new application.
68 - #set($className = "#toXMLName($appName)")
69 - #if($className == '')
70 - (% class="xErrorMsg" %)$services.localization.render('platform.appwithinminutes.appNameInvalidClassNameError')
87 + #getAppDescriptor($appReference)
88 + #if ($appDescriptor)
89 + ## Edit an existing application.
90 + #getAppClassReference($appDescriptor)
91 + #set ($appClassRef = $classReference)
92 + #else
93 + ## Create a new application.
94 + #set ($appCodeRef = $services.model.createSpaceReference('Code', $appReference))
95 + #set ($appClassRef = $services.model.createDocumentReference("$!{appReference.name}Class", $appCodeRef))
71 71   #end
72 - #set($appHomeRef = $services.model.createDocumentReference($doc.wiki, $appName, 'WebHome'))
73 - #set($appClassRef = $services.model.createDocumentReference($doc.wiki, "$!{className}Code", "$!{className}Class"))
97 + <dl>
98 + <dt>$services.localization.render('platform.appwithinminutes.appNamePreviewHomePageUrlLabel')</dt>
99 + <dd><pre>$!escapetool.xml($xwiki.getDocument($appReference).externalURL)</pre></dd>
100 + <dt>$services.localization.render('platform.appwithinminutes.appNamePreviewCodeSpaceLabel')</dt>
101 + <dd>#hierarchy($appClassRef.parent)</dd>
102 + </dl>
103 + #set ($appHomeRef = $services.model.resolveDocument('', 'default', $appReference))
104 + #if ($appDescriptor || $xwiki.exists($appHomeRef) || $xwiki.exists($appClassRef))
105 + <div class="box warningmessage">
106 + $services.localization.render('platform.appwithinminutes.appNameIsUsedWarning')
107 + </div>
108 + #end
109 + #if (!$services.security.authorization.hasAccess('script', $xcontext.userReference, $appHomeRef))
110 + <div class="box errormessage">
111 + $escapetool.xml($services.localization.render('platform.appwithinminutes.appHomePageNoScriptRight'))
112 + </div>
113 + #end
74 74   #end
75 - #set($appHomeURL = $stringtool.removeEnd($xwiki.getDocument($appHomeRef).getExternalURL(), 'WebHome'))
76 - ; $services.localization.render('platform.appwithinminutes.appNamePreviewHomePageUrlLabel')
77 - : {{{$!appHomeURL}}}
78 - ; $services.localization.render('platform.appwithinminutes.appNamePreviewDataSpaceLabel')
79 - : {{{$appHomeRef.wikiReference.name}}} » {{{$appHomeRef.lastSpaceReference.name}}}
80 - ; $services.localization.render('platform.appwithinminutes.appNamePreviewCodeSpaceLabel')
81 - : {{{$appClassRef.wikiReference.name}}} » {{{$appClassRef.lastSpaceReference.name}}}
82 - ; $services.localization.render('platform.appwithinminutes.appNamePreviewClassReferenceLabel')
83 - : {{{$appClassRef.wikiReference.name}}} » {{{$appClassRef.lastSpaceReference.name}}} » {{{$appClassRef.name}}}
84 - #if($appDescriptor || $xwiki.exists($appHomeRef) || $xwiki.exists($appClassRef))
115 +#end
85 85  
86 - {{warning}}$services.localization.render('platform.appwithinminutes.appNameIsUsedWarning'){{/warning}}
117 +#macro (getAppReference)
118 + #if ($request.resolve == 'true')
119 + #set ($appReference = $services.model.resolveSpace($request.appName))
120 + #elseif ("$!request.appName" != '')
121 + #set ($parentReference = $doc.documentReference.wikiReference)
122 + #if ("$!request.appParentReference" != '')
123 + #set ($parentReference = $services.model.resolveSpace($request.appParentReference))
124 + #end
125 + #set ($appReference = $services.model.createSpaceReference($request.appName, $parentReference))
126 + #else
127 + #set ($appReference = $NULL)
87 87   #end
88 88  #end
130 +
131 +#macro (getAppDescriptor $appReference)
132 + #set ($appDescriptorClassName = 'AppWithinMinutes.LiveTableClass')
133 + #set ($appDescriptorStatement = "from doc.object($appDescriptorClassName) as obj where doc.space = :space")
134 + #set ($localSpaceReference = $services.model.serialize($appReference, 'local'))
135 + #set ($appDescriptors = $services.query.xwql($appDescriptorStatement).bindValue('space', $localSpaceReference).execute())
136 + #if ($appDescriptors.size() > 0)
137 + #set ($appDescriptor = $xwiki.getDocument($appDescriptors.get(0)))
138 + #else
139 + #set ($appDescriptor = $NULL)
140 + #end
141 +#end
89 89  {{/velocity}}
90 90  
91 91  {{velocity}}
92 -#if("$!request.appName" != '')
93 - #if($xcontext.action == 'get')
94 - #validateAppName($request.appName)
145 +{{html clean="false"}}
146 +#if ("$!request.appName" != '')
147 + #if ($xcontext.action == 'get')
148 + #validateAppName
95 95   #else
96 96   ## CSRF protection is not needed because this step only redirects to the next one passing data in the query string.
97 - #processStep()
151 + #processStep
98 98   #end
99 -#else
100 - #showStep()
153 +#elseif ($request.wizard == 'true')
154 + #showStep
155 + #set ($displayDocExtra = false)
101 101  #end
157 +{{/html}}
102 102  {{/velocity}}
XWiki.JavaScriptExtension[0]
Code
... ... @@ -1,89 +1,51 @@
1 -var XWiki = (function (XWiki) {
1 +require(['jquery', 'xwiki-form-validation-async'], function($) {
2 + var appNameInput = $('input[name="appName"]');
3 + var appParentInput = $('input[name="appParentReference"]');
4 + var preview = $('.appName-preview');
2 2  
3 -XWiki.DeferredUpdater = Class.create({
4 - initialize : function(updatable) {
5 - this.elapsedHandler = updatable.onUpdate.bind(updatable);
6 - this.updatable = updatable;
7 - },
8 - deferUpdate : function() {
9 - if (this.timer) {
10 - clearTimeout(this.timer);
11 - }
12 - this.timer = setTimeout(this.elapsedHandler, 500);
6 + if (appNameInput.val() === '') {
7 + // We use a function instead of passing directly the promise because we want to avoid the "Uncaught (in promise)"
8 + // error. Basically, we want the rejected promise to be caught.
9 + appNameInput.validateAsync(() => Promise.reject(), 'awm');
13 13   }
14 -});
15 15  
16 -XWiki.AppNameValidator = Class.create({
17 - initialize : function(input, submitButton) {
18 - this.input = input;
19 - this.deferredUpdater = new XWiki.DeferredUpdater(this);
20 - var updateScheduler = this._scheduleUpdate.bindAsEventListener(this);
21 - ['keypress', 'paste', 'cut'].each(function(eventType) {
22 - input.observe(eventType, updateScheduler);
23 - }.bind(this));
12 + var errorMessage = appNameInput.closest('dd').prev('dt').find('.xErrorMsg');
13 + if (!errorMessage.length) {
14 + errorMessage = $(document.createElement('span')).addClass('xErrorMsg').hide().appendTo(errorMessage.addBack());
15 + }
24 24  
25 - this.submitButton = submitButton;
26 - this.submitButton.observe('click', this._onSubmit.bindAsEventListener(this));
27 -
28 - this.preview = new Element('div', {'class': 'appName-preview'});
29 - this.input.insert({after: this.preview});
30 -
31 - var previousDT = input.up('dd').previous();
32 - this.errorMessage = previousDT.down('xErrorMsg');
33 - if (!this.errorMessage) {
34 - this.errorMessage = new Element('span', {'class': 'xErrorMsg'});
35 - previousDT.insert(this.errorMessage.hide());
36 - }
37 - },
38 - _onSubmit : function(event) {
39 - if (!this.input._validated) {
40 - event.stop();
41 - this._scheduleUpdate();
42 - }
43 - },
44 - _scheduleUpdate : function(event) {
45 - if(!this.input._validated || [9, 13, 35, 36, 37, 38, 39, 40].indexOf(event.keyCode) < 0) {
46 - this.input._validated = false;
47 - this.deferredUpdater.deferUpdate();
48 - }
49 - },
50 - _onValidate : function(response) {
51 - this.preview.removeClassName('loading').update(response.responseText);
52 - var error = this.preview.down('.xErrorMsg');
53 - this.input._validated = !error;
54 - this._showError(error ? error.remove().firstChild.nodeValue : '');
55 - },
56 - _showError : function(message) {
17 + var toggleValidationError = function(message) {
57 57   if (message) {
58 - this.input.addClassName('xErrorField').focus();
59 - this.errorMessage.update(message.escapeHTML()).show();
19 + appNameInput.addClass('xErrorField');
20 + errorMessage.text(message).show();
21 + return Promise.reject();
60 60   } else {
61 - this.input.removeClassName('xErrorField');
62 - this.errorMessage.hide();
23 + appNameInput.removeClass('xErrorField');
24 + errorMessage.hide();
63 63   }
64 - },
65 - onUpdate : function() {
66 - if (this.input.value == '') {
67 - this._onValidate({
68 - responseText: '<span class="xErrorMsg">$escapetool.javascript($services.localization.render('platform.appwithinminutes.appNameEmptyError'))</span>'
69 - });
26 + };
27 +
28 + var updatePreview = function(content) {
29 + preview.removeClass('loading').html(content);
30 + var error = preview.find('.xErrorMsg');
31 + return toggleValidationError(error.remove().text());
32 + };
33 +
34 + var fetchPreviewUpdate = function() {
35 + if (appNameInput.val() === '') {
36 + return updatePreview('<span class="xErrorMsg">$escapetool.javascript($services.localization.render("platform.appwithinminutes.appNameEmptyError"))</span>');
70 70   } else {
71 - this.preview.addClassName('loading');
72 - new Ajax.Request('$doc.getURL('get')', {
73 - method: 'get',
74 - parameters: {'appName': this.input.value},
75 - onSuccess: this._onValidate.bind(this)
76 - });
38 + preview.addClass('loading');
39 + return $.get(XWiki.currentDocument.getURL('get'), appNameInput.closest('form').serialize()).then(updatePreview);
77 77   }
78 - }
79 -});
41 + };
80 80  
81 -function init() {
82 - var appNameInput = $('appName');
83 - appNameInput && new XWiki.AppNameValidator(appNameInput, $('wizard-next'));
84 - return !!appNameInput;
85 -}
86 -(XWiki.domIsLoaded && init()) || document.observe('xwiki:dom:loaded', init);
87 -
88 -return XWiki;
89 -}(XWiki || {}));
43 + appNameInput.add(appParentInput).on('input', () => {
44 + appNameInput.validateAsync(fetchPreviewUpdate, 500, 'awm');
45 + }).on('keyup', function(event) {
46 + // Show the error message if the user presses Enter before typing anything.
47 + if (event.which === 13 && appNameInput.val() === '' && !appNameInput.hasClass('xErrorField')) {
48 + appNameInput.validateAsync(fetchPreviewUpdate, 'awm').catch(() => appNameInput.focus());
49 + }
50 + });
51 +});
XWiki.StyleSheetExtension[0]
Code
... ... @@ -24,10 +24,6 @@
24 24   margin-bottom: 1em;
25 25  }
26 26  
27 -.appName-preview dl tt {
28 - color: $theme.textColor;
29 -}
30 -
31 31  .appName-preview dt {
32 32   font-weight: normal;
33 33   margin-top: 1em;
... ... @@ -36,3 +36,8 @@
36 36  .appName-preview dt:after {
37 37   content: ":"
38 38  }
35 +
36 +.appName-preview .breadcrumb {
37 + background-color: transparent;
38 + padding: 0;
39 +}
Content Type
... ... @@ -1,0 +1,1 @@
1 +CSS