/** * Copyright 2010 Google Inc. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */ package org.waveprotocol.wave.client.wavepanel.view.dom; import static org.waveprotocol.wave.client.uibuilder.BuilderHelper.KIND_ATTRIBUTE; import static org.waveprotocol.wave.client.wavepanel.view.dom.DomViewHelper.load; import com.google.gwt.dom.client.Document; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.ImageElement; import org.waveprotocol.wave.client.common.safehtml.SafeHtmlBuilder; import org.waveprotocol.wave.client.common.util.StringSequence; import org.waveprotocol.wave.client.uibuilder.UiBuilder; import org.waveprotocol.wave.client.wavepanel.view.IntrinsicBlipMetaView; import org.waveprotocol.wave.client.wavepanel.view.View.Type; import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipMetaViewBuilder; import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipMetaViewBuilder.Components; import org.waveprotocol.wave.client.wavepanel.view.dom.full.BlipViewBuilder; import org.waveprotocol.wave.client.wavepanel.view.dom.full.TypeCodes; import org.waveprotocol.wave.client.wavepanel.view.dom.full.WavePanelResourceLoader; import org.waveprotocol.wave.model.util.Pair; import java.util.EnumMap; import java.util.EnumSet; import java.util.Set; /** * BlipViewDomImpl of the blip view. * */ public final class BlipMetaDomImpl implements DomView, IntrinsicBlipMetaView { /** * The INLINE_LOCATOR_ATTRIBUTE is the dom element attribute that contains the * serialized string of the inline locator. * The INLINE_LOCATOR_PROPERTY is a the dom element property that contains the deserialized * inline locator object. * The two must be different since in IE, property and attribute are not namespaced separately. */ private static final String INLINE_LOCATOR_PROPERTY = "inlineSequence"; public static final String INLINE_LOCATOR_ATTRIBUTE = "inline"; private final static BlipViewBuilder.Css CSS = WavePanelResourceLoader.getBlip().css(); /** The DOM element of this view. */ private final Element self; /** The HTML id of {@code self}. */ private final String id; // // UI fields for both intrinsic and structural elements. // Element references are loaded lazily and cached. // private Element time; private Element contentContainer; private ImageElement avatar; private Element metaline; private Element metabar; private Element menu; private StringSequence inlineLocators; BlipMetaDomImpl(Element self, String id) { this.self = self; this.id = id; } public static BlipMetaDomImpl of(Element e) { return new BlipMetaDomImpl(e, e.getId()); } @Override public void setTime(String time) { getTime().setInnerText(time); } @Override public void setAvatar(String avatarUrl) { getAvatar().setSrc(avatarUrl); } @Override public void setMetaline(String metaline) { getMetaline().setInnerText(metaline); } @Override public void setRead(boolean read) { // The entire set of class names is always replaced, because // server-generated and client-generated classes do not mix. if (read) { getMetabar().setClassName(CSS.metabar() + " " + CSS.read()); } else { getMetabar().setClassName(CSS.metabar() + " " + CSS.unread()); } } @Override public void enable(Set<MenuOption> toEnable) { Pair<EnumMap<MenuOption, BlipMenuItemDomImpl>, EnumSet<MenuOption>> state = getMenuState(); EnumSet<MenuOption> options = EnumSet.copyOf(state.first.keySet()); EnumSet<MenuOption> selected = state.second; options.addAll(toEnable); setMenuState(options, selected); } @Override public void disable(Set<MenuOption> toDisable) { Pair<EnumMap<MenuOption, BlipMenuItemDomImpl>, EnumSet<MenuOption>> state = getMenuState(); EnumSet<MenuOption> options = EnumSet.copyOf(state.first.keySet()); EnumSet<MenuOption> selected = state.second; options.removeAll(toDisable); selected.removeAll(toDisable); setMenuState(options, selected); } @Override public void select(MenuOption option) { BlipMenuItemDomImpl item = getMenuState().first.get(option); if (item != null) { item.select(); } } @Override public void deselect(MenuOption option) { BlipMenuItemDomImpl item = getMenuState().first.get(option); if (item != null) { item.deselect(); } } /** * Scrapes the menu state from the DOM. The menu state describes what options * exist, and which, if any, are currently selected. * * @return a mapping from available options to their UI objects, and the * subset of those options that are currently selected. */ private Pair<EnumMap<MenuOption, BlipMenuItemDomImpl>, EnumSet<MenuOption>> getMenuState() { EnumMap<MenuOption, BlipMenuItemDomImpl> options = new EnumMap<MenuOption, BlipMenuItemDomImpl>(MenuOption.class); EnumSet<MenuOption> selected = EnumSet.noneOf(MenuOption.class); Element e = getMenu().getFirstChildElement(); while (e != null) { if (e.hasAttribute(KIND_ATTRIBUTE) && e.getAttribute(KIND_ATTRIBUTE).equals(TypeCodes.kind(Type.MENU_ITEM))) { BlipMenuItemDomImpl item = BlipMenuItemDomImpl.of(e); MenuOption option = item.getOption(); options.put(option, item); if (item.isSelected()) { selected.add(option); } } e = e.getNextSiblingElement(); } return Pair.of(options, selected); } /** * Replaces the current menu DOM with a new menu. * * @param options options to include in the menu * @param selected which options, if any, are to be selected. */ private void setMenuState(Set<MenuOption> options, Set<MenuOption> selected) { UiBuilder builder = BlipMetaViewBuilder.menuBuilder(options, selected, CSS); SafeHtmlBuilder out = new SafeHtmlBuilder(); builder.outputHtml(out); getMenu().setInnerHTML(out.toSafeHtml().asString()); } public void clearContent() { getInlineLocators().clear(); getContentContainer().getFirstChildElement().setInnerHTML(""); } public void setContent(Element document) { // Server-side document rendering is not correct - it leaves off the crucial // "document" class. document.addClassName("document"); getContentContainer().getFirstChildElement().appendChild(document); } public StringSequence getInlineLocators() { if (inlineLocators == null) { Element content = getContentContainer().getFirstChildElement(); if (content != null) { inlineLocators = (StringSequence) content.getPropertyObject(INLINE_LOCATOR_PROPERTY); if (inlineLocators == null) { // Note: getAttribute() of a missing attribute does not return null on // all browsers. if (content.hasAttribute(INLINE_LOCATOR_ATTRIBUTE)) { String serial = content.getAttribute(INLINE_LOCATOR_ATTRIBUTE); inlineLocators = StringSequence.create(serial); } else { inlineLocators = StringSequence.create(); } content.setPropertyObject(INLINE_LOCATOR_PROPERTY, inlineLocators); } } else { // Leave inlineLocators as null, since the document is not here yet. } } return inlineLocators; } // // Generated code. There is no informative content in the code below. // private Element getTime() { if (time == null) { time = load(id, Components.TIME); } return time; } private ImageElement getAvatar() { if (avatar == null) { avatar = load(id, Components.AVATAR).cast(); } return avatar; } private Element getMetaline() { if (metaline == null) { metaline = load(id, Components.METALINE); } return metaline; } private Element getMetabar() { if (metabar == null) { metabar = load(id, Components.METABAR); } return metabar; } private Element getMenu() { if (menu == null) { menu = load(id, Components.MENU); } return menu; } // // Structural elements are public, in order to export structural control. // public Element getContentContainer() { if (contentContainer == null) { contentContainer = load(id, Components.CONTENT); } return contentContainer; } public void remove() { getElement().removeFromParent(); } // // DomView nature. // @Override public Element getElement() { return self; } @Override public String getId() { return id; } // // DOM-specific structural knowledge. // Element getInlineAnchorAfter(Element ref) { String id = ref != null ? ref.getId() : null; String nextId = getInlineLocators().getNext(id); return Document.get().getElementById(nextId); } Element getInlineAnchorBefore(Element ref) { String id = ref != null ? ref.getId() : null; String nextId = getInlineLocators().getPrevious(id); return Document.get().getElementById(nextId); } void insertInlineLocatorBefore(Element ref, Element x) { String id = ref != null ? ref.getId() : null; getInlineLocators().insertBefore(id, x.getId()); } void removeInlineLocator(Element x) { getInlineLocators().remove(x.getId()); } @Override public boolean equals(Object obj) { return DomViewHelper.equals(this, obj); } @Override public int hashCode() { return DomViewHelper.hashCode(this); } }