View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   http://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  package org.apache.syncope.client.console.wicket.markup.html.bootstrap.dialog;
20  
21  import de.agilecoders.wicket.core.markup.html.bootstrap.dialog.Modal;
22  import de.agilecoders.wicket.extensions.markup.html.bootstrap.behavior.Draggable;
23  import de.agilecoders.wicket.extensions.markup.html.bootstrap.behavior.DraggableConfig;
24  import java.io.Serializable;
25  import java.util.ArrayList;
26  import java.util.List;
27  import org.apache.syncope.client.console.panels.AbstractModalPanel;
28  import org.apache.syncope.client.console.wicket.ajax.form.IndicatorModalCloseBehavior;
29  import org.apache.syncope.client.console.wicket.markup.html.bootstrap.buttons.DefaultModalCloseButton;
30  import org.apache.syncope.client.ui.commons.panels.ModalPanel;
31  import org.apache.syncope.client.ui.commons.panels.NotificationPanel;
32  import org.apache.syncope.client.ui.commons.panels.SubmitableModalPanel;
33  import org.apache.wicket.Component;
34  import org.apache.wicket.WicketRuntimeException;
35  import org.apache.wicket.ajax.AjaxEventBehavior;
36  import org.apache.wicket.ajax.AjaxRequestTarget;
37  import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink;
38  import org.apache.wicket.core.request.handler.IPartialPageRequestHandler;
39  import org.apache.wicket.event.IEvent;
40  import org.apache.wicket.markup.head.IHeaderResponse;
41  import org.apache.wicket.markup.head.OnDomReadyHeaderItem;
42  import org.apache.wicket.markup.html.WebMarkupContainer;
43  import org.apache.wicket.markup.html.form.Form;
44  import org.apache.wicket.markup.html.list.ListItem;
45  import org.apache.wicket.markup.html.list.ListView;
46  import org.apache.wicket.markup.html.panel.Panel;
47  import org.apache.wicket.model.CompoundPropertyModel;
48  import org.apache.wicket.model.IModel;
49  import org.apache.wicket.util.io.IClusterable;
50  import org.slf4j.Logger;
51  import org.slf4j.LoggerFactory;
52  
53  public class BaseModal<T extends Serializable> extends Modal<T> {
54  
55      private static final long serialVersionUID = -6142277554912316095L;
56  
57      protected static final Logger LOG = LoggerFactory.getLogger(BaseModal.class);
58  
59      /** the default id of the content component */
60      public static final String CONTENT_ID = "content";
61  
62      private static final String SUBMIT = "submit";
63  
64      private static final String FORM = "form";
65  
66      protected NotificationPanel notificationPanel;
67  
68      private final List<Component> components;
69  
70      private WindowClosedCallback windowClosedCallback;
71  
72      private Panel content;
73  
74      private AjaxSubmitLink submitButton;
75  
76      private final Form<T> form;
77  
78      private final DefaultModalCloseButton defaultModalCloseButton;
79  
80      private AjaxEventBehavior closeBehavior;
81  
82      private WebMarkupContainer footer;
83  
84      public BaseModal(final String id) {
85          super(id);
86  
87          form = new Form<>(FORM);
88          form.setOutputMarkupId(true);
89          add(form);
90  
91          content = new AbstractModalPanel<>(this, null) {
92  
93              private static final long serialVersionUID = -6142277554912316095L;
94  
95          };
96  
97          content.setOutputMarkupId(true);
98  
99          form.add(content);
100 
101         useCloseHandler(true);
102         this.windowClosedCallback = null;
103         components = new ArrayList<>();
104 
105         // Note: not adding this would imply adding of WebjarsJavaScriptResourceReference about JQuery draggable
106         add(new Draggable(new DraggableConfig().withHandle(".modal-header").withCursor("move")));
107 
108         defaultModalCloseButton = new DefaultModalCloseButton();
109         addButton(defaultModalCloseButton);
110         setUseKeyboard(true);
111         setFadeIn(true);
112     }
113 
114     public Form<T> getForm() {
115         return form;
116     }
117 
118     public BaseModal<T> setFormModel(final T modelObject) {
119         form.setModel(new CompoundPropertyModel<>(modelObject));
120         return this;
121     }
122 
123     public BaseModal<T> setFormModel(final IModel<T> model) {
124         form.setModel(model);
125         return this;
126     }
127 
128     public BaseModal<T> setFormAsMultipart(final boolean multipart) {
129         form.setMultiPart(multipart);
130         return this;
131     }
132 
133     public T getFormModel() {
134         return form.getModelObject();
135     }
136 
137     public ModalPanel getContent() {
138         if (content instanceof ModalPanel) {
139             return (ModalPanel) content;
140         }
141         throw new IllegalStateException();
142     }
143 
144     public BaseModal<T> setContent(final ModalPanel component) {
145         if (component instanceof Panel) {
146             return setInternalContent(Panel.class.cast(component));
147         }
148         throw new IllegalArgumentException("Panel instance is required");
149     }
150 
151     public BaseModal<T> setContent(final ModalPanel component, final AjaxRequestTarget target) {
152         setContent(component);
153         target.add(content);
154         return this;
155     }
156 
157     public BaseModal<T> changeCloseButtonLabel(final String label) {
158         defaultModalCloseButton.getModel().setObject(label);
159         return this;
160     }
161 
162     public BaseModal<T> changeCloseButtonLabel(final String label, final AjaxRequestTarget target) {
163         changeCloseButtonLabel(label);
164         target.add(defaultModalCloseButton);
165         return this;
166     }
167 
168     private BaseModal<T> setInternalContent(final Panel component) {
169         if (!component.getId().equals(CONTENT_ID)) {
170             throw new WicketRuntimeException("Modal content id is wrong. "
171                     + "Component ID: " + component.getId() + "; content ID: " + CONTENT_ID);
172         }
173 
174         content.replaceWith(component);
175         content = component;
176 
177         return this;
178     }
179 
180     public BaseModal<T> setWindowClosedCallback(final WindowClosedCallback callback) {
181         windowClosedCallback = callback;
182         return this;
183     }
184 
185     @Override
186     protected void onClose(final IPartialPageRequestHandler target) {
187         if (windowClosedCallback != null) {
188             windowClosedCallback.onClose((AjaxRequestTarget) target);
189         }
190     }
191 
192     public AjaxSubmitLink addSubmitButton() {
193         if (!(BaseModal.this.getContent() instanceof SubmitableModalPanel)) {
194             throw new IllegalStateException();
195         }
196 
197         AjaxSubmitLink submit = new AjaxSubmitLink(SUBMIT, form) {
198 
199             private static final long serialVersionUID = -5783994974426198290L;
200 
201             @Override
202             protected void onSubmit(final AjaxRequestTarget target) {
203                 SubmitableModalPanel.class.cast(BaseModal.this.getContent()).onSubmit(target);
204             }
205 
206             @Override
207             protected void onError(final AjaxRequestTarget target) {
208                 SubmitableModalPanel.class.cast(BaseModal.this.getContent()).onError(target);
209             }
210         };
211 
212         submit.setOutputMarkupId(true);
213 
214         if (submitButton == null) {
215             submitButton = submit;
216             components.add(submitButton);
217         } else {
218             submitButton.replaceWith(submit);
219             submitButton = submit;
220         }
221 
222         return submit;
223     }
224 
225     public void removeSubmitButton() {
226         if (!(BaseModal.this.getContent() instanceof SubmitableModalPanel)) {
227             throw new IllegalStateException();
228         }
229 
230         components.stream().
231                 filter(component -> SUBMIT.equals(component.getId())).
232                 findFirst().
233                 ifPresent(components::remove);
234 
235         submitButton = null;
236     }
237 
238     @Override
239     protected void onInitialize() {
240         super.onInitialize();
241 
242         final WebMarkupContainer dialog = (WebMarkupContainer) this.get("dialog");
243         dialog.setMarkupId(this.getId());
244 
245         footer = (WebMarkupContainer) this.get("dialog:footer");
246         footer.addOrReplace(new ListView<>("inputs", components) {
247 
248             private static final long serialVersionUID = 4949588177564901031L;
249 
250             @Override
251             protected void populateItem(final ListItem<Component> item) {
252                 item.add(item.getModelObject());
253             }
254         }.setOutputMarkupId(true)).setOutputMarkupId(true);
255     }
256 
257     /**
258      * Generic modal event.
259      */
260     public static class ModalEvent implements Serializable {
261 
262         private static final long serialVersionUID = 2668922412196063559L;
263 
264         /**
265          * Request target.
266          */
267         private final AjaxRequestTarget target;
268 
269         /**
270          * Constructor.
271          *
272          * @param target request target.
273          */
274         public ModalEvent(final AjaxRequestTarget target) {
275             this.target = target;
276         }
277 
278         /**
279          * Target getter.
280          *
281          * @return request target.
282          */
283         public AjaxRequestTarget getTarget() {
284             return target;
285         }
286     }
287 
288     public static class ChangeFooterVisibilityEvent extends ModalEvent {
289 
290         private static final long serialVersionUID = -6157576856659866343L;
291 
292         public ChangeFooterVisibilityEvent(final AjaxRequestTarget target) {
293             super(target);
294         }
295     }
296 
297     @Override
298     public void onEvent(final IEvent<?> event) {
299         if (event.getPayload() instanceof ChangeFooterVisibilityEvent) {
300             if (BaseModal.this.footer != null) {
301                 final AjaxRequestTarget target = ChangeFooterVisibilityEvent.class.cast(event.getPayload()).getTarget();
302                 target.add(BaseModal.this.footer.setEnabled(!BaseModal.this.footer.isEnabled()));
303             }
304         }
305     }
306 
307     //--------------------------------------------------------
308     // Reqired for SYNCOPE-846
309     //--------------------------------------------------------
310     /**
311      * Sets whether the close handler is used or not. Default is false.
312      *
313      * @param useCloseHandler True if close handler should be used
314      * @return This
315      */
316     public final Modal<T> useCloseHandler(final boolean useCloseHandler) {
317         if (useCloseHandler) {
318             if (closeBehavior == null) {
319                 closeBehavior = new IndicatorModalCloseBehavior() {
320 
321                     private static final long serialVersionUID = -4955472558917915340L;
322 
323                     @Override
324                     protected void onEvent(final AjaxRequestTarget target) {
325                         if (isVisible()) {
326                             onClose(target);
327                             appendCloseDialogJavaScript(target);
328                         }
329                     }
330                 };
331                 add(closeBehavior);
332             }
333         } else if (closeBehavior != null) {
334             remove(closeBehavior);
335             closeBehavior = null;
336         }
337         return this;
338     }
339 
340     @Override
341     public void renderHead(final IHeaderResponse response) {
342         super.renderHead(response);
343         response.render(OnDomReadyHeaderItem.forScript(createInitializerScript(getMarkupId(true))));
344     }
345 
346     /**
347      * creates the initializer script of the modal dialog.
348      *
349      * @param markupId The component's markup id
350      * @return initializer script
351      */
352     private String createInitializerScript(final String markupId) {
353         return addCloseHandlerScript(markupId, createBasicInitializerScript(markupId));
354     }
355 
356     /**
357      * adds close handler to initializer script, if use of close handler has been defined.
358      *
359      * @param markupId markup id
360      * @param script base script to prepend
361      * @return close handler script
362      */
363     private String addCloseHandlerScript(final String markupId, final String script) {
364         if (closeBehavior != null) {
365             return script + ";$('#" + markupId + "').on('hidden', function () { "
366                     + "  Wicket.Ajax.ajax({'u':'" + closeBehavior.getCallbackUrl() + "','c':'" + markupId + "'});"
367                     + "})";
368         }
369 
370         return script;
371     }
372     //--------------------------------------------------------
373 
374     /**
375      * Callback called after the window has been closed. If no callback instance is specified using
376      * {@link BaseModal#setWindowClosedCallback(BaseModal.WindowClosedCallback)}, no ajax
377      * request will be fired.
378      */
379     @FunctionalInterface
380     public interface WindowClosedCallback extends IClusterable {
381 
382         /**
383          * Called after the window has been closed.
384          *
385          * @param target {@link org.apache.wicket.ajax.AjaxRequestTarget} instance bound with the ajax request.
386          */
387         void onClose(AjaxRequestTarget target);
388     }
389 }