diff options
| author | Zog | 2018-01-15 16:35:08 +0100 |
|---|---|---|
| committer | Zog | 2018-01-25 17:17:58 +0100 |
| commit | aefa3a507239b57ebdfe9110f3c8c789da6bce26 (patch) | |
| tree | 393522417aec5dc006680e8e5a2f14386adf45ae | |
| parent | fef78d49bbc89aa41fe043b4035585605e14d389 (diff) | |
| download | chouette-core-aefa3a507239b57ebdfe9110f3c8c789da6bce26.tar.bz2 | |
Refs #5586 @2h; Better implementation of groups
Used it in Lines#index and Lines#show, probably broke everything else
| -rw-r--r-- | app/assets/stylesheets/components/_buttons.sass | 41 | ||||
| -rw-r--r-- | app/decorators/line_decorator.rb | 18 | ||||
| -rw-r--r-- | app/helpers/table_builder_helper.rb | 13 | ||||
| -rw-r--r-- | app/views/layouts/navigation/_page_header.html.slim | 10 | ||||
| -rw-r--r-- | app/views/lines/show.html.slim | 7 | ||||
| -rw-r--r-- | lib/af83/decorator.rb | 138 | ||||
| -rw-r--r-- | spec/lib/af83/decorator/decorator_spec.rb | 194 |
7 files changed, 357 insertions, 64 deletions
diff --git a/app/assets/stylesheets/components/_buttons.sass b/app/assets/stylesheets/components/_buttons.sass index a649a07ef..e70c89733 100644 --- a/app/assets/stylesheets/components/_buttons.sass +++ b/app/assets/stylesheets/components/_buttons.sass @@ -142,16 +142,25 @@ table, .table margin: 0 border-radius: 0 box-shadow: 0 0 3px rgba($darkgrey, 0.25) - - > li > a, > li > button - padding: 5px 15px - - > li.delete-action - > a, > button + > ul + padding: 0 + margin: 0 + > li > a, > li > button + padding: 5px 15px + white-space: nowrap + color: black + padding: 5px 15px + font-weight: normal + line-height: 1.42857 display: block + font-size: 14px + &:hover + text-decoration: none + color: #262626 + background-color: whitesmoke + &:not(:first-child) position: relative margin-top: 11px - &:before content: '' display: block @@ -162,14 +171,18 @@ table, .table height: 1px background-color: $grey - .fa:first-child - margin-right: 0.5em - - & + li.delete-action + > li.delete-action > a, > button - margin-top: 0 - &:before - display: none + display: block + position: relative + .fa:first-child + margin-right: 0.5em + + & + li.delete-action + > a, > button + margin-top: 0 + &:before + display: none &.table-2entries .t2e-item diff --git a/app/decorators/line_decorator.rb b/app/decorators/line_decorator.rb index 6c6df1a95..9325c6a8b 100644 --- a/app/decorators/line_decorator.rb +++ b/app/decorators/line_decorator.rb @@ -27,21 +27,21 @@ class LineDecorator < AF83::Decorator can_edit_line = ->(){ h.policy(Chouette::Line).create? && context[:line_referential].organisations.include?(context[:current_organisation]) } with_condition can_edit_line do - action_link on: :index do |l| - l.content { h.t('lines.actions.new') } - l.href { h.new_line_referential_line_path(context[:line_referential]) } - end - - action_link on: %i(index show), primary: :show do |l| + action_link on: %i(index show), primary: :show, secondary: :index do |l| l.content { h.t('lines.actions.edit') } l.href { h.edit_line_referential_line_path(context[:line_referential], object.id) } end + + action_link on: :index, secondary: :index do |l| + l.content { h.t('lines.actions.new') } + l.href { h.new_line_referential_line_path(context[:line_referential]) } + end end ### the option :policy will automatically check for the corresponding method ### on the object's policy - action_link policy: :deactivate, secondary: true do |l| + action_link policy: :deactivate, secondary: :show, footer: :index do |l| l.content { h.deactivate_link_content('lines.actions.deactivate') } l.href { h.deactivate_line_referential_line_path(context[:line_referential], object) } l.method :put @@ -49,7 +49,7 @@ class LineDecorator < AF83::Decorator l.extra_class "delete-action" end - action_link policy: :activate, secondary: true do |l| + action_link policy: :activate, secondary: :show, footer: :index do |l| l.content { h.activate_link_content('lines.actions.activate') } l.href { h.activate_line_referential_line_path(context[:line_referential], object) } l.method :put @@ -57,7 +57,7 @@ class LineDecorator < AF83::Decorator l.extra_class "delete-action" end - action_link policy: :destroy do |l| + action_link policy: :destroy, footer: true do |l| l.content { h.destroy_link_content('lines.actions.destroy') } l.href { h.line_referential_line_path(context[:line_referential], object) } l.method :delete diff --git a/app/helpers/table_builder_helper.rb b/app/helpers/table_builder_helper.rb index e66e9c942..bece3bb2a 100644 --- a/app/helpers/table_builder_helper.rb +++ b/app/helpers/table_builder_helper.rb @@ -306,12 +306,13 @@ module TableBuilderHelper content_tag :span, '', class: 'fa fa-cog' end - menu = content_tag :ul, class: 'dropdown-menu' do - ( - CustomLinks.new(item, pundit_user, links, referential).links + - item.action_links - ).map do |link| - gear_menu_link(link) + menu = content_tag :div, class: 'dropdown-menu' do + item.action_links(params[:action]).grouped_by(:primary, :secondary, :footer).map do |group, _links| + if _links.any? + content_tag :ul, class: group do + _links.map{|link| gear_menu_link(link)}.join.html_safe + end + end end.join.html_safe end diff --git a/app/views/layouts/navigation/_page_header.html.slim b/app/views/layouts/navigation/_page_header.html.slim index 90fd7d855..076c76de4 100644 --- a/app/views/layouts/navigation/_page_header.html.slim +++ b/app/views/layouts/navigation/_page_header.html.slim @@ -19,5 +19,15 @@ div.page_header - if content_for? :page_header_actions = yield :page_header_actions + - action_links = resource.action_links(params[:action]) rescue nil + - if action_links&.primary&.any? || action_links&.secondary&.any? + .row + .col-lg-12.text-right.mb-sm + - action_links.primary.each do |link| + = link.to_html do |l| + - l.class "btn btn-primary #{l.disabled ? "disabled" : ""} sticky-action" + - action_links.secondary.each do |link| + = link.to_html do |l| + - l.class "btn btn-primary #{l.disabled ? "disabled" : ""}" - if content_for? :page_header_content = yield :page_header_content diff --git a/app/views/lines/show.html.slim b/app/views/lines/show.html.slim index 1c0578b0b..34b907bdd 100644 --- a/app/views/lines/show.html.slim +++ b/app/views/lines/show.html.slim @@ -1,11 +1,4 @@ - breadcrumb :line, @line -- content_for :page_header_content do - .row - .col-lg-12.text-right.mb-sm - - @line.action_links(:show, :primary).each do |link| - = link.to_html do |l| - - l.class "btn btn-primary #{l.disabled ? "disabled" : ""}" - - page_header_content_for @line .page_content diff --git a/lib/af83/decorator.rb b/lib/af83/decorator.rb index 50889ede7..6c7dff59d 100644 --- a/lib/af83/decorator.rb +++ b/lib/af83/decorator.rb @@ -24,16 +24,19 @@ class AF83::Decorator < Draper::Decorator (@_action_links || []).flatten.compact.select{|l| l.for_action?(action)} end - def action_links action=:index, scope=nil - return send("#{scope}_links", action) if scope.present? - - self.class.action_links(action)\ - .map{|l| l.bind_to_context(self)}\ - .select{|l| l.enabled?} + def action_links action=:index, opts={} + links = ActionLinks.new links: self.class.action_links(action), context: self, action: action + group = opts[:group] + links = links.for_group opts[:group] + links end def primary_links action=:index - action_links(action).select{|l| l.primary_for_action?(action)} + action_links(action, group: :primary) + end + + def secondary_links action=:index + action_links(action, group: :secondary) end def check_policy policy @@ -45,7 +48,7 @@ class AF83::Decorator < Draper::Decorator private def self.parse_options args options = {} - %i(weight primary secondary on action actions policy if).each do |k| + %i(weight primary secondary footer on action actions policy if groups group).each do |k| options[k] = args.delete(k) if args.has_key?(k) end link_options = args.dup @@ -56,14 +59,103 @@ class AF83::Decorator < Draper::Decorator actions = [actions] unless actions.is_a?(Array) link_options[:_actions] = actions.compact - link_options[:_primary] = options.delete :primary - link_options[:_secondary] = options.delete :secondary + link_options[:_groups] = options.delete(:groups) + link_options[:_groups] ||= {} + if single_group = options.delete(:group) + if(single_group.is_a?(Symbol) || single_group.is_a?(String)) + link_options[:_groups][single_group] = true + else + link_options[:_groups].update single_group + end + end + link_options[:_groups][:primary] ||= options.delete :primary + link_options[:_groups][:secondary] ||= options.delete :secondary + link_options[:_groups][:footer] ||= options.delete :footer link_options[:_if] = options.delete(:if) link_options[:_policy] = options.delete(:policy) [options, link_options] end + class ActionLinks + attr_reader :options + + def initialize opts + @options = opts.deep_dup + end + + def for_group group + returning_a_copy do + @options[:groups] = [group] if group.present? + end + end + + def for_groups groups + returning_a_copy do + @options[:groups] = groups if groups.present? + end + end + + def primary + for_group :primary + end + + def secondary + for_group :secondary + end + + def resolve + out = @options[:links].map{|l| l.bind_to_context(@options[:context])}.select{|l| l.enabled?} + if @options[:groups].present? + out = out.select do |l| + @options[:groups].any? do |g| + l.in_group_for_action?(@options[:action], g) + end + end + end + out + end + + def grouped_by *groups + add_footer = groups.include?(:footer) + groups -= [:footer] + out = HashWithIndifferentAccess[*groups.map{|g| [g, []]}.flatten(1)] + out[:other] = [] + if add_footer + out[:footer] = [] + groups << :footer + end + + each do |l| + found = false + groups.each do |g| + if l.in_group_for_action?(@options[:action], g) + out[g] << l + found = true + next + end + end + out[:other] << l unless found + end + out + end + + alias_method :to_ary, :resolve + + %w(each map size first last any?).each do |meth| + define_method meth do |*args, &block| + resolve.send meth, *args, &block + end + end + + private + def returning_a_copy &block + out = ActionLinks.new options + out.instance_eval &block + out + end + end + class Link REQUIRED_ATTRIBUTES = %i(href content) @@ -115,21 +207,19 @@ class AF83::Decorator < Draper::Decorator enabled_actions.empty? || enabled_actions.include?(action.to_s) end - %i(primary secondary).each do |k| - define_method "#{k}_for_action?" do |action| - vals = send("#{k}_actions") - if vals.is_a?(Array) - return vals.include?(action.to_s) - elsif vals.is_a?(String) || vals.is_a?(Symbol) - vals.to_s == action.to_s - else - !!vals - end - end + def actions_for_group group + val = @options[:_groups][group] + val.is_a?(Array) ? val.map(&:to_s) : val + end - define_method "#{k}_actions" do - val = @options[:"_#{k}"] - val.is_a?(Array) ? val.map(&:to_s) : val + def in_group_for_action? action, group + vals = actions_for_group(group) + if vals.is_a?(Array) + return vals.include?(action.to_s) + elsif vals.is_a?(String) || vals.is_a?(Symbol) + vals.to_s == action.to_s + else + !!vals end end diff --git a/spec/lib/af83/decorator/decorator_spec.rb b/spec/lib/af83/decorator/decorator_spec.rb index b70e0d7c1..04dc9df09 100644 --- a/spec/lib/af83/decorator/decorator_spec.rb +++ b/spec/lib/af83/decorator/decorator_spec.rb @@ -13,8 +13,8 @@ RSpec.describe AF83::Decorator, type: :decorator do link_options.each do |k, v| expect(_link_options[k]).to eq v end - expect(_link_options[:_primary]).to eq true - expect(_link_options[:_secondary]).to eq %i(index show) + expect(_link_options[:_groups][:primary]).to eq true + expect(_link_options[:_groups][:secondary]).to eq %i(index show) expect(_link_options[:_policy]).to eq :blublu end end @@ -424,6 +424,24 @@ RSpec.describe AF83::Decorator, type: :decorator do end end + describe '#primary' do + let(:decorator) do + Class.new(AF83::Decorator) + end + + let(:decorated) do + obj = create :line + decorator.decorate(obj) + end + + it "should return a new object everytime" do + actions = decorated.action_links + primary = actions.primary + expect(actions.options[:groups]).to be_nil + expect(primary.options[:groups]).to_not be_nil + end + end + describe(:primary_links) do let(:decorated) do obj = create :line @@ -478,7 +496,7 @@ RSpec.describe AF83::Decorator, type: :decorator do let(:primary){ %i(show edit) } it "should return the link" do - links = decorated.action_links(:show, :primary) + links = decorated.action_links(:show, group: :primary) expect(links.size).to eq 1 end end @@ -487,7 +505,7 @@ RSpec.describe AF83::Decorator, type: :decorator do let(:primary){ %i(index edit) } it "should not return the link" do - links = decorated.action_links(:show, :primary) + links = decorated.action_links(:show, group: :primary) expect(links.size).to eq 0 end end @@ -511,4 +529,172 @@ RSpec.describe AF83::Decorator, type: :decorator do end end end + + describe("in a group") do + let(:decorated) do + obj = create :line + decorator.decorate(obj) + end + + context "without links" do + let(:decorator) do + Class.new(AF83::Decorator) + end + + it "should return no link" do + links = decorated.action_links + expect(links.size).to eq 0 + end + end + + + context "with a single link" do + let(:link_options) do + { + href: "/foo/bar/baz", + content: "Blublu", + groups: {foo: group} + } + end + + let(:decorator) do + klass = Class.new(AF83::Decorator) + klass.action_link link_options + klass + end + + context "always in" do + let(:group){ true } + + it "should return the link" do + links = decorated.action_links(:show, group: :foo) + expect(links.size).to eq 1 + end + + context "define with group" do + let(:link_options) do + { + href: "/foo/bar/baz", + content: "Blublu", + group: :foo + } + end + + let(:decorator) do + klass = Class.new(AF83::Decorator) + klass.action_link link_options + klass + end + + it "should return the link" do + links = decorated.action_links(:show, group: :foo) + expect(links.size).to eq 1 + end + + it "should not return the link" do + links = decorated.action_links(:show, group: :bar) + expect(links.size).to eq 0 + end + end + end + + context "primary on this action" do + let(:group){ :show } + + it "should return the link" do + links = decorated.action_links(:show, group: :foo) + expect(links.size).to eq 1 + end + end + + context "in this action among others" do + let(:group){ %i(show edit) } + + it "should return the link" do + links = decorated.action_links(:show, group: :foo) + expect(links.size).to eq 1 + end + end + + context "in other actions" do + let(:group){ %i(index edit) } + + it "should not return the link" do + links = decorated.action_links(:show, group: :foo) + expect(links.size).to eq 0 + end + end + + context "in another action" do + let(:group){ :index } + + it "should not return the link" do + links = decorated.action_links(:show, group: :foo) + expect(links.size).to eq 0 + end + end + + context "never" do + let(:group){ nil } + + it "should not return the link" do + links = decorated.action_links(:show, group: :foo) + expect(links.size).to eq 0 + end + end + end + + describe(:grouped_by) do + let(:link_options_1) do + { + href: "/foo/bar", + content: "Blublu", + primary: true + } + end + + let(:link_options_2) do + { + href: "/foo/bar/baz", + content: "Foo", + groups: {secondary: :show} + } + end + + let(:link_options_3) do + { + href: "/foo/bar/baz/bat", + content: "Foo", + groups: {foo: :show} + } + end + + let(:link_options_4) do + { + href: "/footer", + content: "Foo", + footer: true + } + end + + let(:decorator) do + klass = Class.new(AF83::Decorator) + klass.action_link link_options_1 + klass.action_link link_options_2 + klass.action_link link_options_3 + klass.action_link link_options_4 + klass + end + + it "should return links in their groups" do + links = decorated.action_links(:show).grouped_by(:primary, :secondary, :blu, :footer) + expect(links.size).to eq 5 + instance_exec links[:primary].first, link_options_1, &link_should_match_options + instance_exec links[:secondary].first, link_options_2, &link_should_match_options + expect(links[:blu].size).to eq 0 + instance_exec links[:other].first, link_options_3, &link_should_match_options + instance_exec links[:footer].first, link_options_4, &link_should_match_options + end + end + end end |
