001/*
002 * This library is part of OpenCms -
003 * the Open Source Content Management System
004 *
005 * Copyright (C) Alkacon Software (http://www.alkacon.com)
006 *
007 * This library is free software; you can redistribute it and/or
008 * modify it under the terms of the GNU Lesser General Public
009 * License as published by the Free Software Foundation; either
010 * version 2.1 of the License, or (at your option) any later version.
011 *
012 * This library is distributed in the hope that it will be useful,
013 * but WITHOUT ANY WARRANTY; without even the implied warranty of
014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
015 * Lesser General Public License for more details.
016 *
017 * For further information about Alkacon Software, please see the
018 * company website: http://www.alkacon.com
019 *
020 * For further information about OpenCms, please see the
021 * project website: http://www.opencms.org
022 *
023 * You should have received a copy of the GNU Lesser General Public
024 * License along with this library; if not, write to the Free Software
025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
026 */
027
028package org.opencms.acacia.client;
029
030import org.opencms.acacia.client.css.I_CmsLayoutBundle;
031import org.opencms.acacia.client.entity.I_CmsEntityBackend;
032import org.opencms.acacia.client.ui.CmsAttributeValueView;
033import org.opencms.acacia.client.ui.CmsInlineEntityWidget;
034import org.opencms.acacia.client.ui.CmsValuePanel;
035import org.opencms.acacia.client.widgets.I_CmsEditWidget;
036import org.opencms.acacia.shared.CmsEntity;
037import org.opencms.acacia.shared.CmsEntityAttribute;
038import org.opencms.acacia.shared.CmsTabInfo;
039import org.opencms.acacia.shared.CmsType;
040import org.opencms.gwt.client.I_CmsHasResizeOnShow;
041import org.opencms.gwt.client.ui.CmsTabbedPanel;
042import org.opencms.gwt.client.ui.CmsTabbedPanel.CmsTabbedPanelStyle;
043import org.opencms.gwt.client.util.CmsPositionBean;
044import org.opencms.gwt.shared.CmsGwtConstants;
045
046import java.util.Iterator;
047import java.util.List;
048
049import com.google.gwt.core.client.Scheduler;
050import com.google.gwt.core.client.Scheduler.RepeatingCommand;
051import com.google.gwt.core.client.Scheduler.ScheduledCommand;
052import com.google.gwt.dom.client.Element;
053import com.google.gwt.dom.client.Style.Unit;
054import com.google.gwt.event.logical.shared.ResizeEvent;
055import com.google.gwt.event.logical.shared.ResizeHandler;
056import com.google.gwt.event.logical.shared.SelectionEvent;
057import com.google.gwt.event.logical.shared.SelectionHandler;
058import com.google.gwt.event.logical.shared.ValueChangeEvent;
059import com.google.gwt.event.logical.shared.ValueChangeHandler;
060import com.google.gwt.user.client.DOM;
061import com.google.gwt.user.client.ui.FlowPanel;
062import com.google.gwt.user.client.ui.HTML;
063import com.google.gwt.user.client.ui.Panel;
064import com.google.gwt.user.client.ui.Widget;
065
066/**
067 * Renders the widgets for an in-line form.<p>
068 */
069public class CmsRenderer implements I_CmsEntityRenderer {
070
071    /**
072     * Calls resize on tab selection on the tabs child hierarchy.<p>
073     */
074    protected class TabSelectionHandler implements SelectionHandler<Integer> {
075
076        /** The tabbed panel. */
077        CmsTabbedPanel<FlowPanel> m_tabsPanel;
078
079        /**
080         * Constructor.<p>
081         *
082         * @param tabsPanel the tabbed panel
083         */
084        TabSelectionHandler(CmsTabbedPanel<FlowPanel> tabsPanel) {
085
086            m_tabsPanel = tabsPanel;
087        }
088
089        /**
090         * @see com.google.gwt.event.logical.shared.SelectionHandler#onSelection(com.google.gwt.event.logical.shared.SelectionEvent)
091         */
092        public void onSelection(final SelectionEvent<Integer> event) {
093
094            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
095
096                public void execute() {
097
098                    FlowPanel tab = m_tabsPanel.getWidget(event.getSelectedItem().intValue());
099                    for (Widget w : tab) {
100                        if (w instanceof I_CmsHasResizeOnShow) {
101                            ((I_CmsHasResizeOnShow)w).resizeOnShow();
102                        }
103                    }
104                }
105            });
106
107        }
108    }
109
110    /**
111     * Handles the size of a tabbed panel.<p>
112     */
113    protected class TabSizeHandler implements SelectionHandler<Integer>, ValueChangeHandler<CmsEntity>, ResizeHandler {
114
115        /** The context panel. */
116        private Panel m_context;
117
118        /** The tabbed panel. */
119        private CmsTabbedPanel<FlowPanel> m_tabbedPanel;
120
121        /**
122         * Constructor.<p>
123         *
124         * @param tabbedPanel the tabbed panel
125         * @param context the context panel
126         */
127        public TabSizeHandler(CmsTabbedPanel<FlowPanel> tabbedPanel, Panel context) {
128
129            m_tabbedPanel = tabbedPanel;
130            m_context = context;
131        }
132
133        /**
134         * @see com.google.gwt.event.logical.shared.ResizeHandler#onResize(com.google.gwt.event.logical.shared.ResizeEvent)
135         */
136        public void onResize(ResizeEvent event) {
137
138            triggerHeightAdjustment();
139        }
140
141        /**
142         * @see com.google.gwt.event.logical.shared.SelectionHandler#onSelection(com.google.gwt.event.logical.shared.SelectionEvent)
143         */
144        public void onSelection(SelectionEvent<Integer> event) {
145
146            triggerHeightAdjustment();
147        }
148
149        /**
150         * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
151         */
152        public void onValueChange(ValueChangeEvent<CmsEntity> event) {
153
154            triggerHeightAdjustment();
155        }
156
157        /**
158         * Adjusts the tabbed panel height to the height of the current tab content.<p>
159         */
160        protected void adjustContextHeight() {
161
162            int tabIndex = m_tabbedPanel.getSelectedIndex();
163            FlowPanel tab = m_tabbedPanel.getWidget(tabIndex);
164            int height = CmsPositionBean.getInnerDimensions(tab.getElement()).getHeight()
165                + m_tabbedPanel.getTabBarHeight();
166            int newHeight = 22 + height;
167            m_context.getElement().getStyle().setHeight(newHeight, Unit.PX);
168        }
169
170        /**
171         * Triggers the tab panel height adjustment scheduled after the browsers event loop.<p>
172         */
173        private void triggerHeightAdjustment() {
174
175            Scheduler.get().scheduleDeferred(new ScheduledCommand() {
176
177                public void execute() {
178
179                    adjustContextHeight();
180                }
181            });
182        }
183    }
184
185    /**
186     * The widget value change handler.<p>
187     */
188    protected class WidgetChangeHandler implements ValueChangeHandler<String> {
189
190        /** The attribute handler. */
191        private CmsAttributeHandler m_attributeHandler;
192
193        /** The value index. */
194        private int m_valueIndex;
195
196        /**
197         * Constructor.<p>
198         *
199         * @param attributeHandler the attribute handler
200         * @param valueIndex the value index, only relevant for in-line rendering
201         */
202        protected WidgetChangeHandler(CmsAttributeHandler attributeHandler, int valueIndex) {
203
204            m_attributeHandler = attributeHandler;
205            m_valueIndex = valueIndex;
206        }
207
208        /**
209         * @see com.google.gwt.event.logical.shared.ValueChangeHandler#onValueChange(com.google.gwt.event.logical.shared.ValueChangeEvent)
210         */
211        public void onValueChange(ValueChangeEvent<String> event) {
212
213            m_attributeHandler.handleValueChange(m_valueIndex, event.getValue());
214        }
215    }
216
217    /** The entity CSS class. */
218    public static final String ENTITY_CLASS = I_CmsLayoutBundle.INSTANCE.form().entity();
219
220    /** The attribute label CSS class. */
221    public static final String LABEL_CLASS = I_CmsLayoutBundle.INSTANCE.form().label();
222
223    /** The renderer name. */
224    public static final String RENDERER_NAME = "default";
225
226    /** The widget holder CSS class. */
227    public static final String WIDGET_HOLDER_CLASS = I_CmsLayoutBundle.INSTANCE.form().widgetHolder();
228
229    /** The entity back end instance. */
230    I_CmsEntityBackend m_entityBackEnd;
231
232    /** The widget service. */
233    I_CmsWidgetService m_widgetService;
234
235    /**
236     * Constructor.<p>
237     *
238     * @param entityBackEnd the entity back end instance
239     * @param widgetService the widget service
240     */
241    public CmsRenderer(I_CmsEntityBackend entityBackEnd, I_CmsWidgetService widgetService) {
242
243        m_entityBackEnd = entityBackEnd;
244        m_widgetService = widgetService;
245    }
246
247    /**
248     * Gets the paths of nested choice attributes starting from a given type.<p>
249     *
250     * @param attributeType the type from which to start
251     * @param startingAtChoiceAttribute true if the attribute is a synthetic CHOICE_ATTRIBUTE
252     *
253     * @return the list of nested choice attribute name paths
254     */
255    public static List<CmsChoiceMenuEntryBean> getChoiceEntries(
256        CmsType attributeType,
257        boolean startingAtChoiceAttribute) {
258
259        CmsChoiceMenuEntryBean rootEntry = new CmsChoiceMenuEntryBean(null);
260        collectChoiceEntries(attributeType, startingAtChoiceAttribute, rootEntry);
261        return rootEntry.getChildren();
262    }
263
264    /**
265     * Sets the attribute choices if present.<p>
266     *
267     * @param widgetService the widget service to use
268     * @param valueWidget the value widget
269     * @param attributeType the attribute type
270     */
271    public static void setAttributeChoice(
272        I_CmsWidgetService widgetService,
273        CmsAttributeValueView valueWidget,
274        CmsType attributeType) {
275
276        if (attributeType.isChoice()) {
277            List<CmsChoiceMenuEntryBean> menuEntries = getChoiceEntries(attributeType, false);
278            for (CmsChoiceMenuEntryBean menuEntry : menuEntries) {
279                valueWidget.addChoice(widgetService, menuEntry);
280            }
281        }
282    }
283
284    /**
285     * Recursive helper method to create a tree structure of choice menu entries for a choice type.<p>
286     *
287     * @param startType the type from which to start
288     * @param startingAtChoiceAttribute true if the recursion starts at a synthetic choice attribute
289     * @param currentEntry the current menu entry bean
290     */
291    private static void collectChoiceEntries(
292        CmsType startType,
293        boolean startingAtChoiceAttribute,
294        CmsChoiceMenuEntryBean currentEntry) {
295
296        if (startingAtChoiceAttribute || startType.isChoice()) {
297            CmsType choiceType = startingAtChoiceAttribute
298            ? startType
299            : startType.getAttributeType(CmsType.CHOICE_ATTRIBUTE_NAME);
300            for (String choiceName : choiceType.getAttributeNames()) {
301                CmsChoiceMenuEntryBean subEntry = currentEntry.addChild(choiceName);
302                CmsType includedType = choiceType.getAttributeType(choiceName);
303                collectChoiceEntries(includedType, false, subEntry);
304            }
305        }
306    }
307
308    /**
309     * @see org.opencms.acacia.client.I_CmsEntityRenderer#configure(java.lang.String)
310     */
311    public I_CmsEntityRenderer configure(String configuration) {
312
313        return this;
314    }
315
316    /**
317     * @see org.opencms.acacia.client.I_CmsEntityRenderer#getName()
318     */
319    public String getName() {
320
321        return RENDERER_NAME;
322    }
323
324    /**
325     * @see org.opencms.acacia.client.I_CmsEntityRenderer#renderAttributeValue(org.opencms.acacia.shared.CmsEntity, org.opencms.acacia.client.CmsAttributeHandler, int, com.google.gwt.user.client.ui.Panel)
326     */
327    public void renderAttributeValue(
328        CmsEntity parentEntity,
329        CmsAttributeHandler attributeHandler,
330        int attributeIndex,
331        Panel context) {
332
333        CmsType entityType = m_entityBackEnd.getType(parentEntity.getTypeName());
334        CmsType attributeType = attributeHandler.getAttributeType();
335        String attributeName = attributeHandler.getAttributeName();
336        int minOccurrence = entityType.getAttributeMinOccurrence(attributeName);
337        CmsEntityAttribute attribute = parentEntity.getAttribute(attributeName);
338        if ((attribute == null) && (minOccurrence > 0)) {
339            attribute = createEmptyAttribute(parentEntity, attributeName, attributeHandler, minOccurrence);
340        }
341
342        CmsValuePanel attributeElement = new CmsValuePanel();
343        context.add(attributeElement);
344        context.addStyleName(ENTITY_CLASS);
345        CmsRootHandler parentHandler = new CmsRootHandler();
346        parentHandler.ensureHandlers(attributeIndex);
347        parentHandler.setHandler(attributeIndex, attributeName, attributeHandler);
348        attributeHandler.setSingleValueIndex(attributeIndex);
349        String label = m_widgetService.getAttributeLabel(attributeName);
350        String help = m_widgetService.getAttributeHelp(attributeName);
351        if (attribute != null) {
352            I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(attributeName, attributeType);
353            CmsAttributeValueView valueWidget = new CmsAttributeValueView(attributeHandler, label, help);
354            if (attributeType.isChoice() && (entityType.getAttributeMaxOccurrence(attributeName) == 1)) {
355                valueWidget.setCollapsed(true);
356            }
357            attributeElement.add(valueWidget);
358            if (attribute.isSimpleValue()) {
359                valueWidget.setValueWidget(
360                    m_widgetService.getAttributeFormWidget(attributeName),
361                    attribute.getSimpleValues().get(attributeIndex),
362                    m_widgetService.getDefaultAttributeValue(
363                        attributeName,
364                        attributeHandler.getSimplePath(attributeIndex)),
365                    true);
366                if (m_widgetService.isDisplayCompact(attributeName)) {
367                    // widget should be displayed in compact view, using only 50% of the available width
368                    valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_FIRST_COLUMN);
369                } else {
370                    if (m_widgetService.isDisplaySingleLine(attributeName)) {
371                        valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
372                    }
373                }
374            } else {
375                valueWidget.setValueEntity(renderer, attribute.getComplexValues().get(attributeIndex));
376                if (m_widgetService.isDisplayCompact(attributeName)) {
377                    valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_NESTED);
378                }
379            }
380            setAttributeChoice(valueWidget, attributeType);
381        }
382        attributeHandler.updateButtonVisisbility();
383    }
384
385    /**
386     * @see org.opencms.acacia.client.I_CmsEntityRenderer#renderForm(org.opencms.acacia.shared.CmsEntity, java.util.List, com.google.gwt.user.client.ui.Panel, org.opencms.acacia.client.I_CmsAttributeHandler, int)
387     */
388    public CmsTabbedPanel<FlowPanel> renderForm(
389        CmsEntity entity,
390        List<CmsTabInfo> tabInfos,
391        Panel context,
392        I_CmsAttributeHandler parentHandler,
393        int attributeIndex) {
394
395        if ((tabInfos == null) || (tabInfos.size() < 2)) {
396            if ((tabInfos != null) && (tabInfos.size() == 1)) {
397                renderDescription(tabInfos.get(0), context);
398            }
399            renderForm(entity, context, parentHandler, attributeIndex);
400            finishTab(context);
401            return null;
402        } else {
403
404            context.getElement().getStyle().setHeight(600, Unit.PX);
405            context.getElement().setAttribute("typeof", entity.getTypeName());
406            context.getElement().setAttribute(CmsGwtConstants.ATTR_DATA_ID, entity.getId());
407            context.getElement().getStyle().setPadding(0, Unit.PX);
408            CmsTabbedPanel<FlowPanel> tabbedPanel = new CmsTabbedPanel<FlowPanel>(CmsTabbedPanelStyle.classicTabs);
409            context.add(tabbedPanel);
410
411            tabbedPanel.addStyleName(I_CmsLayoutBundle.INSTANCE.tabbedPanelCss().wrapTabs());
412            final TabSizeHandler tabSizeHandler = new TabSizeHandler(tabbedPanel, context);
413            tabbedPanel.addSelectionHandler(tabSizeHandler);
414            entity.addValueChangeHandler(tabSizeHandler);
415            // adjust the tab panel height after a delay as some widgets may need time to initialize
416            Scheduler.get().scheduleFixedDelay(new RepeatingCommand() {
417
418                private int m_counter;
419
420                /**
421                 * @see com.google.gwt.core.client.Scheduler.RepeatingCommand#execute()
422                 */
423                public boolean execute() {
424
425                    tabSizeHandler.adjustContextHeight();
426                    m_counter++;
427                    return m_counter < 6;
428                }
429            }, 200);
430            CmsAttributeHandler.setResizeHandler(tabSizeHandler);
431            tabbedPanel.addSelectionHandler(new TabSelectionHandler(tabbedPanel));
432            tabbedPanel.getElement().getStyle().setBorderWidth(0, Unit.PX);
433            Iterator<CmsTabInfo> tabIt = tabInfos.iterator();
434            CmsTabInfo currentTab = tabIt.next();
435            CmsTabInfo nextTab = tabIt.next();
436            FlowPanel tabPanel = createTab();
437            renderDescription(currentTab, tabPanel);
438            tabbedPanel.addNamed(tabPanel, currentTab.getTabName(), currentTab.getTabId());
439            CmsType entityType = m_entityBackEnd.getType(entity.getTypeName());
440            List<String> attributeNames = entityType.getAttributeNames();
441            CmsAttributeValueView lastCompactView = null;
442            boolean collapsed = currentTab.isCollapsed()
443                && ((nextTab != null) && attributeNames.get(1).endsWith("/" + nextTab.getStartName()));
444            for (final String attributeName : attributeNames) {
445                if (!m_widgetService.isVisible(attributeName)) {
446                    // attributes configured as invisible, will be skipped
447                    continue;
448                }
449                if ((nextTab != null) && attributeName.endsWith("/" + nextTab.getStartName())) {
450                    currentTab = nextTab;
451                    nextTab = tabIt.hasNext() ? tabIt.next() : null;
452                    finishTab(tabPanel);
453                    tabPanel = createTab();
454                    renderDescription(currentTab, tabPanel);
455                    tabbedPanel.addNamed(tabPanel, currentTab.getTabName(), currentTab.getTabId());
456                    // check if the tab content may be collapsed
457                    if (currentTab.isCollapsed()) {
458                        int currentIndex = attributeNames.indexOf(attributeName);
459                        collapsed = ((currentIndex + 1) == attributeNames.size())
460                            || ((nextTab != null)
461                                && attributeNames.get(currentIndex + 1).endsWith("/" + nextTab.getStartName()));
462                    }
463                    if (lastCompactView != null) {
464                        // previous widget was set to first column mode,
465                        // revert that as no following widget will occupy the second column
466                        lastCompactView.setCompactMode(CmsAttributeValueView.COMPACT_MODE_WIDE);
467                    }
468                }
469                CmsAttributeHandler handler = new CmsAttributeHandler(
470                    m_entityBackEnd,
471                    entity,
472                    attributeName,
473                    m_widgetService);
474                parentHandler.setHandler(attributeIndex, attributeName, handler);
475                CmsType attributeType = entityType.getAttributeType(attributeName);
476                int minOccurrence = entityType.getAttributeMinOccurrence(attributeName);
477                CmsEntityAttribute attribute = entity.getAttribute(attributeName);
478                // only single complex values may be collapsed
479                if (collapsed
480                    && (attribute != null)
481                    && !attributeType.isSimpleType()
482                    && (minOccurrence == 1)
483                    && (entityType.getAttributeMaxOccurrence(attributeName) == 1)) {
484                    I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(
485                        attributeName,
486                        attributeType);
487                    renderer.renderForm(attribute.getComplexValue(), tabPanel, handler, 0);
488                } else {
489                    CmsValuePanel attributeElement = new CmsValuePanel();
490                    tabPanel.add(attributeElement);
491                    if ((attribute == null) && (minOccurrence > 0)) {
492                        attribute = createEmptyAttribute(entity, attributeName, handler, minOccurrence);
493                    }
494                    lastCompactView = renderAttribute(
495                        entityType,
496                        attributeType,
497                        attribute,
498                        handler,
499                        attributeElement,
500                        attributeName,
501                        lastCompactView);
502                }
503                handler.updateButtonVisisbility();
504            }
505            finishTab(tabPanel);
506            if (lastCompactView != null) {
507                // previous widget was set to first column mode,
508                // revert that as no following widget will occupy the second column
509                lastCompactView.setCompactMode(CmsAttributeValueView.COMPACT_MODE_WIDE);
510            }
511
512            return tabbedPanel;
513        }
514    }
515
516    /**
517     * @see org.opencms.acacia.client.I_CmsEntityRenderer#renderForm(org.opencms.acacia.shared.CmsEntity, com.google.gwt.user.client.ui.Panel, org.opencms.acacia.client.I_CmsAttributeHandler, int)
518     */
519    public void renderForm(CmsEntity entity, Panel context, I_CmsAttributeHandler parentHandler, int attributeIndex) {
520
521        context.addStyleName(ENTITY_CLASS);
522        context.getElement().setAttribute("typeof", entity.getTypeName());
523        context.getElement().setAttribute(CmsGwtConstants.ATTR_DATA_ID, entity.getId());
524        CmsType entityType = m_entityBackEnd.getType(entity.getTypeName());
525        CmsAttributeValueView lastCompactView = null;
526        if (entityType.isChoice()) {
527            CmsEntityAttribute attribute = entity.getAttribute(CmsType.CHOICE_ATTRIBUTE_NAME);
528            CmsAttributeHandler handler = new CmsAttributeHandler(
529                m_entityBackEnd,
530                entity,
531                CmsType.CHOICE_ATTRIBUTE_NAME,
532                m_widgetService);
533            parentHandler.setHandler(attributeIndex, CmsType.CHOICE_ATTRIBUTE_NAME, handler);
534            CmsValuePanel attributeElement = new CmsValuePanel();
535            if ((attribute != null) && attribute.isComplexValue()) {
536                for (CmsEntity choiceEntity : attribute.getComplexValues()) {
537                    CmsType choiceType = m_entityBackEnd.getType(choiceEntity.getTypeName());
538                    List<CmsEntityAttribute> choiceAttributes = choiceEntity.getAttributes();
539                    CmsEntityAttribute choiceAttribute = choiceAttributes.get(0);
540                    CmsType attributeType = choiceType.getAttributeType(choiceAttribute.getAttributeName());
541                    I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(
542                        choiceAttribute.getAttributeName(),
543                        attributeType);
544                    String label = m_widgetService.getAttributeLabel(choiceAttribute.getAttributeName());
545                    String help = m_widgetService.getAttributeHelp(choiceAttribute.getAttributeName());
546                    context.add(attributeElement);
547                    CmsAttributeValueView valueWidget = new CmsAttributeValueView(handler, label, help);
548                    attributeElement.add(valueWidget);
549                    if (choiceAttribute.isSimpleValue()) {
550                        valueWidget.setValueWidget(
551                            m_widgetService.getAttributeFormWidget(choiceAttribute.getAttributeName()),
552                            choiceAttribute.getSimpleValue(),
553                            m_widgetService.getDefaultAttributeValue(
554                                choiceAttribute.getAttributeName(),
555                                handler.getSimplePath(attributeIndex)),
556                            true);
557                        if (m_widgetService.isDisplaySingleLine(choiceAttribute.getAttributeName())) {
558                            valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
559                        }
560                    } else {
561                        valueWidget.setValueEntity(renderer, choiceAttribute.getComplexValue());
562                        if (m_widgetService.isDisplayCompact(choiceAttribute.getAttributeName())) {
563                            valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_NESTED);
564                        }
565                    }
566                    setAttributeChoice(valueWidget, entityType);
567                }
568            }
569            handler.updateButtonVisisbility();
570        } else {
571            List<String> attributeNames = entityType.getAttributeNames();
572            for (String attributeName : attributeNames) {
573                if (!m_widgetService.isVisible(attributeName)) {
574                    // attributes configured as invisible, will be skipped
575                    continue;
576                }
577                int minOccurrence = entityType.getAttributeMinOccurrence(attributeName);
578                CmsEntityAttribute attribute = entity.getAttribute(attributeName);
579                CmsAttributeHandler handler = new CmsAttributeHandler(
580                    m_entityBackEnd,
581                    entity,
582                    attributeName,
583                    m_widgetService);
584                parentHandler.setHandler(attributeIndex, attributeName, handler);
585                if ((attribute == null) && (minOccurrence > 0)) {
586                    attribute = createEmptyAttribute(entity, attributeName, handler, minOccurrence);
587                }
588
589                CmsType attributeType = entityType.getAttributeType(attributeName);
590                CmsValuePanel attributeElement = new CmsValuePanel();
591                context.add(attributeElement);
592                lastCompactView = renderAttribute(
593                    entityType,
594                    attributeType,
595                    attribute,
596                    handler,
597                    attributeElement,
598                    attributeName,
599                    lastCompactView);
600            }
601        }
602        if (lastCompactView != null) {
603            // previous widget was set to first column mode,
604            // revert that as no following widget will occupy the second column
605            lastCompactView.setCompactMode(CmsAttributeValueView.COMPACT_MODE_WIDE);
606        }
607    }
608
609    /**
610     * @see org.opencms.acacia.client.I_CmsEntityRenderer#renderInline(org.opencms.acacia.shared.CmsEntity, org.opencms.acacia.client.I_CmsInlineFormParent, org.opencms.acacia.client.I_CmsInlineHtmlUpdateHandler, org.opencms.acacia.client.I_CmsAttributeHandler, int)
611     */
612    public void renderInline(
613        CmsEntity entity,
614        I_CmsInlineFormParent formParent,
615        I_CmsInlineHtmlUpdateHandler updateHandler,
616        I_CmsAttributeHandler parentHandler,
617        int attributeIndex) {
618
619        CmsType entityType = m_entityBackEnd.getType(entity.getTypeName());
620        List<String> attributeNames = entityType.getAttributeNames();
621        for (String attributeName : attributeNames) {
622            CmsType attributeType = entityType.getAttributeType(attributeName);
623            I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(attributeName, attributeType);
624            renderer.renderInline(
625                entity,
626                attributeName,
627                formParent,
628                updateHandler,
629                parentHandler,
630                attributeIndex,
631                entityType.getAttributeMinOccurrence(attributeName),
632                entityType.getAttributeMaxOccurrence(attributeName));
633        }
634    }
635
636    /**
637     * @see org.opencms.acacia.client.I_CmsEntityRenderer#renderInline(org.opencms.acacia.shared.CmsEntity, java.lang.String, org.opencms.acacia.client.I_CmsInlineFormParent, org.opencms.acacia.client.I_CmsInlineHtmlUpdateHandler, org.opencms.acacia.client.I_CmsAttributeHandler, int, int, int)
638     */
639    public void renderInline(
640        CmsEntity parentEntity,
641        String attributeName,
642        I_CmsInlineFormParent formParent,
643        I_CmsInlineHtmlUpdateHandler updateHandler,
644        I_CmsAttributeHandler parentHandler,
645        int attributeIndex,
646        int minOccurrence,
647        int maxOccurrence) {
648
649        CmsEntityAttribute attribute = parentEntity.getAttribute(attributeName);
650        CmsAttributeHandler handler = new CmsAttributeHandler(
651            m_entityBackEnd,
652            parentEntity,
653            attributeName,
654            m_widgetService);
655        parentHandler.setHandler(attributeIndex, attributeName, handler);
656        if (attribute != null) {
657            List<Element> elements = m_entityBackEnd.getAttributeElements(
658                parentEntity,
659                attributeName,
660                formParent.getElement());
661            if (!elements.isEmpty()) {
662                for (int i = 0; i < elements.size(); i++) {
663                    Element element = elements.get(i);
664                    I_CmsEditWidget widget = m_widgetService.getAttributeInlineWidget(attributeName, element);
665                    if (attribute.isSimpleValue() && (widget != null)) {
666                        Element tempSpan = DOM.createSpan();
667                        tempSpan.setInnerHTML(attribute.getSimpleValues().get(i));
668                        String value = tempSpan.getInnerHTML().trim();
669                        // verify the current value equals the element content
670                        String innerHtml = element.getInnerHTML().trim();
671                        if (innerHtml.equals(value)) {
672                            widget.addValueChangeHandler(new WidgetChangeHandler(handler, i));
673                            formParent.adoptWidget(widget);
674                        } else {
675                            CmsInlineEntityWidget.createWidgetForEntity(
676                                element,
677                                formParent,
678                                parentEntity,
679                                handler,
680                                i,
681                                updateHandler,
682                                m_widgetService);
683                        }
684                    } else {
685                        CmsInlineEntityWidget.createWidgetForEntity(
686                            element,
687                            formParent,
688                            parentEntity,
689                            handler,
690                            i,
691                            updateHandler,
692                            m_widgetService);
693                    }
694                }
695            }
696            if (attribute.isComplexValue()) {
697                int index = 0;
698                for (CmsEntity entity : attribute.getComplexValues()) {
699                    renderInline(entity, formParent, updateHandler, handler, index);
700                    index++;
701                }
702            }
703        } else {
704            List<Element> elements = m_entityBackEnd.getAttributeElements(
705                parentEntity,
706                attributeName,
707                formParent.getElement());
708            if (!elements.isEmpty() && (elements.size() == 1)) {
709                CmsInlineEntityWidget.createWidgetForEntity(
710                    elements.get(0),
711                    formParent,
712                    parentEntity,
713                    handler,
714                    -1,
715                    updateHandler,
716                    m_widgetService);
717            }
718        }
719
720    }
721
722    /**
723     * Creates an empty attribute.<p>
724     *
725     * @param parentEntity the parent entity
726     * @param attributeName the attribute name
727     * @param handler the attribute handler
728     * @param minOccurrence the minimum occurrence of the attribute
729     *
730     * @return the entity attribute
731     */
732    protected CmsEntityAttribute createEmptyAttribute(
733        CmsEntity parentEntity,
734        String attributeName,
735        CmsAttributeHandler handler,
736        int minOccurrence) {
737
738        CmsEntityAttribute result = null;
739        CmsType attributeType = m_entityBackEnd.getType(parentEntity.getTypeName()).getAttributeType(attributeName);
740        if (attributeType.isSimpleType()) {
741            for (int i = 0; i < minOccurrence; i++) {
742                parentEntity.addAttributeValue(
743                    attributeName,
744                    m_widgetService.getDefaultAttributeValue(attributeName, handler.getSimplePath(i)));
745            }
746            result = parentEntity.getAttribute(attributeName);
747        } else {
748            for (int i = 0; i < minOccurrence; i++) {
749                parentEntity.addAttributeValue(
750                    attributeName,
751                    m_entityBackEnd.createEntity(null, attributeType.getId()));
752            }
753            result = parentEntity.getAttribute(attributeName);
754        }
755        return result;
756    }
757
758    /**
759     * Creates a tab.<p>
760     *
761     * @return the created tab
762     */
763    private FlowPanel createTab() {
764
765        FlowPanel tabPanel;
766        tabPanel = new FlowPanel();
767        tabPanel.addStyleName(ENTITY_CLASS);
768        tabPanel.addStyleName(I_CmsLayoutBundle.INSTANCE.form().formParent());
769        tabPanel.getElement().getStyle().setMargin(0, Unit.PX);
770        return tabPanel;
771    }
772
773    /**
774     * This is called after the last attribute for a tab (or for the whole content, if no tabs are used) is rendered.
775     *
776     * @param panel the parent panel into which the attributes were rendered
777     */
778    private void finishTab(Panel panel) {
779
780        /*
781         * Place an element after all the other attribute elements in the panel.
782         * We need this because the 'column' layout causes attributes to be floated, which
783         * confuses the algorithm used to resize the tab panel.
784         */
785        FlowPanel formTabTerminator = new FlowPanel();
786        formTabTerminator.addStyleName(I_CmsLayoutBundle.INSTANCE.form().formTabTerminator());
787        panel.add(formTabTerminator);
788    }
789
790    /**
791     * Renders a single attribute.<p>
792     *
793     * @param entityType the type of the entity containing the attribute
794     * @param attributeType the attribute type
795     * @param attribute the attribute, or null if not set
796     * @param handler the attribute handler
797     * @param attributeElement the attribute parent element
798     * @param attributeName the attribute name
799     * @param lastCompactView the previous attribute view that was rendered in compact mode if present
800     *
801     * @return the last attribute view that was rendered in compact mode if present
802     */
803    private CmsAttributeValueView renderAttribute(
804        CmsType entityType,
805        CmsType attributeType,
806        CmsEntityAttribute attribute,
807        CmsAttributeHandler handler,
808        CmsValuePanel attributeElement,
809        String attributeName,
810        CmsAttributeValueView lastCompactView) {
811
812        String label = m_widgetService.getAttributeLabel(attributeName);
813        String help = m_widgetService.getAttributeHelp(attributeName);
814        if (attribute != null) {
815            I_CmsEntityRenderer renderer = m_widgetService.getRendererForAttribute(attributeName, attributeType);
816            for (int i = 0; i < attribute.getValueCount(); i++) {
817                CmsAttributeValueView valueWidget = new CmsAttributeValueView(handler, label, help);
818                if (attributeType.isChoice() && (entityType.getAttributeMaxOccurrence(attributeName) == 1)) {
819                    valueWidget.setCollapsed(true);
820                }
821                attributeElement.add(valueWidget);
822                if (attribute.isSimpleValue()) {
823                    valueWidget.setValueWidget(
824                        m_widgetService.getAttributeFormWidget(attributeName),
825                        attribute.getSimpleValues().get(i),
826                        m_widgetService.getDefaultAttributeValue(attributeName, handler.getSimplePath(i)),
827                        true);
828                    // check for compact view setting
829                    if (m_widgetService.isDisplayCompact(attributeName)) {
830                        // widget should be displayed in compact view, using only 50% of the available width
831                        if (lastCompactView == null) {
832                            // set mode to first column
833                            valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_FIRST_COLUMN);
834                            lastCompactView = valueWidget;
835                        } else {
836                            // previous widget is displayed as first column, set second column mode
837                            valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SECOND_COLUMN);
838                            lastCompactView = null;
839                        }
840                    } else {
841                        if (lastCompactView != null) {
842                            // previous widget was set to first column mode,
843                            // revert that as the current widget will be displayed in a new line
844                            lastCompactView.setCompactMode(CmsAttributeValueView.COMPACT_MODE_WIDE);
845                            lastCompactView = null;
846                        }
847                        if (m_widgetService.isDisplaySingleLine(attributeName)) {
848                            valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
849                        }
850                    }
851                } else {
852                    valueWidget.setValueEntity(renderer, attribute.getComplexValues().get(i));
853                    if (lastCompactView != null) {
854                        // previous widget was set to first column mode,
855                        // revert that as the current widget will be displayed in a new line
856                        lastCompactView.setCompactMode(CmsAttributeValueView.COMPACT_MODE_WIDE);
857                        lastCompactView = null;
858                    }
859                    if (m_widgetService.isDisplayCompact(attributeName)) {
860                        valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_NESTED);
861                    }
862                }
863                setAttributeChoice(valueWidget, attributeType);
864            }
865        } else {
866            CmsAttributeValueView valueWidget = new CmsAttributeValueView(handler, label, help);
867            attributeElement.add(valueWidget);
868            if (attributeType.isSimpleType()) {
869                // create a deactivated widget, to add the attribute on click
870                valueWidget.setValueWidget(
871                    m_widgetService.getAttributeFormWidget(attributeName),
872                    "",
873                    m_widgetService.getDefaultAttributeValue(attributeName, handler.getSimplePath(0)),
874                    false);
875                // check for compact view setting
876                if (m_widgetService.isDisplayCompact(attributeName)) {
877                    // widget should be displayed in compact view, using only 50% of the available width
878                    if (lastCompactView == null) {
879                        // set mode to first column
880                        valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_FIRST_COLUMN);
881                        lastCompactView = valueWidget;
882                    } else {
883                        // previous widget is displayed as first column, set second column mode
884                        valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SECOND_COLUMN);
885                        lastCompactView = null;
886                    }
887                } else {
888                    if (lastCompactView != null) {
889                        // previous widget was set to first column mode,
890                        // revert that as the current widget will be displayed in a new line
891                        lastCompactView.setCompactMode(CmsAttributeValueView.COMPACT_MODE_WIDE);
892                        lastCompactView = null;
893                    }
894                    if (m_widgetService.isDisplaySingleLine(attributeName)) {
895                        valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_SINGLE_LINE);
896                    }
897                }
898            } else {
899                if (lastCompactView != null) {
900                    // previous widget was set to first column mode,
901                    // revert that as the current widget will be displayed in a new line
902                    lastCompactView.setCompactMode(CmsAttributeValueView.COMPACT_MODE_WIDE);
903                    lastCompactView = null;
904                }
905                if (m_widgetService.isDisplayCompact(attributeName)) {
906                    valueWidget.setCompactMode(CmsAttributeValueView.COMPACT_MODE_NESTED);
907                }
908            }
909            setAttributeChoice(valueWidget, attributeType);
910        }
911        handler.updateButtonVisisbility();
912        return lastCompactView;
913    }
914
915    /**
916     * Renders the tab description in a given panel.<p>
917     * @param tabInfo the tab info object
918     *
919     * @param descriptionParent the panel in which to render the tab description
920     */
921    private void renderDescription(CmsTabInfo tabInfo, Panel descriptionParent) {
922
923        if (tabInfo.getDescription() != null) {
924            HTML descriptionLabel = new HTML();
925            descriptionLabel.addStyleName(I_CmsLayoutBundle.INSTANCE.form().tabDescription());
926            if (!tabInfo.getDescription().startsWith("<div")) {
927                // add default styling in case of simple HTML
928                descriptionLabel.addStyleName(I_CmsLayoutBundle.INSTANCE.form().tabDescriptionPanel());
929            }
930            descriptionLabel.setHTML(tabInfo.getDescription());
931            descriptionParent.add(descriptionLabel);
932        }
933    }
934
935    /**
936     * Sets the attribute choices if present.<p>
937     *
938     * @param valueWidget the value widget
939     * @param attributeType the attribute type
940     */
941    private void setAttributeChoice(CmsAttributeValueView valueWidget, CmsType attributeType) {
942
943        setAttributeChoice(m_widgetService, valueWidget, attributeType);
944    }
945}