Java tutorial
/** * 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 com.google.livingstories.client.lsp; import com.google.gwt.dom.client.Element; import com.google.gwt.dom.client.NodeList; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.FlowPanel; import com.google.gwt.user.client.ui.HTMLPanel; import com.google.gwt.user.client.ui.Widget; import com.google.livingstories.client.ui.Link; import com.google.livingstories.client.ui.ReadMoreLink; import java.util.ArrayList; import java.util.List; /** * A class to render HTML content from the summary or within content items. It does 2 things * currently: * 1. Breaks up the content into chunks separate by 'Read more' links if the text contains * break tags. * 2. If the text contains tags for other content items, converts them to links with appropriate * behavior. * If the tag contains the id of only 1 item, its content is displayed in a popup panel. If the tag * has multiple ids, the area below the summary is repopulated with the linked content items. */ public class ContentRenderer extends Composite { private static final String BREAK_TAG = "<!--lsp:break-->"; private static final String PARAGRAPH_END_TAG = "</p>"; private static final String HIGHLIGHT_CLASS = "summaryHighlights"; // Use a code tag for this since it's actually a valid html tag, recognized by JTidy. private static final String TIMELINE_TAG_NAME = "code"; private static final String TIMELINE_IDENTIFIER = "lsp:timeline"; private FlowPanel container; private String content; private int initiallyVisibleChunkCount; private boolean openHighlightedChunks; private boolean trackReadMoreWithAnalytics = false; private static String SHOW_LESS = LspMessageHolder.consts.showLess(); public ContentRenderer(String content, boolean openHighlightedChunks) { this(content, openHighlightedChunks, false); } public ContentRenderer(String content, boolean openHighlightedChunks, boolean trackReadMoreWithAnalytics) { super(); this.container = new FlowPanel(); this.content = content; this.openHighlightedChunks = openHighlightedChunks; this.trackReadMoreWithAnalytics = trackReadMoreWithAnalytics; populate(); initWidget(container); } /** * Populates the flow panel with the content after inserting 'Read more' links where * <break> tags are present. Clicking on the link reveals the next section of the content. */ private void populate() { String[] chunks = content.split(BREAK_TAG); if (chunks.length == 1) { container.add(processTagsInChunk(chunks[0])); return; } ReadMoreLink readMoreLink = null; boolean shouldExpand = false; boolean reachedExpandedChunk = false; // Create a link that causes everything to be hidden Link readLessLink = new Link(SHOW_LESS) { @Override protected void onClick(ClickEvent e) { // Hide all the widgets except the first few, as specified by the // initiallyVisibleChunkCount variable. for (int i = 1; i < container.getWidgetCount(); i++) { container.getWidget(i).setVisible(i < initiallyVisibleChunkCount); } } }; readLessLink.setVisible(false); container.add(readLessLink); for (int i = chunks.length - 1; i >= 0; i--) { String chunk = chunks[i].trim(); if (readMoreLink != null) { // Insert the previously created "read more" link at the beginning. container.insert(readMoreLink, 0); } // If this is the first chunk or if this chunk contains a highlight for new content, // expand it. if (i == 0 || (openHighlightedChunks && chunk.contains(HIGHLIGHT_CLASS))) { shouldExpand = true; if (!reachedExpandedChunk && readMoreLink != null) { // If all the previous chunks were hidden, then we should show the // read more link after this expanded chunk, if there is one. readMoreLink.setVisible(true); } if (i > 0) { // If this expanded chunk isn't the first chunk, then we should show // the read less link. readLessLink.setVisible(true); } reachedExpandedChunk = true; } // Process this chunk. First, check if this chunk ends with text in paragraph tags // followed by non-text content. int position = 0; int finalParagraphIndex = chunk.toLowerCase().lastIndexOf(PARAGRAPH_END_TAG); int finalParagraphEndIndex = finalParagraphIndex + PARAGRAPH_END_TAG.length(); if (finalParagraphIndex > 0 && finalParagraphEndIndex < chunk.length()) { // There's non-text stuff after the last paragraph tag. // Split this into two chunks for processing, // and put the 'read more' link before the non-text content. HTMLPanel html1 = processTagsInChunk(chunk.substring(0, finalParagraphEndIndex + 1)); html1.setVisible(shouldExpand); container.insert(html1, position++); if (readMoreLink != null) { container.insert(readMoreLink, position++); } HTMLPanel html2 = processTagsInChunk(chunk.substring(finalParagraphEndIndex)); html2.setVisible(shouldExpand); container.insert(html2, position++); // Create a new readMoreLink that will show the text chunk, // the next read more link, the non-text chunk, and the read less link. readMoreLink = new ReadMoreLink(trackReadMoreWithAnalytics, html1, readMoreLink, html2, readLessLink); initiallyVisibleChunkCount = 3; } else { // Otherwise, process this as a single chunk. HTMLPanel html = processTagsInChunk(chunk); html.setVisible(shouldExpand); container.insert(html, position++); if (readMoreLink != null) { container.insert(readMoreLink, position++); } // Create a new readMoreLink that will show the chunk, // the next read more link, and the read less link. readMoreLink = new ReadMoreLink(trackReadMoreWithAnalytics, html, readMoreLink, readLessLink); initiallyVisibleChunkCount = 2; } } } /** * Find the custom tags in the HTML and process them. */ private HTMLPanel processTagsInChunk(String chunk) { HTMLPanel contentPanel = new HTMLPanel(chunk); try { // Process each type of tag for (ContentTag tag : contentTags) { NodeList<Element> tagNodeList = contentPanel.getElement().getElementsByTagName(tag.getTagName()); List<Element> tagElements = new ArrayList<Element>(); for (int i = 0; i < tagNodeList.getLength(); i++) { // First iterate over the node list and copy all the elements into a new list. Can't // iterate and modify them at the same time because the list changes dynamically. tagElements.add(tagNodeList.getItem(i)); } for (Element tagElement : tagElements) { Widget widget = tag.createWidgetToReplaceTag(tagElement); if (widget != null) { // To replace the existing tag with the widget created above, the HTMLPanel needs // to have the id of the element being replaced. Since we can't expect users to assign // unique ids in every tag, we do this here automatically. String uniqueId = HTMLPanel.createUniqueId(); tagElement.setId(uniqueId); contentPanel.addAndReplaceElement(widget, uniqueId); } } } } catch (Exception e) { // Just return the panel with the original content } return contentPanel; } /** * Interface for the tags within the content that will be processed. * To add a new type of tag, create an implementation of this interface and also add the tag * to the contentTags array */ private interface ContentTag { String getTagName(); Widget createWidgetToReplaceTag(Element tagElement); } /** * Processes timeline tags in the content, replacing them with timeline widgets. * * To insert a timeline, use the following syntax: * <code>lsp:timeline</code> * * To set the width and height, just set the 'width' and 'height' attributes on the code tag: * <code width="700" height="125">lsp:timeline</code> */ private static class TimelineTag implements ContentTag { @Override public String getTagName() { return TIMELINE_TAG_NAME; } @Override public Widget createWidgetToReplaceTag(final Element tag) { if (!tag.getInnerHTML().equals(TIMELINE_IDENTIFIER)) { return null; } Integer width = null; Integer height = null; try { width = Integer.valueOf(tag.getAttribute("width")); height = Integer.valueOf(tag.getAttribute("height")); } catch (NumberFormatException ex) { // No size or invalid size specified. Use defaults. } return EventTimelineCreator.createTimeline(width, height); } } private static final ContentTag[] contentTags = new ContentTag[] { new TimelineTag() }; }