Package org.itsnat.impl.core.mut.client

Source Code of org.itsnat.impl.core.mut.client.ClientMutationEventListenerHTMLMSIEOldImpl

/*
  ItsNat Java Web Application Framework
  Copyright (C) 2007-2011 Jose Maria Arranz Santamaria, Spanish citizen

  This software 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 software 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 org.itsnat.impl.core.mut.client;

import java.util.Map;
import org.itsnat.impl.core.clientdoc.ClientDocumentStfulImpl;
import org.itsnat.impl.core.clientdoc.SVGWebInfoImpl;
import org.itsnat.impl.core.jsren.dom.node.JSRenderAttributeImpl;
import org.itsnat.impl.core.domutil.NamespaceUtil;
import org.itsnat.impl.core.jsren.dom.node.html.msie.JSRenderHTMLTextMSIEOldImpl;
import org.w3c.dom.Attr;
import org.w3c.dom.CharacterData;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.Text;
import org.w3c.dom.events.MutationEvent;

/**
*
* @author jmarranz
*/
public class ClientMutationEventListenerHTMLMSIEOldImpl extends ClientMutationEventListenerHTMLImpl
{
    public ClientMutationEventListenerHTMLMSIEOldImpl(ClientDocumentStfulImpl clientDoc)
    {
        super(clientDoc);
    }

    public boolean isSVGNodeAndAdobeSVGInline(Node node)
    {
        // Si no hemos detectado SVGWeb suponemos ASV inline
        return NamespaceUtil.isSVGNode(node) &&
               !SVGWebInfoImpl.isSVGWebEnabled(clientDoc);
    }

    @Override
    public void preRenderAndSendMutationCode(MutationEvent mutEvent)
    {
        super.preRenderAndSendMutationCode(mutEvent);

        String type = mutEvent.getType();

        if (type.equals("DOMNodeInserted"))
        {
            Node newNode = (Node)mutEvent.getTarget();
            fixTreeNamespaces(newNode);
        }
        else if (type.equals("DOMNodeRemoved"))
        {
            Node node = (Node)mutEvent.getTarget();
            Node parentNode = node.getParentNode();
            if (isSVGNodeAndAdobeSVGInline(parentNode))
            {
                // Suponemos ASV inline
                // Ver notas en DOMNodeInserted
                // Si el nodo a eliminar es el nodo ra�z de un trozo SVG (el padre ya no es SVG) no es necesario hacer
                // nada especial porque aunque se reinserte en el servidor, en el cliente ser� un nodo nuevo,
                // por eso preguntamos si es el padre el SVG (para evitar el caso de nodo ra�z SVG).
                fixTreeRemovedSVGNodeInAdobeSVGInline(node);
            }
        }
    }

    @Override
    public void postRenderAndSendMutationCode(MutationEvent mutEvent)
    {
        super.postRenderAndSendMutationCode(mutEvent);

        // No consideramos "desregistrar" namespaces al eliminar nodos
        // con namespaces, por una parte porque la colecci�n document.namespaces
        // no tiene m�todo remove y por otra parte porque otro trozo de c�digo
        // en otro lugar podr�a necesitarlo. No pasa nada por dejarlo declarado en el cliente.
        String type = mutEvent.getType();

        if (type.equals("DOMNodeInserted"))
        {
            Node newNode = (Node)mutEvent.getTarget();
            Node parentNode = newNode.getParentNode();
            if (isSVGNodeAndAdobeSVGInline(parentNode))
            {
                // Suponemos ASV inline
                // S�lo procesamos el nodo insertado si es hijo de un elemento SVG
                // porque si es hijo de un elemento X/HTML entonces es un trozo de SVG nuevo,
                // el ASV lo detectar� y sabemos que lo procesar� el propio ASV creando los elementos SVG
                // espejo (peer) autom�ticamente, aunque lo har� inmediatamente despu�s de que se termine el script
                // (o que se muestre un alert) por lo que la propiedad _svg_peer no estar� presente
                // y por tanto la llamada no har�a nada.

                fixTreeInsertedSVGNodeInAdobeSVGInline(newNode);
            }
        }
        else if (type.equals("DOMAttrModified"))
        {
            Element elem = (Element)mutEvent.getTarget();
            if (isSVGNodeAndAdobeSVGInline(elem))
            {
                // Suponemos ASV inline
                Attr attr = (Attr)mutEvent.getRelatedNode();
                int changeType = mutEvent.getAttrChange();
                fixAttrModifiedSVGNodeInAdobeSVGInline(elem,attr,changeType);
            }
        }
        else if (type.equals("DOMCharacterDataModified"))
        {
            CharacterData charDataNode = (CharacterData)mutEvent.getTarget();
            if ((charDataNode instanceof Text) &&
                 isSVGNodeAndAdobeSVGInline(charDataNode))
            {
                // Suponemos ASV inline
                fixTextDataModifiedSVGNodeInAdobeSVGInline((Text)charDataNode);
            }
        }
    }

    private void fixTreeNamespaces(Node node)
    {
        // Solucionamos el problema de insertar via JavaScript un elemento con namespaces,
        // por ejemplo <svg:svg xmlns:svg="...">, pues al crear el elemento via createElement("svg:svg")
        // tiene dos comportamientos muy diferentes:
        // 1) Si el prefijo svg no ha sido declarado en <html> como <html xmlns:svg="..."> o bien
        //    no est� en la colecci�n document.namespaces, el m�todo funciona pero el elemento creado
        //    es una especie de elemento desconocido que por ejemplo no admite nodos hijo.
        // 2) En caso contrario el elemento es creado correctamente siendo la propiedad tagUrn equivalente a la W3C namespaceURI
        //    y scopeName equivalente al prefix W3C.
        // Algo similar ocurre si el elemento es insertado dentro de un innerHTML.
        // Por tanto es necesario a�adir el namespace al <html> o a document.namespaces antes
        // de la inserci�n, en xmlns:svg="..." en el propio elemento NO tiene ning�n efecto.
        // Como en carga ya se soluciona este problema via a�adiendo atributos en <html> antes de enviar al cliente,
        // aqu� tenemos que usar document.namespaces pues tras la carga definir atributos namespace en <html> no hace nada

        // => document.namespaces : http://msdn.microsoft.com/en-us/library/ms537470%28VS.85%29.aspx
        // => namespace object : http://msdn.microsoft.com/en-us/library/ms535854%28VS.85%29.aspx

        StringBuilder code = fixTreeNamespaces(node,null);

        if ((code != null) && (code.length() > 0))
            clientDoc.addCodeToSend(code.toString());
    }

    private StringBuilder fixTreeNamespaces(Node node,StringBuilder code)
    {
        if (node.getNodeType() != Node.ELEMENT_NODE) return code;

        Element elem = (Element)node;

        if (elem.hasAttributes())
        {
            NamedNodeMap attribs = elem.getAttributes();
            for(int i = 0; i < attribs.getLength(); i++)
            {
                Attr attr = (Attr)attribs.item(i);
                String prefix = attr.getPrefix();
                if ((prefix != null) && prefix.equals("xmlns"))
                {
                    // No consideramos el caso de xmlns:itsnat="itsnat namespace"
                    // porque nunca enviaremos elementos al cliente tal y como
                    // <itsnat:nombre>... pues estos se procesan y se substituyen en el servidor.
                    // Evitando este caso, evitamos poner en <html> la declaraci�n xmlns:itsnat que no
                    // ha hecho el programador y que puede confundirle.
                    String value = attr.getValue();
                    if (NamespaceUtil.isItsNatNamespace(value))
                        continue;

                    String localName = attr.getName().substring(prefix.length() + 1);

                    // Si el namespace ya estuviera definido lo actualiza con el nuevo URL (no hay duplicidad)
                    // esto hace que este m�todo no sirva para casos en donde se utiliza el mismo prefijo para varios
                    // URLs diferentes.
                    if (code == null) code = new StringBuilder();
                    code.append("itsNatDoc.doc.namespaces.add(\"" + localName + "\",\"" + value + "\");\n");
                }
            }
        }

        Node child = elem.getFirstChild();
        while (child != null)
        {
            code = fixTreeNamespaces(child,code);
            child = child.getNextSibling();
        }

        return code;
    }

    private void fixTreeInsertedSVGNodeInAdobeSVGInline(Node node)
    {
        StringBuilder code = new StringBuilder();

        String methodName = "fixSVGNodeInsertedAdobeSVG";
        if (!clientDoc.isClientMethodBounded(methodName))
            code.append(bindFixSVGNodeInsertedAdobeSVGMethod(methodName));

        String jsRef = clientDoc.getNodeReference(node,true,true);

        code.append("itsNatDoc." + methodName + "(" + jsRef + ");\n");

        clientDoc.addCodeToSend(code);
    }

    private String bindFixSVGNodeInsertedAdobeSVGMethod(String methodName)
    {
        // El Adobe SVG Viewer puede renderizar SVG inline dentro de un documento X/HTML
        // en MSIE usando namespaces <object classid="..."> e <import namespace...>
        // En este modo el funcionamiento es MUY PECULIAR, pues cuando se inserta un trozo de markup SVG
        // ya sea en carga, o via DOM o innerHTML, el ASV detecta esta inserci�n y, o bien cuando se carga
        // el plugin o cuando el script que realiz� la inserci�n termina acaba (setTimeout(func,0) puede ser �til),
        // renderiza el markup visualmente.
        // Resultado de esta renderizaci�n es que el ASV crea un DOM sim�trico al del MSIE pero siguiendo el W3C
        // con un documento por cada bloque de markup SVG inline, siendo el root el elemento
        // root del bloque SVG. Ambos DOMs (MSIE y ASV) est�n vinculados a trav�s de una propiedad "_svg_peer"
        // que tienen los elementos en el DOM MSIE, el _svg_peer apunta al elemento en el DOM ASV,
        // en el DOM ASV podemos obtener el documento ASV via ownerDocument a trav�s de un elemento ASV o "peer".
        // El problema surge si queremos modificar un bloque SVG YA renderizado, ASV s�lo crea
        // el DOM ASV paralelo si el bloque no fue renderizado, pero si ya fue renderizado los cambios en el DOM MSIE
        // son ignorados.
        // Sin embargo lo interesante es que si hacemos cambios "manualmente" en el DOM ASV, el ASV
        // los procesa y modifica visualmente el bloque SVG. Al parecer el MSIE notifica al ASV cuando
        // un nodo SVG ra�z se inserta (tiene que ver con los "behaviors") pero no es capaz de notificar ante cambios dentro del mismo
        // (pues no hay mutation listeners en MSIE).
        // Por tanto, nuestro objetivo es replicar los nodos con significado visual SVG del DOM MSIE en el DOM ASV
        // si detectamos que ya ha sido renderizado el bloque (hay un _svg_peer),
        // creando nodos ASV manualmente y vincul�ndolos con el DOM MSIE usando
        // _svg_peer definidos manualmente.
        // En los nodos de texto el ASV no define _svg_peer, por ello nos aprovechamos
        // de la idea de que en SVG los nodos de texto con texto significativo son siempre (?)
        // el �nico nodo hijo (aunque tambi�n puedan existir comentarios, nos referimos
        // a que nodos elemento hijos). De esta forma evitaremos indirectamente los nodos
        // con espacios etc que esos s� que estar�n mezclados con elementos.

        StringBuilder code = new StringBuilder();
        code.append("var func = function(node)\n");
        code.append("{\n");
        code.append("  if (node == null) return;"); // Posible caso de nodo texto con cadena nula (en el servidor puede existir en el cliente puede que no)
        code.append("  var parentPeer = node.parentNode._svg_peer;");
        code.append("  if (!parentPeer) return;"); // El contenedor no se ha renderizado todav�a
        code.append("  var svgDoc = parentPeer.ownerDocument;");
        code.append("  var nodePeer;");
        code.append("  if (node.nodeType == 1)"); // Node.ELEMENT_NODE
        code.append("  {");
        code.append("    nodePeer = svgDoc.createElement(node.nodeName);")// Tanto nodeName como tagName no incluyen el prefijo
        code.append("    node._svg_peer = nodePeer;");
        code.append("    for(var i = 0; i < node.attributes.length; i++)");
        code.append("    {");
        code.append("      var attr = node.attributes[i];");
        code.append("      if (attr.specified) nodePeer.setAttribute(attr.name,this.getAttribute(node,attr.name));");
        code.append("    }");
        code.append("    var sibElem = node.nextSibling;");
        code.append("    while((sibElem != null) && !sibElem._svg_peer) sibElem = sibElem.nextSibling;"); // De esta manera filtramos nodos de texto etc
        code.append("    var sibElemPeer = sibElem != null? sibElem._svg_peer : null;");
        code.append("    parentPeer.insertBefore(nodePeer,sibElemPeer);");
        code.append("  }");
        code.append("  else if (node.nodeType == 3)"); // Node.TEXT_NODE
        code.append("  {");
                         // El _svg_peer no est� definido en nodos de texto (s�lo elementos) por el ASV
                         // Afortunadamente todos los nodos de texto con texto significativo son nodos hijo �nicos en SVG (CREO, si no es as� no funcionar�)
        code.append("    var childNodes = node.parentNode.childNodes;");
        code.append("    var i;");
        code.append("    for(i = 0; i < childNodes.length; i++)");
        code.append("    {");
        code.append("      var currNode = childNodes[i];");
        code.append("      if (node == currNode) continue;");
        code.append("      if ((currNode.nodeType == 1)||(currNode.nodeType == 3)) break;");
        code.append("    }");
        code.append("    if (i != childNodes.length) return;"); // Adem�s del nodo de texto hay alg�n elemento hijo u otro nodo de texto (los comentarios etc no nos interesan), ignoramos el nodo de texto porque seguramente es un separador
        code.append("    nodePeer = svgDoc.createTextNode(node.data);");
        code.append("    parentPeer.appendChild(nodePeer);");
        code.append("  }");
        code.append("  else return;"); // No consideramos a�adir como peer otros tipos de nodos tal y como comentarios pues no tienen impacto visual.

        code.append("  if (!node.hasChildNodes()) return;");
        code.append("  for(var i = node.childNodes.length - 1; i >= 0; i--)"); // Importante iterar al rev�s por el c�lculo del nextSibling
        code.append("  {");
        code.append("    var childNode = node.childNodes[i];");
        code.append("    this." + methodName + "(childNode);");
        code.append("  }");

        code.append("};\n");
        code.append("itsNatDoc." + methodName + " = func;\n");

        clientDoc.bindClientMethod(methodName);

        return code.toString();
    }

    private void fixTreeRemovedSVGNodeInAdobeSVGInline(Node node)
    {
        // Si el nodo a eliminar est� dentro del SVG necesitamos remover los peer tambi�n

        StringBuilder code = new StringBuilder();

        String methodName = "fixSVGNodeRemovedAdobeSVG";
        if (!clientDoc.isClientMethodBounded(methodName))
            code.append(bindFixSVGNodeRemovedAdobeSVGMethod(methodName));

        String nodeRef = clientDoc.getNodeReference(node,false,true); // No cacheamos pues se va a eliminar

        code.append("itsNatDoc." + methodName + "(" + nodeRef + ");\n");

        clientDoc.addCodeToSend(code);
    }

    private String bindFixSVGNodeRemovedAdobeSVGMethod(String methodName)
    {
        StringBuilder code = new StringBuilder();
        code.append("var func = function(node)\n");
        code.append("{\n");
        code.append("  if (node == null) return;"); // Posible caso de nodo texto con cadena nula (en el servidor puede existir en el cliente puede que no)
        code.append("  var parentPeer = node.parentNode._svg_peer;");
        code.append("  if (!parentPeer) return;"); // El contenedor no se ha renderizado todav�a
        code.append("  var nodePeer;");
        code.append("  if (node.nodeType == 1)"); // Node.ELEMENT_NODE
        code.append("    nodePeer = node._svg_peer;");
        code.append("  else if (node.nodeType == 3)"); // Node.TEXT_NODE
        code.append("  {");
                        // Afortunadamente todos los nodos de texto con texto significativo son nodos hijo �nicos en SVG (CREO, si no es as� no funcionar�)
        code.append("    var childNodes = node.parentNode.childNodes;");
        code.append("    var i;");
        code.append("    for(i = 0; i < childNodes.length; i++)");
        code.append("    {");
        code.append("      var currNode = childNodes[i]; ");
        code.append("      if (node == currNode) continue;");
        code.append("      if ((currNode.nodeType == 1)||(currNode.nodeType == 3)) break;");
        code.append("    }");
        code.append("    if (i != childNodes.length) return;"); // Adem�s del nodo de texto hay alg�n elemento hijo u otro nodo de texto (los comentarios etc no nos interesan), ignoramos el nodo de texto porque seguramente es un separador
        code.append("    nodePeer = parentPeer.firstChild;");
        code.append("    while((nodePeer != null)&&(nodePeer.nodeType != 3)) nodePeer = nodePeer.nextSibling;"); // Filtramos posibles comentarios etc que el propio ASV podr�a haber a�adido como peer si estaban en el DOM MSIE cuando se renderiz� por primera vez por ASV
        code.append("    if (!nodePeer) return;"); // No existe, no hace falta eliminar (probablemente en el servidor el nodo texto ten�a cadena nula)
        code.append("    nodePeer.data = '';"); // ES NECESARIO
        code.append("  }");
        code.append("  else return;"); // No consideramos otros nodos sin impacto visual

        code.append("  parentPeer.removeChild(nodePeer);");
        code.append("};\n");
        code.append("itsNatDoc." + methodName + " = func;\n");

        clientDoc.bindClientMethod(methodName);

        return code.toString();
    }

    private void fixAttrModifiedSVGNodeInAdobeSVGInline(Element elem,Attr attr,int changeType)
    {
        String valueJS = null;
        switch(changeType)
        {
            case MutationEvent.ADDITION:
            case MutationEvent.MODIFICATION:
                // El m�todo toJSAttrValue(...) hace cosas que no nos interesan en este contexto
                valueJS = JSRenderAttributeImpl.toTransportableStringLiteral(attr.getValue(),clientDoc.getBrowser());
                break;
            case MutationEvent.REMOVAL:
                valueJS = "null";
                break;
            // No hay m�s casos
        }

        StringBuilder code = new StringBuilder();

        String methodName = "fixSVGNodeAttrAdobeSVG";
        if (!clientDoc.isClientMethodBounded(methodName))
            code.append(bindFixSVGNodeAttrAdobeSVGMethod(methodName));

        String jsRef = clientDoc.getNodeReference(elem,false,true); // No cacheamos pues se va a eliminar

        code.append("itsNatDoc." + methodName + "(" + jsRef + ",\"" + attr.getName() + "\"," + valueJS + "," + changeType + ");\n");

        clientDoc.addCodeToSend(code);
    }

    private String bindFixSVGNodeAttrAdobeSVGMethod(String methodName)
    {
        StringBuilder code = new StringBuilder();
        code.append("var func = function(node,name,value,action)\n");
        code.append("{\n");
        code.append("  var nodePeer = node._svg_peer;");
        code.append("  if (!nodePeer) return;"); // El contenedor no se ha renderizado todav�a
        code.append("  if (action == 3) nodePeer.removeAttribute(name);"); // MutationEvent.REMOVAL
        code.append("  else nodePeer.setAttribute(name,value);");
        code.append("};\n");
        code.append("itsNatDoc." + methodName + " = func;\n");

        clientDoc.bindClientMethod(methodName);

        return code.toString();
    }

    private void fixTextDataModifiedSVGNodeInAdobeSVGInline(Text node)
    {
        JSRenderHTMLTextMSIEOldImpl render = JSRenderHTMLTextMSIEOldImpl.SINGLETON;
        String dataJS = render.dataTextToJS(node, clientDoc);

        StringBuilder code = new StringBuilder();

        String methodName = "fixSVGNodeTextModAdobeSVG";
        if (!clientDoc.isClientMethodBounded(methodName))
            code.append(bindFixSVGNodeTextModifiedAdobeSVGMethod(methodName));

        String jsRef = clientDoc.getNodeReference(node,false,true); // No cacheamos pues se va a eliminar

        code.append("itsNatDoc." + methodName + "(" + jsRef + "," + dataJS + ");\n");

        clientDoc.addCodeToSend(code);
    }

    private String bindFixSVGNodeTextModifiedAdobeSVGMethod(String methodName)
    {
        StringBuilder code = new StringBuilder();
        code.append("var func = function(node,data)\n");
        code.append("{\n");
        code.append("  if (node == null) return;"); // Posible caso de nodo texto con cadena nula (en el servidor puede existir en el cliente puede que no)
        code.append("  var parentPeer = node.parentNode._svg_peer;");
        code.append("  if (!parentPeer) return;"); // El contenedor no se ha renderizado todav�a
        code.append("  var nodePeer;");
                        // Afortunadamente todos los nodos de texto con texto significativo son nodos hijo �nicos en SVG (CREO, si no es as� no funcionar�)
        code.append("  var childNodes = node.parentNode.childNodes;");
        code.append("  var i;");
        code.append("  for(i = 0; i < childNodes.length; i++)");
        code.append("  {");
        code.append("    var currNode = childNodes[i]; ");
        code.append("    if (node == currNode) continue;");
        code.append("    if ((currNode.nodeType == 1)||(currNode.nodeType == 3)) break;");
        code.append("  }");
        code.append("  if (i != childNodes.length) return;"); // Adem�s del nodo de texto hay alg�n elemento hijo u otro nodo de texto (los comentarios etc no nos interesan), ignoramos el nodo de texto porque seguramente es un separador
        code.append("  nodePeer = parentPeer.firstChild;");
        code.append("  while((nodePeer != null) && (nodePeer.nodeType != 3)) nodePeer = nodePeer.nextSibling;"); // Filtramos posibles comentarios etc que el propio ASV podr�a haber a�adido como peer si estaban en el DOM MSIE cuando se renderiz� por primera vez por ASV
        code.append("  if (nodePeer == null)"); // No existe, probablemente en el servidor antes el nodo de texto ten�a cadena nula
        code.append("  {");
        code.append("    var svgDoc = parentPeer.ownerDocument;");
        code.append("    nodePeer = svgDoc.createTextNode('');");
        code.append("    parentPeer.appendChild(nodePeer);");
        code.append("  }");
        code.append("  nodePeer.data = data;");
        code.append("};\n");
        code.append("itsNatDoc." + methodName + " = func;\n");

        clientDoc.bindClientMethod(methodName);

        return code.toString();
    }
}
TOP

Related Classes of org.itsnat.impl.core.mut.client.ClientMutationEventListenerHTMLMSIEOldImpl

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.