#296 Mercury Editor
- Download:
- source codeProject Files in Zip (88.9 KB)
- mp4Full Size H.264 Video (32.8 MB)
- m4vSmaller H.264 Video (15.4 MB)
- webmFull Size VP8 Video (16.6 MB)
- ogvFull Size Theora Video (39.7 MB)
Mercury es un proyecto de Jeremy Jackson que permite editar secciones de una página HTML directamente en el navegador. Se puede ver en funcionamiento haciendo clic en “Test it Out” en la página del proyecto. Al hacerlo aparece una barra de herramientas en la parte superior de la página, en la que se iluminan ciertas secciones que se podrán editar directamente y enviar sus cambios al servidor. En este episodio veremos cómo añadir Mercury a una aplicación Rails.
Debajo aparece una página de un sencillo gestor de contenidos. La aplicación tiene un modelo Page
con tres registros cada uno de los cuales tiene un nombre y contenido. Ahora mismo no tenemos forma de editar estas páginas por lo que añadiremos el editor Mercury para poder modificarlas directamente.
Instalación de Mercury
El primer paso para instalar Mercury es ir al /Gemfile
y añadir la gema mercury-rails
. Al tratarse de un proyecto de mucha actividad descargaremos una versión reciente directamente desde Github.
source 'http://rubygems.org' gem 'rails', '3.1.1' gem 'sqlite3' # Gems used only for assets and not required # in production environments by default. group :assets do gem 'sass-rails', '~> 3.1.4' gem 'coffee-rails', '~> 3.1.1' gem 'uglifier', '>= 1.0.3' end gem 'jquery-rails' gem 'mercury-rails', git: 'https://github.com/jejacks0n/mercury.git', ref: 'a2b16bcdc9'
Puede verse la versión que hemos utilizado en este episodio examinando el SHA de su commit. Como siempre que cambiamos el Gemfile
tenemos que ejecutar bundle para instalar la nueva gema.
Tras la instalación de la gema tenemos que ejecutar el generador de instalaciones de Mercury, que nos preguntará si queremos instalar los ficheros de CSS y layout y le diremos que sí.
$ rails g mercury:install create app/assets/javascripts/mercury.js route Mercury::Engine.routes Install the layout and CSS overrides files? [yN] y create app/views/layouts/mercury.html.erb create app/assets/stylesheets/mercury_overrides.css
La salida de la orden nos indica que tenemos que instalar las migraciones de Mercury tras lo cual migraremos la base de datos.
$ rake mercury_engine:install:migrations Copied migration 20111108202946_create_images.rb from mercury_engine noonoo:cms eifion$ rake db:migrate == CreateImages: migrating =================================================== -- create_table(:images) -> 0.0017s == CreateImages: migrated (0.0018s) ==========================================
Tras todo esto nuestra aplicación ahora tiene un fichero llamado mercury.js
en el directorio app/assets
. Este fichero contiene todas las opciones de configuración de Mercury así como comentarios que explican su cometido. Más adelante volveremos a este archivo; por ahora tenemos que ver cómo lo cargamos. En una aplicación de Rails 3.1 este JavaScript será cargado por defecto en todas las páginas porque el fichero de manifiesto application.js
incluye todos los archivos que cuelguen de app/assets/javascripts
. El JavaScript de Mercury es bastante pesado por lo que sólo queremos cargarlo en las páginas editables así que vamos a cambiar el fichero de manifiesto para que sólo cargue los ficheros que queramos (como alternativa, podríamos mover el fichero mercury.js
a /vendor/assets
). Cuando estamos editando una página Mercury utilizará su propio fichero de layout que incluirá este JavaScript por lo que aunque en principio hemos quitado este fichero, seguirá cargándose cuando haga falta.
// This is a manifest file that'll be compiled into including all the files listed below. // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically // be included in the compiled file accessible from http://example.com/assets/application.js // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the // the compiled file. // //= require jquery //= require jquery_ujs //= require pages
Podríamos hacer lo mismo con la hoja de estilos de Mercury pero por ahora no nos será necesario.
Cómo hacer que las páginas sean editables
Una vez instalado Mercury ya tenemos acceso al editor desde cualquier página de la aplicación. Para poner en modo edición una página tna sólo tenemos que añadir /editor
entre el host y la ruta de la URL.
Como ya sabemos la URL para editar una página podemos añadir el enlace de edición.
<div id="header"> <h1><%= raw @page.name %></h1> <ul id="navigation"> <% Page.all.each do |page| %> <li><%= link_to_unless_current page.name, page %></li> <% end %> </ul> </div> <div class="content"> <%= raw @page.content %> <p><%= link_to "Edit Page", "/editor" + request.path %></p> </div>
Cómo hacer áreas editables
Ya tenemos un enlace que nos permite editar cualquier página pero todavía no hemos definido cuáles son las áreas editables de la misma. Tenemos dos secciones que queremos poder editar: el título de la página y su contenido, que se mapean a atributos en el modelo Page
. Para que una sección de una página sea editable tiene que estar rodeada por un elemento que tenga la clase mercury-region
y data-type
editable
. También tenemos que darle a cada envoltorio un id
para que puedan ser identificados cuando se envíe la página actualizada al servidor. Nuestras dos regiones se llamarán page_name
y page_content
.
<div id="header"> <h1><span id="page_name" class="mercury-region" data-type="editable"><%= raw @page.name %></span></h1> <ul id="navigation"> <% Page.all.each do |page| %> <li><%= link_to_unless_current page.name, page %></li> <% end %> </ul> </div> <div class="content"> <div id="page_content" class="mercury-region" data-type="editable"> <%= raw @page.content %> </div> <p><%= link_to "Edit Page", "/editor" + request.path %></p> </div>
Si recargamos la página veremos las dos secciones editables en azul.
Almacenamiento de los cambios
Podemos editar las regiones y previsualizar los cambios, pero todavía no los podemos guardar porque no hemos configurado nuestra aplicación para que lo haga. Al hacer clic en el icono “Save” Mercury mostrará una alerta indicando a dónde estaba intentando enviar los cambios.
La URL en la que Mercury intenta guardar los cambios es la URL de la página. Mercury envía una petición POST a esta URL con los cambios. Modificaremos este comportamiento para que el contenido actualizado se envíe a la acción mercury_update
del PagesController
. Tenemos que modificar el fichero de rutas para que sepa qué hacer con esta nueva acción.
Cms::Application.routes.draw do Mercury::Engine.routes root to: 'pages#index' resources :pages do member { post :mercury_update } end end
Nótese que Mercury ya ha añadido una línea al fichero de rutas para activar la URL de edición de cada página.
Lo siguiente es escribir la acción mercury_update
. Tendremos que encontrar la página por su id
y luego actualizarla y devolver la respuesta. Por ahora, pondremos un comentario en el sitio donde tendríamos que escribir el código.
def mercury_update page = Page.find(params[:id]) # Update page render text: "" end
Ahora tenemos que decirle a Mercury que no queremos usar la URL que se utiliza por defecto para actualizar las páginas. Para no tener que configurar la URL en JavaScript directamente editaremos el atributo data
del enlace a “Edit Page”. También le daremos al enlace un id
para poder acceder a él desde JavaScript.
<p><%= link_to "Edit Page", "/editor" + request.path, id: "edit_link", data: { save_url: mercury_update_page_path(@page) } %></p>
Le podemos informar a Mercury de esta nueva URL al final de la configuración de mercury.js
añadiendo el siguiente código:
$(window).bind('mercury:ready', function() { var link = $('#mercury_iframe').contents().find('#edit_link'); Mercury.saveURL = link.data('save-url'); link.hide(); });
El código se engancha al evento mercury:ready
, y cuando dicho evento se produzca buscará el enlace a “Edit Page” y establecerá el atributo saveURL
de Mercury al valor que aparecía en el atributo data-save-url
que le añadimos. Mercury carga el contenido de la página actual en un iframe por lo que tenemos que obtener su contenido para buscar en él el enlace. Como este código sólo se activa cuando la página pasa a modo de edición le hemos añadido una línea para ocultar el enlace.
Si ahora hacemos algunos cambios en la página e intentamos guardarlo veremos que el mensaje de error ya no aparece, lo que significa que Mercury ha enviado los cambios al servidor. Si inspeccionamos el registro de desarrollo veremos que al guardar la página se invoca la acción mercury_update
, enviándose el contenido como una cadena JSON que contiene todos los atributos necesarios para actualizar nuestro modelo Page
.
Started POST "/pages/1/mercury_update" for 127.0.0.1 at 2011-11-10 18:31:59 +0000 Processing by PagesController#mercury_update as JSON Parameters: {"content"=>"{\"page_name\":{\"type\":\"editable\",\"value\":\"Welcome!!\",\"snippets\":{}},\"page_content\":{\"type\":\"editable\",\"value\":\"<p>In this ASCIIcasts episode we are going to look at the <a href=\\\"http://jejacks0n.github.com/mercury/\\\">Mercury Editor</a>. It allows you to edit a document in-place, right in the HTML. It works in the following browsers.</p>\\n<ul>\\n <li>Firefox 4+</li>\\n <li>Chrome 10+</li>\\n <li>Safari 5+</li>\\n</ul>\\n<p>Try it out here by clicking on the <strong><em>Edit Page</em></strong> link below. There you will be able to change this page content and even the title above.</p>\",\"snippets\":{}}}", "id"=>"1"}
En lugar de usar JSON Mercury puede también guardar los datos como parámetros de formularios anidados. Para que se comporte de esta forma tenemos que cambiar el fichero mercury.html.erb
que se generó automáticamente. Este fichero contiene un objeto options
que posee la propiedad saveStyle
. Por defecto esta propiedad tiene el valor null
por lo que se usará JSON para enviar la página al servidor. Nosotros los cambiaremos a 'form'
.
<script type="text/javascript"> var saveUrl = null; var options = { saveStyle: 'form', // 'form', or 'json' (default json) saveMethod: null, // 'POST', or 'PUT', (create, vs. update -- default POST) visible: null // if the interface should start visible or not (default true) }; new Mercury.PageEditor(saveUrl, options); </script>
Cuando ahora se guarden los cambios en las páginas veremos que se envía como parámetros anidados en lugar de datos JSON, que podemos emplear para guardar los cambios en la base de datos. El nombre y contenido de la página aparecerán anidados en el parámetro content
, y cada uno de ellos tendrá una propiedad value
, que es lo que nos hace falta para guardar el contenido en base de datos.
def mercury_update page = Page.find(params[:id]) page.name = params[:content][:page_name][:value] page.content = params[:content][:page_content][:value] page.save! render text: "" end
Si ahora cambiamos la página y guardamos la página no parecerá que ocurra nada pero si volvemos a la página veremos que los cambios se han guardado.
Queremos que nuestra aplicación redirija a la página una vez que se hayan efectuado los cambios, lo que podemos hacer atendiendo al evento mercury:saved
y haciendo ahí la redirección.
$(window).bind('mercury:saved', function() { window.location = window.location.href.replace(/\/editor\//i, '/'); });
Cuando se guarden los cambios, esto nos dirigirá de vuelta a la página del contenido.
Para ir más lejos
Mercury ofrece mucho más de lo que hemos podido repasar aquí. Podemos hacernos una idea de todas sus posibilidades leyendo los comentarios del fichero mercury.js
. Podemos, por ejemplo, personalizar lo que aparece en la barra de herramientas; hay varias funcionalidades como fragmentos de código, historial y notas que podemos activar y muchas más cosas. Todas se encuentran documentadas en mercury.js
.
Con esto terminamos nuestro repaso a Mercury. Es una proyecto muy interesante y divertido, pero debemos tener en cuenta que para utilizarlo en nuestros proyectos Rails debemos usar una versión moderna de Firefox, Chrome o Safari.