Changes for page Create Application

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

From 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]
To version 4.1
edited by Ludovic Dubost
on 2015/09/01 11:46
Change comment: Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui-7.1]

Summary

Details

Page properties
Syntax
... ... @@ -1,1 +1,1 @@
1 -XWiki 2.1
1 +XWiki 2.0
Content
... ... @@ -1,158 +1,103 @@
1 -{{include reference="AppWithinMinutes.VelocityMacros"/}}
1 +{{include reference="AppWithinMinutes.WizardStep"/}}
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 -
11 11  {{velocity output="false"}}
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>
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 + )))
29 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>
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)
53 53   </form>
22 + {{/html}}
54 54  #end
55 55  
56 -#macro (processStep)
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)
57 57   ## Check if the application already exists.
58 - #getAppReference
59 - #getAppDescriptor($appReference)
60 - #if ($appDescriptor)
61 - ## Edit an existing application.
62 - #getAppClassReference($appDescriptor)
63 - #set ($appClassRef = $classReference)
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))
64 64   #else
65 65   ## Create a new application. Use the default class name.
66 - #set ($appCodeRef = $services.model.createSpaceReference('Code', $appReference))
67 - #set ($appClassRef = $services.model.createDocumentReference("$!{appReference.name}Class", $appCodeRef))
45 + #set($className = "#toXMLName($appName)")
46 + #set($classRef = $services.model.createDocumentReference($doc.wiki, "${className}Code", "${className}Class"))
68 68   #end
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 - }))
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)")
78 78   #end
79 - $response.sendRedirect($xwiki.getURL($appClassRef, 'edit', $escapetool.url($queryString)))
55 + $response.sendRedirect($xwiki.getURL($classRef, 'edit', $queryString))
80 80  #end
81 81  
82 -#macro (validateAppName)
83 - #getAppReference
84 - #if (!$appReference)
85 - <span class="xErrorMsg">$services.localization.render('platform.appwithinminutes.appNameEmptyError')</span>
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))
86 86   #else
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))
67 + ## Create a new application.
68 + #set($className = "#toXMLName($appName)")
69 + #if($className == '')
70 + (% class="xErrorMsg" %)$services.localization.render('platform.appwithinminutes.appNameInvalidClassNameError')
96 96   #end
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
72 + #set($appHomeRef = $services.model.createDocumentReference($doc.wiki, $appName, 'WebHome'))
73 + #set($appClassRef = $services.model.createDocumentReference($doc.wiki, "$!{className}Code", "$!{className}Class"))
114 114   #end
115 -#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))
116 116  
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)
86 + {{warning}}$services.localization.render('platform.appwithinminutes.appNameIsUsedWarning'){{/warning}}
128 128   #end
129 129  #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
142 142  {{/velocity}}
143 143  
144 144  {{velocity}}
145 -{{html clean="false"}}
146 -#if ("$!request.appName" != '')
147 - #if ($xcontext.action == 'get')
148 - #validateAppName
92 +#if("$!request.appName" != '')
93 + #if($xcontext.action == 'get')
94 + #validateAppName($request.appName)
149 149   #else
150 150   ## CSRF protection is not needed because this step only redirects to the next one passing data in the query string.
151 - #processStep
97 + #processStep()
152 152   #end
153 -#elseif ($request.wizard == 'true')
154 - #showStep
155 - #set ($displayDocExtra = false)
99 +#else
100 + #showStep()
156 156  #end
157 -{{/html}}
102 +#set($docextras=[])
158 158  {{/velocity}}
XWiki.JavaScriptExtension[0]
Code
... ... @@ -1,51 +1,89 @@
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');
1 +var XWiki = (function (XWiki) {
5 5  
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');
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);
10 10   }
14 +});
11 11  
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 - }
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));
16 16  
17 - var toggleValidationError = function(message) {
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) {
18 18   if (message) {
19 - appNameInput.addClass('xErrorField');
20 - errorMessage.text(message).show();
21 - return Promise.reject();
58 + this.input.addClassName('xErrorField').focus();
59 + this.errorMessage.update(message.escapeHTML()).show();
22 22   } else {
23 - appNameInput.removeClass('xErrorField');
24 - errorMessage.hide();
61 + this.input.removeClassName('xErrorField');
62 + this.errorMessage.hide();
25 25   }
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>');
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 + });
37 37   } else {
38 - preview.addClass('loading');
39 - return $.get(XWiki.currentDocument.getURL('get'), appNameInput.closest('form').serialize()).then(updatePreview);
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 + });
40 40   }
41 - };
42 -
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 - });
78 + }
51 51  });
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 || {}));
XWiki.StyleSheetExtension[0]
Code
... ... @@ -24,6 +24,10 @@
24 24   margin-bottom: 1em;
25 25  }
26 26  
27 +.appName-preview dl tt {
28 + color: $theme.textColor;
29 +}
30 +
27 27  .appName-preview dt {
28 28   font-weight: normal;
29 29   margin-top: 1em;
... ... @@ -32,8 +32,3 @@
32 32  .appName-preview dt:after {
33 33   content: ":"
34 34  }
35 -
36 -.appName-preview .breadcrumb {
37 - background-color: transparent;
38 - padding: 0;
39 -}
Content Type
... ... @@ -1,1 +1,0 @@
1 -CSS