From 1538d080718aadabcb09166aaf51c6934deac9d0 Mon Sep 17 00:00:00 2001 From: Greg Bell Date: Thu, 3 May 2012 09:37:16 -0700 Subject: [PATCH] Major refactoring of the batch actions UI code. More to come. --- Guardfile | 8 - .../javascripts/active_admin/application.js | 292 ------------------ app/assets/javascripts/active_admin/base.js | 5 +- .../active_admin/coffeescripts/application.js | 4 - .../pages/batch_actions.js.coffee | 45 --- .../jquery.aa.checkbox-toggler.js.coffee | 0 .../jquery.aa.dropdown-menu.js.coffee | 107 +++++++ .../components/jquery.aa.popover.js.coffee | 29 +- ...jquery.aa.table-checkbox-toggler.js.coffee | 2 - .../lib/namespace.js.coffee | 0 .../pages/application.js.coffee | 3 +- .../pages/batch_actions.js.coffee | 26 ++ .../stylesheets/active_admin/_base.css.scss | 1 + .../active_admin/components/_buttons.scss | 3 +- .../components/_dropdown_menu.scss | 151 +++++++++ .../components/_table_tools.css.scss | 34 +- features/index/batch_actions.feature | 4 +- .../step_definitions/batch_action_steps.rb | 16 +- lib/active_admin/batch_actions.rb | 3 +- .../batch_actions/views/batch_action_form.rb | 21 +- .../views/batch_action_selector.rb | 63 ++++ .../views/components/dropdown_menu.rb | 73 +++++ lib/active_admin/views/components/popover.rb | 31 +- lib/active_admin/views/pages/base.rb | 5 - lib/active_admin/views/pages/index.rb | 31 +- .../jquery.aa.checkbox-toggler-spec.js.coffee | 2 +- 26 files changed, 497 insertions(+), 462 deletions(-) delete mode 100644 app/assets/javascripts/active_admin/application.js delete mode 100644 app/assets/javascripts/active_admin/coffeescripts/application.js delete mode 100644 app/assets/javascripts/active_admin/coffeescripts/pages/batch_actions.js.coffee rename app/assets/javascripts/active_admin/{coffeescripts => }/components/jquery.aa.checkbox-toggler.js.coffee (100%) create mode 100644 app/assets/javascripts/active_admin/components/jquery.aa.dropdown-menu.js.coffee rename app/assets/javascripts/active_admin/{coffeescripts => }/components/jquery.aa.popover.js.coffee (90%) rename app/assets/javascripts/active_admin/{coffeescripts => }/components/jquery.aa.table-checkbox-toggler.js.coffee (99%) rename app/assets/javascripts/active_admin/{coffeescripts => }/lib/namespace.js.coffee (100%) rename app/assets/javascripts/active_admin/{coffeescripts => }/pages/application.js.coffee (76%) create mode 100644 app/assets/javascripts/active_admin/pages/batch_actions.js.coffee create mode 100644 app/assets/stylesheets/active_admin/components/_dropdown_menu.scss create mode 100644 lib/active_admin/batch_actions/views/batch_action_selector.rb create mode 100644 lib/active_admin/views/components/dropdown_menu.rb diff --git a/Guardfile b/Guardfile index 67ec7a0cbe8..882515b251f 100644 --- a/Guardfile +++ b/Guardfile @@ -5,11 +5,3 @@ guard 'rspec', :all_on_start => false, :version => 2 do watch(%r{^lib/active_admin/(.+)\.rb$}) { |m| "spec/unit/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec/" } end - -guard 'coffeescript', :output => 'spec/javascripts/compiled', :all_on_start => true do - watch(%r{spec/javascripts/coffeescripts/(.*)\.coffee}) -end - -guard 'sprockets', :destination => "app/assets/javascripts/active_admin/", :asset_paths => ['app/assets/javascripts/active_admin/coffeescripts/'] do - watch(%r{^app/assets/javascripts/active_admin/coffeescripts/.+$}) -end diff --git a/app/assets/javascripts/active_admin/application.js b/app/assets/javascripts/active_admin/application.js deleted file mode 100644 index 7e882f7c5f7..00000000000 --- a/app/assets/javascripts/active_admin/application.js +++ /dev/null @@ -1,292 +0,0 @@ -(function() { - - window.AA = {}; - -}).call(this); -(function() { - - window.AA.CheckboxToggler = AA.CheckboxToggler = (function() { - - function CheckboxToggler(options, container) { - var defaults; - this.options = options; - this.container = container; - defaults = {}; - this.options = $.extend({}, defaults, options); - this._init(); - this._bind(); - } - - CheckboxToggler.prototype._init = function() { - if (!this.container) { - throw new Error("Container element not found"); - } else { - this.$container = $(this.container); - } - if (!this.$container.find(".toggle_all").length) { - throw new Error("'toggle all' checkbox not found"); - } else { - this.toggle_all_checkbox = this.$container.find(".toggle_all"); - } - return this.checkboxes = this.$container.find(":checkbox").not(this.toggle_all_checkbox); - }; - - CheckboxToggler.prototype._bind = function() { - var _this = this; - this.checkboxes.bind("change", function(e) { - return _this._didChangeCheckbox(e.target); - }); - return this.toggle_all_checkbox.bind("change", function(e) { - return _this._didChangeToggleAllCheckbox(); - }); - }; - - CheckboxToggler.prototype._didChangeCheckbox = function(checkbox) { - if (this.checkboxes.filter(":checked").length === this.checkboxes.length - 1) { - return this._uncheckToggleAllCheckbox(); - } else if (this.checkboxes.filter(":checked").length === this.checkboxes.length) { - return this._checkToggleAllCheckbox(); - } - }; - - CheckboxToggler.prototype._didChangeToggleAllCheckbox = function() { - if (this.toggle_all_checkbox.attr("checked") === "checked") { - return this._checkAllCheckboxes(); - } else { - return this._uncheckAllCheckboxes(); - } - }; - - CheckboxToggler.prototype._uncheckToggleAllCheckbox = function() { - return this.toggle_all_checkbox.removeAttr("checked"); - }; - - CheckboxToggler.prototype._checkToggleAllCheckbox = function() { - return this.toggle_all_checkbox.attr("checked", "checked"); - }; - - CheckboxToggler.prototype._uncheckAllCheckboxes = function() { - var _this = this; - return this.checkboxes.each(function(index, el) { - $(el).removeAttr("checked"); - return _this._didChangeCheckbox(el); - }); - }; - - CheckboxToggler.prototype._checkAllCheckboxes = function() { - var _this = this; - return this.checkboxes.each(function(index, el) { - $(el).attr("checked", "checked"); - return _this._didChangeCheckbox(el); - }); - }; - - return CheckboxToggler; - - })(); - - (function($) { - return $.widget.bridge('checkboxToggler', AA.CheckboxToggler); - })(jQuery); - -}).call(this); -(function() { - - window.AA.Popover = AA.Popover = (function() { - - function Popover(options, element) { - var defaults; - this.options = options; - this.element = element; - this.$element = $(this.element); - defaults = { - fadeInDuration: 20, - fadeOutDuration: 100, - autoOpen: true, - pageWrapperElement: "#wrapper", - onClickActionItemCallback: null - }; - this.options = $.extend({}, defaults, options); - this.$popover = null; - this.isOpen = false; - if ($(this.$element.attr("href")).length > 0) { - this.$popover = $(this.$element.attr("href")); - } - this._buildPopover(); - this._bind(); - return this; - } - - Popover.prototype.open = function() { - this.isOpen = true; - this.$popover.fadeIn(this.options.fadeInDuration); - this._positionPopover(); - this._positionNipple(); - return this; - }; - - Popover.prototype.close = function() { - this.isOpen = false; - this.$popover.fadeOut(this.options.fadeOutDuration); - return this; - }; - - Popover.prototype.destroy = function() { - this.$element.removeData('popover'); - this.$element.unbind(); - this.$element = null; - return this; - }; - - Popover.prototype.option = function() {}; - - Popover.prototype._buildPopover = function() { - this.$popover.prepend("
"); - this.$popover.hide(); - return this.$popover.addClass("popover"); - }; - - Popover.prototype._bind = function() { - var _this = this; - $(this.options.pageWrapperElement).bind('click', function(e) { - if (_this.isOpen === true) return _this.close(); - }); - if (this.options.autoOpen === true) { - return this.$element.bind('click', function() { - _this.open(); - return false; - }); - } - }; - - Popover.prototype._positionPopover = function() { - var centerOfButtonFromLeft, centerOfPopoverFromLeft, popoverLeftPos; - centerOfButtonFromLeft = this.$element.offset().left + this.$element.outerWidth() / 2; - centerOfPopoverFromLeft = this.$popover.outerWidth() / 2; - popoverLeftPos = centerOfButtonFromLeft - centerOfPopoverFromLeft; - return this.$popover.css("left", popoverLeftPos); - }; - - Popover.prototype._positionNipple = function() { - var $nipple, bottomOfButtonFromTop, centerOfPopoverFromLeft, centerOfnippleFromLeft, nippleLeftPos; - centerOfPopoverFromLeft = this.$popover.outerWidth() / 2; - bottomOfButtonFromTop = this.$element.offset().top + this.$element.outerHeight() + 10; - this.$popover.css("top", bottomOfButtonFromTop); - $nipple = this.$popover.find(".popover_nipple"); - centerOfnippleFromLeft = $nipple.outerWidth() / 2; - nippleLeftPos = centerOfPopoverFromLeft - centerOfnippleFromLeft; - return $nipple.css("left", nippleLeftPos); - }; - - return Popover; - - })(); - - (function($) { - return $.widget.bridge('popover', AA.Popover); - })(jQuery); - -}).call(this); -(function() { - var __hasProp = Object.prototype.hasOwnProperty, - __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; }; - - window.AA.TableCheckboxToggler = AA.TableCheckboxToggler = (function(_super) { - - __extends(TableCheckboxToggler, _super); - - function TableCheckboxToggler() { - TableCheckboxToggler.__super__.constructor.apply(this, arguments); - } - - TableCheckboxToggler.prototype._init = function() { - return TableCheckboxToggler.__super__._init.apply(this, arguments); - }; - - TableCheckboxToggler.prototype._bind = function() { - var _this = this; - TableCheckboxToggler.__super__._bind.apply(this, arguments); - return this.$container.find("tbody").find("td").bind("click", function(e) { - if (e.target.type !== 'checkbox') return _this._didClickCell(e.target); - }); - }; - - TableCheckboxToggler.prototype._didChangeCheckbox = function(checkbox) { - var $row; - TableCheckboxToggler.__super__._didChangeCheckbox.apply(this, arguments); - $row = $(checkbox).parents("tr"); - if (checkbox.checked) { - return $row.addClass("selected"); - } else { - return $row.removeClass("selected"); - } - }; - - TableCheckboxToggler.prototype._didClickCell = function(cell) { - return $(cell).parent("tr").find(':checkbox').click(); - }; - - return TableCheckboxToggler; - - })(AA.CheckboxToggler); - - (function($) { - return $.widget.bridge('tableCheckboxToggler', AA.TableCheckboxToggler); - })(jQuery); - -}).call(this); -(function() { - - $(function() { - $(".datepicker").datepicker({ - dateFormat: "yy-mm-dd" - }); - return $(".clear_filters_btn").click(function() { - window.location.search = ""; - return false; - }); - }); - -}).call(this); -(function() { - - jQuery(function($) { - $("#batch_actions_button").popover({ - autoOpen: false - }); - $(document).delegate("#batch_actions_popover li a", "click.rails", function() { - $("#batch_action").val($(this).attr("data-action")); - return $("#collection_selection").submit(); - }); - $("#batch_actions_button").click(function() { - if (!$(this).hasClass("disabled")) { - if ($("#batch_actions_popover").is(":hidden")) { - $(this).popover("open"); - return false; - } else { - $(this).popover("close"); - return false; - } - } - }); - if ($("#batch_actions_button").length && $(":checkbox.toggle_all").length) { - if ($(".paginated_collection").find("table").length) { - $(".paginated_collection table").tableCheckboxToggler(); - } else { - $(".paginated_collection").checkboxToggler(); - } - return $(".paginated_collection").find(":checkbox").bind("change", function() { - if ($(".paginated_collection").find(":checkbox").filter(":checked").length > 0) { - return $("#batch_actions_button").removeClass("disabled"); - } else { - return $("#batch_actions_button").addClass("disabled"); - } - }); - } - }); - -}).call(this); - - - - diff --git a/app/assets/javascripts/active_admin/base.js b/app/assets/javascripts/active_admin/base.js index 0dea74c80d3..c9a7003f65d 100644 --- a/app/assets/javascripts/active_admin/base.js +++ b/app/assets/javascripts/active_admin/base.js @@ -1,5 +1,8 @@ //= require jquery //= require jquery-ui //= require jquery_ujs -//= require active_admin/application +//= require_tree ./lib/ +//= require_tree ./components/ +//= require_tree ./pages/ +//= require_directory ./ diff --git a/app/assets/javascripts/active_admin/coffeescripts/application.js b/app/assets/javascripts/active_admin/coffeescripts/application.js deleted file mode 100644 index 2a9112b2251..00000000000 --- a/app/assets/javascripts/active_admin/coffeescripts/application.js +++ /dev/null @@ -1,4 +0,0 @@ -//= require_tree ./lib/ -//= require_tree ./components/ -//= require_tree ./pages/ -//= require_directory ./ diff --git a/app/assets/javascripts/active_admin/coffeescripts/pages/batch_actions.js.coffee b/app/assets/javascripts/active_admin/coffeescripts/pages/batch_actions.js.coffee deleted file mode 100644 index b533bb59342..00000000000 --- a/app/assets/javascripts/active_admin/coffeescripts/pages/batch_actions.js.coffee +++ /dev/null @@ -1,45 +0,0 @@ -jQuery ($) -> - - # - # Init batch action popover - # - - $("#batch_actions_button").popover autoOpen: false - - # - # Use Rails.js click handler to allow for Rails' confirm dialogs - # - - $(document).delegate "#batch_actions_popover li a", "click.rails", -> - $("#batch_action").val $(this).attr("data-action") - $("#collection_selection").submit() - - # - # Toggle showing / hiding the batch actions popover - # - - $("#batch_actions_button").click -> - unless $(this).hasClass("disabled") - if $("#batch_actions_popover").is(":hidden") - $(this).popover "open" - return false - else - $(this).popover "close" - return false - - # - # Add checkbox selection to resource tables and lists if batch actions are enabled - # - - if $("#batch_actions_button").length && $(":checkbox.toggle_all").length - - if $(".paginated_collection").find("table").length - $(".paginated_collection table").tableCheckboxToggler() - else - $(".paginated_collection").checkboxToggler() - - $(".paginated_collection").find(":checkbox").bind "change", -> - if $(".paginated_collection").find(":checkbox").filter(":checked").length > 0 - $("#batch_actions_button").removeClass("disabled") - else - $("#batch_actions_button").addClass("disabled") diff --git a/app/assets/javascripts/active_admin/coffeescripts/components/jquery.aa.checkbox-toggler.js.coffee b/app/assets/javascripts/active_admin/components/jquery.aa.checkbox-toggler.js.coffee similarity index 100% rename from app/assets/javascripts/active_admin/coffeescripts/components/jquery.aa.checkbox-toggler.js.coffee rename to app/assets/javascripts/active_admin/components/jquery.aa.checkbox-toggler.js.coffee diff --git a/app/assets/javascripts/active_admin/components/jquery.aa.dropdown-menu.js.coffee b/app/assets/javascripts/active_admin/components/jquery.aa.dropdown-menu.js.coffee new file mode 100644 index 00000000000..70714c4301a --- /dev/null +++ b/app/assets/javascripts/active_admin/components/jquery.aa.dropdown-menu.js.coffee @@ -0,0 +1,107 @@ +window.AA.DropdownMenu = class AA.DropdownMenu + + constructor: (@options, @element) -> + + @$element = $(@element) + + defaults = { + fadeInDuration: 20, + fadeOutDuration: 100, + onClickActionItemCallback: null + } + + @options = $.extend({}, defaults, options) + + @$menuButton = @$element.find(".dropdown_menu_button") + @$menuList = @$element.find(".dropdown_menu_list_wrapper") + + @isOpen = false + + @_buildMenuList() + @_bind() + + return @ + + open: -> + @isOpen = true + @$menuList.fadeIn @options.fadeInDuration + + @_positionMenuList() + @_positionNipple() + + return @ + + + close: -> + @isOpen = false + @$menuList.fadeOut this.options.fadeOutDuration + + return @ + + destroy: -> + @$element.unbind() + @$element = null + + return @ + + isDisabled: -> + @$menuButton.hasClass("disabled") + + disable: -> + @$menuButton.addClass("disabled") + + enable: -> + @$menuButton.removeClass("disabled") + + option: (key, value) -> + if $.isPlainObject(key) + return @options = $.extend(true, @options, key) + + else if key? + return @options[key] + + else + return @options[key] = value + + # Private + + _buildMenuList: -> + @$menuList.prepend("
") + @$menuList.hide() + + _bind: -> + $("body").bind 'click', () => + if @isOpen is true + @close() + + @$menuButton.bind 'click', () => + unless @isDisabled() + if @isOpen is true + @close() + else + @open() + + # Return false so that the event is stopped + false + + _positionMenuList: -> + centerOfButtonFromLeft = @$menuButton.offset().left + @$menuButton.outerWidth() / 2 + centerOfmenuListFromLeft = @$menuList.outerWidth() / 2 + menuListLeftPos = centerOfButtonFromLeft - centerOfmenuListFromLeft + @$menuList.css "left", menuListLeftPos + + _positionNipple: -> + centerOfmenuListFromLeft = @$menuList.outerWidth() / 2 + bottomOfButtonFromTop = @$menuButton.offset().top + @$menuButton.outerHeight() + 10 + @$menuList.css "top", bottomOfButtonFromTop + $nipple = @$menuList.find(".dropdown_menu_nipple") + centerOfnippleFromLeft = $nipple.outerWidth() / 2 + nippleLeftPos = centerOfmenuListFromLeft - centerOfnippleFromLeft + $nipple.css "left", nippleLeftPos + +(($) -> + $.widget.bridge 'aaDropdownMenu', AA.DropdownMenu + + $ -> + $(".dropdown_menu").aaDropdownMenu() +)(jQuery) diff --git a/app/assets/javascripts/active_admin/coffeescripts/components/jquery.aa.popover.js.coffee b/app/assets/javascripts/active_admin/components/jquery.aa.popover.js.coffee similarity index 90% rename from app/assets/javascripts/active_admin/coffeescripts/components/jquery.aa.popover.js.coffee rename to app/assets/javascripts/active_admin/components/jquery.aa.popover.js.coffee index b5885b506b9..7465c8da31e 100644 --- a/app/assets/javascripts/active_admin/coffeescripts/components/jquery.aa.popover.js.coffee +++ b/app/assets/javascripts/active_admin/components/jquery.aa.popover.js.coffee @@ -13,13 +13,16 @@ window.AA.Popover = class AA.Popover onClickActionItemCallback: null } - @options = $.extend( {}, defaults, options ); + @options = $.extend({}, defaults, options) @$popover = null @isOpen = false - + if $(@$element.attr("href")).length > 0 @$popover = $(@$element.attr("href")) + else + @$popover = @$element.next(".popover") + @_buildPopover() @_bind() @@ -40,39 +43,41 @@ window.AA.Popover = class AA.Popover close: -> @isOpen = false; @$popover.fadeOut this.options.fadeOutDuration; - + return @ destroy: -> @$element.removeData('popover'); @$element.unbind(); @$element = null; - + return @ option: -> # ?? # Private - + _buildPopover: -> @$popover.prepend("
") - + @$popover.hide() @$popover.addClass "popover" _bind: -> - - $(@options.pageWrapperElement).bind 'click', (e) => if @isOpen is true @close() - + if @options.autoOpen is true @$element.bind 'click', () => - @open() + if @isOpen is true + @close() + else + @open() + false _positionPopover: -> @@ -90,6 +95,6 @@ window.AA.Popover = class AA.Popover nippleLeftPos = centerOfPopoverFromLeft - centerOfnippleFromLeft $nipple.css "left", nippleLeftPos -( ( $ ) -> +(($) -> $.widget.bridge 'popover', AA.Popover -)( jQuery ) +)(jQuery) diff --git a/app/assets/javascripts/active_admin/coffeescripts/components/jquery.aa.table-checkbox-toggler.js.coffee b/app/assets/javascripts/active_admin/components/jquery.aa.table-checkbox-toggler.js.coffee similarity index 99% rename from app/assets/javascripts/active_admin/coffeescripts/components/jquery.aa.table-checkbox-toggler.js.coffee rename to app/assets/javascripts/active_admin/components/jquery.aa.table-checkbox-toggler.js.coffee index 6a6b14542e9..8cd51614e28 100644 --- a/app/assets/javascripts/active_admin/coffeescripts/components/jquery.aa.table-checkbox-toggler.js.coffee +++ b/app/assets/javascripts/active_admin/components/jquery.aa.table-checkbox-toggler.js.coffee @@ -25,5 +25,3 @@ window.AA.TableCheckboxToggler = class AA.TableCheckboxToggler extends AA.Checkb ( ( $ ) -> $.widget.bridge 'tableCheckboxToggler', AA.TableCheckboxToggler )( jQuery ) - - diff --git a/app/assets/javascripts/active_admin/coffeescripts/lib/namespace.js.coffee b/app/assets/javascripts/active_admin/lib/namespace.js.coffee similarity index 100% rename from app/assets/javascripts/active_admin/coffeescripts/lib/namespace.js.coffee rename to app/assets/javascripts/active_admin/lib/namespace.js.coffee diff --git a/app/assets/javascripts/active_admin/coffeescripts/pages/application.js.coffee b/app/assets/javascripts/active_admin/pages/application.js.coffee similarity index 76% rename from app/assets/javascripts/active_admin/coffeescripts/pages/application.js.coffee rename to app/assets/javascripts/active_admin/pages/application.js.coffee index c1a9b9bc7f4..94439d1e1b8 100644 --- a/app/assets/javascripts/active_admin/coffeescripts/pages/application.js.coffee +++ b/app/assets/javascripts/active_admin/pages/application.js.coffee @@ -2,11 +2,12 @@ # Active Admin JS # -# Date picker $ -> + # Date picker $(".datepicker").datepicker dateFormat: "yy-mm-dd" $(".clear_filters_btn").click -> window.location.search = "" false + $(".dropdown_button").popover() diff --git a/app/assets/javascripts/active_admin/pages/batch_actions.js.coffee b/app/assets/javascripts/active_admin/pages/batch_actions.js.coffee new file mode 100644 index 00000000000..a69e7d45fc4 --- /dev/null +++ b/app/assets/javascripts/active_admin/pages/batch_actions.js.coffee @@ -0,0 +1,26 @@ +jQuery ($) -> + + # + # Use Rails.js click handler to allow for Rails' confirm dialogs + # + + $(document).delegate "#batch_actions_selector li a", "click.rails", -> + $("#batch_action").val $(this).attr("data-action") + $("#collection_selection").submit() + + # + # Add checkbox selection to resource tables and lists if batch actions are enabled + # + + if $("#batch_actions_selector").length && $(":checkbox.toggle_all").length + + if $(".paginated_collection").find("table").length + $(".paginated_collection table").tableCheckboxToggler() + else + $(".paginated_collection").checkboxToggler() + + $(".paginated_collection").find(":checkbox").bind "change", -> + if $(".paginated_collection").find(":checkbox").filter(":checked").length > 0 + $("#batch_actions_selector").aaDropdownMenu("enable") + else + $("#batch_actions_selector").aaDropdownMenu("disable") diff --git a/app/assets/stylesheets/active_admin/_base.css.scss b/app/assets/stylesheets/active_admin/_base.css.scss index 8dab465393a..b54b06453bb 100644 --- a/app/assets/stylesheets/active_admin/_base.css.scss +++ b/app/assets/stylesheets/active_admin/_base.css.scss @@ -14,6 +14,7 @@ @import "active_admin/components/batch_actions"; @import "active_admin/components/blank_slates"; @import "active_admin/components/breadcrumbs"; +@import "active_admin/components/dropdown_menu"; @import "active_admin/components/buttons"; @import "active_admin/components/grid"; @import "active_admin/components/links"; diff --git a/app/assets/stylesheets/active_admin/components/_buttons.scss b/app/assets/stylesheets/active_admin/components/_buttons.scss index e95e848ae18..f7e416465fe 100644 --- a/app/assets/stylesheets/active_admin/components/_buttons.scss +++ b/app/assets/stylesheets/active_admin/components/_buttons.scss @@ -1,4 +1,3 @@ - td, p { @include icon(#B3BCC1, 0.8em); span.icon { margin: 0 3px; } @@ -9,4 +8,4 @@ a.member_link { white-space: nowrap; } -a.button, a:link.button, a:visited.button, input[type=submit] { @include dark-button; } \ No newline at end of file +a.button, a:link.button, a:visited.button, input[type=submit] { @include dark-button; } diff --git a/app/assets/stylesheets/active_admin/components/_dropdown_menu.scss b/app/assets/stylesheets/active_admin/components/_dropdown_menu.scss new file mode 100644 index 00000000000..2f22bac2c08 --- /dev/null +++ b/app/assets/stylesheets/active_admin/components/_dropdown_menu.scss @@ -0,0 +1,151 @@ +.dropdown_menu { + display: inline; + + .dropdown_menu_button { + @include light-button; + position: relative; + padding-right: 22px !important; + cursor: pointer; + + &:before { + content: ' '; + position: absolute; + width: 0; + height: 0; + border-width: 3px 3px 0; + border-style: solid; + border-color: #FFF transparent; + right: 12px; + top: 45%; + } + + &:after { + content: ' '; + position: absolute; + width: 0; + height: 0; + border-width: 3px 3px 0; + border-style: solid; + border-color: #777 transparent; + right: 12px; + top: 45%; + } + } + + .dropdown_menu_nipple { + + // The nipple's border + content: ""; + position: absolute; + top: -6px; + display: block; + width: 0; + height: 0; + border-width: 0 6px 6px; + border-style: solid; + border-color: darken($primary-color, 4%) transparent; + z-index: 100; + + // The nipple's inner shadow + + &:before { + content: ' '; + position: absolute; + width: 0; + height: 0; + border-width: 0 5px 5px; + border-style: solid; + border-color: lighten($primary-color, 15%) transparent; + left: -5px; + top: 1px; + } + + // The nipple's background color + + &:after { + content: ' '; + position: absolute; + width: 0; + height: 0; + border-width: 0 5px 5px; + border-style: solid; + border-color: lighten($primary-color, 4%) transparent; + left: -5px; + top: 2px; + } + } + + .dropdown_menu_list_wrapper { + display: inline-block; + position: absolute; + background-color: white; + padding: 2px; + @include box-shadow(rgba(0,0,0,0.4) 0 1px 3px, lighten($primary-color, 15%) 0px 1px 0px 0px inset); + background-color: $primary-color; + @include gradient(lighten($primary-color, 4%), darken($primary-color, 5%)); + border: solid 1px darken($primary-color, 10%); + border-top-color: darken($primary-color, 4%); + border-bottom-color: darken($primary-color, 17%); + + border-radius: 4px; + + .dropdown_menu_list { + display: block; + background-color: #FFF; + border: solid 1px darken($primary-color, 10%); + @include box-shadow(lighten($primary-color, 5%) 0px 1px 0px 0px); + border-radius: 3px; + margin: 0; + overflow: hidden; + padding: 8px; + + list-style-type: none; + padding: 0; + + li { + display: block; + border-bottom: solid 1px #ebebeb; + @include box-sizing(border-box); + + a { + display: block; + @include box-sizing(padding-box); + font-size: 0.95em; + font-weight: bold; + padding: 7px 16px 5px; + text-decoration: none; + text-align: center; + -webkit-font-smoothing: antialiased; + + &:hover { + @include highlight-gradient; + @include text-shadow(#5a83aa); + color: #FFF; + } + + &:active { + @include reverse-highlight-gradient; + color: #FFF; + } + + } + + &:first-child { + a { + border-top-left-radius: 2px; + border-top-right-radius: 2px; + } + + } + + &:last-child { + a { + border-bottom-left-radius: 2px; + border-bottom-right-radius: 2px; + } + border: none; + } + } + } + } +} diff --git a/app/assets/stylesheets/active_admin/components/_table_tools.css.scss b/app/assets/stylesheets/active_admin/components/_table_tools.css.scss index 3be4f378f8c..b531c4b76a0 100644 --- a/app/assets/stylesheets/active_admin/components/_table_tools.css.scss +++ b/app/assets/stylesheets/active_admin/components/_table_tools.css.scss @@ -16,9 +16,11 @@ .table_tools_segmented_control + .table_tools_button { margin-left: 6px; } + } -a.table_tools_button { + +a.table_tools_button, .table_tools .dropdown_menu_button { @include light-button; @include gradient(#FFF, #F0F0F0); @include border-colors(#d9d9d9, #d0d0d0, #c5c5c5); @@ -39,36 +41,6 @@ a.table_tools_button { @include gradient(#FFF, #E8E8E8); } } - - &.dropdown_button { - padding-right: 22px; - - &:before { - content: ' '; - position: absolute; - width: 0; - height: 0; - border-width: 3px 3px 0; - border-style: solid; - border-color: #FFF transparent; - right: 9px; - top: 10px; - } - - &:after { - content: ' '; - position: absolute; - width: 0; - height: 0; - border-width: 3px 3px 0; - border-style: solid; - border-color: #777 transparent; - right: 9px; - top: 9px; - } - - } - } .table_tools_segmented_control { diff --git a/features/index/batch_actions.feature b/features/index/batch_actions.feature index b7e8d8d88f3..c31254ee844 100644 --- a/features/index/batch_actions.feature +++ b/features/index/batch_actions.feature @@ -40,10 +40,10 @@ Feature: Batch Actions And an index configuration of: """ ActiveAdmin.register Post do - batch_action :destroy, false + batch_action :destroy, false end """ - Then I should not see the batch action :destroy "Delete Selected" + Then I should not see the batch actions selector And I should not see checkboxes in the table Scenario: Optional display of batch actions diff --git a/features/step_definitions/batch_action_steps.rb b/features/step_definitions/batch_action_steps.rb index e5a1b6c05ae..157df835f26 100644 --- a/features/step_definitions/batch_action_steps.rb +++ b/features/step_definitions/batch_action_steps.rb @@ -14,7 +14,7 @@ end Then /^I (should|should not) see the batch action :([^\s]*) "([^"]*)"$/ do |maybe, sym, title| - within "#batch_actions_popover" do + within "#batch_actions_selector" do unless maybe == "should not" link = page.find "a.batch_action", :text => title link["data-action"].should match( sym ) @@ -26,7 +26,7 @@ end Then /^the (\d+)(?:st|nd|rd|th) batch action should be "([^"]*)"$/ do |index, title| - within "#batch_actions_popover" do + within "#batch_actions_selector" do page.all( "a.batch_action" )[index.to_i - 1].text.should match( title ) end end @@ -55,19 +55,23 @@ end Then /^I should see that the batch action button is disabled$/ do - page.should have_css("#batch_actions_button.disabled") + page.should have_css("#batch_actions_selector .dropdown_menu_button.disabled") end Then /^I (should|should not) see the batch action button$/ do |maybe| if maybe == "should not" - page.should_not have_css("div.table_tools #batch_actions_button") + page.should_not have_css("div.table_tools #batch_actions_selector .dropdown_menu_button") else - page.should have_css("div.table_tools #batch_actions_button") + page.should have_css("div.table_tools #batch_actions_selector .dropdown_menu_button") end end +Then "I should not see the batch actions selector" do + page.should_not have_css("div.table_tools #batch_actions_selector") +end + Then /^I should see the batch action popover exists$/ do - page.should have_css("#batch_actions_popover") + page.should have_css("#batch_actions_selector") end Given /^I submit the batch action form with "([^"]*)"$/ do |action| diff --git a/lib/active_admin/batch_actions.rb b/lib/active_admin/batch_actions.rb index 236e764b67c..a6864da1097 100644 --- a/lib/active_admin/batch_actions.rb +++ b/lib/active_admin/batch_actions.rb @@ -10,7 +10,8 @@ require "active_admin/batch_actions/views/batch_action_form" require "active_admin/batch_actions/views/batch_action_popover" require "active_admin/batch_actions/views/selection_cells" + require "active_admin/batch_actions/views/batch_action_selector" # Register the views with the view factory - app.view_factory.register :batch_action_popover => ActiveAdmin::BatchActions::BatchActionPopover + app.view_factory.register :batch_action_selector => ActiveAdmin::BatchActions::BatchActionSelector end diff --git a/lib/active_admin/batch_actions/views/batch_action_form.rb b/lib/active_admin/batch_actions/views/batch_action_form.rb index ed1e152b410..3fd2b82123d 100644 --- a/lib/active_admin/batch_actions/views/batch_action_form.rb +++ b/lib/active_admin/batch_actions/views/batch_action_form.rb @@ -11,22 +11,23 @@ class BatchActionForm < ActiveAdmin::Component def build(options = {}, &block) options[:id] ||= "collection_selection" - @contents = input(:name => :batch_action, :id => :batch_action, :type => :hidden) - @prefix_html = text_node(form_tag send(active_admin_config.batch_action_path), :id => options[:id]) + + # Open a form + text_node form_tag(send(active_admin_config.batch_action_path), :id => options[:id]) + input(:name => :batch_action, :id => :batch_action, :type => :hidden) + super(options) end - # Override to_html to wrap the custom form stuff + # Override the default to_s to include a closing form tag def to_s - @prefix_html + content + text_node(''.html_safe) + content + closing_form_tag end - def add_child(child) - if @contents - @contents << child - else - super - end + private + + def closing_form_tag + ''.html_safe end end diff --git a/lib/active_admin/batch_actions/views/batch_action_selector.rb b/lib/active_admin/batch_actions/views/batch_action_selector.rb new file mode 100644 index 00000000000..69ae00b8eda --- /dev/null +++ b/lib/active_admin/batch_actions/views/batch_action_selector.rb @@ -0,0 +1,63 @@ +require 'active_admin/component' + +module ActiveAdmin + module BatchActions + + class BatchActionSelector < ActiveAdmin::Component + builder_method :batch_action_selector + + # Build a new batch actions selector + # + # @param [Array] batch_actions An array of batch actions + def build(batch_actions) + @batch_actions = Array(batch_actions) + @drop_down = build_drop_down + end + + # We don't want to wrap the action list (or any other children) in + # an unecessary div, so instead we just return the children + def to_s + children.to_s + end + + private + + def build_drop_down + dropdown_menu I18n.t("active_admin.batch_actions.button_label"), + :id => "batch_actions_selector", + :button => { :class => "disabled" } do + batch_actions_to_display.each do |batch_action| + options = { + :class => "batch_action", + "data-action" => batch_action.sym, + "data-confirm" => batch_action.confirm + } + + title = I18n.t("active_admin.batch_actions.labels.#{batch_action.sym}", :default => batch_action.title) + label = I18n.t("active_admin.batch_actions.action_label", :title => title) + + item label, "#", options + end + end + end + + # Return the set of batch actions that should be displayed + def batch_actions_to_display + @batch_actions.select do |batch_action| + call_method_or_proc_on(self, batch_action.display_if_block) + end + end + + # def build_batch_action_button + # a :class => 'table_tools_button dropdown_button disabled', :href => "#batch_actions_popover", :id => "batch_actions_button" do + # text_node I18n.t("active_admin.batch_actions.button_label") + # end + # end + + # def build_batch_action_popover + # end + + end + + end +end diff --git a/lib/active_admin/views/components/dropdown_menu.rb b/lib/active_admin/views/components/dropdown_menu.rb new file mode 100644 index 00000000000..00491e96104 --- /dev/null +++ b/lib/active_admin/views/components/dropdown_menu.rb @@ -0,0 +1,73 @@ +require 'active_admin/views/components/popover' + +module ActiveAdmin + module Views + + # Action List - A button with a drop down menu of links + # + # Creating a new action list: + # + # dropdown_menu "Administration" do + # item "Edit Details", edit_details_path + # item "Edit My Account", edit_my_acccount_path + # end + # + # This will create a button with the label "Administration" and + # a drop down once clicked with 2 options. + # + class DropdownMenu < ActiveAdmin::Component + builder_method :dropdown_menu + + # Build a new action list + # + # @param [String] name The name to display in the button + # + # @param [Hash] options A set of options that get passed along to + # to the parent dom element. + def build(name, options = {}) + options = options.dup + + # Easily set options for the button or menu + button_options = options.delete(:button) || {} + menu_options = options.delete(:menu) || {} + + @button = build_button(name, button_options) + @menu = build_menu(menu_options) + + super(options) + end + + def item(*args) + within @menu do + li link_to(*args) + end + end + + private + + def build_button(name, button_options) + button_options[:class] ||= "" + button_options[:class] << " dropdown_menu_button" + + button_options[:href] = "#" + + a name, button_options + end + + def build_menu(options) + options[:class] ||= "" + options[:class] << " dropdown_menu_list" + + menu_list = nil + + div :class => "dropdown_menu_list_wrapper", :style => "display:none;" do + menu_list = ul(options) + end + + menu_list + end + + end + + end +end diff --git a/lib/active_admin/views/components/popover.rb b/lib/active_admin/views/components/popover.rb index c8858360cad..b0bb147cbac 100644 --- a/lib/active_admin/views/components/popover.rb +++ b/lib/active_admin/views/components/popover.rb @@ -1,34 +1,27 @@ module ActiveAdmin module Views - # Build a Popover + class Popover < ActiveAdmin::Component builder_method :popover - - def default_class_name - 'popover' - end - + def build(options = {}, &block) - - self.id = options[:id] - + options = options.dup + contents_root_tag = options.delete(:contents_root_tag) || :div + options[:style] = "display: none" + super(options) - - @contents ||= div(:class => "popover_contents") - - # Hide the popover by default - - attributes[:style] = "display: none" + + @contents_root = send(contents_root_tag, :class => "popover_contents") end - + def add_child(child) - if @contents - @contents << child + if @contents_root + @contents_root << child else super end end - + end end end diff --git a/lib/active_admin/views/pages/base.rb b/lib/active_admin/views/pages/base.rb index 323d429fee6..10d0f8b8748 100644 --- a/lib/active_admin/views/pages/base.rb +++ b/lib/active_admin/views/pages/base.rb @@ -41,7 +41,6 @@ def build_page build_page_content build_footer end - build_extra_content end end @@ -128,10 +127,6 @@ def build_footer insert_tag view_factory.footer end - def build_extra_content - # Put popovers, etc here - end - end end end diff --git a/lib/active_admin/views/pages/index.rb b/lib/active_admin/views/pages/index.rb index 121c34d88fb..aaf76ae3378 100644 --- a/lib/active_admin/views/pages/index.rb +++ b/lib/active_admin/views/pages/index.rb @@ -15,12 +15,7 @@ def config # Render's the index configuration that was set in the # controller. Defaults to rendering the ActiveAdmin::Pages::Index::Table def main_content - if active_admin_config.batch_actions.any? - batch_action_form do - build_table_tools - build_collection - end - else + wrap_with_batch_action_form do build_table_tools build_collection end @@ -28,8 +23,12 @@ def main_content protected - def build_extra_content - build_batch_action_popover + def wrap_with_batch_action_form(&block) + if active_admin_config.batch_actions.any? + batch_action_form(&block) + else + block.call + end end def items_in_collection? @@ -65,22 +64,14 @@ def build_download_format_links(formats = [:csv, :xml, :json]) def build_table_tools div :class => "table_tools" do - - if active_admin_config.batch_actions.any? - a :class => 'table_tools_button dropdown_button disabled', :href => "#batch_actions_popover", :id => "batch_actions_button" do - text_node I18n.t("active_admin.batch_actions.button_label") - end - end - + build_batch_actions_selector build_scopes end end - def build_batch_action_popover - insert_tag view_factory.batch_action_popover do - active_admin_config.batch_actions.each do |the_action| - action the_action if call_method_or_proc_on(self, the_action.display_if_block) - end + def build_batch_actions_selector + if active_admin_config.batch_actions.any? + insert_tag view_factory.batch_action_selector, active_admin_config.batch_actions end end diff --git a/spec/javascripts/coffeescripts/jquery.aa.checkbox-toggler-spec.js.coffee b/spec/javascripts/coffeescripts/jquery.aa.checkbox-toggler-spec.js.coffee index dd4106582cf..755761caba1 100644 --- a/spec/javascripts/coffeescripts/jquery.aa.checkbox-toggler-spec.js.coffee +++ b/spec/javascripts/coffeescripts/jquery.aa.checkbox-toggler-spec.js.coffee @@ -1,7 +1,7 @@ describe "AA.CheckboxToggler", -> beforeEach -> - loadFixtures('checkboxes.html'); + loadFixtures('checkboxes.html') @collection = $("#collection") @toggle_all = @collection.find(".toggle_all")