PageRenderTime 61ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/episodes/296 - Mercury Editor/en.html

https://github.com/mrnbrkt/asciicasts.com-translations
HTML | 232 lines | 175 code | 57 blank | 0 comment | 0 complexity | 3967ff142d3381fc2bbc338e36b87731 MD5 | raw file
  1. <p><a href="http://jejacks0n.github.com/mercury/">Mercury</a> is a project by Jeremy Jackson that allows you to edit sections of an HTML page directly in the browser. You can see it in action by clicking &ldquo;Test it Out&rdquo; on the project&rsquo;s home page. Doing so adds a toolbar to the top of the page and highlights certain sections of it. You can then edit these sections directly and save the changes back to the server. In this episode we&rsquo;ll add Mercury to a Rails application.</p>
  2. <p>Below is a page from a simple Content Management System. This application has a <code>Page</code> model and three page records each with a name and some content. Currently there&rsquo;s no way to edit the pages so we&rsquo;ll add Mercury to the app so that we can edit them directly.</p>
  3. <div class="imageWrapper">
  4. <img src="http://asciicasts.com/system/photos/823/original/E296I01.png" width="800" height="437" alt="Our Simple CMS Application."/>
  5. </div>
  6. <h3>Installing Mercury</h3>
  7. <p>The first step to installing Mercury is to go into the <code>/Gemfile</code> and add the <code>mercury-rails</code> gem. This gem is being developed fairly rapidly so we&rsquo;ll grab a recent version directly from Github.</p>
  8. ``` /Gemfile
  9. source 'http://rubygems.org'
  10. gem 'rails', '3.1.1'
  11. gem 'sqlite3'
  12. # Gems used only for assets and not required
  13. # in production environments by default.
  14. group :assets do
  15. gem 'sass-rails', '~> 3.1.4'
  16. gem 'coffee-rails', '~> 3.1.1'
  17. gem 'uglifier', '>= 1.0.3'
  18. end
  19. gem 'jquery-rails'
  20. gem 'mercury-rails', git: 'https://github.com/jejacks0n/mercury.git', ref: 'a2b16bcdc9'
  21. ```
  22. <p>The commit SHA of the version we&rsquo;re using is included above so that you know which version we&rsquo;ve used. As ever once we&rsquo;ve changed the <code>Gemfile</code> we&rsquo;ll need to run bundle to install the new gem.</p>
  23. <p>Once the gem has installed we need to run Mercury&rsquo;s installation generator. This will ask us if we want to install the layout and CSS overrides files and we do.</p>
  24. ``` terminal
  25. $ rails g mercury:install
  26. create app/assets/javascripts/mercury.js
  27. route Mercury::Engine.routes
  28. Install the layout and CSS overrides files? [yN] y
  29. create app/views/layouts/mercury.html.erb
  30. create app/assets/stylesheets/mercury_overrides.css
  31. ```
  32. <p>The output from this command tells us that we need to run another command to install Mercury&rsquo;s migrations so we&rsquo;ll run that next and then migrate the database.</p>
  33. ``` terminal
  34. $ rake mercury_engine:install:migrations
  35. Copied migration 20111108202946_create_images.rb from mercury_engine
  36. noonoo:cms eifion$ rake db:migrate
  37. == CreateImages: migrating ===================================================
  38. -- create_table(:images)
  39. -> 0.0017s
  40. == CreateImages: migrated (0.0018s) ==========================================
  41. ```
  42. <p>Our application now has a <code>mercury.js</code> file in its app/assets directory. This file contains all of the configuration options for Mercury and comments explaining them. We&rsquo;ll come back to this file later; first we&rsquo;ll address how this file is loaded. In a Rails 3.1 application this file will be loaded by default on every page as the <code>application.js</code> manifest file includes every file under <code>app/assets/javascripts</code>. Mercury is quite JavaScript-heavy so we only want it to load on the Mercury-editable pages. We&rsquo;ll modify the manifest so that is only loads the files we specify. (As an alternative we could move the <code>mercury.js</code> file out to <code>/vendor/assets</code> instead.) When we&rsquo;re editing a page Mercury will use its own layout file that includes this JavaScript file so though it appears that we&rsquo;ve removed this file completely it will still be loaded when necessary.</p>
  43. ``` /app/assets/javascripts/application.js
  44. // This is a manifest file that'll be compiled into including all the files listed below.
  45. // Add new JavaScript/Coffee code in separate files in this directory and they'll automatically
  46. // be included in the compiled file accessible from http://example.com/assets/application.js
  47. // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
  48. // the compiled file.
  49. //
  50. //= require jquery
  51. //= require jquery_ujs
  52. //= require pages
  53. ```
  54. <p>We can do something similar for Mercury&rsquo;s stylesheet but we won&rsquo;t do that here.</p>
  55. <h3>Making Pages Editable</h3>
  56. <p>With Mercury installed we now have access to the Mercury editor from any page in our application. To put any page into edit mode we just need to add <code>/editor</code> between the URL&rsquo;s host and path. </p>
  57. <div class="imageWrapper">
  58. <img src="http://asciicasts.com/system/photos/824/original/E296I02.png" width="800" height="551" alt="The Mercury toolbar."/>
  59. </div>
  60. <p>Now that we know the URL to edit a page we can add the &ldquo;Edit Page&rdquo; link.</p>
  61. ``` /app/views/pages/show.html.erb
  62. <div id="header">
  63. <h1><%= raw @page.name %></h1>
  64. <ul id="navigation">
  65. <% Page.all.each do |page| %>
  66. <li><%= link_to_unless_current page.name, page %></li>
  67. <% end %>
  68. </ul>
  69. </div>
  70. <div class="content">
  71. <%= raw @page.content %>
  72. <p><%= link_to "Edit Page", "/editor" + request.path %></p>
  73. </div>
  74. ```
  75. <h3>Adding Editable Areas</h3>
  76. <p>We now have a link to edit any page but we haven&rsquo;t yet defined any editable areas on the page. There are two sections we want to be editable: the page&rsquo;s title and its content and each of these map to attributes in the <code>Page</code> model. To make part of a page editable we need to wrap it in an element that has a <code>class</code> of <code>mercury-region</code> and a <code>data-type</code> of <code>editable</code>. We also need to give each wrapper element an <code>id</code> so that we can identify it when the updated page is sent back to the server. We&rsquo;ll call our two regions <code>page_name</code> and <code>page_content</code>.</p>
  77. ``` /app/views/pages/show.html.erb
  78. <div id="header">
  79. <h1><span id="page_name" class="mercury-region" data-type="editable"><%= raw @page.name %></span></h1>
  80. <ul id="navigation">
  81. <% Page.all.each do |page| %>
  82. <li><%= link_to_unless_current page.name, page %></li>
  83. <% end %>
  84. </ul>
  85. </div>
  86. <div class="content">
  87. <div id="page_content" class="mercury-region" data-type="editable">
  88. <%= raw @page.content %>
  89. </div>
  90. <p><%= link_to "Edit Page", "/editor" + request.path %></p>
  91. </div>
  92. ```
  93. <p>When we reload the page now we&rsquo;l see the two editable regions outlined in blue.</p>
  94. <div class="imageWrapper">
  95. <img src="http://asciicasts.com/system/photos/825/original/E296I03.png" width="800" height="551" alt="The page now has editable regions."/>
  96. </div>
  97. <h3>Saving Changes</h3>
  98. <p>We can edit the two regions as much as we like now and preview our changes but we can&rsquo;t save any changes we make as we haven&rsquo;t yet set up our application to be able to do that. When we click the &ldquo;Save&rdquo; icon Mercury will pop up an alert showing where it was trying to save the changes to.</p>
  99. <div class="imageWrapper">
  100. <img src="http://asciicasts.com/system/photos/826/original/E296I04.png" width="420" height="153" alt="Mercury show an alert if it can&rsquo;t save changes to the server."/>
  101. </div>
  102. <p>The URL that Mercury tries to save to is the page&rsquo;s URL. Mercury sends a POST request to this URL containing the changes. We&rsquo;ll change this so that the updated content is sent to a new <code>mercury_update</code> action on the <code>PagesController</code>. We&rsquo;ll need to modify the routes file so that it knows how to handle this new action.</p>
  103. ``` /config/routes.rb
  104. Cms::Application.routes.draw do
  105. Mercury::Engine.routes
  106. root to: 'pages#index'
  107. resources :pages do
  108. member { post :mercury_update }
  109. end
  110. end
  111. ```
  112. <p>Note that Mercury has added its own line to the routes file to enable the editor URL for each page.</p>
  113. <p>Next we&rsquo;ll write the new <code>mercury_update</code> action. This will need to find a page by its <code>id</code> then update it and return a response. For now we&rsquo;ll just put a placeholder comment where the update code will go.</p>
  114. ``` /app/controllers/pages_controller.rb
  115. def mercury_update
  116. page = Page.find(params[:id])
  117. # Update page
  118. render text: ""
  119. end
  120. ```
  121. <p>Now we need to tell Mercury that we&rsquo;re not using its default URL for updating edited pages. Rather than hard-code this in JavaScript we&rsquo;ll define it in a <code>data</code> attribute it the &ldquo;Edit Page&rdquo; link. We&rsquo;ll also give the link an <code>id</code> now so that we can access it from JavaScript.</p>
  122. ``` /app/views/pages/show.html.erb
  123. <p><%= link_to "Edit Page", "/editor" + request.path, id: "edit_link", data: { save_url: mercury_update_page_path(@page) } %></p>
  124. ```
  125. <p>We can tell Mercury about this URL at the bottom of the <code>mercury.js</code> configuration file by adding the following code.</p>
  126. ``` /app/assets/javascripts/mercury.js
  127. $(window).bind('mercury:ready', function() {
  128. var link = $('#mercury_iframe').contents().find('#edit_link');
  129. Mercury.saveURL = link.data('save-url');
  130. link.hide();
  131. });
  132. ```
  133. <p>This code binds to the <code>mercury:ready</code> event and when that event fires it finds the &ldquo;Edit Page&rdquo; link and sets Mercury&rsquo;s <code>saveURL</code> to the value in the <code>data-save-url</code> attribute we added to it. Mercury loads the content of the current page into an iframe so we have to fetch its contents and find the link through there. As this code is triggered when the page is put into edit mode we&rsquo;ll add a line to hide the &ldquo;Edit Page&rdquo; link here, too.</p>
  134. <p>When we make some changes to the page now and try to save them the error message no longer shows which means that Mercury has submitted them to the server. If we look in the development log we&rsquo;ll see that saving the page triggers the <code>mercury_update</code> action and that it has submitted a JSON string containing all the attributes necessary to update our <code>Page</code> model.</p>
  135. ``` terminal
  136. Started POST "/pages/1/mercury_update" for 127.0.0.1 at 2011-11-10 18:31:59 +0000
  137. Processing by PagesController#mercury_update as JSON
  138. Parameters: {"content"=>"{\"page_name\":{\"type\":\"editable\",\"value\":\"Welcome!!\",\"snippets\":{}},\"page_content\":{\"type\":\"editable\",\"value\":\"<p>In this ASCIIcasts&nbsp;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"}
  139. ```
  140. <p>Mercury has the option to save the data as nested form parameters instead of by using JSON. To set this option we need to modify the <code>mercury.html.erb</code> file that was generated when we ran the Mercury generator. This file contains an <code>options</code> object with a <code>saveStyle</code> property. By default this property has a value of <code>null</code> which means that JSON will be used when a page update is sent back to the server. We&rsquo;ll change this to <code>&#x27;form&#x27;</code>.</p>
  141. ``` /app/views/layouts/mercury.html.erb
  142. <script type="text/javascript">
  143. var saveUrl = null;
  144. var options = {
  145. saveStyle: 'form', // 'form', or 'json' (default json)
  146. saveMethod: null, // 'POST', or 'PUT', (create, vs. update -- default POST)
  147. visible: null // if the interface should start visible or not (default true)
  148. };
  149. new Mercury.PageEditor(saveUrl, options);
  150. </script>
  151. ```
  152. <p>When we save the changes to a page now they&rsquo;re sent as nested parameters rather than as JSON data and we can use this data to save the changes to the database. The page&rsquo;s name and content are both nested under the <code>content</code> parameter and each of these has a <code>value</code> property. We need these to save the page&rsquo;s new content back to the database.</p>
  153. ``` /app/controllers/pages_controller.rb
  154. def mercury_update
  155. page = Page.find(params[:id])
  156. page.name = params[:content][:page_name][:value]
  157. page.content = params[:content][:page_content][:value]
  158. page.save!
  159. render text: ""
  160. end
  161. ```
  162. <p>When we change the page now and save the changes nothing appears to happen but when we go back to the page we&rsquo;ll see that the changes have been saved.</p>
  163. <div class="imageWrapper">
  164. <img src="http://asciicasts.com/system/photos/827/original/E296I05.png" width="800" height="280" alt="The changes are now saved."/>
  165. </div>
  166. <p>We want our application to redirect back to the page once we&rsquo;ve saved the changes and we can do that by listening for the <code>mercury:saved</code> event and redirecting when it fires.</p>
  167. ``` /app/assets/javascripts/mercury.js
  168. $(window).bind('mercury:saved', function() {
  169. window.location = window.location.href.replace(/\/editor\//i, '/');
  170. });
  171. ```
  172. <p>Now when we save changes we&rsquo;re redirected back to the page itself.</p>
  173. <h3>Going Further</h3>
  174. <p>There&rsquo;s much more to Mercury than we can cover here. Reading through the comments in the <code>mercury.js</code> file will give you a good idea as to what is possible. For example we can customize exactly what appears in the toolbar; there are various features like snippets, history and notes that we can enable and a whole lot more. All of these are documented in <code>mercury.js</code>.</p>
  175. <p>That&rsquo;s it for our look at Mercury. It&rsquo;s a great project and a lot of fun to use. If you want to add it to one of your Rails projects, though, bear in mind that you&rsquo;ll need a modern version of Firefox, Chrome or Safari to use it.</p>