bz.davide.dmweb.shared.view.AbstractHtmlElementView.java Source code

Java tutorial

Introduction

Here is the source code for bz.davide.dmweb.shared.view.AbstractHtmlElementView.java

Source

/*
DMWeb - Java web framework - http://www.davide.bz/dmweb
    
Copyright (C) 2013-2014 Davide Montesin <d@vide.bz> - Bolzano/Bozen - Italy
    
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
    
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
    
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>
*/

package bz.davide.dmweb.shared.view;

import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import com.google.gwt.dom.client.Document;
import com.google.gwt.user.client.DOM;
import com.google.gwt.user.client.Element;
import com.google.gwt.user.client.Event;
import com.google.gwt.user.client.Window;

/**
 * @author Davide Montesin <d@vide.bz>
 */
public abstract class AbstractHtmlElementView implements HtmlElementView {

    public static boolean clientSide = true;
    public static int clientSideIdSeq = 0;

    private transient DMServerSideElement serverSideElement = null;
    String id = null;
    private transient Element clientSideElement__ = null;

    ArrayList<AttachListener> attachHandlers = null;
    /* 
     * This list will not contains all the childs nodes. Normally contains only child elements that
     * need to be informed about attach. Server side can contain temporary text node.
     */
    ArrayList<Node> childs = null;

    int eventBits = 0;

    ArrayList<DMClickHandler> clickHandlers = null;

    final static String ELEMENTID_PREFIX = "dmweb_";

    protected AbstractHtmlElementView(String tagName) {
        this.attachHandlers = new ArrayList<AttachListener>();
        this.clickHandlers = new ArrayList<DMClickHandler>();
        this.childs = new ArrayList<Node>();

        if (clientSide) {
            this.clientSideElement__ = DOM.createElement(tagName);
            this.id = ELEMENTID_PREFIX + clientSideIdSeq;
            clientSideIdSeq++;
            this.clientSideElement__.setId(this.id);
        } else {
            this.serverSideElement = new DMServerSideElement(tagName);
        }
    }

    @Override
    public Element getElement() {
        if (!clientSide) {
            throw new IllegalStateException("Element can be requested only on client not on server");
        }
        if (this.clientSideElement__ == null && this.id != null) {
            this.clientSideElement__ = DOM.getElementById(this.id);
        }
        return this.clientSideElement__;
    }

    public void setStyleName(String name) {
        if (clientSide) {
            this.getElement().setClassName(name);
        } else {
            this.serverSideElement.setClassName(name);
        }
    }

    public void addStyleName(String name) {
        if (clientSide) {
            this.getElement().addClassName(name);
        } else {
            this.serverSideElement.addClassName(name);
        }

    }

    public void removeStyleName(String name) {
        if (clientSide) {
            this.getElement().removeClassName(name);
        } else {
            this.serverSideElement.removeClassName(name);
        }
    }

    public void addAttachHandler(AttachListener attachHandler) {
        this.attachHandlers.add(attachHandler);
        if (clientSide && this.isAttacchedToDOM()) {
            attachHandler.onAttachOrDetach(new AttachEvent(true));
        }
    }

    public void addClickHandler(DMClickHandler clickHandler) {
        this.clickHandlers.add(clickHandler);
        this.addSinkEvents(Event.ONCLICK);
    }

    public void clear() {
        ArrayList<Node> tmp = new ArrayList<Node>(this.childs);
        for (Node node : tmp) {
            if (node instanceof AbstractHtmlElementView) {
                this.remove((AbstractHtmlElementView) node);
            }
        }
        // some elements may be not in childs (those that are static: no attachlistner or reference)
        this.childs.clear();
        if (clientSide) {
            this.getElement().setInnerText("");
        }
    }

    protected void appendChildInternal(Node node) {
        if (node instanceof TextNodeView) {
            if (clientSide) {
                this.getElement().appendChild(Document.get().createTextNode(((TextNodeView) node).value));
            } else {
                this.childs.add(node);
            }
            return;
        }
        AbstractHtmlElementView widget = (AbstractHtmlElementView) node;
        this.childs.add(widget);
        if (clientSide) {
            this.getElement().appendChild(widget.getElement());

            if (this.isAttacchedToDOM()) {
                AbstractHtmlElementView.notifyAttachRecursive(widget);
            }
        }
    }

    public static void notifyAttachRecursive(AbstractHtmlElementView widget) {
        for (AttachListener attachHandler : widget.attachHandlers) {
            attachHandler.onAttachOrDetach(new AttachEvent(true));
        }
        for (Node child : widget.childs) {
            if (child instanceof AbstractHtmlElementView) {
                notifyAttachRecursive((AbstractHtmlElementView) child);
            }
        }
    }

    public static void notifyDetachRecursive(AbstractHtmlElementView widget) {
        if (widget.isAttacchedToDOM()) // Child of an attached can be already unattached!
        {
            for (Node child : widget.childs) {
                if (child instanceof AbstractHtmlElementView) {
                    notifyDetachRecursive((AbstractHtmlElementView) child);
                }
            }
            for (AttachListener attachHandler : widget.attachHandlers) {
                attachHandler.onAttachOrDetach(new AttachEvent(false));
            }
        }

    }

    public void remove(AbstractHtmlElementView dmWidget) {
        for (int i = 0; i < this.childs.size(); i++) {
            if (this.childs.get(i) == dmWidget) {
                this.childs.remove(i);
                if (clientSide) {
                    if (this.isAttacchedToDOM()) {
                        AbstractHtmlElementView.notifyDetachRecursive(dmWidget);
                    }
                    this.getElement().removeChild(dmWidget.getElement());
                }
                return;
            }
        }
        throw new IllegalArgumentException("Child not found!");
    }

    private boolean isEventAttachHandlerAlreadyRegistered() {
        for (AttachListener attachHandler : this.attachHandlers) {
            if (attachHandler instanceof DMWidgetEventAttachHandler) {
                return true;
            }
        }
        return false;
    }

    protected void addSinkEvents(int addEventBits) {
        this.eventBits |= addEventBits;
        if (!this.isEventAttachHandlerAlreadyRegistered()) {
            this.addAttachHandler(new DMWidgetEventAttachHandler(this));
        } else if (this.isAttacchedToDOM()) {
            DOM.sinkEvents(this.getElement(), this.eventBits);
        }
    }

    protected void onBrowserEvent(Event event) {
        String eventType = event.getType();
        if (eventType.equals("click")) {
            for (DMClickHandler clickHandler : this.clickHandlers) {
                clickHandler.onClick(new DMClickEvent(event));
            }
        }
    }

    boolean isAttacchedToDOM() {
        if (!clientSide) {
            throw new IllegalStateException("Only callable client side!");
        }
        Element elem = DOM.getElementById(this.id);
        if (elem != null) // isAttached!
        {
            // Replace the current element with this new! In this case the parent node is updated
            // even in case was attached not using gwt
            this.clientSideElement__ = elem;
            return true;
        }
        return false;

    }

    protected void setElementAttribute(String name, String value) {
        if (clientSide) {
            this.getElement().setAttribute(name, value);
        } else {
            value = AbstractHtmlElementView.escapeText4html(value);
            this.serverSideElement.attributes.put(name, value);
        }
    }

    public static void generateServerSideHtml(DMWidgetSerializationData serializationData,
            AbstractHtmlElementView widget, StringBuffer out) {
        DMServerSideElement element = widget.serverSideElement;

        out.append("<");
        out.append(element.tagName);

        String id = ELEMENTID_PREFIX + serializationData.idseq;
        serializationData.idseq++;

        out.append(" id=\"" + id + "\"");

        widget.id = id;

        if (element.styles.size() > 0) {
            out.append(" class=\"");
            for (String c : element.styles.keySet()) {
                out.append(c);
                out.append(" ");
            }
            out.append("\"");
        }

        for (String k : element.attributes.keySet()) {
            out.append(" ");
            out.append(k);
            out.append("=\"");
            out.append(element.attributes.get(k));
            out.append("\"");
        }
        out.append(">");

        if (!element.tagName.equals("img")) {

            for (int i = 0; i < widget.childs.size(); /* increment is in else statement */) {
                Node child = widget.childs.get(i);
                if (child instanceof TextNodeView) {
                    out.append(escapeText4html(((TextNodeView) child).value));
                    widget.childs.remove(i);
                } else {
                    out.append("\n");
                    generateServerSideHtml(serializationData, (AbstractHtmlElementView) child, out);
                    out.append("\n");
                    i++;
                }
            }

            out.append("</");
            out.append(element.tagName);
            out.append(">");
        }

        serializationData.attachHandlers.addAll(widget.attachHandlers);

    }

    public static String escapeText4html(String title) {
        StringBuffer result = new StringBuffer();
        for (int i = 0; i < title.length(); i++) {
            String singleChar = title.substring(i, i + 1);
            if (!singleChar.matches("[a-zA-Z0-9 /.()_:-]")) {
                // short versions? amp, apos, etc...
                int unicode = singleChar.charAt(0);
                String hex = Integer.toHexString(unicode);
                while (hex.length() < 4) {
                    hex = "0" + hex;
                }
                singleChar = "&#x" + hex + ";";
            }
            result.append(singleChar);
        }
        return result.toString();
    }

    public static String escapeText4url(String title) {
        try {
            StringBuffer result = new StringBuffer();
            for (int i = 0; i < title.length(); i++) {
                String singleChar = title.substring(i, i + 1);
                if (!singleChar.matches("[a-zA-Z0-9]")) {
                    byte[] utf8bytes = singleChar.getBytes("UTF-8"); // lower case does not work in gwt compiled code
                    singleChar = "%";
                    for (byte b : utf8bytes) {
                        String hexDigit = Integer.toHexString(b & 0xFF);
                        while (hexDigit.length() < 2) {
                            hexDigit = "0" + hexDigit;
                        }
                        singleChar += hexDigit;
                    }
                }
                result.append(singleChar);
            }
            return result.toString();
        } catch (UnsupportedEncodingException encodingException) {
            Window.alert("UnsupportedEncodingException");
            throw new RuntimeException(encodingException);
        }
    }
}