/* * Copyright 2008 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 com.google.gwt.uibinder.elementparsers; import com.google.gwt.core.ext.UnableToCompleteException; import com.google.gwt.core.ext.typeinfo.JClassType; import com.google.gwt.core.ext.typeinfo.TypeOracle; import com.google.gwt.uibinder.rebind.FieldManager; import com.google.gwt.uibinder.rebind.FieldWriter; import com.google.gwt.uibinder.client.LazyDomElement; import com.google.gwt.uibinder.rebind.UiBinderWriter; import com.google.gwt.uibinder.rebind.XMLElement; import com.google.gwt.uibinder.rebind.messages.MessageWriter; import com.google.gwt.uibinder.rebind.messages.MessagesWriter; import com.google.gwt.user.client.ui.HasHTML; import com.google.gwt.user.client.ui.HasText; import java.util.HashMap; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * Used by {@link HTMLPanelParser}. Refines {@link HtmlPlaceholderInterpreter} * to allow widgets to appear inside msg elements in an HTMLPanel. * <p> * HasText and HasHTML get special treatment, where their innerText or innerHTML * become part of the {@literal @}Default value of the message being generated. * E.g., this markup in an HTMLPanel: * * <pre> * <m:msg>Hello <gwt:HyperLink>click here</gwt:HyperLink> thank you.</m:msg></pre> * * becomes a message like this: <pre> * {@literal @}Default("Hello {0}click here{1} thank you.") * String getMessage1( * {@literal @}Example("<span>") String widget1Begin, * {@literal @}Example("</span>") String widget1End * );</pre> * * <p> * The contents of other widget types are opaque to the message, and are covered * by a single placeholder. One implication of this is that the content of an * HTMLPanel inside a msg in another HTMLPanel must always be in a separate * message. */ class WidgetPlaceholderInterpreter extends HtmlPlaceholderInterpreter { /* * Could break this up into three further classes, for HasText, HasHTML and * Other, but that seems more trouble than it's worth. */ private final FieldManager fieldManager; private int serial = 0; private final String ancestorExpression; private final String fieldName; private final Map<String, XMLElement> idToWidgetElement = new HashMap<String, XMLElement>(); private final Set<String> idIsHasHTML = new HashSet<String>(); private final Set<String> idIsHasText = new HashSet<String>(); WidgetPlaceholderInterpreter(String fieldName, UiBinderWriter writer, MessageWriter message, String ancestorExpression) { super(writer, message, ancestorExpression); this.fieldName = fieldName; this.ancestorExpression = ancestorExpression; this.fieldManager = uiWriter.getFieldManager(); } @Override public String interpretElement(XMLElement elem) throws UnableToCompleteException { if (!uiWriter.isWidgetElement(elem)) { return super.interpretElement(elem); } JClassType type = uiWriter.findFieldType(elem); TypeOracle oracle = uiWriter.getOracle(); MessagesWriter mw = uiWriter.getMessages(); String name = mw.consumeMessageAttribute("ph", elem); if ("".equals(name)) { name = "widget" + (++serial); } String idHolder = uiWriter.declareDomIdHolder(null); idToWidgetElement.put(idHolder, elem); if (oracle.findType(HasHTML.class.getName()).isAssignableFrom(type)) { return handleHasHTMLPlaceholder(elem, name, idHolder); } if (oracle.findType(HasText.class.getName()).isAssignableFrom(type)) { return handleHasTextPlaceholder(elem, name, idHolder); } return handleOpaqueWidgetPlaceholder(elem, name, idHolder); } /** * Called by {@link XMLElement#consumeInnerHtml} after all elements * have been handed to {@link #interpretElement}. */ @Override public String postProcess(String consumed) throws UnableToCompleteException { FieldWriter fieldWriter = fieldManager.require(fieldName); for (String idHolder : idToWidgetElement.keySet()) { XMLElement childElem = idToWidgetElement.get(idHolder); FieldWriter childFieldWriter = uiWriter.parseElementToField(childElem); genSetWidgetTextCall(idHolder, childFieldWriter.getName()); if (uiWriter.useLazyWidgetBuilders()) { // Register a DOM id field. String lazyDomElementPath = LazyDomElement.class.getCanonicalName(); String elementPointer = idHolder + "Element"; FieldWriter elementWriter = fieldManager.registerField( lazyDomElementPath, elementPointer); elementWriter.setInitializer(String.format("new %s<Element>(%s)", lazyDomElementPath, fieldManager.convertFieldToGetter(idHolder))); // Add attach/detach sections for this element. fieldWriter.addAttachStatement("%s.get();", fieldManager.convertFieldToGetter(elementPointer)); fieldWriter.addDetachStatement( "%s.addAndReplaceElement(%s, %s.get());", fieldName, fieldManager.convertFieldToGetter(childFieldWriter.getName()), fieldManager.convertFieldToGetter(elementPointer)); } else { uiWriter.addInitStatement( "%1$s.addAndReplaceElement(%2$s, %3$s);", fieldName, childFieldWriter.getName(), idHolder); } } /* * We get used recursively, so this will be called again. Empty the map * or else we'll re-register things. */ idToWidgetElement.clear(); return super.postProcess(consumed); } private String genCloseTag(String name) { String closePlaceholder = nextClosePlaceholder(name + "End", "</span>"); return closePlaceholder; } private String genOpenTag(XMLElement source, String name, String idHolder) { idHolder = fieldManager.convertFieldToGetter(idHolder); if (uiWriter.useSafeHtmlTemplates()) { idHolder = uiWriter.tokenForStringExpression(source, idHolder); } else { idHolder = "\" + " + idHolder + " + \""; } String openTag = String.format("<span id='%s'>", idHolder); String openPlaceholder = nextOpenPlaceholder(name + "Begin", openTag); return openPlaceholder; } private void genSetWidgetTextCall(String idHolder, String childField) { if (uiWriter.useLazyWidgetBuilders()) { if (idIsHasText.contains(idHolder)) { fieldManager.require(fieldName).addAttachStatement( "%s.setText(%s.getElementById(%s).getInnerText());", fieldManager.convertFieldToGetter(childField), fieldName, fieldManager.convertFieldToGetter(idHolder)); } else if (idIsHasHTML.contains(idHolder)) { fieldManager.require(fieldName).addAttachStatement( "%s.setHTML(%s.getElementById(%s).getInnerHTML());", fieldManager.convertFieldToGetter(childField), fieldName, fieldManager.convertFieldToGetter(idHolder)); } } else { if (idIsHasText.contains(idHolder)) { uiWriter.addInitStatement( "%s.setText(%s.getElementById(%s).getInnerText());", childField, fieldName, idHolder); } if (idIsHasHTML.contains(idHolder)) { uiWriter.addInitStatement( "%s.setHTML(%s.getElementById(%s).getInnerHTML());", childField, fieldName, idHolder); } } } private String handleHasHTMLPlaceholder(XMLElement elem, String name, String idHolder) throws UnableToCompleteException { idIsHasHTML.add(idHolder); String openPlaceholder = genOpenTag(elem, name, idHolder); String body = elem.consumeInnerHtml(new HtmlPlaceholderInterpreter(uiWriter, message, ancestorExpression)); String bodyToken = tokenator.nextToken(body); String closePlaceholder = genCloseTag(name); return openPlaceholder + bodyToken + closePlaceholder; } private String handleHasTextPlaceholder(XMLElement elem, String name, String idHolder) throws UnableToCompleteException { idIsHasText.add(idHolder); String openPlaceholder = genOpenTag(elem, name, idHolder); String body = elem.consumeInnerText(new TextPlaceholderInterpreter(uiWriter, message)); String bodyToken = tokenator.nextToken(body); String closePlaceholder = genCloseTag(name); return openPlaceholder + bodyToken + closePlaceholder; } private String handleOpaqueWidgetPlaceholder(XMLElement source, String name, String idHolder) { idHolder = fieldManager.convertFieldToGetter(idHolder); if (uiWriter.useSafeHtmlTemplates()) { idHolder = uiWriter.tokenForStringExpression(source, idHolder); } else { idHolder = "\" + " + idHolder + " + \""; } String tag = String.format("<span id='%s'></span>", idHolder); String placeholder = nextPlaceholder(name, "<span></span>", tag); return placeholder; } }