0 Votes

Changes for page LiveTable View Sheet

Last modified by Сергей Коршунов on 2025/05/01 21:19

From version 1.1
edited by admins admins
on 2021/12/11 17:11
Change comment: Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui/13.10]
To version 6.1
edited by Сергей Коршунов
on 2024/01/18 17:27
Change comment: Install extension [org.xwiki.platform:xwiki-platform-appwithinminutes-ui/15.10.5]

Summary

Details

Page properties
Author
... ... @@ -1,1 +1,1 @@
1 -XWiki.admins
1 +XWiki.skorshunov
Content
... ... @@ -11,16 +11,8 @@
11 11   ## Display the live table only if it was generated.
12 12   #if ($doc.content.length() > 0)
13 13   = $services.localization.render('platform.appwithinminutes.appLiveTableHeading') =
14 - ## We don't use the Include macro (with empty reference) because we want the content to be executed with the rights
15 - ## of the current document rather than the rights of the sheet. This is important because the user can modify the
16 - ## content of the application home page which means we could execute untrusted content with the rights of the sheet.
17 - ## Ideally we should use the Display macro with a parameter to disable the sheet, but we don't have this parameter.
18 - ## We don't clean the HTML content because getRenderedContent() should produce clean HTML, unless the user has
19 - ## disabled the HTML cleaning, in which case he will get what he asked for. Note that one good reason to disable
20 - ## HTML cleaning is to preserve the whitespaces in the attribute values.
21 - ## Escape {{ in the rendered content to be sure that the HTML macro is not closed unintentionally.
22 - {{html clean="false"}}$doc.getRenderedContent($doc.content,
23 - $doc.syntax.toIdString()).replace('{{', '{{'){{/html}}
14 +
15 + {{include reference="" author="target"/}}
24 24   #end
25 25  #end
26 26  
... ... @@ -68,6 +68,7 @@
68 68   #end
69 69   ## We need to set the title if we want to be able to sort or filter the doc.title live table column.
70 70   #set ($params = {
63 + 'form_token': $services.csrf.token,
71 71   'template': "${className}Template",
72 72   'title': '__entryName__',
73 73   'parent': $services.model.serialize($doc.documentReference, 'local')
... ... @@ -122,23 +122,30 @@
122 122   data-backdrop="static" data-keyboard="false">
123 123   <div class="modal-dialog" role="document">
124 124   <form class="modal-content xform">
125 - <div class="modal-header">
126 - <button type="button" class="close" data-dismiss="modal" aria-label="Close">
127 - <span aria-hidden="true">&times;</span>
128 - </button>
129 - <span class="modal-title" id="renameAppModal-label">Rename Application</span>
130 - </div>
131 - <div class="modal-body">
132 - #renameAppModalBody
133 - </div>
134 - <div class="modal-footer">
135 - <button type="button" class="btn btn-default" data-dismiss="modal">
136 - $escapetool.xml($services.localization.render('cancel'))
137 - </button>
138 - <button type="submit" class="btn btn-primary" disabled="disabled">
139 - $escapetool.xml($services.localization.render('core.rename.submit'))
140 - </button>
141 - </div>
118 + ## The fieldset allows us to disable and enable the entire form quickly and easy.
119 + <fieldset>
120 + <div class="modal-header">
121 + <button type="button" class="close" data-dismiss="modal"
122 + title="$escapetool.xml($services.localization.render('appWithinMinutes.renameApp.close'))"
123 + aria-label="$escapetool.xml($services.localization.render('appWithinMinutes.renameApp.close'))">
124 + <span aria-hidden="true">&times;</span>
125 + </button>
126 + <span class="modal-title" id="renameAppModal-label">
127 + $escapetool.xml($services.localization.render('appWithinMinutes.renameApp.label'))
128 + </span>
129 + </div>
130 + <div class="modal-body">
131 + #renameAppModalBody
132 + </div>
133 + <div class="modal-footer">
134 + <button type="button" class="btn btn-default" data-dismiss="modal">
135 + $escapetool.xml($services.localization.render('cancel'))
136 + </button>
137 + <button type="submit" class="btn btn-primary" disabled="disabled">
138 + $escapetool.xml($services.localization.render('core.rename.submit'))
139 + </button>
140 + </div>
141 + </fieldset>
142 142   </form>
143 143   </div>
144 144   </div>
XWiki.JavaScriptExtension[0]
Code
... ... @@ -58,8 +58,9 @@
58 58  /**
59 59   * Rename Application
60 60   */
61 -require(['jquery', 'bootstrap'], function($) {
62 - #set ($currentDocReference = $xwiki.getDocument($request.currentApp).getDocumentReference())
61 +require(['jquery', 'bootstrap', 'xwiki-form-validation-async'], function($) {
62 + ## Note: if not currentApp request param is not passed, we default to the wiki home page reference to avoid a NPE
63 + #set ($currentDocReference = $xwiki.getDocument("$!request.currentApp").getDocumentReference())
63 63   // if we cannot find any extension related to this page app, it's not part of an extension.
64 64   var isNotAnExtension = $services.extension.xar.getInstalledExtensions($currentDocReference).isEmpty();
65 65  
... ... @@ -69,7 +69,7 @@
69 69   }
70 70   // Hijack the rename page action.
71 71   var renameAppModal = $('#renameAppModal');
72 - $('#tmActionRename').click(function(event) {
73 + $('#tmActionRename').on('click', function(event) {
73 73   event.preventDefault();
74 74   renameAppModal.modal();
75 75   });
... ... @@ -77,7 +77,6 @@
77 77   // Form validation.
78 78   var appNameInput = $('#renameAppTitle');
79 79   var appParentInput = $('#renameAppParentReference');
80 - var submitButton = renameAppModal.find('.btn-primary[type="submit"]');
81 81  
82 82   var appNameEmptyError = renameAppModal.find('.appNameEmptyError');
83 83   var pageExistsError = renameAppModal.find('.pageExistsError');
... ... @@ -100,52 +100,44 @@
100 100  
101 101   var startValidation = function() {
102 102   if (appNameInput.val() === '') {
103 - endValidation(appNameEmptyError);
103 + return Promise.reject(appNameEmptyError);
104 104   } else {
105 105   var newAppHomePage = getNewAppHomePage();
106 106   if (newAppHomePage.documentReference.equals(XWiki.currentDocument.documentReference)) {
107 - endValidation(pageExistsError);
107 + return Promise.reject(pageExistsError);
108 108   } else {
109 - $.ajax({
110 - type: 'HEAD',
111 - url: newAppHomePage.getURL()
112 - }).done(function() {
113 - endValidation(pageExistsError);
114 - }).fail(function(response) {
115 - if (response.status === 404) {
116 - $.ajax({
117 - type: 'HEAD',
118 - url: newAppHomePage.getURL('edit')
119 - }).done(function() {
120 - endValidation();
121 - }).fail(function() {
122 - endValidation(locationForbiddenError);
123 - });
124 - } else if (response.status === 403) {
125 - endValidation(locationForbiddenError);
126 - } else {
127 - endValidation();
128 - }
109 + return new Promise((resolve, reject) => {
110 + $.ajax({
111 + type: 'HEAD',
112 + url: newAppHomePage.getURL()
113 + }).then(reject.bind(null, pageExistsError), response => {
114 + if (response.status === 404) {
115 + $.ajax({
116 + type: 'HEAD',
117 + url: newAppHomePage.getURL('edit')
118 + }).then(
119 + () => resolve(),
120 + () => reject(locationForbiddenError)
121 + );
122 + } else if (response.status === 403) {
123 + reject(locationForbiddenError);
124 + } else {
125 + resolve();
126 + }
127 + });
129 129   });
130 130   }
131 131   }
132 132   };
133 133  
134 - var endValidation = function(error) {
135 - if (error) {
136 - error.show();
137 - }
138 - appNameInput.removeClass('loading');
139 - submitButton.prop('disabled', !!error);
140 - };
141 -
142 - var validationTimeout;
143 143   var scheduleValidation = function() {
144 - clearTimeout(validationTimeout);
134 + // Hide all error messages before starting the asynchronous validation.
145 145   renameAppModal.find('.xErrorMsg').hide();
146 - appNameInput.addClass('loading');
147 - submitButton.prop('disabled', true);
148 - validationTimeout = setTimeout(startValidation, 500);
136 + appNameInput.addClass('loading').validateAsync(startValidation, 500, 'awm').catch((error) => {
137 + error.show();
138 + }).finally(() => {
139 + appNameInput.removeClass('loading');
140 + });
149 149   };
150 150  
151 151   appNameInput.add(appParentInput).on('input', scheduleValidation);
... ... @@ -167,7 +167,7 @@
167 167   renameAppModal.modal('show');
168 168   });
169 169  
170 - renameAppModal.find('form').submit(function(event) {
162 + renameAppModal.find('form').on('submit', function(event) {
171 171   event.preventDefault();
172 172   renameApp(getRenameData($(this)));
173 173   });
... ... @@ -194,13 +194,13 @@
194 194  
195 195   var renameApp = function(data) {
196 196   // Disable the form to prevent it from being submitted twice.
197 - renameAppModal.find(':input').prop('disabled', true);
189 + renameAppModal.find('fieldset').prop('disabled', true);
198 198   var notification = new XWiki.widgets.Notification(
199 199   $jsontool.serialize($services.localization.render('appWithinMinutes.renameApp.inProgress')),
200 200   'inprogress'
201 201   );
202 202   var renameAppURL = new XWiki.Document('RenameApplication', 'AppWithinMinutes').getURL('get');
203 - $.post(renameAppURL, data).then(updateAppHomePage).done(function() {
195 + Promise.resolve($.post(renameAppURL, data)).then(updateAppHomePage).then(function() {
204 204   renameAppModal.modal('hide');
205 205   notification.replace(new XWiki.widgets.Notification(
206 206   $jsontool.serialize($services.localization.render('appWithinMinutes.renameApp.done')),
... ... @@ -210,33 +210,29 @@
210 210   setTimeout(function() {
211 211   window.location.href = getNewAppHomePage().getURL();
212 212   }, 0);
213 - }).fail(function() {
205 + }).catch(function() {
214 214   notification.replace(new XWiki.widgets.Notification(
215 215   $jsontool.serialize($services.localization.render('appWithinMinutes.renameApp.failed')),
216 216   'error'
217 217   ));
218 - }).always(function() {
210 + }).finally(function() {
219 219   // Re-enable the form.
220 - renameAppModal.find(':input').prop('disabled', false);
212 + renameAppModal.find('fieldset').prop('disabled', false);
221 221   });
222 222   };
223 223  
224 224   var updateAppHomePage = function() {
225 - var deferred = $.Deferred();
226 - var newAppHomePageEditURL = getNewAppHomePage().getURL('edit');
227 - $('<div/>').load(newAppHomePageEditURL + ' #inline', function() {
228 - var formData = $(this).children('form#inline').serializeArray();
229 - if (formData.length > 0) {
230 - formData.push({name: 'xaction_save', value: true});
231 - $.post(newAppHomePageEditURL, formData).done(function() {
232 - deferred.resolve();
233 - }).fail(function() {
234 - deferred.reject();
235 - });
236 - } else {
237 - deferred.reject();
238 - }
217 + return new Promise((resolve, reject) => {
218 + var newAppHomePageEditURL = getNewAppHomePage().getURL('edit');
219 + $('<div/>').load(newAppHomePageEditURL + ' #inline', function() {
220 + var formData = $(this).children('form#inline').serializeArray();
221 + if (formData.length) {
222 + formData.push({name: 'xaction_save', value: true});
223 + $.post(newAppHomePageEditURL, formData).then(resolve, reject);
224 + } else {
225 + reject();
226 + }
227 + });
239 239   });
240 - return deferred.promise();
241 241   }
242 242  });