Insertar imágenes en RichTextEditor de Flex

Esta semana, a raíz de un mensaje en la lista de correo de Made in Flex, recordé una de las primeras aplicaciones que hice en Flex hace ya dos años con lo cual me lancé a recuperar aquél código, limpiarlo de cosas supérfluas y publicarlo. Os dejo también el enlace a un post mio en la lista Flexcoders donde explicaba como hacerlo, de hecho ha sido mucha la gente que, a partir de ese post, me ha escrito preguntando detalles.

Para los puristas, os aviso de antemano que puede haber cosas que no funcionen bien, código extraño y mil historias más, es un código que tiene dos años, si hacéis cuentas veréis que justo estos días Flex2 cumple dos años. Sí, lo comencé con las primeras betas de aquella versión. Lo único que he hecho ha sido crearlo en Flex3 y dejar la aplicación limpia para que se vea el funcionamiento. Si me váis a dejar un comentario diciendo que tal o cual cosa no funciona bien, podéis ahorrárosla.

Como una imagen vale más que mil palabras, aquí tienes el ejemplo y el código fuente de todo el proyecto.

Todos a los que en algún momento se nos ocurrió utilizar Flex para crear un gestor de contenidos nos hemos dado con un grave problema en la frente: el componente ideal para escribir y actualizar contenidos, RichTextEditor, NO permite, por defecto, insertar imágenes, con lo cual pierde prácticamente su utilidad. Escarbando en la ayuda, sin embargo, te das cuenta de que entre los tag HTML soportados por el componente Textarea se encuentra <IMG>, con lo que, a priori, nada impediría insertar una imagen. En efecto así es y en eso se basa todo este artículo/proyecto. Un detalle importante es que la utilización de imágenes no está bien conseguida en el propio Flash Player, con lo que si comenzamos a añadir y quitar imágenes llegará un momento en el que todo el Textarea será inestable y hará cosas extrañas.

El proyecto se basa en dos componentes, nuestro editor de texto y un explorador de archivos. Desde el editor tendremos un botón Insertar imagen que abrirá un explorador al más puro estilo del escritorio del sistema operativo donde nos mostrará los archivos que tenemos en el servidor, pudiendo subir y eliminar. La lógica del explorador con el servidor la he eliminado al máximo, dejando sólamente unos XML estáticos que listan las carpetas e imágenes disponibles. Para hacerlo bien haríais un script con salida similar a esta pero que liste lo que en realidad hubiese en una ruta de tu servidor.

Nuestro editor de texto

Intentaré explicar como hice todo el proceso, pero fue hace bastante tiempo así que es posible que se me olvide algún detalle.

La idea era hacer un editor avanzado, con las caracteristicas que le faltaban al RichTextEditor original:

  • Insertar imágenes.
  • Posibilidad de añadir datos tabulados (que no tablas como tales).
  • Cambiar el color de fondo del editor para, por ejemplo, crear texto en color blanco.
  • Eliminar todo el texto (e imágenes).
  • Editor avanzado de links.
  • Botón guardar texto (lo insertaría en tu base de datos)

Partimos para ello de un RichTextEditor al que le quitamos el botón predefinido de Añadir link y añadimos nuestros nuevos botones. Siguiendo el código entenderás lo que hacen, por lo que nos centraremos en el de imágenes que es el objeto del artículo. Primero os explico en qué consiste el botón de tabular datos. Es muy sencillo. Si quisiésemos, en el RichTextEditor, crear una tabla de valores con el tabulador, no podríamos, ya que al presionar tabulador saldría el foco del Textarea de escribir  y se iría, por defecto, al selector de fuente. Para solucionarlo añadimos un botón que lo único que hace es añadir un carácter de tabulación (t) en el texto, con lo que comprobaréis que ya puedes tabular datos perfectamente. Muy útil el truco.

Pasemos pues al botón de insertar imágenes. El botón, tal y como expliqué anteriormente, abre el popup del explorador de archivos, con lo que esta parte la veremos en la siguiente sección. Cuando en el explorador seleccionamos la imagen a insertar se devuelve el control al editor y es éste el que añade la imagen. Para añadir la imagen inserta en la posición del cursor el tag <IMG> con los parámetros necesarios. Aparentemente no tiene más truco, pero al empezar a probar cosas nos damos cuenta que sí que lo tiene.

Para empezar, si guardamos el htmltext de este RichTextEditor en la base de datos y posteriormente intentamos recuperarlo recibiremos un desagradable error de validación XML. Quiero aclarar antes de nada, que este error me ocurría en aquél entonces, quizás, ojalá, las ultimas versiones de Flex lo hayan solucionado, te ahorraría muchos problemas. La causa del error de validación era que internamente, aunque tu añadieses un tag <img … /> (o <img…></img>) válido, el editor te devolvía siempre <img .. > , es decir, el XML sin cerrar, con lo cual al cargarlo de nuevo saltaba error de validación. Para solucionar este problema creamos un método desProcesaTexto que lo que hace es convertir todos los tags <img..> no válidos a tag válidos, con lo que tenemos el primer problema resuelto. Utilizando expresiones regulares será extremadamente sencillo.

var pattern:RegExp = /<IMG([^>]*)>/gi;
texto=texto.replace(pattern, "<IMG $1 ></IMG>");

Segundo problema. Una vez insertamos una imagen, ¿cómo podemos quitarla o modificarla? Aquí viene parte del truco. La idea, básicamente, es añadir a cada imagen un link de manera que al hacer click en ella se nos abra un popup que nos permita eliminar la imagen o modificar sus atributos. Esto genera otro inconveniente. Cuando queremos recuperar el contenido del Textarea para guardarlo en la base de datos debemos eliminar estos links falsos añadidos para la interfaz de usuario, pero que no queremos que salgan cuando se muestre el texto. Por contra, cuando cargamos un texto desde la base de datos debemos procesar los atributos de imágenes para añadirles este link y podamos operar con ellos.

var temp:XML=XML("<texto>"+texto+"</texto>");
var allTags: XMLList;
var item:XML;
var tempitem:XML;allTags= temp..IMG;
for each(item in allTags) {
    var xlcParent:XMLListCollection = new XMLListCollection(item.parent().parent().children());
    tempitem=XML(xlcParent.toXMLString());
    xlcParent.setItemAt(item, 0);
}

Como se aprecia en el código, bucamos todos los tags <IMG> y, asumiendo que todos tendrán un tag <A> anterior, reemplazamos el nodo <A> completo por el <IMG> y todo solucionado, más sencillo de lo que parece una vez se entiende el procedimiento.

Para contemplar estas peculiaridades cada vez que asignamos el texto al componente o cada vez que lo recuperamos, tenemos los siguientes métodos:

public function getHtmlText():String{
    return desProcesaTexto(this.htmlText);
}

public function setHtmlText(texto:String):void{
    this.htmlText=procesaTexto(texto);
}

No hay mucho más que explicar. Como peculiaridad, veremos como procesaríamos los tags <IMG> cuando cargamos un texto nuevo en el componente. La idea, básicamente, es añadir el link que nos permita modificarla, pero este link necesita conocer los datos de la imagen (src, width, height..).

allTags= temp..IMG;
for each(item in allTags) {
    var xlcParent:XMLListCollection = new XMLListCollection(item.parent().children());
    var iIndex:int = xlcParent.getItemIndex(item);    idlink=Math.round((Math.random()*100000)*(Math.random()*100000));
    nuevoNodo="<A href="event:IMAGEN##||##"+item.@ID+"##||##"+item.@SRC+"##||##"+item.@WIDTH +
    "##||##"+item.@HEIGHT+"##||##"+item.@VSPACE+"##||##"+item.@HSPACE+"##||##"+item.@ALIGN +
    "##||##"+idlink.toString()+"" ID=""+idlink.toString()+"">"+xlcParent.toXMLString()+"</A>";

    xlcParent.setItemAt(XML(nuevoNodo), iIndex);
}

Sencillo.

El explorador de archivos

El explorador de archivos es un componente bastante resultón que, si lo trabajas un poco, te puede solucionar muchas tareas. Igual que el Explorador de Archivos de Windows, tienes a la izquierda un árbol de directorios y a la derecha los archivos de la carpeta seleccionada y sus detalles. Arriba tenemos botones para subir archivos nuevos y crear nuevas carpetas (la lógica de estos botones es cosa tuya) y, sobre todo, un combo para cambiar el modo de ver los archivos de manera que puedes ver la lista de los mismos o las miniaturas. Si, si listas imágenes verás un thumbnail de las mismas, perfecto para nuestro editor. No voy a enrollarme mucho más con el funcionamiento ya que en el código fuente tienes todo lo necesario. El evento interesante es el doble click sobre un archivo, que devuelve el control a una nueva ventana donde puede configurar los parametros de la imagen a insertar. Con el ejemplo lo entenderas.

En tu editor deberás crear los métodos adecuados en el servidor para listar las carpetas y archivos de tu directorio de uploads si quieres que todo sea dinámico y el usuario pueda organizarse por su cuenta los archivos.

Tal como había comentado, no he sido tan explícito como en otras ocasiones. Si tienes algún problema no dudéis en dejar un comentario e intentaré solucionarlo a la mayor brevedad posible.

Aquí tienes el ejemplo y el código fuente de todo el proyecto.

16 comentarios en “Insertar imágenes en RichTextEditor de Flex

  1. Hola Osus la verdad que esta muy bueno tu código fijate que quise pasarlo a AIR, todo funciona muy bien excepto que el RichTextEditor no lee la imagen, la verdad un 10 por este código, saludos.

  2. Es un buen ejemplo, per sigo teniendo el mismo problema con las imagenes, al intentar combinar una imagen y un texto en la misma linea, parece como si una de las dos cosas se descuadrase, ¿alguna sugerencia?

    La intencion seria cargar un texto y una imagen en la misma linea, y que siga un relativo orden (dependiendo del tamaño de la imagen y tal…)

    Un saludo

  3. Clásico problema Flash, insertar smilies.
    Lamento decirte que no se puede hacer lo que indicas insertando las imágenes tal cual. La mejor opción es añadir las imágenes al componente de texto en las posiciones que irían calculándolas con las funciones que hay para ello. Eso sí, tendrás que desplazarlas a medida que se mueva el scroll del textarea.

    Otra poción podría ser el nuevo Text Layout Framework de Adobe, no se si se podrá hacer lo que pretendes.
    http://labs.adobe.com/technologies/textlayout/

  4. Llegado esta respuesta tengo dos cuestiones que no me han quedado clara:

    1ª La mejor opción es añadir las imágenes al componente de texto.

    Esto puedo interpretarlo como crear por ejemplo con esa imagen un tipo de obejto X (que por cierto, no se cual seria el tipo) y despues añadirlo al texto como si fuese una parte mas de el.

    Y la 2ª es, en el momento que tecnicamente el scroll se vaya desarrollando por el area de texto, deberia “moverse solo”, osease tengo claro que efectivamente cuando añado la imagen tengo que añadirlo en una posicion del area de texto, pero cuando se escriba una linea nueva ¿debo redibujar todo el area?

    Un saludo y muchas gracias 🙂

  5. Piensa en el textarea como un lienzo sobre el que hay un campo de texto. Eso es el textarea. Ahora piensa en que quieres insertar un smiley.

    1)En el campo de texto debes reservar el espacio necesario para que quede bien insertado y no se monte sobre el resto de palabras.
    2)Añades el componente imagen (addchild) al lienzo y la situas en la posición que le corresponda.

    Si entiendes lo que te explico entenderás que al avanzar el scroll deberás desplazar todos los smileys que tengas en tu “lienzo”.
    La teoría es cojonuda, la práctica es bastante más complicada.

  6. Buenos días,

    Vale la teoría “parece fácil”, solo tendría que reservar el espacio para poner la imagen, realizar todo el proceso de obtención del texto y luego añadir la imagen (con addchild) con las dimensiones y posición adecuadas. (En esta primera fase vamos a poner que solo hay una línea con imágenes, a ver que tal sale).

    Esto por tanto me debería de dar el lienzo que seria el textarea, con los distintos campos de texto (que serian propiamente el texto) y luego una imagen insertada.

    De momento ese es mi objetivo, ¿Voy por buen camino?

    Un saludo y muchísimas gracias 🙂

  7. Muchas gracias por este componente la verdad me es muy util… pero tengo un pequeño problema al hacer debug… cuando hago click sobre una de las carpetas que contienen las imagenes me salta un error te lo transcribo:

    Main Thread (Suspended: TypeError: Error #1034: Error de conversión forzada: no se puede convertir mx.managers::DragManagerImpl@221b6d61 en mx.managers.IDragManager.)
    mx.managers::DragManager$/get impl
    mx.managers::DragManager$/get isDragging
    mx.controls.listClasses::ListBase/mouseOverHandler
    mx.controls::Tree/mouseOverHandler

    me di cuenta del problema ya que necesito parsear todo el contenido en html.. y cuando ingresaba una imagen la funcion no tiraba ningun resultado… espero que me puedas ayudar!

  8. Hola Nahuel,

    La verdad es que no te puedo ayudar mucho ya que no me acuerdo de cómo lo hice. Tendrás que revisar tú mismo el código, está bastante bien explicado en el artículo.

    Osus

  9. El código está genial. Muchas gracias.

    Por cierto, la solución para el cierre de etiquetas /> en img es utilizar para procesar el xml la etiqueta para evitar que lo procese como xml.

    Un saludo,

  10. Hola, estuve viendo el ejemplo al igual pasa con el codigo fuente, cuando se coloca una imagen, luego texto debajo de esta imagen, y parte de ese texto se lo coloca en BOLD, al insertar una imagen posicionando el cursor en medio del texto en BOLD, se rompe el contenido HTML, se borra parte del texto html.

    Por lo tanto, no tiene soporte para multiples imagenes incluidas en medio del contenido HTML, este es mi gran problema para encontrar un componente que permital tal cosa.

    Saludos.

  11. Disculpa sabes como hacer que se guarde el texto editado en mysql ?
    pues cuando lo quiero mostrar despues, se muestra el texto sin editar
    o como hacerle para que al usuario le aparezca tal como lo puso

  12. Hola Osus.

    En primer lugar agradecerte el aporte. Está Genial.

    Lo he implementado en mi aplicación y tengo un error que no se si me podrás orientar. En tu ejemplo, si inserto una imagen, lo hace sin problemas. Pero cuando inserto la 2ª imagen, me desaparce el contenido del RichTextEditor. Lo mismo que pasa en tu ejemplo, pasa en mi aplicación. ¿Puedes echarme un cable con este problema?.

    Gracias de nuevo y un saludo.

Responder a Nahuel Chaves Cancelar la respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *