Textos enriquecidos, personalizando nuestro editor de textos TinyMCE

La inclusión de textos enriquecidos

La inclusión de textos enriquecidos es indispensable hoy en día en una página web. Nos permite destacar palabras, estructurar el texto, añadir citas, enlaces, formatos, etc. Esto se realiza con etiquetas html, que es lo que lee nuestro navegador web y lo interpreta como un subrayado, una negrita, o un bloque de cita, dependiendo de la etiqueta html donde esté englobado.

Pero esto es algo complejo para aquellos que no tienen conocimientos de desarrollo web y/o no les aporta nada en su día a día. Para facilitar esta tarea se desarrollaron los editores de texto online, que a través de una llamada JavaScript podíamos añadir un editor de textos a un campo de texto concreto.

Estas aplicaciones permiten que el usuario se abstraiga o ignore la parte más técnica de la creación de contenidos web, y pueda centrarse únicamente en el contenido que está creando.

 

Pero no siempre es suficiente. Hay veces que es necesario añadir alguna característica más, o modificar una ya existente del editor para adaptarlo a las necesidades de cada uno. Este es un problema que nos encontramos de vez en cuando en los proyectos que desarrollamos para nuestros clientes.

Nosotros utilizamos el editor de texto TinyMCE (https://www.tinymce.com/) para los proyectos que realizamos, principalmente por ser ligero y muy personalizable. Este mismo editor es utilizado en los CMS más importantes: WordPress, Drupal y Joomla.

Algunas de las características que mostramos por defecto son las siguientes:

  • Edición: cortar, copiar, pegar, selección de textos, búsqueda, etc.
  • Inserción de elementos: enlaces, fecha y/o hora, imágenes, tablas y listados.
  • Formatos: negrita, cursiva, subrayado, titulares en diferentes tamaños, bloques, etc.

Aunque disponemos de una configuración global para la mayoría de tareas que se pueden realizar en el editor, no siempre es suficiente. Aquí entraríamos en la personalización del editor, la cual, por desgracia no es posible realizarla desde el mismo editor, sino que hay que realizar un desarrollo específico para cada caso a través de plugins.

Esta forma de trabajar nos permite adaptar el editor a las necesidades de nuestros clientes como permitir que se puedan crear bloques con un contenido específico que el usuario seleccionará (por ejemplo, una imagen con un pie de página); dar un formato específico a un párrafo, etc.

A continuación explicaré como configurar y personalizar TinyMCE orientado a los desarrolladores, sin embargo, si no tienes conocimientos técnicos, no te preocupes, aunque no entiendas el código podrás tener una idea general de lo que podemos llegar a hacer con este editor de textos y cómo lo podríamos adaptar a las necesidades de los clientes.

 

Creando el proyecto

Vamos a desarrollar un pequeño ejemplo para poder probar cada uno de los parámetros y características que modificaremos o añadiremos. Para que tengas siempre una idea de cómo quedaría terminado, el código del ejemplo lo encontrarás en GitHub: https://github.com/migrad/example-plugin-for-tinymce/

La versión de TinyMCE que utilizaremos es la 4.2.8, que es la última versión estable en el momento de escribir este post.

En el directorio que hayas creado para el proyecto vamos a añadir TinyMCE. Si utilizas Bower para añadir las dependencias solo tienes que ejecutar el siguiente comando:

bower install tinymce

Si no tienes ni idea de lo que es Bower o no quieres complicarte vamos a bajarnos el editor desde su página web y lo descomprimimos en el directorio del proyecto. La url para nuestra versión es esta: http://download.ephox.com/tinymce/community/tinymce_4.3.8.zip

Creamos un archivo de texto plano y lo llamamos index.html. Añadimos el siguiente código:

<html>
  <head>
    <title>Example plugin for tinymce</title>
    <meta charset="UTF-8">
  </head>
  <body>



<div>Example plugin for tinymce.</div>



  </body>
</html>

Ahora antes de cerrar la etiqueta <head> añadimos la siguiente línea si has usado Bower para instalar TinyMCE:

<script src="bower_components/tinymce/tinymce.min.js"></script>

Si te lo has descargado desde la página web sería algo parecido a esto, pero revisa la ruta donde lo has guardado:

<script src="tinymce/tinymce.min.js"></script>

Ahora añadimos, antes del cierre de la etiqueta <body> el textarea que uniremos a TinyMCE:

<textarea id="editor"></textarea>

Le he añadido la propiedad id=”editor”, que nos hará falta para vincular TinyMCE al campo de texto.

De nuevo, antes del cierre de la etiqueta <head> le añadimos el siguiente código, que le dará al campo de texto un alto y un ancho.


<style>
textarea {
  width: 600px;
  height: 300px;
}
</style>

Ahora creamos un directorio llamado “js” y dentro de él creamos un archivo JavaScript, que yo he llamado “custom.tinymce.js”.

Para que TinyMCE reconozca los cambios que realizaremos en este archivo tenemos que añadirlo a la cabecera de la página. Así que de nuevo después de la etiqueta de cierre de <script> y antes de la de apertura de <style> (todo esto dentro de <head>), añadimos la siguiente línea:

<script src="js/custom.tinymce.js"></script>

Ahora que tenemos lo básico vamos a empezar con el código de verdad.

En custom.tinymce.js vamos primero a crear un objeto que nos permitirá añadir los cambios que deseamos modificar en la configuración de TinyMCE y le indicamos, con el parámetro “selector” el id del elemento en el que el editor se debe mostrar:

var tinyinit = {selector: ‘#editor’};

Y añadimos la siguiente línea que iniciará el editor:

tinymce.init(tinyinit);

Ahora podemos probar nuestra página en el navegador y podremos ver el editor de texto con las opciones básicas.

Dentro del objeto “tinyinit” podemos ir añadiendo todas las configuraciones que vamos a modificar.

 

Personalizando TinyMCE

Empecemos con las opciones más básicas como el idioma. Solo tendríamos que añadir este parámetro dentro del objeto tinyinit:

language: 'es'

En nuestro caso queremos la interfaz de TinyMCE en español. Si lo probamos en el navegador nos dará un error, pues por defecto TinyMCE solo se muestra en inglés. Para poder verlo en español es necesario descargar el archivo JavaScript que traduce la interfaz al idioma indicado desde esta url: http://archive.tinymce.com/i18n/

Una vez descargado tendrás que guardarlo dentro del directorio de TinyMCE, pero en la carpeta “lang”, tal que así: tinymce/lang/es.js

Si pruebas de nuevo verás que ya está traducido.

También podemos cambiar la altura del editor con la siguiente línea:

height: 400

Si queremos que el editor sirva para múltiples idiomas, como el japonés, podemos indicarle que use UTF-8 como codificación:

encoding: ‘UTF-8’,

Si además queremos guardar el contenido en una base de datos, muy posiblemente necesitaremos que las entidades utf-8 no sean modificadas por TinyMCE. Para ello tendremos que añadir el siguiente parámetro:

entity_encoding: ‘raw’,

Si queremos que el contenido sea lo más parecido a los estilos que presentará cuando sea publicado podemos indicar una ruta a un archivo CSS.

content_css: ‘css/styles.css’,

Puedes crear el directorio y el archivo, nos hará falta más adelante.

Para evitar que sea cacheado por el navegador, y así poder hacer todas las pruebas que necesites le puedes pasar al archivo CSS un parámetro por url con la fecha. El parámetro quedaría así:

content_css: ‘css/styles.css?’ + new Date().getTime(),

Podemos cambiar el menú. Por ejemplo vamos a quitar el menú de archivo que no nos hace falta.

Aquí te indico un detalle: No podemos simplemente decirle a TinyMCE que no nos muestre un menú, o no cargue un plugin concreto antes de iniciar el editor, lo que nos obliga a obtener los valores por defecto que tiene TinyMCE e indicárselos con los cambios que queramos hacer.

Por ejemplo, si solo indicásemos que la propiedad “menu” no tuviera contenido:

menu: {}

TinyMCE sobreescribe la configuración del menú y entiende que no hay nada, por tanto, cuando lo vemos en el navegador no nos mostrará el menú. Útil en algunas ocasiones, pero no para nuestro caso.

Para solucionarlo tenemos que saber los valores por defecto que tiene el editor. TinyMCE tiene un tema por defecto, y su archivo “theme.js” contiene esos datos. La ubicación del archivo es themes/modern/theme.js, y los valores por defecto los crea al principio del código, con lo que te resultará bastante fácil encontrarlo. Sería el siguiente:

var defaultMenus = {
  file: {title: 'File', items: 'newdocument'},
  edit: {title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall'},
  insert: {title: 'Insert', items: '|'},
  view: {title: 'View', items: 'visualaid |'},
  format: {title: 'Format', items: 'bold italic underline strikethrough superscript subscript | formats | removeformat'},
  table: {title: 'Table'},
  tools: {title: 'Tools'}
};

Así que tendríamos que añadir ese código a nuestra configuración y eliminar el menú “Archivo” que no nos interesa. ¿Cómo lo hacemos? Pues de la siguiente manera:

menu: {
  //file: {title: 'File', items: 'newdocument'},
  edit: {title: 'Edit', items: 'undo redo | cut copy paste pastetext | selectall'},
  insert: {title: 'Insert', items: '|'},
  view: {title: 'View', items: 'visualaid |'},
  format: {title: 'Format', items: 'bold italic underline strikethrough superscript subscript | formats | removeformat'},
  table: {title: 'Table'},
  tools: {title: 'Tools'}
},

Dentro del objeto menu que antes hemos añadido pero vacío, ahora añadimos el contenido de la variable defaultMenus del tema. Con la excepción del menú “file” que yo solo he comentado. Si lo probamos en nuestro navegador veremos que ya no aparece “file”.

Si observas el código verás que cada menú está separado por un espacio en blanco, y para crear grupos de menús se usa la barra vertical “|”.

Podemos añadir nuevos elementos a nuestra barra de herramientas de la misma forma que el menú. Recuerda que tenemos que coger lo que hay por defecto para evitar reescribirlo:

toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',

Por defecto, la línea anterior son los elementos de la barra de herramientas que muestra TinyMCE. Vamos a quitar el selector de formatos, nos quedaría lo siguiente:

toolbar: 'undo redo | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image',

Si te fijas, los últimos elementos “link image” no aparecen en la barra de herramientas. Eso es porque son elementos que pertenecen a plugins, así que vamos a añadirlos.

Para añadir plugins que añadan características a nuestro editor es bastante sencillo, solo hay que añadir otro parámetro más a nuestro objeto de configuración:

plugins: 'link image',

Ahora sí verás los botones para añadir un enlace al contenido y una imagen.

TinyMCE tiene cientos de plugins a tu disposición en su página web: https://www.tinymce.com/docs/plugins/

Por defecto, el editor trae unos cuantos plugins para añadir al editor. Por ejemplo podemos añadir el plugin “media”:

plugins:’link image | media’,

El hecho de añadir el plugin a la propiedad plugins no tiene por qué producir ningún efecto en nuestro editor. Para poder ver el botón que ofrece el plugin tendremos que añadirlo en la propiedad toolbar:

toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media',

Ahora podremos añadir videos a nuestro contenido.

Los estilos también los podemos modificar o añadir otros.

Si quiesieramos añadir otros formatos de estilos a los que ya hay por defecto lo podemos hacer indicando la siguiente propiedad:

style_formats_merge: true,

Vamos a añadir algún formato nuevo. Por ejemplo que una lista desordenada <ul> aparezca, en vez de con un punto para indicar un elemento de la lista, con caracteres griegos.

Primero vamos a crear nuestro estilo CSS:

.greek-symbols {
  list-style-type:lower-greek;
}

A continuación creamos el formato:

style_formats: [
  {
    title: 'Change circle for greek symbols', selector: 'ul', classes:'greek-symbols',
  },
],

Como ves, le estamos indicando a TinyMCE que para el selector “ul” nos añada la clase “greek-symbols” cuando hagamos click en el elemento de menú “Change circle for greek symbols”.

Añadimos a nuestra hoja de estilos otro estilo para cambiar el fondo de los elementos li pares:

.background-grey li:nth-child(2n) {
  background-color: #ccc;
}

Y lo añadimos a los formatos:

style_formats: [
  {
    title: 'Change circle for greek symbols', selector: 'ul', classes:'greek-symbols',
  },
  {
    title: 'Background gray for elem lists', selector: 'ul', classes:'background-grey',
  },
],

Ahora al crear una lista desordenada podremos aplicar un formato a los elementos pares de la lista.

¿Y si quisiéramos añadir las dos clases a la vez?

Pues las añadimos separándolas por un espacio en blanco:

style_formats: [
  {
    title: 'Change circle for greek symbols', selector: 'ul', classes:'greek-symbols',
  },
  {
    title: 'Background gray for elem lists', selector: 'ul', classes:'background-grey',
  },
  {
    title: 'Background gray and greek symbols', selector: 'ul', classes:'greek-symbols background-grey',
  },
],

Si queremos agrupar estos formatos tendríamos que añadirlos dentro de la propiedad ítems:

style_formats: [
  {
    title: 'Formats group', items:[
      {
        title: 'Change circle for greek symbols', selector: 'ul', classes:'greek-symbols',
      },
      {
        title: 'Background gray for elem lists', selector: 'ul', classes:'background-grey',
      },
      {
        title: 'Background gray and greek symbols', selector: 'ul', classes:'greek-symbols background-grey',
      },
    ],
  },
],

Por último tenemos que traducir los títulos de los formatos que hemos creado, ya que tenemos la interfaz en español. Para ello utilizaremos el método addI18n del objeto tinymce:

tinymce.addI18n('es_ES',{
  'Formats group': 'Grupo de formatos',
  'Change circle for greek symbols': 'Cambiar circulo por símbolos griegos',
  'Background gray for elem lists': 'Fondo gris para elementos de lista',
  'Background gray and greek symbols': 'Fondo gris y símbolos griegos',
});

Como ves, el primer parámetro del método es el idioma, y el segundo un objeto con los textos a traducir, y como valor el texto en el idioma especificado antes.

 

Crear plugins para TinyMCE

Si la personalización que se necesita no puede ser realizada con las propiedades anteriores, tenemos la opción de crear un plugin.

Los plugins nos permiten añadir bloques de código HTML basándonos en un conjunto de opciones que le permitirán al usuario personalizar el bloque a crear a través de una ventana de diálogo.

Si lo que necesitas es realizar una pequeña adaptación de un plugin ya existente, lo mejor, antes de crear un plugin desde cero es copiar ese plugin, cambiarle el nombre y realizar la modificación oportuna. Esto te ahorrará mucho trabajo.

Normalmente los plugins deberán ir en archivos JavaScript independientes, por comodidad para esta guía lo añadiré al mismo archivo que tiene la configuración de TinyMCE.

Todos los plugins se crean de la siguiente manera:

tinymce.PluginManager.add('nombre_plugin', function(editor) {

});

En este código le pasamos el nombre del plugin y un callback con un parámetro que nos permitirá acceder a las propiedades del editor dentro del plugin.

En nuestro caso vamos a llamarlo “custom_plugin”:

tinymce.PluginManager.add('custom_plugin', function(editor) {

});

Y lo añadimos a la lista de plugins de la configuración de TinyMCE:

plugins: 'link image media code custom_plugin',

Vamos a crear un plugin para crear un bloque de cita. Para ello vamos a añadir un botón para la barra de herramientas y un ítem de menú. Por tanto, dentro de la función de callback añadimos lo siguiente:

// Toolbar Button
editor.addButton('custom_plugin', {
  tooltip: 'Custom plugin',
  onclick: showDialog,
  stateSelector: 'cite'
});

Aquí le estamos indicando al editor que nos añada un botón con el identificador “custom_plugin”, con título “Custom plugin”, con el evento onclick que llamará a la función “showDialog” que crearemos en un momento y que el selector a utilizar (“stateSelector”) sea “cite” que es la etiqueta html con la que vamos a trabajar.

Ahora añadimos un elemento de menú:

// Menubar option
editor.addMenuItem('custom_plugin', {
  text: 'Custom plugin',
  onclick: showDialog,
  context: 'insert',
  prependToContext: true
});

Al igual que con el botón de la barra de tareas le tenemos que pasar un identificador (no importa que sea el mismo), un título (en este caso la propiedad es “text”), la función que se ejecutará cuando se realice un evento “onclick”. “context” es el contexto donde se añadirá el elemento de menú, dicho de otra manera: se indica el menú padre del que colgará el elemento que estamos creando, en este caso “insert”. Por último “preprendToContext” añadirá el ítem al final de todos los elementos del menú.

A ambos elementos (botón y elemento de menú) puedes añadirle la porpiedad “image” e indicarle una ruta a una imagen que se mostrará como un icono. Muy útil para el botón.

Aunque con esto debería ser suficiente para añadir un botón y un elemento de menú, no es así. Para que se muestre en la barra de herramientas y el menú hay que indicarlo en la configuración explícitamente. A sí que lo añadimos:

Barra de herramientas:

toolbar: 'undo redo | styleselect | bold italic | alignleft aligncenter alignright alignjustify | bullist numlist outdent indent | link image media | code custom_plugin',

Menú:

insert: {title: 'Insert', items: 'custom_plugin'},

Ahora creamos el formulario con las opciones para personalizar la cita. Para ello utilizaremos la función “showDialog”. Pero primero creamos varias variables necesarias para lo que queremos hacer:

var win, data = {}, dom = editor.dom, element = editor.selection.getNode();
var parent = dom.getParent(editor.selection.getNode(), 'cite');

if (parent) {
  element = parent;
}

Si al mostrar el diálogo tenemos seleccionado un bloque de cita, el condicional vinculará los datos de ese bloque con la variable “element” que es con la que trabajaremos. Ahora comprobamos que esa variable (“element”) es una etiqueta “cite” y obtiene los datos del bloque para mostrarlos en el formulario:

if (element.nodeName == 'CITE' &amp;&amp; !element.getAttribute('data-mce-object')) {
  var q = new tinymce.dom.DomQuery(element);
  var autor = q.find('span').html();
  var position = q.attr('class');
  var cite = q.text();

  cite = cite.replace(autor, '');

  data = {
    autor: autor,
    cite: cite,
    position: position,
  };

}
else {
  element = null;
}

Dentro de la función y debajo de las variables añadimos lo siguiente:

// Simple default dialog
win = editor.windowManager.open({
  title: 'Insert/edit cite',
  data: data,
  body: generalFormItems,
  onSubmit: onSubmitForm
});

Este código será el encargado de crear el diálogo y vincular el evento onsubmit del formulario con la función correspondiente. Le indicamos un título (title), los datos que tendrá el formulario(data). En el caso de que estemos editando un bloque de cita este objeto contendrá los datos correspondientes a ese bloque. La propiedad “body” nos permite crear el formulario que se mostrará en el diálogo.

En “body” le estamos pasando la variable “generalFormItems”, su contenido es el siguiente:

// General settings
var generalFormItems = [
  {name: 'autor', type: 'textbox', label: 'Autor'},
  {name: 'cite', type: 'textbox', label: 'Cite', multiline: true, rows: 8, size: '300px'},
  {name: 'position', type: 'listbox', label: 'Position',
    values: [
      {text: '', value: ''},
      {text: 'Left', value: 'left'},
      {text: 'Right', value: 'right'}
    ]
  },
];

En esta variable estamos indicando los campos que contendrá el formulario. Dos campos de texto para el autor y la cita, este último campo será multilínea. Además añadimos un desplegable para poder indicar la ubicación del bloque a la derecha o la izquierda.

Una vez que hemos rellenado los campos y seleccionado la ubicación del bloque tenemos que hacer que se genere el bloque. Para ello vamos a crear la función “onSubmitForm” que está vinculada al evento “onSubmit”.

function onSubmitForm() {
  var data = win.toJSON();

  data = {
    autor: data.autor,
    cite: data.cite,
    position: data.position,
  };
}

Las primeras líneas que añadimos a la función son para obtener los datos del formulario en formato JSON, y a continuación creamos el objeto que contendrá los datos. Sí, el último objeto me lo podría haber ahorrado, pero de esta forma podéis ver los datos que contiene el objeto.

Lo siguiente es iniciar la creación del bloque:

editor.undoManager.transact(function() {
  if (!data.cite) {
    if (element) {
      dom.remove(element);
      editor.nodeChanged();
    }

    return;
  }

  if (!element) {
    data.id = '__mcenew';

    var cite = createCite(data);
    editor.selection.setContent(cite.outerHTML);
    element = dom.get('__mcenew');
    dom.setAttrib(element, 'id', null);
  } 
  else {
    var cite = createCite(data);
    dom.remove(element);
    editor.selection.setContent(cite.outerHTML);
  }
});

En este código le estamos pasando un callback al método “transact” del objeto “undoManager”, el cual está dividido en dos condicionales. El primero (if (¡data.cite)) elimina el bloque si en el diálogo el campo “cite” está vacío. El segundo condicional (if (¡element)) comprueba si el bloque existe o no. Si no existe crea un nuevo id (data.id), y llama a la función “createCite(data)” con los datos del formulario para crear la estructura html que se le pasa al editor (editor.selection.setContent(cite.outerHTML)). Las dos siguientes líneas obtiene el objeto que acabamos de crear y elimina el atributo id.

En el caso de que el objeto ya existiese, crearíamos la estructura html del bloque, eliminaríamos la existente, y añadiríamos al editor la nueva. De esta forma evitamos que pudiera producirse un duplicado del bloque.

Por último creamos la función “createCite(data)” fuera de la función “onSubmitForm”:

function createCite(data) {
  var cite = document.createElement('cite');
  var autor = document.createElement('span');
  var text_cite = document.createTextNode(data.cite);
  var text_autor = document.createTextNode(data.autor);

  cite.appendChild(text_cite);
  cite.setAttribute('class', data.position);

  if (data.autor != '') {
    autor.appendChild(text_autor);
    autor.setAttribute('class', 'autor');
    cite.appendChild(autor);
  }

return cite;
}

El código es sencillo: creamos dos etiquetas “cite” y “span” y dos nodos de texto “text_cite” y “text_autor” y los vamos uniendo hasta completar la estructura del bloque.

Nos queda un detalle, añadir los estilos a nuestra hoja de estilos:

cite {
  background-color: #eee;
  padding: 10px 10px;
  position: relative;
  border-radius: 10px;
  width: 100%;
}

.autor {
  position: absolute;
  bottom: -15px;
  text-align: right;
  right: 5px;
}

.left {
  float: left;
}

.right {
  float: right;
}

Con esto hemos podido ver lo sencillo que es personalizar el editor TinyMCE y crear un plugin básico con modificación del DOM del documento.

Si te surge cualquier duda puedes dejarla en los comentarios.

Miguel Gil es desarrollador web en Yunbit desde 2013, donde ha participado en proyectos de la mayor parte de áreas de desarrollo de producto: CRM, ERP, SGA, eCommerce y RRHH. Así se define él: "Empecé desde pequeño a enredar con los ordenadores y preguntarme cómo podían hacer las cosas que hacían, y esto me llevó a aprender a programar, y con el tiempo a especializarme en tecnologías web. He aprendido de todo dentro de la web: desde el diseño a la gestión de sistemas, pasando por la programación, en la que centré mi carrera profesional. Astronomía y astronáutica, y cacharrear con la electrónica son mis otras aficiones."
Entrada Relacionada