├── VERSION
├── .rspec
├── spec
├── spec_helper.rb
└── lib
│ ├── mmmenu_level_spec.rb
│ └── mmmenu_spec.rb
├── lib
├── mmmenu
│ ├── version.rb
│ ├── engine.rb
│ └── level.rb
├── generators
│ ├── templates
│ │ ├── views
│ │ │ └── mmmenu
│ │ │ │ ├── _level_1.erb
│ │ │ │ ├── _level_2.erb
│ │ │ │ ├── _item.erb
│ │ │ │ └── _current_item.erb
│ │ └── helpers
│ │ │ └── mmmenu_helper.rb
│ └── mmmenu
│ │ └── mmmenu_generator.rb
└── mmmenu.rb
├── Gemfile
├── .gitignore
├── Rakefile
├── Gemfile.lock
├── mmmenu.gemspec
└── README.markdown
/VERSION:
--------------------------------------------------------------------------------
1 | 0.5.2
--------------------------------------------------------------------------------
/.rspec:
--------------------------------------------------------------------------------
1 | --color
2 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/lib/mmmenu/version.rb:
--------------------------------------------------------------------------------
1 | class Mmmenu
2 | VERSION = '0.1'
3 | end
--------------------------------------------------------------------------------
/lib/generators/templates/views/mmmenu/_level_1.erb:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/generators/templates/views/mmmenu/_level_2.erb:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/lib/generators/templates/views/mmmenu/_item.erb:
--------------------------------------------------------------------------------
1 | <%= link_to text, link, options[:html_options] %>
2 |
--------------------------------------------------------------------------------
/lib/generators/templates/views/mmmenu/_current_item.erb:
--------------------------------------------------------------------------------
1 | <%= link_to text, link, options[:html_options].merge({:class => "current"}) %>
2 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source "http://rubygems.org"
2 |
3 | group :development do
4 | gem "bundler"
5 | gem "rspec"
6 | gem "jeweler"
7 | #gem "rcov", ">= 0"
8 | end
9 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # rcov generated
2 | coverage
3 |
4 | # rdoc generated
5 | rdoc
6 |
7 | # yard generated
8 | doc
9 | .yardoc
10 |
11 | # bundler
12 | .bundle
13 |
14 | # jeweler generated
15 | pkg
16 |
--------------------------------------------------------------------------------
/lib/mmmenu/engine.rb:
--------------------------------------------------------------------------------
1 | class Mmmenu::Engine < Rails::Engine
2 |
3 | paths["app/helpers"] << File.expand_path("../../generators/templates/helpers", __FILE__)
4 | paths["app/views"] << File.expand_path("../../generators/templates/views", __FILE__)
5 |
6 | end if defined?(Rails::Engine)
7 |
8 | ActionController::Base.class_eval do
9 |
10 | private
11 |
12 | def mmmenu(&block)
13 | @menu = Mmmenu.new(:request => request, &block)
14 | end
15 |
16 | end if defined?(ActionController::Base)
17 |
--------------------------------------------------------------------------------
/lib/mmmenu/level.rb:
--------------------------------------------------------------------------------
1 | class Mmmenu
2 |
3 | class Level
4 |
5 | def initialize(&block)
6 | @items = []
7 | yield(self)
8 | end
9 |
10 | def add(title, href, options={}, &block)
11 | children = {}
12 | if block_given? # which means current item has children
13 | children = { :children => self.class.new(&block).to_a }
14 | end
15 | @items << { :title => title, :href => href }.merge(options).merge(children)
16 | end
17 |
18 | def to_a
19 | @items
20 | end
21 |
22 | end
23 |
24 | end
25 |
--------------------------------------------------------------------------------
/lib/generators/mmmenu/mmmenu_generator.rb:
--------------------------------------------------------------------------------
1 | class MmmenuGenerator < Rails::Generators::Base
2 |
3 | source_root File.expand_path("../../templates", __FILE__)
4 |
5 | def create_helper
6 | copy_file "helpers/mmmenu_helper.rb", "app/helpers/mmmenu_helper.rb"
7 | copy_file "views/mmmenu/_item.erb", "app/views/mmmenu/_item.erb"
8 | copy_file "views/mmmenu/_current_item.erb", "app/views/mmmenu/_current_item.erb"
9 | copy_file "views/mmmenu/_level_1.erb", "app/views/mmmenu/_level_1.erb"
10 | copy_file "views/mmmenu/_level_2.erb", "app/views/mmmenu/_level_2.erb"
11 | end
12 |
13 |
14 | end
15 |
--------------------------------------------------------------------------------
/spec/lib/mmmenu_level_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/../../lib/mmmenu/level')
2 |
3 | describe Mmmenu::Level do
4 |
5 | it "builds a menu level hash representation" do
6 | @menu_level = Mmmenu::Level.new do |l1|
7 | l1.add "Title1", "/path1"
8 | l1.add "Title2", "/path2" do |l2|
9 | l2.add "SubTitle", "/path2/subpath"
10 | end
11 | end
12 |
13 | expect(@menu_level.to_a).to eq [
14 | {:title => "Title1", :href => "/path1"},
15 | {:title => "Title2", :href => "/path2", :children => [
16 | { :title => "SubTitle", :href => "/path2/subpath" }
17 | ]}
18 | ]
19 | end
20 |
21 | end
22 |
--------------------------------------------------------------------------------
/lib/generators/templates/helpers/mmmenu_helper.rb:
--------------------------------------------------------------------------------
1 | module MmmenuHelper
2 |
3 | def build_mmmenu(menu, options = {})
4 | return nil unless menu
5 | options = { templates_path: 'mmmenu' }.merge(options)
6 | templates_path = options[:templates_path]
7 | menu.item_markup(1) do |link, text, options|
8 | render(:partial => "#{templates_path}/item", :locals => { :link => link, :text => text, :options => options })
9 | end
10 | menu.current_item_markup(1) do |link, text, options|
11 | render(:partial => "#{templates_path}/current_item", :locals => { :link => link, :text => text, :options => options })
12 | end
13 | menu.level_markup(1) { |m| render(:partial => "#{templates_path}/level_1", :locals => { :menu => m }) }
14 | menu.level_markup(2) { |m| render(:partial => "#{templates_path}/level_2", :locals => { :menu => m }) }
15 | menu.build.html_safe
16 | end
17 |
18 | end
19 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | # encoding: utf-8
2 |
3 | require 'rubygems'
4 | require 'bundler'
5 | require 'rspec/core/rake_task'
6 |
7 | begin
8 | Bundler.setup(:default, :development)
9 | rescue Bundler::BundlerError => e
10 | $stderr.puts e.message
11 | $stderr.puts "Run `bundle install` to install missing gems"
12 | exit e.status_code
13 | end
14 | require 'rake'
15 |
16 | require 'jeweler'
17 | Jeweler::Tasks.new do |gem|
18 | # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
19 | gem.name = "mmmenu"
20 | gem.homepage = "http://github.com/snitko/mmmenu"
21 | gem.license = "MIT"
22 | gem.summary = %Q{Flexible menu generator for Rails.}
23 | gem.description = %Q{Defines multilevel menu structures, uses custom html templates to render them.}
24 | gem.email = "roman.snitko@gmail.com"
25 | gem.authors = ["Roman Snitko"]
26 | # dependencies defined in Gemfile
27 | end
28 | Jeweler::RubygemsDotOrgTasks.new
29 |
30 | RSpec::Core::RakeTask.new(:default)
31 |
--------------------------------------------------------------------------------
/Gemfile.lock:
--------------------------------------------------------------------------------
1 | GEM
2 | remote: http://rubygems.org/
3 | specs:
4 | addressable (2.3.6)
5 | builder (3.2.2)
6 | descendants_tracker (0.0.4)
7 | thread_safe (~> 0.3, >= 0.3.1)
8 | diff-lcs (1.2.5)
9 | faraday (0.9.0)
10 | multipart-post (>= 1.2, < 3)
11 | git (1.2.7)
12 | github_api (0.11.3)
13 | addressable (~> 2.3)
14 | descendants_tracker (~> 0.0.1)
15 | faraday (~> 0.8, < 0.10)
16 | hashie (>= 1.2)
17 | multi_json (>= 1.7.5, < 2.0)
18 | nokogiri (~> 1.6.0)
19 | oauth2
20 | hashie (3.1.0)
21 | highline (1.6.21)
22 | jeweler (2.0.1)
23 | builder
24 | bundler (>= 1.0)
25 | git (>= 1.2.5)
26 | github_api
27 | highline (>= 1.6.15)
28 | nokogiri (>= 1.5.10)
29 | rake
30 | rdoc
31 | json (1.8.1)
32 | jwt (1.0.0)
33 | mini_portile (0.6.0)
34 | multi_json (1.10.1)
35 | multi_xml (0.5.5)
36 | multipart-post (2.0.0)
37 | nokogiri (1.6.2.1)
38 | mini_portile (= 0.6.0)
39 | oauth2 (0.9.4)
40 | faraday (>= 0.8, < 0.10)
41 | jwt (~> 1.0)
42 | multi_json (~> 1.3)
43 | multi_xml (~> 0.5)
44 | rack (~> 1.2)
45 | rack (1.5.2)
46 | rake (10.3.2)
47 | rdoc (4.1.1)
48 | json (~> 1.4)
49 | rspec (3.0.0)
50 | rspec-core (~> 3.0.0)
51 | rspec-expectations (~> 3.0.0)
52 | rspec-mocks (~> 3.0.0)
53 | rspec-core (3.0.2)
54 | rspec-support (~> 3.0.0)
55 | rspec-expectations (3.0.2)
56 | diff-lcs (>= 1.2.0, < 2.0)
57 | rspec-support (~> 3.0.0)
58 | rspec-mocks (3.0.2)
59 | rspec-support (~> 3.0.0)
60 | rspec-support (3.0.2)
61 | thread_safe (0.3.4)
62 |
63 | PLATFORMS
64 | ruby
65 |
66 | DEPENDENCIES
67 | bundler
68 | jeweler
69 | rspec
70 |
--------------------------------------------------------------------------------
/mmmenu.gemspec:
--------------------------------------------------------------------------------
1 | # Generated by jeweler
2 | # DO NOT EDIT THIS FILE DIRECTLY
3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4 | # -*- encoding: utf-8 -*-
5 | # stub: mmmenu 0.5.2 ruby lib
6 |
7 | Gem::Specification.new do |s|
8 | s.name = "mmmenu"
9 | s.version = "0.5.2"
10 |
11 | s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
12 | s.require_paths = ["lib"]
13 | s.authors = ["Roman Snitko"]
14 | s.date = "2014-07-07"
15 | s.description = "Defines multilevel menu structures, uses custom html templates to render them."
16 | s.email = "roman.snitko@gmail.com"
17 | s.extra_rdoc_files = [
18 | "README.markdown"
19 | ]
20 | s.files = [
21 | ".rspec",
22 | "Gemfile",
23 | "Gemfile.lock",
24 | "README.markdown",
25 | "Rakefile",
26 | "VERSION",
27 | "lib/generators/mmmenu/mmmenu_generator.rb",
28 | "lib/generators/templates/helpers/mmmenu_helper.rb",
29 | "lib/generators/templates/views/mmmenu/_current_item.erb",
30 | "lib/generators/templates/views/mmmenu/_item.erb",
31 | "lib/generators/templates/views/mmmenu/_level_1.erb",
32 | "lib/generators/templates/views/mmmenu/_level_2.erb",
33 | "lib/mmmenu.rb",
34 | "lib/mmmenu/engine.rb",
35 | "lib/mmmenu/level.rb",
36 | "lib/mmmenu/version.rb",
37 | "mmmenu.gemspec",
38 | "spec/lib/mmmenu_level_spec.rb",
39 | "spec/lib/mmmenu_spec.rb",
40 | "spec/spec_helper.rb"
41 | ]
42 | s.homepage = "http://github.com/snitko/mmmenu"
43 | s.licenses = ["MIT"]
44 | s.rubygems_version = "2.2.2"
45 | s.summary = "Flexible menu generator for Rails."
46 |
47 | if s.respond_to? :specification_version then
48 | s.specification_version = 4
49 |
50 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
51 | s.add_development_dependency(%q, [">= 0"])
52 | s.add_development_dependency(%q, [">= 0"])
53 | s.add_development_dependency(%q, [">= 0"])
54 | else
55 | s.add_dependency(%q, [">= 0"])
56 | s.add_dependency(%q, [">= 0"])
57 | s.add_dependency(%q, [">= 0"])
58 | end
59 | else
60 | s.add_dependency(%q, [">= 0"])
61 | s.add_dependency(%q, [">= 0"])
62 | s.add_dependency(%q, [">= 0"])
63 | end
64 | end
65 |
66 |
--------------------------------------------------------------------------------
/spec/lib/mmmenu_spec.rb:
--------------------------------------------------------------------------------
1 | require File.expand_path(File.dirname(__FILE__) + '/../../lib/mmmenu/level')
2 | require File.expand_path(File.dirname(__FILE__) + '/../../lib/mmmenu')
3 |
4 | describe Mmmenu do
5 |
6 | before(:all) do
7 | @items = [
8 | { :name => :numbers, :title => 'Item1', :children => [
9 | { :title => 'Create', :href => '/items1/new', :paths => ['/items1/new'] },
10 | { :title => 'Index', :href => '/items1/' },
11 | { :title => 'Print', :href => '/items1/print' }
12 | ]},
13 | { :title => 'Item2', :href => '/items2' },
14 | { :title => 'Item3', :href => '/items3' },
15 | { :title => 'Item4', :href => '/items4' }
16 | ]
17 | end
18 |
19 | before(:each) do
20 | @request = double('request')
21 | allow(@request).to receive(:path).and_return('/items1/new')
22 | allow(@request).to receive(:method).and_return('get')
23 | allow(@request).to receive(:params).and_return({})
24 | @menu = Mmmenu.new(:items => @items, :request => @request )
25 | end
26 |
27 | it "renders one level" do
28 | set_menu_markup
29 | @menu.item_markup(2) do |link, text, options|
30 | " #{text}: #{link}\n"
31 | end
32 | @menu.current_item_markup(2) do |link, text, options|
33 | " #{text}: #{link} current\n"
34 | end
35 | @menu.level_markup(1) { |menu| menu }
36 | expect(@menu.build).to eq < 'item1', :href => '/item1', :paths => [['/item', 'post']] },
52 | { :title => 'item2', :href => '/item2', :paths => [['/item', 'get']] }
53 | ]
54 | request = double('request')
55 | allow(request).to receive(:path).once.and_return('/item')
56 | allow(request).to receive(:method).once.and_return('get')
57 | allow(request).to receive(:params).once.and_return({})
58 | @menu = Mmmenu.new(:items => items, :request => request )
59 | set_menu_markup
60 | expect(@menu.build).to eq < 'item1', :href => '/item1', :paths => [['/item1', 'get', {:param => 1}]] },
70 | { :title => 'item2', :href => '/item1', :paths => [['/item1', 'get', {:param => 2}]] },
71 | { :title => 'item3', :href => '/item1', :paths => [['/item1', 'get', {:param => nil}]] }
72 | ]
73 | request = double('request')
74 | allow(request).to receive(:path).once.and_return('/item1')
75 | allow(request).to receive(:params).once.and_return({"param" => "1"})
76 | allow(request).to receive(:method).once.and_return('get')
77 | @menu = Mmmenu.new(:items => items, :request => request )
78 | set_menu_markup
79 | expect(@menu.build).to eq < 'item1', :href => '/item1' },
89 | { :title => 'item2', :href => '/item2' },
90 | { :title => 'item3', :href => '/item3' }
91 | ]
92 | request = double('request')
93 | allow(request).to receive(:path).once.and_return('/item1')
94 | allow(request).to receive(:method).once.and_return('get')
95 | allow(request).to receive(:params).once
96 | @menu = Mmmenu.new(:items => items, :request => request )
97 | @menu.current_item = "/item2"
98 | set_menu_markup
99 |
100 | expect(@menu.build).to eq < @request) do |m|
110 | m.add 'Item1', '/items1', :match_subpaths => true
111 | m.add 'Item2', '/items2' do |subm|
112 | subm.add 'New', '/item2/new'
113 | subm.add 'Edit', '/item2/edit'
114 | end
115 | end
116 | set_menu_markup
117 |
118 | expect(@menu.build).to eq < @request) do |m|
130 | m.add 'Item1', '/items1', :match_subpaths => true
131 | m.add 'Item2', '/items2' do |subm|
132 | subm.add 'New', '/item2/new'
133 | subm.add 'Edit', '/item2/edit'
134 | end
135 | end
136 | set_menu_markup
137 |
138 | expect(@menu.build).to eq <"Create", :href=>"/items1/new", :paths=>["/items1/new"], :html_options=>{}})
151 | end
152 |
153 | def set_menu_markup(level=1)
154 | @menu.item_markup(level) do |link, text, options|
155 | "#{text}: #{link}\n"
156 | end
157 | @menu.current_item_markup(level) do |link, text, options|
158 | "#{text}: #{link} current\n"
159 | end
160 | end
161 |
162 | end
163 |
--------------------------------------------------------------------------------
/lib/mmmenu.rb:
--------------------------------------------------------------------------------
1 | require 'mmmenu/level'
2 | require 'mmmenu/engine'
3 |
4 | class Mmmenu
5 |
6 | attr_accessor :current_item, :options
7 |
8 | def initialize(options, &block)
9 | @items = options[:items] || Mmmenu::Level.new(&block).to_a
10 | @current_path = options[:request].path.chomp('/')
11 | @request_params = options[:request].params
12 | @request_type = options[:request].method.to_s.downcase
13 |
14 | @item_markup = []
15 | @current_item_markup = []
16 | @level_markup = []
17 | end
18 |
19 | # The following two methods define
20 | # the markup for each menu item on the current and
21 | # lower levels (unless lower levels are not redefined later).
22 | # Example:
23 | # @menu.item_markup(0, options) do |link, text, options|
24 | # "#{text}"
25 | # end
26 | def item_markup(level, options={}, &block)
27 | level -= 1
28 | @item_markup[level] = { :block => block, :options => options }
29 | end
30 | def current_item_markup(level, options={}, &block)
31 | level -= 1
32 | @current_item_markup[level] = { :block => block, :options => options }
33 | end
34 |
35 | # Defines the markup wrapper for the current menu level and
36 | # lower menu levels (unless lower levels are not redefined later).
37 | # Example:
38 | # @menu.level_markup(0) { |level_content| "#{level_content}
" }
39 | def level_markup(level=1, &block)
40 | level -= 1
41 | @level_markup[level] = block
42 | end
43 |
44 | def build
45 | result = build_level[:output]
46 | @current_item = @deepest_current_item
47 | result
48 | end
49 |
50 | private
51 |
52 | def build_level(items=@items, level=0)
53 |
54 | item_markup = build_item_markup(level)
55 | level_markup = build_level_markup(level)
56 |
57 | # Parsing of a single menu level happens here
58 | output = ''
59 | has_current_item = false
60 |
61 | raise("Mmmenu object #{self} is empty, no items defined!") if items.nil? or items.empty?
62 | items.each do |item|
63 |
64 | item[:html_options] = {} unless item[:html_options]
65 | child_menu = build_level(item[:children], level+1) if item[:children]
66 | child_output = child_menu[:output] if child_menu
67 |
68 | #############################################################
69 | # Here's where we check if the current item is a current item
70 | # and we should use a special markup for it
71 | #############################################################
72 | if (
73 | item[:href] == current_item or
74 | ( current_item.nil? && (
75 | item_paths_match?(item) or
76 | (child_menu and child_menu[:has_current_item]) or
77 | item_href_match?(item)
78 | ))
79 | ) && !has_current_item
80 |
81 | then
82 | unless item[:children]
83 | @deepest_current_item = item
84 | end
85 | has_current_item = true
86 | item_output = item_markup[:current][:block].call(item[:href], item[:title], item_markup[:current][:options].merge(html_options: {}).merge(item.reject { |k,v| [:href, :title].include?(k) }))
87 | else
88 | item_output = item_markup[:basic][:block].call(item[:href], item[:title], item_markup[:basic][:options].merge(html_options: {}).merge(item.reject { |k,v| [:href, :title].include?(k) }))
89 | end
90 | #############################################################
91 |
92 | output += "#{item_output}#{child_output}"
93 | end
94 |
95 | output = level_markup.call(output)
96 | { :has_current_item => has_current_item, :output => output }
97 |
98 | end
99 |
100 |
101 | # Matches menu item against :paths option
102 | def item_paths_match?(item)
103 | if item[:paths]
104 |
105 | item[:paths].each do |path|
106 | if path.kind_of?(Array)
107 | # IF path matches perfectly
108 | request_type_option = path[1] || ""
109 | if ((@current_path == path[0].chomp('/') and @request_type == request_type_option.downcase) or
110 | # OR IF * wildcard is used and path matches
111 | (path[0] =~ /\*$/ and @current_path =~ /^#{path[0].chomp('*')}(.+)?$/)) and
112 | # all listed request params match
113 | params_match?(path)
114 | return true
115 | end
116 | else
117 | return true if @current_path == path
118 | end
119 | end
120 |
121 | end
122 | return false
123 | end
124 |
125 | # Matches menu item against the actual path it's pointing to.
126 | # Is only applied when :path option is not present.
127 | def item_href_match?(item)
128 | if item[:href]
129 | item_href = item[:href].chomp('/')
130 | if (@current_path == item_href) or # IF path matches perfectly
131 | (item[:match_subpaths] and @current_path =~ /^#{item_href}(\/.+)?$/) # OR IF :match_subpaths options is used and path matches
132 | return true
133 | end
134 | end unless item[:paths]
135 | return false
136 | end
137 |
138 | def build_item_markup(level)
139 | if @item_markup[level]
140 | { :basic => @item_markup[level], :current => @current_item_markup[level] }
141 | else
142 | unless @item_markup.empty?
143 | { :basic => @item_markup.last, :current => @current_item_markup.last }
144 | else
145 | { :basic => {:block => lambda { |link,text,options| "#{text} #{link} #{options}\n" }, :options => {} }, :current => { :block => lambda { |link,text,options| "#{text} #{link} #{options} current\n" }, :options => {} } }
146 | end
147 | end
148 | end
149 |
150 | def build_level_markup(level)
151 | if @level_markup[level]
152 | @level_markup[level]
153 | else
154 | if @level_markup.empty?
155 | lambda { |menu| menu }
156 | else
157 | @level_markup.last
158 | end
159 | end
160 | end
161 |
162 | def params_match?(path)
163 | path[2].each do |k,v|
164 | return false unless (@request_params[k.to_s].nil? and v.nil?) or @request_params[k.to_s] == v.to_s
165 | end if path[2]
166 | true
167 | end
168 |
169 | end
170 |
--------------------------------------------------------------------------------
/README.markdown:
--------------------------------------------------------------------------------
1 | Mmmenu
2 | ======
3 |
4 | *Flexible menu generator for rails*
5 |
6 | Why another menu plugin?
7 | ------------------------
8 | All menu plugins I've seen have HTML markup hardcoded into them.
9 | *Mmmenu* offers you a chance to define your own markup, along with
10 | a nice DSL to describe multi-level menu structures.
11 |
12 | INSTALLATION
13 | ------------
14 |
15 | 1. gem install mmmenu
16 | 2. rails generate mmmenu (optional)
17 |
18 | Basic Usage
19 | ---------------
20 |
21 | Imagine you put this into your controller:
22 |
23 | mmmenu do |l1|
24 |
25 | l1.add "Articles", "/articles" do |l2|
26 | l2.add "Create article", new_article_path
27 | l2.add "Articles authors", "/articles/authors", :match_subpaths => true
28 | end
29 | l1.add "Item2", "/path2"
30 | l1.add "Item3", "/path3"
31 | l1.add "Item4", "/path4"
32 |
33 | end
34 |
35 | As you can see, we specify the paths, so our menu does not depend on the routes.
36 | `#mmmenu` method automatically puts your menu into @menu instance variable. If you wish to use another variable,
37 | you may use a more explicit syntax:
38 |
39 | @my_menu = Mmmenu.new(:request => request) { |l1| ... }
40 |
41 | Now let's see what happens in the views:
42 |
43 | <%= build_mmmenu(@menu) %>
44 |
45 | And that's it, you get your menu rendered.
46 |
47 | Customizing Views
48 | ------------------------
49 | Now, like I promised, the html-markup is totally
50 | configurable.
51 |
52 | Run `rails generate mmmenu`, you'll get your app/helpers/mmmenu_helper.rb file and a bunch of templates copied out of the plugin views/mmmenu directory into app/views/mmmenu directory, thus replacing the plugin default files. Here's what those template files are and what they mean:
53 |
54 | `_current_item.erb` is responsible for the current item in the menu
55 | `_item.erb` is responsible for the non-current items
56 | `level_1.erb` is a wrapper for menu level 1
57 | `level_2.erb` is a wrapper for menu level 2 (submenu)
58 |
59 | You can also has various templates for various menus on your page. Simply, provide a :templates_path option to #build_mmmenu helper like this:
60 |
61 | <%= build_mmmenu(@menu, templates_path: 'mmmenu/my_custom_menu') %>
62 |
63 | Then you can put all the same files mentioned above in this directory and change them. This is useful when you have various types of menus
64 | requiring different html-markup. If you wish to customize deeper levels of menus and items in them, you should take a look at the generated
65 | `mmmenu_helper.rb` file.
66 |
67 | Customizing the Helper
68 | ----------------------------
69 |
70 | Let's take a look at what's inside this helper:
71 |
72 | def build_mmmenu(menu, options = {})
73 | return nil unless menu
74 | options = {templates_path: 'mmmenu' }.merge(options)
75 | templates_path = options[:templates_path]
76 | menu.item_markup(1) do |link, text, options|
77 | render(:partial => "#{templates_path}/item", :locals => { :link => link, :text => text, :options => options })
78 | end
79 | menu.current_item_markup(1) do |link, text, options|
80 | render(:partial => "#{templates_path}/current_item", :locals => { :link => link, :text => text, :options => options })
81 | end
82 | menu.level_markup(1) { |m| render(:partial => "#{templates_path}/level_1", :locals => { :menu => m }) }
83 | menu.level_markup(2) { |m| render(:partial => "#{templates_path}/level_2", :locals => { :menu => m }) }
84 | menu.build.html_safe
85 | end
86 |
87 | You can see now, that `#item_markup` method defines the html markup for menu item,
88 | and `#level_markup` does the same for menu level wrapper. The first argument is the menu level.
89 | You may define as much levels as you want, but you don't need to define markup for each level: the deepest level of the markup
90 | defined will be used for all of the deeper levels.
91 |
92 | By default, build_mmmenu helper defines two partial templates for level 1 of the menu.
93 | It does so by calling two methods on a Mmmenu object:
94 | #current_item_markup defines markup for the current menu item
95 | #item_markup defines markup for all other menu items
96 | Both methods accept an optional second argument - a hash of html_options:
97 |
98 | menu.item_markup(1, :class => "mmmenu")
99 |
100 | which is later used in the templates like this:
101 |
102 | <%= link_to text, link, options %>
103 |
104 | Note, that this is an example from a default template and options hash may not be present in the customized template.
105 |
106 | Disclaimer: if you call Mmmenu#item_markup for a certain level, you MUST call Mmmenu#current_item_markup
107 | for the same level.
108 |
109 | Most of the time, you will want to customize your views, not the helper, so you may as well delete it from your application/helpers dir.
110 |
111 |
112 | Finally, let's take a closer look at some of the options and what they mean.
113 | ---------------------------------------------------------------------
114 | ##### Current item
115 | Mmmenu automatically marks each current menu_item with the markup, that you provide in `#current_item_markup` method's block. The item is considered current not only if the paths
116 | match, but also if one of the children of the item is marked as current. You may as well set the current item manually like that:
117 |
118 | @menu.current_item = '/articles'
119 |
120 | In this case `'/articles'` would match against the second argument that you pass to the `#add` method, which is the path the menu itme points to.
121 | Note that unless the item with such path is not present in the menu, then no item will be makred as current. It is a useful technique when you want
122 | to prevent the item from being current in the controller. For example:
123 |
124 | @menu.current_item = '/search-results-pertending-to-be-a-list' if search_query_empty?
125 |
126 | ##### Paths
127 | For each menu item you may specify a number of paths, that should match for the item to be active.
128 | Unless you provide the `:path` option for `Mmmenu#add`, the second argument is used as the matching path.
129 | If you'd like to specify paths explicitly, do something like this:
130 |
131 | `l1.add "Articles" articles_path, :paths => [[new_article_path, 'get'], [articles_path, 'post'], [articles_path, 'get']]`
132 |
133 | This way, the menu item will appear active even when you're on the /articles/new page.
134 | There's also a third array element, which must be hash. In it may list request_params that should match, for example:
135 |
136 | `l1.add "Articles" articles_path, :paths => [[articles_path, 'get', {:filter => 'published'}]]`
137 |
138 | That way, only a request to "/articles?filter=published" will make this menu item active.
139 | Of course it doesn't matter, if the request contains some other params. But you can make sure it doesn't by saying something like `{:personal => nil}`
140 |
141 | Alernatively, you can do this:
142 |
143 | l1.add "Articles" articles_path, :match_subpaths => true
144 |
145 | Or you may use wildcards:
146 |
147 | l1.add "Articles" articles_path, :paths => [["/articles/*"]]
148 |
149 | That's much easier usually.
150 |
--------------------------------------------------------------------------------