diff --git a/lib/assets/javascripts/best_in_place.js b/lib/assets/javascripts/best_in_place.js index db3f8bde..834cf6c5 100644 --- a/lib/assets/javascripts/best_in_place.js +++ b/lib/assets/javascripts/best_in_place.js @@ -120,7 +120,14 @@ BestInPlaceEditor.prototype = { this.previousCollectionValue = value; // search for the text for the span - $.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); }); + if (this.selectMultiple) { + var selectedValues = this.values.map( function(v) { if (value.indexOf(v[0]) > -1) { return v[1] } }) + .filter( function(v) { return v != undefined } ) + editor.element.html(selectedValues.join(',')); + } + else { + $.each(this.values, function(index, arr){ if (String(arr[0]) === String(value)) editor.element.html(arr[1]); }); + } break; case "checkbox": @@ -189,6 +196,7 @@ BestInPlaceEditor.prototype = { self.cancelButton = self.element.data("bipCancelButton") || self.cancelButton; self.cancelButtonClass = self.element.data("bipCancelButtonClass") || self.cancelButtonClass || BestInPlaceEditor.defaults.cancelButtonClass; self.skipBlur = self.element.data("bipSkipBlur") || self.skipBlur || BestInPlaceEditor.defaults.skipBlur; + self.selectMultiple = self.element.data("bipSelectMultiple"); // Fix for default values of 0 if (self.element.data("bipPlaceholder") == null) { @@ -254,7 +262,17 @@ BestInPlaceEditor.prototype = { csrf_param = jQuery('meta[name=csrf-param]').attr('content'); var data = "_method=" + BestInPlaceEditor.defaults.ajaxMethod; - data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(this.getValue()); + + var value = this.getValue() + + if (jQuery.isArray(value)) { + for (var i in value) { + data += "&" + this.objectName + '[' + this.attributeName + '][]=' + encodeURIComponent(value[i]); + } + } + else { + data += "&" + this.objectName + '[' + this.attributeName + ']=' + encodeURIComponent(value); + } if (csrf_param !== undefined && csrf_token !== undefined) { data += "&" + csrf_param + "=" + encodeURIComponent(csrf_token); @@ -458,10 +476,12 @@ BestInPlaceEditor.forms = { .attr('style', 'display:inline'), selected = '', select_elt = jQuery(document.createElement('select')) - .attr('class', this.inner_class !== null ? this.inner_class : ''), + .attr('class', this.inner_class !== null ? this.inner_class : '') + .attr('multiple', this.selectMultiple), currentCollectionValue = this.collectionValue, key, value, a = this.values; + var _self = this; $.each(a, function(index, arr){ key = arr[0]; @@ -469,16 +489,35 @@ BestInPlaceEditor.forms = { var option_elt = jQuery(document.createElement('option')) .val(key) .html(value); - if (String(key) === String(currentCollectionValue)) option_elt.attr('selected', 'selected'); + + if (_self.selectMultiple) { + if (currentCollectionValue && currentCollectionValue.indexOf(String(key)) > -1) option_elt.attr('selected', 'selected'); + } + else { + if (String(key) === String(currentCollectionValue)) option_elt.attr('selected', 'selected'); + } + select_elt.append(option_elt); }); output.append(select_elt); + if (this.selectMultiple) { + if (!this.okButton) this.okButton = 'submit' + this.placeButtons(output, this); + } + this.element.html(output); + + if (this.selectMultiple) { + this.element.find("form").bind('submit', {editor: this}, BestInPlaceEditor.forms.input.submitHandler) + } + else { + this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler); + this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler); + this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler); + } + this.setHtmlAttributes(); - this.element.find("select").bind('change', {editor: this}, BestInPlaceEditor.forms.select.blurHandler); - this.element.find("select").bind('blur', {editor: this}, BestInPlaceEditor.forms.select.blurHandler); - this.element.find("select").bind('keyup', {editor: this}, BestInPlaceEditor.forms.select.keyupHandler); this.element.find("select")[0].focus(); // automatically click on the select so you @@ -495,7 +534,16 @@ BestInPlaceEditor.forms = { getValue: function () { 'use strict'; - return this.sanitizeValue(this.element.find("select").val()); + var val = this.element.find("select").val(); + if (this.selectMultiple) { + var _self = this; + return val.map( function(v) { + return _self.sanitizeValue(v); + }) + } + else { + return this.sanitizeValue(val); + } }, blurHandler: function (event) { diff --git a/lib/best_in_place/helper.rb b/lib/best_in_place/helper.rb index 74d5e6e7..94027d12 100644 --- a/lib/best_in_place/helper.rb +++ b/lib/best_in_place/helper.rb @@ -19,10 +19,14 @@ def best_in_place(object, field, opts = {}) if opts[:collection] or type == :checkbox collection = opts[:collection] - value = value.to_s collection = best_in_place_default_collection if collection.blank? collection = best_in_place_collection_builder(type, collection) - display_value = collection.flat_map{|a| a[0].to_s == value ? a[1] : nil }.compact[0] + if value.is_a? Array + display_value = collection.flat_map{|a| value.include?(a[0].to_s) ? a[1] : nil }.compact.join(',') + else + value = value.to_s + display_value = collection.flat_map{|a| a[0].to_s == value ? a[1] : nil }.compact[0] + end collection = collection.to_json options[:data]['bip-collection'] = html_escape(collection) end @@ -53,6 +57,8 @@ def best_in_place(object, field, opts = {}) options[:data]['bip-confirm'] = opts[:confirm].presence options[:data]['bip-value'] = html_escape(value).presence + options[:data]['bip-select-multiple'] = opts[:multiple].presence + if opts[:raw] options[:data]['bip-raw'] = 'true' end @@ -82,7 +88,7 @@ def pass_through_html_options(opts, options) :activator, :cancel_button, :cancel_button_class, :html_attrs, :inner_class, :nil, :object_name, :ok_button, :ok_button_class, :display_as, :display_with, :path, :value, :use_confirm, :confirm, :sanitize, :raw, :helper_options, :url, :place_holder, :class, - :as, :param, :container] + :as, :param, :container, :multiple] uknown_keys = opts.keys - known_keys uknown_keys.each { |key| options[key] = opts[key] } end diff --git a/lib/best_in_place/test_helpers.rb b/lib/best_in_place/test_helpers.rb index 68f47b3b..ae6b5486 100644 --- a/lib/best_in_place/test_helpers.rb +++ b/lib/best_in_place/test_helpers.rb @@ -35,6 +35,19 @@ def bip_select(model, attr, name) wait_for_ajax end + def bip_multi_select(model, attr, opts, unselect=[]) + id = BestInPlace::Utils.build_best_in_place_id model, attr + find("##{id}").trigger('click') + opts.each do |opt| + find("##{id}").select(opt) + end + unselect.each do |opt| + find("##{id}").unselect(opt) + end + find("##{id} input[type=submit]").trigger('click') + wait_for_ajax + end + def wait_for_ajax return unless respond_to?(:evaluate_script) wait_until { finished_all_ajax_requests? } diff --git a/spec/integration/js_spec.rb b/spec/integration/js_spec.rb index 5bbbbd4d..be658e75 100644 --- a/spec/integration/js_spec.rb +++ b/spec/integration/js_spec.rb @@ -213,6 +213,33 @@ expect(find('#country')).to have_content('France') end + it "should be able to use bip_multi_select to render and update a multi-select field" do + @user.favorite_snacks = %w(whoppers sun-chips) + @user.save! + visit user_path(@user) + + expect(find('#favorite_snacks')).to have_content('Whoppers,Sun Chips') + + bip_multi_select @user, :favorite_snacks, ['Twizzlers','Kettle Corn'], ['Sun Chips'] + + expect(find('#favorite_snacks')).to have_content('Twizzlers,Whoppers,Kettle Corn') + expect(find('#favorite_snacks')).not_to have_content('Sun Chips') + visit user_path(@user) + + expect(find('#favorite_snacks')).to have_content('Twizzlers,Whoppers,Kettle Corn') + end + + it "does not error when value is nil" do + @user.save! + visit user_path(@user) + expect(find('#favorite_snacks')).to have_content('-') + + bip_multi_select @user, :favorite_snacks, ['Sun Chips'] + expect(find('#favorite_snacks')).to have_content('Sun Chips') + visit user_path(@user) + expect(find('#favorite_snacks')).to have_content('Sun Chips') + end + it "should apply the inner_class option to a select field" do @user.save! visit user_path(@user) diff --git a/spec/internal/app/assets/stylesheets/application.css b/spec/internal/app/assets/stylesheets/application.css new file mode 100644 index 00000000..31dd104d --- /dev/null +++ b/spec/internal/app/assets/stylesheets/application.css @@ -0,0 +1,5 @@ +/* +*= require scaffold.css +*= require style.css +*= require jquery-ui-1.8.16.custom.css +*/ diff --git a/spec/internal/app/helpers/users_helper.rb b/spec/internal/app/helpers/users_helper.rb index 007e8808..aa5557a4 100644 --- a/spec/internal/app/helpers/users_helper.rb +++ b/spec/internal/app/helpers/users_helper.rb @@ -26,4 +26,8 @@ def height_collection def bb(value) "#{value} €" end + + def snacks_options + Hash[ ["Twizzlers", "Whoppers", "Krispy Kremes", "Sun Chips", "Kit Kat", "Kettle Corn"].map { |o| [o.downcase.gsub(/\s+/, '-'), o] } ] + end end diff --git a/spec/internal/app/models/user.rb b/spec/internal/app/models/user.rb index 86365fd6..c435bf8e 100644 --- a/spec/internal/app/models/user.rb +++ b/spec/internal/app/models/user.rb @@ -21,6 +21,8 @@ class User < ActiveRecord::Base alias_attribute :receive_email_image, :receive_email alias_attribute :description_simple, :description + serialize :favorite_snacks + def address_format "addr => [#{address}]".html_safe end @@ -32,4 +34,5 @@ def markdown_desc def zip_format nil end + end diff --git a/spec/internal/app/views/layouts/application.html.erb b/spec/internal/app/views/layouts/application.html.erb index 87b58033..984fd21b 100644 --- a/spec/internal/app/views/layouts/application.html.erb +++ b/spec/internal/app/views/layouts/application.html.erb @@ -2,7 +2,7 @@ Best In Place TEST APP <%= Rails::VERSION::STRING %> - <%= stylesheet_link_tag "scaffold", "style", "jquery-ui-1.8.16.custom" %> + <%= stylesheet_link_tag "application" %> <%= javascript_include_tag "application" %> <%= csrf_meta_tag %> diff --git a/spec/internal/app/views/users/show.html.erb b/spec/internal/app/views/users/show.html.erb index b10f3f8e..781dccaf 100644 --- a/spec/internal/app/views/users/show.html.erb +++ b/spec/internal/app/views/users/show.html.erb @@ -137,6 +137,12 @@ <%= best_in_place @user, :favorite_movie, opts %> + + Favorite snacks + + <%= best_in_place @user, :favorite_snacks, as: :select, multiple: true, collection: snacks_options %> + + Zero Field diff --git a/spec/internal/db/schema.rb b/spec/internal/db/schema.rb index 8b5deae4..b885679c 100644 --- a/spec/internal/db/schema.rb +++ b/spec/internal/db/schema.rb @@ -23,5 +23,6 @@ t.string "favorite_movie" t.string "favorite_locale" t.integer "zero_field" + t.string "favorite_snacks" end end diff --git a/spec/rails_helper.rb b/spec/rails_helper.rb index 3f20dd08..a6eaad5f 100644 --- a/spec/rails_helper.rb +++ b/spec/rails_helper.rb @@ -12,8 +12,12 @@ require 'best_in_place' -Combustion.initialize! :active_record, :action_controller, - :action_view, :sprockets +Combustion.initialize! :active_record, :action_controller, :action_view, :sprockets do + config.assets.compile = true + config.assets.compress = false + config.assets.debug = false + config.assets.digest = false +end require 'rspec/rails' require 'capybara/rails'