Edit objects after creating them with x-editable.
authorTom Clegg <tom@curoverse.com>
Mon, 5 May 2014 10:06:16 +0000 (06:06 -0400)
committerTom Clegg <tom@curoverse.com>
Mon, 5 May 2014 10:06:16 +0000 (06:06 -0400)
apps/workbench/app/assets/javascripts/editable.js
apps/workbench/app/controllers/application_controller.rb
apps/workbench/app/controllers/folders_controller.rb
apps/workbench/app/helpers/application_helper.rb
apps/workbench/app/views/folders/show.html.erb
apps/workbench/test/integration/folders_test.rb
services/api/test/fixtures/specimens.yml

index ce502b38120c42e52a61dad0a8e43c166d416a57..8eea1693e8721efcf401c4c2c3c55bb0205180d1 100644 (file)
@@ -12,11 +12,17 @@ $.fn.editable.defaults.send = 'always';
 $.fn.editable.defaults.params = function (params) {
     var a = {};
     var key = params.pk.key;
-    a.id = params.pk.id;
+    a.id = $(this).attr('data-object-uuid') || params.pk.id;
     a[key] = params.pk.defaults || {};
+    // Remove null values. Otherwise they get transmitted as empty
+    // strings in request params.
+    for (i in a[key]) {
+        if (a[key][i] == null)
+            delete a[key][i];
+    }
     a[key][params.name] = params.value;
-    if (params.pk._method) {
-        a['_method'] = params.pk._method;
+    if (!a.id) {
+        a['_method'] = 'post';
     } else {
         a['_method'] = 'put';
     }
@@ -35,20 +41,34 @@ $(document).
             console.log($(this));
         });
         $('.editable').
-            editable().
-            on('hidden', function(e, reason) {
-                if (reason == 'save') {
-                    var html = $(this).html();
-                    var uuid = $(this).attr('data-object-uuid');
-                    var attr = $(this).attr('data-name');
-                    var edited = this;
-                    if (uuid && attr) {
-                        $("[data-object-uuid='" + uuid + "']" +
-                          "[data-name='" + attr + "']").each(function() {
-                              if (this != edited)
-                                  $(this).html(html);
-                          });
+            editable({
+                success: function(response, newValue) {
+                    // If we just created a new object, stash its UUID
+                    // so we edit it next time instead of creating
+                    // another new object.
+                    if (!$(this).attr('data-object-uuid') && response.uuid) {
+                        $(this).attr('data-object-uuid', response.uuid);
+                    }
+                    if (response.href) {
+                        $(this).editable('option', 'url', response.href);
                     }
+                    return;
+                }
+            }).
+            on('hidden', function(e, reason) {
+                // After saving a new attribute, update the same
+                // information if it appears elsewhere on the page.
+                if (reason != 'save') return;
+                var html = $(this).html();
+                var uuid = $(this).attr('data-object-uuid');
+                var attr = $(this).attr('data-name');
+                var edited = this;
+                if (uuid && attr) {
+                    $("[data-object-uuid='" + uuid + "']" +
+                      "[data-name='" + attr + "']").each(function() {
+                          if (this != edited)
+                              $(this).html(html);
+                      });
                 }
             });
     });
index 8c540fc227b6660b836253af11ff3c3e68932180..784c06a9b971ca2f1a5e6452afe564898e4e4989 100644 (file)
@@ -95,7 +95,7 @@ class ApplicationController < ActionController::Base
       return render_not_found("object not found")
     end
     respond_to do |f|
-      f.json { render json: @object }
+      f.json { render json: @object.attributes.merge(href: url_for(@object)) }
       f.html {
         if request.method == 'GET'
           render
@@ -145,14 +145,7 @@ class ApplicationController < ActionController::Base
     @new_resource_attrs.reject! { |k,v| k.to_s == 'uuid' }
     @object ||= model_class.new @new_resource_attrs
     @object.save!
-
-    respond_to do |f|
-      f.json { render json: @object }
-      f.html {
-        redirect_to(params[:return_to] || @object)
-      }
-      f.js { render }
-    end
+    show
   end
 
   def destroy
index e0ac625bab8a042a483b9eb56c74837cc16590b7..906ef27d72cd746197bb97fcaf058f055b0fa529 100644 (file)
@@ -9,6 +9,7 @@ class FoldersController < ApplicationController
 
   def remove_item
     @removed_uuids = []
+    links = []
     item = ArvadosBase.find params[:item_uuid]
     if (item.class == Link and
         item.link_class == 'name' and
@@ -16,8 +17,15 @@ class FoldersController < ApplicationController
       # Given uuid is a name link, linking an object to this
       # folder. First follow the link to find the item we're removing,
       # then delete the link.
-      link = item
-      item = ArvadosBase.find link.head_uuid
+      links << item
+      item = ArvadosBase.find item.head_uuid
+    else
+      # Given uuid is an object. Delete all names.
+      links += Link.where(tail_uuid: @object.uuid,
+                          head_uuid: item.uuid,
+                          link_class: 'name')
+    end
+    links.each do |link|
       @removed_uuids << link.uuid
       link.destroy
     end
index 2e85f0f0ceb316d0ccbca3fecd1ffe69be8d106c..dbb05d6ad4ae1d0b9a027194c93d596f9b8007c8 100644 (file)
@@ -152,7 +152,6 @@ module ApplicationHelper
     else
       ajax_options['data-url'] = url_for(action: "create", controller: object.class.to_s.pluralize.underscore)
       ajax_options['data-pk'][:defaults] = object.attributes
-      ajax_options['data-pk'][:_method] = 'post'
     end
     ajax_options['data-pk'] = ajax_options['data-pk'].to_json
 
index 7759b5567b7bd75e4022cb6d3b0aad62533e39e3..11bb52c70b7bbb8290f6dabf1e4670d354fa61c1 100644 (file)
               <col width="8%" />
             </colgroup>
             <% @objects_and_names.each do |object, name_link| %>
-            <tr data-object-uuid="<%= (name_link || object).uuid %>">
-              <td>
-                <%= render :partial => "selection_checkbox", :locals => {object: object} %>
-              </td>
-              <td>
-                <%= render :partial => "show_object_button", :locals => {object: object, size: 'xs'} %>
-              </td>
-              <td>
-                <%= render_editable_attribute name_link, 'name', nil, {data: {emptytext: "Unnamed #{object.class_for_display}"}} %>
-              </td>
-              <td>
-                <%= object.content_summary %>
-              </td>
-              <td title="<%= object.modified_at %>">
-                <span>
-                  <%= raw distance_of_time_in_words(object.modified_at, Time.now).sub('about ','~').sub(' ','&nbsp;') + '&nbsp;ago' rescue object.modified_at %>
-                </span>
-              </td>
-              <td class="arvados-uuid">
-                <%= object.uuid %>
-              </td>
-              <td>
-                <% if @object.editable? %>
-                  <%= link_to({action: 'remove_item', id: @object.uuid, item_uuid: (name_link || object).uuid}, method: :delete, remote: true, data: {confirm: "You are about to remove #{object.class_for_display} #{object.uuid} from this folder.\n\nAre you sure?"}, class: 'btn btn-xs btn-default') do %>
-                    Remove <i class="fa fa-fw fa-ban"></i>
+              <tr data-object-uuid="<%= (name_link && name_link.uuid) || object.uuid %>">
+                <td>
+                  <%= render :partial => "selection_checkbox", :locals => {object: object} %>
+                </td>
+                <td>
+                  <%= render :partial => "show_object_button", :locals => {object: object, size: 'xs'} %>
+                </td>
+                <td>
+                  <%= render_editable_attribute name_link, 'name', nil, {data: {emptytext: "Unnamed #{object.class_for_display}"}} %>
+                </td>
+                <td>
+                  <%= object.content_summary %>
+                </td>
+                <td title="<%= object.modified_at %>">
+                  <span>
+                    <%= raw distance_of_time_in_words(object.modified_at, Time.now).sub('about ','~').sub(' ','&nbsp;') + '&nbsp;ago' rescue object.modified_at %>
+                  </span>
+                </td>
+                <td class="arvados-uuid">
+                  <%= object.uuid %>
+                </td>
+                <td>
+                  <% if @object.editable? %>
+                    <%= link_to({action: 'remove_item', id: @object.uuid, item_uuid: ((name_link && name_link.uuid) || object.uuid)}, method: :delete, remote: true, data: {confirm: "You are about to remove #{object.class_for_display} #{object.uuid} from this folder.\n\nAre you sure?"}, class: 'btn btn-xs btn-default') do %>
+                      Remove <i class="fa fa-fw fa-ban"></i>
+                    <% end %>
                   <% end %>
-                <% end %>
-              </td>
-            </tr>
+                </td>
+              </tr>
             <% end %>
           </tbody>
           <thead>
index d9322e60cd813bafa51af6357cdedc832987f453..da3d4c067533caa313b2f0cd1138e34200a820a2 100644 (file)
@@ -8,7 +8,7 @@ class FoldersTest < ActionDispatch::IntegrationTest
     Capybara.current_driver = Capybara.javascript_driver
     visit page_with_token 'active', '/'
     find('nav a', text: 'Folders').click
-    find('tr', text: 'A Folder').
+    find('.side-nav', text: 'A Folder').
       find('a,button', text: 'Show').
       click
     within('.panel', text: api_fixture('groups')['afolder']['name']) do
@@ -21,4 +21,25 @@ class FoldersTest < ActionDispatch::IntegrationTest
     #find('.panel', text: 'I just edited this.')
   end
 
+  test 'Add a new name, then edit it, without creating a duplicate' do
+    Capybara.current_driver = Capybara.javascript_driver
+    folder_uuid = api_fixture('groups')['afolder']['uuid']
+    specimen_uuid = api_fixture('specimens')['owned_by_afolder_with_no_name_link']['uuid']
+    visit page_with_token 'active', '/folders/' + folder_uuid
+    within('.panel', text: 'Contents') do
+      find('.tr[data-object-uuid="'+specimen_uuid+'"] .editable[data-name="name"]').click
+      find('.editable-input input').set('Now I have a name.')
+      find('.glyphicon-ok').click
+      find('.editable', text: 'Now I have a name.').click
+      find('.editable-input input').set('Now I have a new name.')
+      find('.glyphicon-ok').click
+      find('.editable', text: 'Now I have a new name.')
+    end
+    visit current_path
+    within '.panel', text: 'Contents' do
+      find '.editable', text: 'Now I have a new name.'
+      page.assert_no_selector '.editable', text: 'Now I have a name.'
+    end
+  end
+
 end
index 3465167a653cbd83571def5ed64c086f67415e11..c48bff71470606c31f8aaf4f0f10bd60c6d1f71f 100644 (file)
@@ -33,3 +33,9 @@ in_afolder_linked_from_asubfolder:
   owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
   created_at: 2014-04-21 15:37:48 -0400
   modified_at: 2014-04-21 15:37:48 -0400
+
+owned_by_afolder_with_no_name_link:
+  uuid: zzzzz-j58dm-ypsjlol9dofwijz
+  owner_uuid: zzzzz-j7d0g-v955i6s2oi1cbso
+  created_at: 2014-05-05 04:11:52 -0400
+  modified_at: 2014-05-05 04:11:52 -0400