Skip to content

Commit

Permalink
Ajaxify playlist item edit subforms and fix bugs
Browse files Browse the repository at this point in the history
  • Loading branch information
bkeese committed May 24, 2016
1 parent b0dbb59 commit ab3cd69
Show file tree
Hide file tree
Showing 8 changed files with 149 additions and 71 deletions.
4 changes: 0 additions & 4 deletions app/assets/javascripts/avalon_playlists/playlist_items.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
// This is for the playlists edit page
Blacklight.onLoad(function(){

$('#select_all').change(function() {
$('input:checkbox').prop('checked', $(this).prop('checked'))
})

// Display the drag handle
$('.dd-handle').removeClass('hidden');

Expand Down
15 changes: 8 additions & 7 deletions app/controllers/playlist_items_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,16 +29,17 @@ def create
def update
playlist_item = PlaylistItem.find(params['id'])
annotation = AvalonAnnotation.find(playlist_item.annotation.id)
annotation.title = params[:title]
annotation.comment = params[:comment]
annotation.start_time = time_str_to_milliseconds params[:start_time]
annotation.end_time = time_str_to_milliseconds params[:end_time]
annotation.title = playlist_item_params[:title]
annotation.comment = playlist_item_params[:comment]
annotation.start_time = time_str_to_milliseconds playlist_item_params[:start_time]
annotation.end_time = time_str_to_milliseconds playlist_item_params[:end_time]
if annotation.save
flash[:success] = "Playlist item details saved successfully."
render json: { message: "Item was updated successfully." }, status: 201 and return
else
flash[:error] = "Playlist item details could not be saved: #{annotation.errors.full_messages}"
render json: { message: "Item was not updated: #{annotation.errors.full_messages.join(', ')}" }, status: 500 and return
end
redirect_to edit_playlist_path(@playlist)
rescue StandardError => error
render json: { message: "Item was not updated: #{error.message}" }, status: 500 and return
end

private
Expand Down
1 change: 0 additions & 1 deletion app/controllers/playlists_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,6 @@ def reorder_items(playlist)
:unchanged
end
end.values_at(:changed_playlist, :new, :changed_position, :unchanged).map(&:to_a)

# items that will be in this playlist
unmoved_items = unchanged
# place items whose positions were specified
Expand Down
3 changes: 1 addition & 2 deletions app/helpers/playlists_helper.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
module PlaylistsHelper
def human_friendly_visibility(visibility)
icon = visibility == Playlist::PUBLIC ? 'unlock' : 'lock'
safe_join([content_tag(:span, '', class:"glyphicon glyphicon-#{icon}"),
t("activerecord.attributes.playlist.visibility.#{visibility}")], ' ')
safe_join([content_tag(:span, '', class:"glyphicon glyphicon-#{icon}", title: t("playlist.#{icon}AltText")),t("playlist.#{icon}Text")], ' ')
end
end
187 changes: 134 additions & 53 deletions app/views/playlists/_form.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -15,35 +15,35 @@
<a id="playlist_edit_button" data-toggle="collapse" data-target="#playlist_edit_div, #playlist_view_div" class="fa fa-edit btn btn-sm"></a>
</div>
</div>
<div id="playlist_view_div" class="container collapse in">
<div id="playlist_view_div" class="container collapse <%= 'in' if @playlist.persisted? %>">
<div class="row">
<div class="col-sm-2">Name</div>
<div class="col-sm-10"><%= @playlist.title %></div>
</div>
<div class="row">
<div class="col-sm-2">Comment</div>
<div class="col-sm-2">Description</div>
<div class="col-sm-10"><%= @playlist.comment %></div>
</div>
<div class="row">
<div class="col-sm-2"><%= t("blacklight/folders/folder.visibility", scope: "helpers.label") %></div>
<div class="col-sm-10">
<% if @playlist.visibility==Playlist::PUBLIC %>
<%= human_friendly_visibility Playlist::PUBLIC %>
<%= human_friendly_visibility Playlist::PUBLIC %>
<% else %>
<%= human_friendly_visibility Playlist::PRIVATE %>
<%= human_friendly_visibility Playlist::PRIVATE %>
<% end %>
</div>
</div>
</div>

<div id="playlist_edit_div" class="container collapse">
<div id="playlist_edit_div" class="container collapse <%= 'in' if !@playlist.persisted? %>">
<%= form_for(@playlist, html: { id: 'playlist_form', class: 'form-horizontal playlist_actions' }) do |f| %>
<div class="row form-group <% if @playlist.errors[:title].any? %>has-error<% end %>">
<%= f.label "Name", class: 'col-sm-2 control-label' %>
<div class="col-sm-10"><%= f.text_field :title, class: 'form-control' %></div>
</div>
<div class="row form-group">
<%= f.label :comment, class: 'col-sm-2 control-label' %>
<%= f.label :comment, 'Description', class: 'col-sm-2 control-label' %>
<div class="col-sm-10"><%= f.text_area :comment, class: 'form-control', rows: '4' %></div>
</div>
<div class="row form-group">
Expand All @@ -67,7 +67,7 @@
</div>
</div>
</div>
<% end %>
<% end # form_for playlist_form%>
</div>
<hr>
<% if @playlist.items.empty? %>
Expand All @@ -80,102 +80,116 @@
</div>
<div class="col-sm-3 text-right">
<div class="form-group select-delete" style="margin-top:20px">
<% if @playlists.present? %>
<% if @playlists.present? %>
<div class="btn-group bootstrap-select" id="move_to">
<%= hidden_field_tag "new_playlist_id", @playlist.id, form:"edit_playlist_#{@playlist.id}" %>
<%= button_tag( { type: 'button', class: "btn btn-default btn-xs dropdown-toggle", data: { toggle: "dropdown"}, disabled:'disabled' } ) do %>
<%= hidden_field_tag "new_playlist_id", @playlist.id, form:"edit_playlist_#{@playlist.id}" %>
<span class="filter-option pull-left">Move to... </span><span class="fa fa-caret-down"></span>
<% end %>
<% end # button_tag %>
<ul class="dropdown-menu">
<% @playlists.each do |p| %>
<li><a data-id="<%= p.id %>" style="text-decoration:underline" class="move_to_playlist" form="edit_playlist_<%= @playlist.id %>"><%= p.title %></a></li>
<% end %>
<% @playlists.each do |p| %>
<li><a data-id="<%= p.id %>" style="text-decoration:underline" class="move_to_playlist" form="edit_playlist_<%= @playlist.id %>"><%= p.title %></a></li>
<% end # @playlist.each%>
</ul>
</div>
<% end %>
<% end # playlists.prsent?%>
<%= f.submit "Delete Selected", class: 'btn btn-danger btn-confirmation btn-xs', form:"edit_playlist_#{@playlist.id}", data: { placement: 'bottom' }, disabled:'disabled' %>
</div>
</div>
</div>
<div class="col-sm-1 text-right">
<div class="form-group" style="margin-top:20px">
<%= check_box_tag 'select_all', "Select All", false, form:"edit_playlist_#{@playlist.id}" %>
Select All
</div>
</div>
</div>
</div>
<% end %>
<% end #form_for update_multiple %>
<%= form_for(@playlist, html: { id: 'playlist_sort_form', class: 'form-horizontal playlist_actions' }) do |fs| %>
<div class="dd" data-playlist_id="<%= @playlist.id %>">
<ol id="items" class="dd-list" style="list-style: none">
<% @playlist.items.each_with_index do |i, index| %>
<li class="container dd-item" data-id="<%= i.id %>">
<div class="row">
<div class="col-sm-2">
<span class="glyphicon glyphicon-resize-vertical hidden pull-left dd-handle"></span>
<%= text_field_tag "playlist_items_attributes_#{index}_position", i.position, class: 'form-control position-input', form: 'playlist_form' %>
<%= hidden_field_tag "playlist_items_attributes_#{index}_id", i.id, form: 'playlist_form' %>
</div>
<div class="col-sm-8 title" style="margin-top:5px">
<%= link_to i.annotation.title, i.annotation.mediafragment_uri %>
</div>
<div class="col-sm-1 text-right" style="margin-top:5px">
<a id="playlist_item_edit_button" data-toggle="collapse" href="#playlist_item_edit_<%= i.id %>" aria-expanded="false" aria-controls="collapseExample" class="fa fa-edit btn btn-sm"></a>
</div>
<div class="col-sm-1 checkbox text-right">
<label>
<%= check_box_tag 'annotation_ids[]', i.id, false, form:"edit_playlist_#{@playlist.id}", class:"playlist_item_select" %>
Select
</label>
</div>
<li class="container dd-item" data-id="<%= i.id %>">
<div class="row">
<div class="col-sm-2">
<span class="glyphicon glyphicon-resize-vertical hidden pull-left dd-handle"></span>
<%= text_field_tag "playlist[items_attributes[#{index}[position]]]", i.position, class: 'form-control position-input', form: 'playlist_sort_form' %>
<%= hidden_field_tag "playlist[items_attributes[#{index}[id]]]", i.id, form: 'playlist_sort_form' %>
</div>
<div class="col-sm-8 title" style="margin-top:5px">
<%= link_to i.annotation.title, i.annotation.mediafragment_uri %>
</div>
<div class="col-sm-1 text-right" style="margin-top:5px">
<a id="playlist_item_edit_button" data-toggle="collapse" href="#playlist_item_edit_<%= i.id %>" aria-expanded="false" aria-controls="collapseExample" class="fa fa-edit btn btn-sm"></a>
</div>
<div class="col-sm-1 checkbox text-right">
<label>
<%= check_box_tag 'annotation_ids[]', i.id, false, form:"edit_playlist_#{@playlist.id}", class:"playlist_item_select" %>
Select
</label>
</div>
<div id="playlist_item_edit_<%= i.id %>" class="row collapse">
<div class="col-sm-9 col-sm-offset-2 playlist_item_edit">
<%= form_for(i, html: { id: "playlist_item_form_#{i.id}" }) do |pif| %>
</div>
<div id="playlist_item_edit_<%= i.id %>" class="row collapse">
<div class="col-sm-9 col-sm-offset-2 playlist_item_edit">
<%= bootstrap_form_for i, remote: true, html: { id: "playlist_item_form_#{i.id}", class: "playlist_item_edit_form" }, format: 'json' do |pif| %>
<%= hidden_field_tag "playlist_id", @playlist.id, form: "playlist_item_form_#{i.id}" %>
<div class="row form-group">
<div class="row form-group">
<%= pif.label :title, class: 'col-sm-2 control-label' %>
<div class="col-sm-10">
<div class="col-sm-10">
<%= text_field_tag :title, i.title, class: 'form-control', id: "avalon_annotation_title_#{i.id}", form:"playlist_item_form_#{i.id}" %>
<span style='display:none' class='title_original' data-value="<%= i.title %>"></span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-2" style="text-align:right;margin-top:2pt"><%= pif.label :start_time, class: 'control-label' %></div>
<div class="col-sm-4">
<%= text_field_tag :start_time, pretty_time(i.start_time), class: 'form-control', id: "avalon_annotation_start_time_#{i.id}", form:"playlist_item_form_#{i.id}" %>
<span style='display:none' class='start_time_original' data-value="<%= pretty_time(i.start_time) %>"></span>
</div>
<div class="col-sm-2" style="text-align:right;margin-top:2pt">
<%= pif.label :end_time, class: 'control-label' %>
</div>
<div class="col-sm-4">
<%= text_field_tag :end_time, pretty_time(i.end_time), class: 'form-control', id: "avalon_annotation_end_time_#{i.id}", form:"playlist_item_form_#{i.id}" %>
<span style='display:none' class='end_time_original' data-value="<%= pretty_time(i.end_time) %>"></span>
</div>
</div>
<div class="row form-group">
<%= pif.label :comment, class: 'col-sm-2 control-label' %>
<div class="col-sm-10">
<%= text_area_tag :comment, i.comment, class: 'form-control', id: "avalon_annotation_comment_#{i.id}", form:"playlist_item_form_#{i.id}" %>
<span style='display:none' class='comment_original' data-value="<%= i.comment %>"></span>
</div>
</div>
<div class="row form-group">
<div class="col-sm-3 col-sm-offset-2">
<button class='btn btn-xs btn-primary playlist_item_form_submit' data-loading-text="Saving..." data-id="<%= i.id %>" form="playlist_item_form_<%= i.id %>">Save Item</button>
<span id="playlist_item_edit_cancel_<%= i.id %>" class="btn btn-default btn-xs playlist_item_edit_cancel" data-id="<%= i.id %>" data-target="playlist_item_edit_<%= i.id %>">Cancel</span>
</div>
<div class="col-sm-7" id="playlist_item_edit_alert_<%= i.id %>"></div>
</div>
</div>
<div class="row form-group">
<div class="col-sm-2"></div>
<div class="col-sm-10"><%= pif.submit "Save Item", class: 'btn btn-sm btn-primary', form:"playlist_item_form_#{i.id}" %></div>
</div>
<% end %>
<% end #bootstrap_form_for playlist_item_edit %>
</div>
</div>
</div>
</li>
<% end %>
</li>
<% end #playlist.items.each %>
</ol>
</div>
<% end %>
<%= fs.submit class: 'btn btn-primary btn-xs', value: 'Save Changes', form: 'playlist_sort_form', style: 'visibility:hidden' %>
<% end #form_for playlist_sort_form %>
<% end #playlist empty else%>
</div>
<% content_for :page_scripts do %>
<script>
// Handle move selected items to new playlist
$('.move_to_playlist').click(function(event) {
$('#new_playlist_id').val($(this).data('id'));
$('#'+$(this).attr('form')).submit();
});
// Handle playlist edit cancel
$('#playlist_edit_cancel').click(function(event) {
$('#playlist_form #playlist_title').val('<%= @playlist.title %>');
$('#playlist_form #playlist_comment').val('<%= @playlist.comment %>');
Expand All @@ -185,9 +199,76 @@
$('#playlist_form #playlist_visibility_public').prop('checked', true);
<% end %>
});
$('.playlist_item_select, #select_all').click(function(event) {
// Handle select all checkbox (enable/disable move and delete buttons)
$('#select_all').click(function(event) {
$('input:checkbox').prop('checked', $(this).prop('checked'))
$('#edit_playlist_<%= @playlist.id %> :submit').prop('disabled', !$(this).prop('checked') );
$('#move_to button').prop('disabled', !$(this).prop('checked') )
});
// Handle selection of playlist item (enable/disable move and delete buttons)
$('.playlist_item_select').click(function(event) {
$('#edit_playlist_<%= @playlist.id %> :submit').prop('disabled', $( ".playlist_item_select:checked" ).length < 1 );
$('#move_to button').prop('disabled', $( ".playlist_item_select:checked" ).length < 1 )
$('#move_to button').prop('disabled', $( ".playlist_item_select:checked" ).length < 1 );
$('#select_all').prop('checked', $( ".playlist_item_select:checked" ).length > 0 );
});
// Handle playlist item edit form submission
$('.playlist_item_form_submit').click(function(event) {
event.preventDefault();
var $btn = $(this).button('loading');
var $form = $(this).closest('form');
var id = $(this).data('id');
return $.ajax({
url: '/playlist_items/' + id + '.json',
type: 'PATCH',
data: {
playlist_id: $('#playlist_id').val(),
playlist_item: {
title: $('#avalon_annotation_title_'+id).val(),
comment: $('#avalon_annotation_comment_'+id).val(),
start_time: $('#avalon_annotation_start_time_'+id).val(),
end_time: $('#avalon_annotation_end_time_'+id).val(),
}
},
success: function(response) {
// alert success
var alert = "<div class='alert alert-success' style='padding:0 10px; margin-bottom: 0;'>";
alert += "<button type='button' class='close' data-dismiss='alert'>&times;</button>";
alert += "<span>"+response.message+"</span></div>";
$('#playlist_item_edit_alert_'+id).html(alert)
// update original values with those newly saved in case of future cancel
$form.find('.title_original').data('value',$('#avalon_annotation_title_'+id).val());
$form.find('.comment_original').data('value',$('#avalon_annotation_comment_'+id).val());
$form.find('.start_time_original').data('value',$('#avalon_annotation_start_time_'+id).val());
$form.find('.end_time_original').data('value',$('#avalon_annotation_end_time_'+id).val());
$btn.button('reset')
},
error: function(response) {
// alert failure
var alert = "<div class='alert alert-danger' style='padding:0 10px; margin-bottom: 0;'>";
alert += "<button type='button' class='close' data-dismiss='alert'>&times;</button>";
alert += "<span>"+response.responseJSON.message+"</span></div>";
$('#playlist_item_edit_alert_'+id).html(alert)
$btn.button('reset')
}
});
});
// Cancel playlist item editing
$('.playlist_item_edit_cancel').click(function(event){
var $form = $(this).closest('form');
var id = $(this).data('id');
// reset form to original values
$('#avalon_annotation_title_'+id).val($form.find('.title_original').data('value'));
$('#avalon_annotation_comment_'+id).val($form.find('.comment_original').data('value'));
$('#avalon_annotation_start_time_'+id).val($form.find('.start_time_original').data('value'));
$('#avalon_annotation_end_time_'+id).val($form.find('.end_time_original').data('value'));
$('#playlist_item_edit_alert_'+id).html('');
$('#'+$(this).data('target')).removeClass('in');
});
</script>
<% end %>
4 changes: 3 additions & 1 deletion config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -186,4 +186,6 @@ en:
playlist:
ago: "%{time} ago"
lockAltText: "This playlist is private."
unlockAltText: "This playlist is public."
unlockAltText: "This playlist is public."
lockText: "Private"
unlockText: "Public"
2 changes: 1 addition & 1 deletion config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@

resources :comments, only: [:index, :create]

resources :playlist_items, only: [:update]
resources :playlist_items, only: [:update], :constraints => {:format => /(js|json)/}

#match 'search/index' => 'search#index'
#match 'search/facet/:id' => 'search#facet'
Expand Down
4 changes: 2 additions & 2 deletions spec/controllers/playlist_items_controller_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -76,14 +76,14 @@
context 'with valid params' do
it 'updates Playlist Item' do
expect do
patch :update, { playlist_id: playlist.id, id: playlist_item.id, title: Faker::Lorem.word, start_time:'00:20', end_time:'1:20' }, valid_session
patch :update, { playlist_id: playlist.id, id: playlist_item.id, playlist_item: { title: Faker::Lorem.word, start_time:'00:20', end_time:'1:20' }}, valid_session
end.to change{ playlist_item.reload.title }
end
end
context 'with invalid params' do
it 'fails to update Playlist Item' do
expect do
patch :update, { playlist_id: playlist.id, id: playlist_item.id, title: Faker::Lorem.word, start_time:'00:20', end_time:'not-a-time' }, valid_session
patch :update, { playlist_id: playlist.id, id: playlist_item.id, playlist_item: { title: Faker::Lorem.word, start_time:'00:20', end_time:'not-a-time' }}, valid_session
end.not_to change{ playlist_item.reload.title }
end
end
Expand Down

0 comments on commit ab3cd69

Please sign in to comment.