2010-05-22 22 views
8

Estoy mostrando texto de estilo simple como html en un UIWebView en iPhone. Básicamente es una serie de párrafos con alguna frase fuerte o enfatizada. En tiempo de ejecución necesito aplicar estilos a rangos de texto.aplicar estilo al rango de texto con javascript en uiwebview

Existen algunas situaciones similares, una de las cuales es resaltar los resultados de búsqueda. Si el usuario ha buscado "algo", me gustaría cambiar el color de fondo detrás de las ocurrencias de la palabra, luego restaurar el fondo original.

¿Es posible aplicar estilos a rangos de texto usando javascript? Una parte clave de esto también es poder desarmar los estilos.

Parece que hay dos caminos posibles a seguir. Uno estaría modificando algunos html en Objective-C y pasando a través de javascript como el nuevo innerHTML de algún contenedor. El otro sería usar javascript para manipular directamente los nodos DOM.

Podría manipular html, pero eso suena tedioso en Objective-C, así que preferiría manipular el DOM si ese es un enfoque razonable. No estoy tan familiarizado con javascript y DOM así que no sé si es un enfoque razonable.

Escribí algunas rutinas para traducir entre rangos de texto y rangos de nodos con desplazamientos. Entonces, si comienzo con el rango de texto 100-200 y comienza en un párrafo y termina en un tercero, puedo obtener los nodos de texto y los desplazamientos dentro de los nodos que representan el rango de texto dado. Solo necesito una forma de dividir un nodo de texto en un desplazamiento en el texto. Actualmente solo aplico estilos a los párrafos que contienen el rango de texto.

Unas pocas notas:

  • javascript recta por favor, no hay marcos externos como jQuery.
  • los cambios nunca necesitan escribirse en el disco.
  • los cambios deben ser intercambiables o al menos extraíbles.
  • los estilos para aplicar ya existen en un archivo css.
  • necesita funcionar en iPhone 3.0 y reenviar.
  • todos los archivos fuente se envían con la aplicación.
  • por favor sean prolijo.

Gracias por cualquier sugerencia.

Respuesta

19

Creo que está pidiendo mucho para obtener una solución completa para esto, pero parecía interesante, así que lo he implementado. Lo siguiente funciona en los últimos navegadores WebKit, incluido Safari en iPhone que ejecuta OS 3.0. Utiliza el método intersectsNode no estándar pero conveniente de Range, que existe en WebKit pero se eliminó de Firefox en 3.0, por lo que no funciona en las versiones recientes de Firefox, pero podría hacerse de manera trivial.

Lo siguiente rodeará cada nodo de texto seleccionado con un elemento <span> con una clase de "someclass" y también una clase única para permitir la operación de deshacer fácil. applyClassToSelection devuelve esta clase única; pase esta clase al removeSpansWithClass para eliminar los tramos.

ACTUALIZACIÓN: problema fijo cuando la selección está totalmente contenida dentro de un único nodo de texto

ACTUALIZACIÓN 2: Probado y funciona en iPhone con OS 3.0.

ACTUALIZACIÓN 3: Se agregó la función rangeIntersectsNode para agregar compatibilidad con Firefox 3.0 y posterior. Este código ahora debería funcionar en Firefox 1.0+, Safari 3.1+, Google Chrome, Opera 9.6+ y posiblemente otros (aún no probados). No funciona en absoluto en Internet Explorer y dará errores en ese navegador. Planeo trabajar en una versión de IE pronto.

<script type="text/javascript"> 
    var nextId = 0; 

    var rangeIntersectsNode = (typeof window.Range != "undefined" 
      && Range.prototype.intersectsNode) ? 

     function(range, node) { 
      return range.intersectsNode(node); 
     } : 

     function(range, node) { 
      var nodeRange = node.ownerDocument.createRange(); 
      try { 
       nodeRange.selectNode(node); 
      } catch (e) { 
       nodeRange.selectNodeContents(node); 
      } 

      return range.compareBoundaryPoints(Range.END_TO_START, nodeRange) == -1 && 
       range.compareBoundaryPoints(Range.START_TO_END, nodeRange) == 1; 
     }; 

    function applyClassToSelection(cssClass) { 
     var uniqueCssClass = "selection_" + (++nextId); 
     var sel = window.getSelection(); 
     if (sel.rangeCount < 1) { 
      return; 
     } 
     var range = sel.getRangeAt(0); 
     var startNode = range.startContainer, endNode = range.endContainer; 

     // Split the start and end container text nodes, if necessary 
     if (endNode.nodeType == 3) { 
      endNode.splitText(range.endOffset); 
      range.setEnd(endNode, endNode.length); 
     } 

     if (startNode.nodeType == 3) { 
      startNode = startNode.splitText(range.startOffset); 
      range.setStart(startNode, 0); 
     } 

     // Create an array of all the text nodes in the selection 
     // using a TreeWalker 
     var containerElement = range.commonAncestorContainer; 
     if (containerElement.nodeType != 1) { 
      containerElement = containerElement.parentNode; 
     } 

     var treeWalker = document.createTreeWalker(
      containerElement, 
      NodeFilter.SHOW_TEXT, 
      // Note that Range.intersectsNode is non-standard but 
      // implemented in WebKit 
      function(node) { 
       return rangeIntersectsNode(range, node) ? 
        NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT; 
      }, 
      false 
     ); 

     var selectedTextNodes = []; 
     while (treeWalker.nextNode()) { 
      selectedTextNodes.push(treeWalker.currentNode); 
     } 

     var textNode, span; 

     // Place each text node within range inside a <span> 
     // element with the desired class 
     for (var i = 0, len = selectedTextNodes.length; i < len; ++i) { 
      textNode = selectedTextNodes[i]; 
      span = document.createElement("span"); 
      span.className = cssClass + " " + uniqueCssClass; 
      textNode.parentNode.insertBefore(span, textNode); 
      span.appendChild(textNode); 
     } 

     return uniqueCssClass; 
    } 

    function removeSpansWithClass(cssClass) { 
     var spans = document.body.getElementsByClassName(cssClass), 
      span, parentNode; 

     // Convert spans to an array to prevent live updating of 
     // the list as we remove the spans 
     spans = Array.prototype.slice.call(spans, 0); 

     for (var i = 0, len = spans.length; i < len; ++i) { 
      span = spans[i]; 
      parentNode = span.parentNode; 
      parentNode.insertBefore(span.firstChild, span); 
      parentNode.removeChild(span); 

      // Glue any adjacent text nodes back together 
      parentNode.normalize(); 
     } 
    } 

    var c; 
</script> 

<input type="button" onclick="c = applyClassToSelection('someclass')" 
    value="Add class"> 
<input type="button" onclick="removeSpansWithClass(c)" 
    value="Remove class"> 
+0

Gracias. Esto es más de lo que esperaba. – drawnonward

+0

Quizás ponga esto en github, para que podamos seguir al menos. – Mark

+1

Estoy trabajando en una biblioteca de rango/selección entre navegadores que incluirá una versión mejorada de esto. Aún no se ha avanzado mucho, pero he creado un proyecto de Google Code: http://code.google.com/p/rangy/ –

Cuestiones relacionadas