diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ae3fdc2 --- /dev/null +++ b/.gitignore @@ -0,0 +1,14 @@ +/.bundle/ +/.yardoc +/Gemfile.lock +/_yardoc/ +/coverage/ +/doc/ +/pkg/ +/spec/reports/ +/tmp/ +*.bundle +*.so +*.o +*.a +mkmf.log diff --git a/Gemfile b/Gemfile new file mode 100644 index 0000000..dd1c5f5 --- /dev/null +++ b/Gemfile @@ -0,0 +1,4 @@ +source 'https://rubygems.org' + +# Specify your gem's dependencies in live-front-rails.gemspec +gemspec diff --git a/LICENSE.txt b/LICENSE.txt new file mode 100644 index 0000000..cbf8ed0 --- /dev/null +++ b/LICENSE.txt @@ -0,0 +1,22 @@ +Copyright (c) 2014 Andrey Krivko + +MIT License + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..ee0717e --- /dev/null +++ b/README.md @@ -0,0 +1,104 @@ +# Live Front + +Useful helpers used at [Live Typing](http://ltst.ru). + +## List of helpers + +### TabHelper + +Navigation link helper. Returns an `li` element with an 'active' class if the supplied +controller(s) and/or action(s) are currently active. The content of the +element is the value passed to the block. Initially was copied from +[GitLab project](https://gitlab.com/gitlab-org/gitlab-ce). Also it was added ability to specify +`params` option to make link active when current path suits specified params. + +**options** + +The options hash used to determine if the element is "active" (default: {}) + +`:controller` - One or more controller names to check (optional). + +`:action` - One or more action names to check (optional). + +`:path` - A shorthand path, such as 'dashboard#index', to check (optional). + +`:params` - One or more params to check. It also can be specified `:_blank` value to be able to specify either blank or non-blank param (optional). + +`:html_options` - Extra options to be passed to the list element (optional). + +**block** + +An optional block that will become the contents of the returned `li` element. + +When both `:controller` and `:action` are specified, BOTH must match in order +to be marked as active. When only one is given, either can match. + +#### Examples: + +Assuming we're on `TreeController#show` + +``` +# Controller matches, but action doesn't +nav_link(controller: [:tree, :refs], action: :edit) { "Hello" } +# => '
  • Hello
  • ' +``` + +``` +# Controller matches +nav_link(controller: [:tree, :refs]) { "Hello" } +# => '
  • Hello
  • ' +``` + +``` +# Shorthand path +nav_link(path: 'tree#show') { "Hello" } +# => '
  • Hello
  • ' +``` + +``` +# Supplying custom options for the list element +nav_link(controller: :tree, html_options: {class: 'home'}) { "Hello" } +# => '
  • Hello
  • ' +``` + +``` +# Supplying params options when `params[:state] # => 'archived'` +nav_link(path: 'tree#show', params: {state: 'archived'}) { "Hello" } +# => '
  • Hello
  • ' +``` + +``` +# Supplying params options when `params[:state] # => nil` +nav_link(path: 'tree#show', params: {state: 'archived'}) { "Hello" } +# => '
  • Hello
  • ' +``` + +``` +# Supplying params options when `params[:state] # => nil` +nav_link(path: 'tree#show', params: {state: [:_blank, :active]}) { "Hello" } +# => '
  • Hello
  • ' +``` + +## Installation + +Add this line to your application's Gemfile: + +```ruby +gem 'live-front-rails' +``` + +And then execute: + + $ bundle + +Or install it yourself as: + + $ gem install live-front-rails + +## Contributing + +1. Fork it ( https://github.com/LiveTyping/live-front-rails/fork ) +2. Create your feature branch (`git checkout -b my-new-feature`) +3. Commit your changes (`git commit -am 'Add some feature'`) +4. Push to the branch (`git push origin my-new-feature`) +5. Create a new Pull Request diff --git a/Rakefile b/Rakefile new file mode 100644 index 0000000..721edb6 --- /dev/null +++ b/Rakefile @@ -0,0 +1,10 @@ +require 'rspec/core/rake_task' +require 'bundler/gem_tasks' + +# Default directory to look in is `/specs` +# Run with `rake spec` +RSpec::Core::RakeTask.new(:spec) do |task| + task.rspec_opts = ['--color'] +end + +task default: :spec diff --git a/lib/live_front.rb b/lib/live_front.rb new file mode 100644 index 0000000..ecf2bb3 --- /dev/null +++ b/lib/live_front.rb @@ -0,0 +1,13 @@ +require 'live_front/version' +require 'rails' +require 'live_front/application_helper' +require 'live_front/tab_helper' + +module LiveFront + class Engine < ::Rails::Engine + initializer 'live_front.view_helpers' do + ActionView::Base.send :include, LiveFront::ApplicationHelper + ActionView::Base.send :include, LiveFront::TabHelper + end + end +end diff --git a/lib/live_front/application_helper.rb b/lib/live_front/application_helper.rb new file mode 100644 index 0000000..afcdfa5 --- /dev/null +++ b/lib/live_front/application_helper.rb @@ -0,0 +1,31 @@ +module LiveFront + module ApplicationHelper + # Check if a particular controller is the current one + # + # args - One or more controller names to check + # + # Examples + # + # # On TreeController + # current_controller?(:tree) # => true + # current_controller?(:commits) # => false + # current_controller?(:commits, :tree) # => true + def current_controller?(*args) + args.any? { |v| v.to_s.downcase == controller.controller_name } + end + + # Check if a particular action is the current one + # + # args - One or more action names to check + # + # Examples + # + # # On Projects#new + # current_action?(:new) # => true + # current_action?(:create) # => false + # current_action?(:new, :create) # => true + def current_action?(*args) + args.any? { |v| v.to_s.downcase == action_name } + end + end +end diff --git a/lib/live_front/tab_helper.rb b/lib/live_front/tab_helper.rb new file mode 100644 index 0000000..d96eeef --- /dev/null +++ b/lib/live_front/tab_helper.rb @@ -0,0 +1,102 @@ +module LiveFront + # Copied from GitLab project + # https://gitlab.com/gitlab-org/gitlab-ce + module TabHelper + # Navigation link helper + # + # Returns an `li` element with an 'active' class if the supplied + # controller(s) and/or action(s) are currently active. The content of the + # element is the value passed to the block. + # + # options - The options hash used to determine if the element is "active" (default: {}) + # :controller - One or more controller names to check (optional). + # :action - One or more action names to check (optional). + # :path - A shorthand path, such as 'dashboard#index', to check (optional). + # :html_options - Extra options to be passed to the list element (optional). + # block - An optional block that will become the contents of the returned + # `li` element. + # + # When both :controller and :action are specified, BOTH must match in order + # to be marked as active. When only one is given, either can match. + # + # Examples + # + # # Assuming we're on TreeController#show + # + # # Controller matches, but action doesn't + # nav_link(controller: [:tree, :refs], action: :edit) { "Hello" } + # # => '
  • Hello
  • ' + # + # # Controller matches + # nav_link(controller: [:tree, :refs]) { "Hello" } + # # => '
  • Hello
  • ' + # + # # Shorthand path + # nav_link(path: 'tree#show') { "Hello" } + # # => '
  • Hello
  • ' + # + # # Supplying custom options for the list element + # nav_link(controller: :tree, html_options: {class: 'home'}) { "Hello" } + # # => '
  • Hello
  • ' + # + # Returns a list item element String + def nav_link(options = {}, &block) + c, a = fetch_controller_and_action(options) + p = options.delete(:params) || {} + + klass = page_active?(c, a, p) ? 'active' : '' + + + # Add our custom class into the html_options, which may or may not exist + # and which may or may not already have a :class key + o = options.delete(:html_options) || {} + o[:class] = "#{o[:class]} #{klass}".strip + + if block_given? + content_tag(:li, capture(&block), o) + else + content_tag(:li, nil, o) + end + end + + private + + def fetch_controller_and_action(options) + path = options.delete(:path) + + if path + if path.respond_to?(:each) + c = path.map { |p| p.split('#').first } + a = path.map { |p| p.split('#').last } + else + c, a, _ = path.split('#') + end + else + c = options.delete(:controller) + a = options.delete(:action) + end + [c, a] + end + + def page_active?(controller, action, parameters = {}) + match_action_controller = if controller && action + # When given both options, make sure BOTH are active + current_controller?(*controller) && current_action?(*action) + else + # Otherwise check EITHER option + current_controller?(*controller) || current_action?(*action) + end + + match_action_controller && match_params?(parameters) + end + + def match_params?(parameters) + return true if parameters.empty? + + parameters.all? do |key, value| + value = *value + value.any? { |v| v == :_blank ? params[key].to_s.blank? : v.to_s == params[key].to_s } + end + end + end +end diff --git a/lib/live_front/version.rb b/lib/live_front/version.rb new file mode 100644 index 0000000..a7b1f62 --- /dev/null +++ b/lib/live_front/version.rb @@ -0,0 +1,3 @@ +module LiveFront + VERSION = '0.0.1'.freeze +end diff --git a/live-front-rails.gemspec b/live-front-rails.gemspec new file mode 100644 index 0000000..034afa0 --- /dev/null +++ b/live-front-rails.gemspec @@ -0,0 +1,27 @@ +# coding: utf-8 +lib = File.expand_path('../lib', __FILE__) +$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib) +require 'live_front/version' + +Gem::Specification.new do |spec| + spec.name = 'live-front-rails' + spec.version = LiveFront::VERSION + spec.authors = ['Andrey Krivko'] + spec.email = ['jastkand@gmail.com'] + spec.summary = %q{Useful helpers used at Live Typing} + spec.description = %q{Useful helpers used at Live Typing} + spec.homepage = '' + spec.license = 'MIT' + + spec.files = `git ls-files -z`.split("\x0") + spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) } + spec.test_files = spec.files.grep(%r{^(test|spec|features)/}) + spec.require_paths = ['lib'] + + spec.add_dependency 'rails', '~> 4.1' + + spec.add_development_dependency 'bundler', '~> 1.7' + spec.add_development_dependency 'rake', '~> 10.0' + spec.add_development_dependency 'rspec', '~> 3.1.0' + spec.add_development_dependency 'rspec-mocks', '~> 3.1.0' +end diff --git a/spec/helpers/tab_helper_spec.rb b/spec/helpers/tab_helper_spec.rb new file mode 100644 index 0000000..5faf73f --- /dev/null +++ b/spec/helpers/tab_helper_spec.rb @@ -0,0 +1,49 @@ +require 'spec_helper' + +describe LiveFront::TabHelper do + describe '#nav_link' do + before do + allow(self).to receive(:action_name).and_return('foo') + end + + it 'captures block output' do + expect(nav_link { 'Testing Blocks' }).to match(/Testing Blocks/) + end + + it 'performs checks on the current controller' do + expect(nav_link(controller: :foo)).to match(/
  • /) + expect(nav_link(controller: :bar)).not_to match(/active/) + expect(nav_link(controller: [:foo, :bar])).to match(/active/) + end + + it 'performs checks on the current action' do + expect(nav_link(action: :foo)).to match(/
  • /) + expect(nav_link(action: :bar)).not_to match(/active/) + expect(nav_link(action: [:foo, :bar])).to match(/active/) + end + + it 'performs checks on both controller and action when both are present' do + expect(nav_link(controller: :bar, action: :foo)).not_to match(/active/) + expect(nav_link(controller: :foo, action: :bar)).not_to match(/active/) + expect(nav_link(controller: :foo, action: :foo)).to match(/active/) + end + + it 'accepts a path shorthand' do + expect(nav_link(path: 'foo#bar')).not_to match(/active/) + expect(nav_link(path: 'foo#foo')).to match(/active/) + end + + it 'accepts a params value and checks if they are correct' do + expect(nav_link(path: 'foo#foo', params: { p1: :s1 })).to match(/active/) + expect(nav_link(path: 'foo#foo', params: { p2: [:_blank, :s2] })).to match(/active/) + expect(nav_link(path: 'foo#foo', params: { p2: :s1 })).not_to match(/active/) + end + + it 'passes extra html options to the list element' do + expect( + nav_link(action: :foo, html_options: { class: 'home' }) + ).to match(/
  • /) + expect(nav_link(html_options: { class: 'active' })).to match(/
  • /) + end + end +end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb new file mode 100644 index 0000000..b80bf17 --- /dev/null +++ b/spec/spec_helper.rb @@ -0,0 +1,37 @@ +ENV['RAILS_ENV'] ||= 'test' + +require 'active_support' +require 'action_view' +require 'action_controller' + +require 'live_front' + +include ActionView::Context +include ActionView::Helpers::FormTagHelper +include LiveFront::ApplicationHelper +include LiveFront::TabHelper + +class FooController < ActionController::Base + def foo + end +end + +def controller + FooController.new +end + +def params + ActionController::Parameters.new p1: 's1' +end + +require 'rspec/core' +require 'rspec/mocks' + +# RSpec.configure do |config| +# config.before :all do +# include ActionView::Helpers::FormTagHelper +# include LiveFront::ApplicationHelper +# include LiveFront::TabHelper +# include ActionView::Context +# end +# end