View Javadoc
1   /**
2    * This file Copyright (c) 2013-2018 Magnolia International
3    * Ltd.  (http://www.magnolia-cms.com). All rights reserved.
4    *
5    *
6    * This file is dual-licensed under both the Magnolia
7    * Network Agreement and the GNU General Public License.
8    * You may elect to use one or the other of these licenses.
9    *
10   * This file is distributed in the hope that it will be
11   * useful, but AS-IS and WITHOUT ANY WARRANTY; without even the
12   * implied warranty of MERCHANTABILITY or FITNESS FOR A
13   * PARTICULAR PURPOSE, TITLE, or NONINFRINGEMENT.
14   * Redistribution, except as permitted by whichever of the GPL
15   * or MNA you select, is prohibited.
16   *
17   * 1. For the GPL license (GPL), you can redistribute and/or
18   * modify this file under the terms of the GNU General
19   * Public License, Version 3, as published by the Free Software
20   * Foundation.  You should have received a copy of the GNU
21   * General Public License, Version 3 along with this program;
22   * if not, write to the Free Software Foundation, Inc., 51
23   * Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
24   *
25   * 2. For the Magnolia Network Agreement (MNA), this file
26   * and the accompanying materials are made available under the
27   * terms of the MNA which accompanies this distribution, and
28   * is available at http://www.magnolia-cms.com/mna.html
29   *
30   * Any modifications to this file must keep this entire header
31   * intact.
32   *
33   */
34  package info.magnolia.ui.form.field.upload.basic;
35  
36  import info.magnolia.i18nsystem.SimpleTranslator;
37  import info.magnolia.ui.api.context.UiContext;
38  import info.magnolia.ui.form.field.definition.BasicUploadFieldDefinition;
39  import info.magnolia.ui.form.field.upload.AbstractUploadField;
40  import info.magnolia.ui.form.field.upload.UploadProgressIndicator;
41  import info.magnolia.ui.form.field.upload.UploadReceiver;
42  import info.magnolia.ui.imageprovider.ImageProvider;
43  import info.magnolia.ui.vaadin.overlay.MessageStyleTypeEnum;
44  
45  import org.apache.commons.io.FileUtils;
46  import org.apache.commons.lang3.StringUtils;
47  import org.slf4j.Logger;
48  import org.slf4j.LoggerFactory;
49  
50  import com.vaadin.ui.Alignment;
51  import com.vaadin.ui.Button;
52  import com.vaadin.ui.Button.ClickEvent;
53  import com.vaadin.ui.Component;
54  import com.vaadin.ui.CssLayout;
55  import com.vaadin.ui.FormLayout;
56  import com.vaadin.ui.Layout;
57  import com.vaadin.ui.NativeButton;
58  import com.vaadin.v7.data.Property;
59  import com.vaadin.v7.shared.ui.label.ContentMode;
60  import com.vaadin.v7.ui.HorizontalLayout;
61  import com.vaadin.v7.ui.Label;
62  import com.vaadin.v7.ui.TextField;
63  
64  /**
65   * Basic implementation of {@link AbstractUploadField}.<br>
66   * Define the Layout components for
67   * <ul>
68   * <li>EmptyLayout (no File are yet uploaded)
69   * <li>InProgressLayout (ProgressBar / Cancel Button...)
70   * <li>CompletedLayout (File Detail / Preview ...)
71   * </ul>
72   *
73   * @param <T> {@link UploadReceiver} implemented class.
74   * @deprecated since 6.2 - use {@link info.magnolia.ui.field.UploadField} instead.
75   *
76   * @see <a href="https://documentation.magnolia-cms.com/display/DOCS62/Upgrading+to+Magnolia+6.2.x">Upgrading to Magnolia 6.2.x</a>
77   */
78  @Deprecated
79  public class BasicUploadField<T extends UploadReceiver> extends AbstractUploadField<T> {
80      private static final long serialVersionUID = 1L;
81      private static final Logger log = LoggerFactory.getLogger(BasicUploadField.class);
82  
83      private static final String PREFIX_MEDIA_KEY = "field.upload.media";
84      private static final String MEDIA = "media";
85  
86      // Root layout
87      private final CssLayout layout;
88      private UploadProgressIndicator progress;
89      protected final ImageProvider imageProvider;
90      private boolean editFileName = false;
91      private boolean editFileFormat = false;
92      protected UiContext uiContext;
93      private final SimpleTranslator i18n;
94  
95      public BasicUploadField(ImageProvider imageProvider, UiContext uiContext, BasicUploadFieldDefinition definition, SimpleTranslator i18n) {
96          super();
97          // Propagate definition.
98          populateFromDefinition(definition);
99  
100         this.imageProvider = imageProvider;
101         this.layout = new CssLayout();
102         this.layout.setSizeUndefined();
103         this.uiContext = uiContext;
104         this.i18n = i18n;
105 
106         setRootLayout(createDropZone(layout));
107         // Update Style Name
108         addStyleName("upload-image-field");
109         addStyleName("no-horizontal-drag-hints");
110         addStyleName("no-vertical-drag-hints");
111     }
112 
113     /**
114      * Initialize the root component.
115      * Build the initial layout:
116      * - Empty Layout if the incoming Item is empty.
117      * - Completed Layout with the Item Informations if this Item is not empty.
118      */
119     @Override
120     public void attach() {
121         super.attach();
122         updateDisplay();
123         log.debug("Component was attached ...");
124     }
125 
126     /**
127      * Main entry point to create the Empty Layout.
128      * This Basic implementation of Empty layout is composed of <br>
129      * - An Upload button <br>
130      * - A Label inviting to Drag files <br>
131      */
132     @Override
133     protected void buildEmptyLayout() {
134         layout.removeAllComponents();
135         if (isReadOnly()) {
136             return;
137         }
138         // Add Upload Button
139         getUpload().setButtonCaption(getCaption(selectNewCaption, null));
140         layout.addComponent(getUpload());
141         // Add DropZone Label
142         Label uploadText = new Label(getCaption(dropZoneCaption, null), ContentMode.HTML);
143         uploadText.addStyleName("upload-text");
144         layout.addComponent(uploadText);
145 
146         // Update Style Name
147         getRootLayout().removeStyleName("start");
148         getRootLayout().removeStyleName("done");
149         getRootLayout().removeStyleName("in-progress");
150         getRootLayout().addStyleName("upload");
151         getRootLayout().addStyleName("initial");
152 
153         log.debug("buildEmptyLayout() called ...");
154     }
155 
156     /**
157      * Main entry point to create the In Progress Layout.
158      * This Basic implementation of In Progress is composed of <br>
159      * - A Progress Bar <br>
160      * - A Cancel Button <br>
161      */
162     @Override
163     protected void buildInProgressLayout(String uploadedFileMimeType) {
164         layout.removeAllComponents();
165         // Update the caption Extension
166         setCaptionExtension(uploadedFileMimeType);
167         // Create the process Indigator
168         progress = new BasicUploadProgressIndicator(inProgressCaption, inProgressRatioCaption, i18n);
169         progress.setVisible(true);
170         progress.setProgress(0);
171         layout.addComponent(progress.asVaadinComponent());
172 
173         // Add the Cancel Button
174         layout.addComponent(createCancelButton());
175 
176         // Update Style Name
177         getRootLayout().removeStyleName("done");
178         getRootLayout().addStyleName("upload");
179         getRootLayout().addStyleName("initial");
180         getRootLayout().addStyleName("in-progress");
181 
182         log.debug("buildInProgressLayout() called ...");
183     }
184 
185     @Override
186     protected void refreshInProgressLayout(long readBytes, long contentLength, String fileName) {
187         if (progress != null) {
188             progress.refreshLayout(readBytes, contentLength, fileName);
189         }
190     }
191 
192     /**
193      * Main entry point to create the Completed Layout.
194      * This Basic implementation of Completed Layout is composed of <br>
195      * - An Icon representing the File type <br>
196      * - A Detail text information <br>
197      * - An Action bar: UploadNew and Delete Actions <br>
198      * <b>Override</b><br>
199      * getFileInfo() In order to change the Displayed Text <br>
200      * createIconStyleName() In order to change the Icon Style displayed.
201      */
202     @Override
203     protected void buildCompletedLayout() {
204         layout.removeAllComponents();
205         // Update the caption Extension
206         setCaptionExtension(null);
207         // Create the File Detail Component
208         layout.addComponent(createFileInfoComponent());
209         // Create the Action Layout
210         layout.addComponent(createCompletedActionLayout());
211 
212         // Create Preview
213         layout.addComponent(createThumbnailComponent());
214 
215         // Update Style Name
216         getRootLayout().addStyleName("upload");
217         getRootLayout().removeStyleName("in-progress");
218         getRootLayout().removeStyleName("initial");
219         getRootLayout().addStyleName("done");
220 
221         log.debug("buildCompletedLayout() called ...");
222     }
223 
224     /**
225      * Build the Completed Action Layout.
226      */
227     protected Layout createCompletedActionLayout() {
228         // Action Button (Upload new or delete). Default is always Upload
229         HorizontalLayout actionLayout = new HorizontalLayout();
230         actionLayout.setSizeUndefined();
231         actionLayout.addStyleName("buttons");
232         actionLayout.setSpacing(true);
233         // Add Upload Button
234         getUpload().setButtonCaption(getCaption(selectAnotherCaption, null));
235         actionLayout.addComponent(getUpload());
236         // Add Remove Button if a file is present.
237         if (!getValue().isEmpty()) {
238             Button delete = createDeleteButton();
239             actionLayout.addComponent(delete);
240             actionLayout.setComponentAlignment(delete, Alignment.MIDDLE_RIGHT);
241         }
242         return actionLayout;
243     }
244 
245     /**
246      * Create the Cancel Button.
247      * Used to cancel an ongoing Upload.
248      */
249     private Button createCancelButton() {
250         Button cancelButton = new NativeButton(null, new Button.ClickListener() {
251             private static final long serialVersionUID = 1L;
252 
253             @Override
254             public void buttonClick(ClickEvent event) {
255                 interruptUpload(InterruptionReason.USER);
256             }
257         });
258         cancelButton.addStyleName("cancel");
259         return cancelButton;
260     }
261 
262     /**
263      * Create Delete button.
264      */
265     protected Button createDeleteButton() {
266         Button deleteButton = new Button();
267         deleteButton.setHtmlContentAllowed(true);
268         deleteButton.setCaption("<span class=\"" + "icon-trash" + "\"></span>");
269         deleteButton.addStyleName("inline");
270         deleteButton.setDescription(i18n.translate(deleteCaption));
271 
272         deleteButton.addClickListener(new Button.ClickListener() {
273             private static final long serialVersionUID = 1L;
274 
275             @Override
276             public void buttonClick(ClickEvent event) {
277                 resetDataSource();
278                 updateDisplay();
279             }
280         });
281         return deleteButton;
282     }
283 
284     /**
285      * Initialize a Component displaying some File Informations.
286      * Override getFileDetail...() in order to display custom info's you may want to display.
287      *
288      * @return A file Info Component. Generally a {@link FormLayout}.
289      */
290     private Component createFileInfoComponent() {
291         FormLayout fileInfo = new FormLayout();
292         fileInfo.setSizeUndefined();
293         fileInfo.addStyleName("file-details");
294         fileInfo.addComponent(getFileDetailHeader());
295         fileInfo.addComponent(getFileDetailFileName());
296         fileInfo.addComponent(getFileDetailSize());
297         fileInfo.addComponent(getFileDetailFileFormat());
298         return fileInfo;
299     }
300 
301     /**
302      * Add Title.
303      */
304     protected Component getFileDetailHeader() {
305         Label label = new Label();
306         label.setValue(getCaption(fileDetailHeaderCaption, null));
307         return label;
308     }
309 
310     /**
311      * Add File Name.<br>
312      * If editFileName is true, display an Input Text Field. <br>
313      * Else display a simple label.
314      */
315     protected Component getFileDetailFileName() {
316         // Build the file name without the extension
317         final boolean hasExtension = StringUtils.isNotBlank(getValue().getExtension());
318         final String extension = hasExtension ? "." + getValue().getExtension() : "";
319         String fileName = StringUtils.removeEnd(getValue().getFileName(), extension);
320 
321         if (this.editFileName && !isReadOnly()) {
322             TextField textField = new TextField(i18n.translate(fileDetailNameCaption), fileName);
323             textField.setNullRepresentation("");
324             textField.setCaption(i18n.translate(fileDetailNameCaption));
325             textField.addValueChangeListener(new Property.ValueChangeListener() {
326                 @Override
327                 public void valueChange(Property.ValueChangeEvent event) {
328                     Object newFileNameObject = event.getProperty().getValue();
329                     String newFileName = (newFileNameObject != null && StringUtils.isNotBlank(newFileNameObject.toString())) ? newFileNameObject.toString() : UploadReceiver.INVALID_FILE_NAME;
330                     getValue().setFileName(newFileName + extension);
331                     getPropertyDataSource().setValue(getValue());
332                 }
333             });
334             return textField;
335         } else {
336             Label label = new Label();
337             label.setCaption(i18n.translate(fileDetailNameCaption));
338             label.setValue(fileName);
339             return label;
340         }
341     }
342 
343     /**
344      * Add File Info.
345      */
346     protected Component getFileDetailSize() {
347         Label label = new Label();
348         label.setCaption(i18n.translate(fileDetailSizeCaption));
349         label.setValue(FileUtils.byteCountToDisplaySize(getValue().getFileSize()));
350         return label;
351     }
352 
353     /**
354      * Add File Format.<br>
355      * If editFileFormat is true, display an Input Text Field. <br>
356      * Else display a simple label.
357      */
358     protected Component getFileDetailFileFormat() {
359         if (this.editFileFormat && !isReadOnly()) {
360             TextField textField = new TextField(i18n.translate(fileDetailFormatCaption), getValue().getExtension());
361             textField.setNullRepresentation("");
362             textField.setCaption(i18n.translate(fileDetailFormatCaption));
363             return textField;
364         } else {
365             Label label = new Label();
366             label.setValue(getValue().getExtension());
367             label.setCaption(i18n.translate(fileDetailFormatCaption));
368             return label;
369         }
370     }
371 
372     /**
373      * @return Thumbnail Component.
374      */
375     protected Component createThumbnailComponent() {
376         Label thumbnail = new Label();
377         thumbnail.setSizeUndefined();
378         thumbnail.addStyleName("preview-image");
379         thumbnail.addStyleName("file-preview");
380         thumbnail.addStyleName(createIconStyleName());
381         return thumbnail;
382     }
383 
384     /**
385      * Create the Icon related to a File. <br>
386      * <b>Override this method in order to change the Displayed Icon .</b>
387      *
388      * @return
389      */
390     protected String createIconStyleName() {
391         return "icon-" + imageProvider.resolveIconClassName(getValue().getMimeType());
392     }
393 
394     @Override
395     protected Component initContent() {
396         return getRootLayout();
397     }
398 
399     /**
400      * Configure Field based on the definition.
401      */
402     protected void populateFromDefinition(BasicUploadFieldDefinition definition) {
403         this.setMaxUploadSize(definition.getMaxUploadSize());
404         this.setAllowedMimeTypePattern(definition.getAllowedMimeTypePattern());
405 
406         this.setSelectNewCaption(definition.getSelectNewCaption());
407         this.setSelectAnotherCaption(definition.getSelectAnotherCaption());
408         this.setDropZoneCaption(definition.getDropZoneCaption());
409         this.setInProgressCaption(definition.getInProgressCaption());
410         this.setInProgressRatioCaption(definition.getInProgressRatioCaption());
411         this.setFileDetailHeaderCaption(definition.getFileDetailHeaderCaption());
412         this.setFileDetailNameCaption(definition.getFileDetailNameCaption());
413         this.setFileDetailSizeCaption(definition.getFileDetailSizeCaption());
414         this.setFileDetailFormatCaption(definition.getFileDetailFormatCaption());
415         this.setFileDetailSourceCaption(definition.getFileDetailSourceCaption());
416         this.setSuccessNoteCaption(definition.getSuccessNoteCaption());
417         this.setWarningNoteCaption(definition.getWarningNoteCaption());
418         this.setErrorNoteCaption(definition.getErrorNoteCaption());
419         this.setDeteteCaption(definition.getDeleteCaption());
420         this.setEditFileFormat(definition.isEditFileFormat());
421         this.setEditFileName(definition.isEditFileName());
422         this.setUserInterruption(definition.getUserInterruption());
423         this.setTypeInterruption(definition.getTypeInterruption());
424         this.setSizeInterruption(definition.getSizeInterruption());
425     }
426 
427     /**
428      * Caption section.
429      */
430     protected String captionExtension;
431 
432     protected void setCaptionExtension(String mimeType) {
433         captionExtension = "";
434     }
435 
436     protected String getCaption(String caption, String[] args) {
437         if (StringUtils.isEmpty(caption)) {
438             return "";
439         }
440         if (StringUtils.isNotBlank(captionExtension)) {
441             String mediaName = i18n.translate(PREFIX_MEDIA_KEY + '.' + captionExtension);
442             String[] paras;
443             if (args != null && args.length > 0) {
444                 paras = new String[args.length + 1];
445                 paras[0] = mediaName;
446                 System.arraycopy(args, 0, paras, 1, args.length);
447             } else {
448                 paras = new String[] { mediaName };
449             }
450             return i18n.translate(caption + '.' + MEDIA, paras);
451         }
452         if (args != null && args.length > 0) {
453             return i18n.translate(caption, args);
454         } else {
455             return i18n.translate(caption);
456         }
457     }
458 
459     protected String selectNewCaption;
460     protected String selectAnotherCaption;
461     protected String deleteCaption;
462     protected String dropZoneCaption;
463     protected String inProgressCaption;
464     protected String inProgressRatioCaption;
465     protected String fileDetailHeaderCaption;
466     protected String fileDetailNameCaption;
467     protected String fileDetailSizeCaption;
468     protected String fileDetailFormatCaption;
469     protected String fileDetailSourceCaption;
470     protected String successNoteCaption;
471     protected String warningNoteCaption;
472     protected String errorNoteCaption;
473     private String sizeInterruption;
474     private String typeInterruption;
475     private String userInterruption;
476 
477     public void setSelectNewCaption(String selectNewCaption) {
478         this.selectNewCaption = selectNewCaption;
479     }
480 
481     public void setSelectAnotherCaption(String selectAnotherCaption) {
482         this.selectAnotherCaption = selectAnotherCaption;
483     }
484 
485     public void setDropZoneCaption(String dropZoneCaption) {
486         this.dropZoneCaption = dropZoneCaption;
487     }
488 
489     public void setInProgressCaption(String inProgressCaption) {
490         this.inProgressCaption = inProgressCaption;
491     }
492 
493     public void setInProgressRatioCaption(String inProgressRatioCaption) {
494         this.inProgressRatioCaption = inProgressRatioCaption;
495     }
496 
497     public void setFileDetailHeaderCaption(String fileDetailHeaderCaption) {
498         this.fileDetailHeaderCaption = fileDetailHeaderCaption;
499     }
500 
501     public void setFileDetailNameCaption(String fileDetailNameCaption) {
502         this.fileDetailNameCaption = fileDetailNameCaption;
503     }
504 
505     public void setFileDetailSizeCaption(String fileDetailSizeCaption) {
506         this.fileDetailSizeCaption = fileDetailSizeCaption;
507     }
508 
509     public void setFileDetailFormatCaption(String fileDetailFormatCaption) {
510         this.fileDetailFormatCaption = fileDetailFormatCaption;
511     }
512 
513     public void setFileDetailSourceCaption(String fileDetailSourceCaption) {
514         this.fileDetailSourceCaption = fileDetailSourceCaption;
515     }
516 
517     public void setSuccessNoteCaption(String successNoteCaption) {
518         this.successNoteCaption = successNoteCaption;
519     }
520 
521     public void setWarningNoteCaption(String warningNoteCaption) {
522         this.warningNoteCaption = warningNoteCaption;
523     }
524 
525     public void setErrorNoteCaption(String errorNoteCaption) {
526         this.errorNoteCaption = errorNoteCaption;
527     }
528 
529     public void setDeteteCaption(String deleteCaption) {
530         this.deleteCaption = deleteCaption;
531     }
532 
533     public void setSizeInterruption(String sizeInterruption) {
534         this.sizeInterruption = sizeInterruption;
535     }
536 
537     public void setTypeInterruption(String typeInterruption) {
538         this.typeInterruption = typeInterruption;
539     }
540 
541     public void setUserInterruption(String userInterruption) {
542         this.userInterruption = userInterruption;
543     }
544 
545     @Override
546     protected void displayUploadInterruptNote(InterruptionReason reason) {
547         String caption = "";
548         if (reason.equals(InterruptionReason.USER)) {
549             caption = userInterruption;
550         } else if (reason.equals(InterruptionReason.FILE_SIZE)) {
551             caption = sizeInterruption;
552         } else {
553             caption = typeInterruption;
554         }
555         uiContext.openNotification(MessageStyleTypeEnum.WARNING, true, getCaption(warningNoteCaption, new String[] { i18n.translate(caption) }));
556     }
557 
558     @Override
559     protected void displayUploadFinishedNote(String fileName) {
560         uiContext.openNotification(MessageStyleTypeEnum.INFO, true, getCaption(successNoteCaption, new String[] { fileName }));
561     }
562 
563     @Override
564     protected void displayUploadFailedNote(String fileName) {
565         uiContext.openAlert(MessageStyleTypeEnum.ERROR, "ERROR", getCaption(errorNoteCaption, new String[] { fileName }), i18n.translate("button.ok"), null);
566     }
567 
568     public void setEditFileName(boolean editFileName) {
569         this.editFileName = editFileName;
570     }
571 
572     public void setEditFileFormat(boolean editFileFormat) {
573         this.editFileFormat = editFileFormat;
574     }
575 
576     /**
577      * For test cases.
578      */
579     public CssLayout getCssLayout() {
580         return this.layout;
581     }
582 
583     @Override
584     public boolean isEmpty() {
585         return getValue().isEmpty();
586     }
587 
588     @Override
589     public void setReadOnly(boolean readOnly) {
590         super.setReadOnly(readOnly);
591         if (readOnly) {
592             // Remove drop zone
593             if (getDropZone() != null) {
594                 getDropZone().setDropHandler(null);
595             }
596             if (getUpload() != null) {
597                 getUpload().removeStartedListener(this);
598                 getUpload().removeFinishedListener(this);
599                 getUpload().removeProgressListener(this);
600             }
601             if (getValue().isEmpty()) {
602                 buildEmptyLayout();
603             }
604         }
605 
606     }
607 }