mobile_view 0.2.0 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore CHANGED
@@ -5,3 +5,6 @@ spec/dummy/db/*.sqlite3
5
5
  spec/dummy/log/*.log
6
6
  spec/dummy/tmp/
7
7
  spec/dummy/.sass-cache
8
+ .yardoc/
9
+ doc/
10
+ .DS_Store
@@ -0,0 +1,3 @@
1
+ --protected
2
+ --no-private
3
+
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- mobile_view (0.2.0)
4
+ mobile_view (0.3.0)
5
5
  rack-mobile-detect (~> 0.4.0)
6
6
  rails (~> 3.2.0)
7
7
 
@@ -1,48 +1,104 @@
1
1
  # MobileView
2
2
 
3
- Rails view template for mobile devices made easy.
3
+ Easily specify mobile-specific view template for mobile devices in Rails application.
4
+
5
+ **Warning** This gem still has major version of `0` which means it's during early alpha and APIs and usages are subject to change. Strategies and implementations may not be the best practice. Bugs may exist. Feel free to send patches for everyting that you think it's not good or would like to change. I'll add your name to README after approved.
4
6
 
5
7
  ## Installation
6
8
 
7
9
  Add this line to your application's Gemfile:
8
10
 
9
- gem "mobile_view", "~> 0.2.0"
11
+ gem "mobile_view", "~> 0.3.0"
10
12
 
11
13
  And then execute:
12
14
 
13
15
  $ bundle
14
16
 
17
+ Add these lines into your base controller:
18
+
19
+ ```ruby
20
+ class ApplicationController < ActionController::Base
21
+ has_mobile_view
22
+ end
23
+ ```
24
+
15
25
  ## Dependency
16
26
 
17
27
  * Rails 3.2.x
18
28
 
29
+ Development dependency:
30
+
31
+ * RSpec
32
+ * Capybara
33
+ * [PhantomJS](http://phantomjs.org/) (Capybara's integration test is driven by [poltergeist](https://github.com/jonleighton/poltergeist), for easier cookie and header modification.)
34
+
19
35
  ## Usage
20
36
 
21
37
  ### `action.mobile.html.erb` View Template
22
38
 
23
- #### Scenario
39
+ If you have problems like this:
40
+
41
+ 1. You have a view template for `posts#show`, with many DOM elements and JavaScripts, which is good for desktop, but painful for mobile devices. (In such case even Responsive Web Design with CSS media queries doesn't help you much.)
42
+ * You're tired of `<%= render_something if mobile? %>` conditional hell.
43
+ * You want to make a mobile-friendly view, which is mostly different from desktop version, build form scratch.
24
44
 
25
- You have a view template for `posts#show`, with many DOM elements and JavaScripts, which is good for desktop, but painful for mobile devices. You're tired of `<%= render_something if mobile? %>` conditional hell. You want to make it mobile-friendly, with a view that is mostly different form scratch.
45
+ With MobileView, you can add `posts/show.mobile.html.erb` along with `posts/show.html.erb`. Then, if the browser is a mobile device, Rails will choose the mobile template first:
26
46
 
27
- #### Solution
47
+ * <tt>posts/show.html.erb</tt> = default view for `posts#show`
48
+ * <tt>posts/show<b>.mobile</b>.html.erb</tt> = mobile view for `posts#show`
28
49
 
29
- Add `posts/show.mobile.html.erb` along with `posts/show.html.erb`. If the browser is a mobile device, then Rails will choose the mobile template first.
50
+ It also works for partial views and layout views:
30
51
 
31
- It works for any format handlers (`erb`, `haml` etc.) and formats (`json`, `js`, `xml` etc.) available in your Rails application. You just have to add `mobile` after the view's name.
52
+ * <tt>posts/_post.html.erb</tt> = default view for partial view `posts/post`
53
+ * <tt>posts/_post<b>.mobile</b>.html.erb</tt> = mobile view for partial view `posts/post`
32
54
 
33
- It also works for partial views. When a mobile version of partial view is not available, it will choose non-mobile version automatically. You just have to add `mobile` after the view's name.
55
+ and
34
56
 
35
- It also works for layout view. So you can now use `views/layouts/application.mobile.html.erb` for mobile devices.
57
+ * <tt>layouts/application.html.erb</tt> = default layout
58
+ * <tt>layouts/application<b>.mobile</b>.html.erb</tt> = mobile layout
59
+
60
+ #### ERb, HAML; JSON, JavaScript etc. All Supported.
61
+
62
+ You can use any template handlers (`erb`, `haml` etc.) and formats (`json`, `js`, `xml` etc.) available in your Rails application. All you have to do is add another view template with `mobile` after the view's main name (before locale, format and handler). It follows the naming rule of Rails's view template files; the only difference is the `.mobile` interpolation:
63
+
64
+ * <tt>prefix/name<i>.locale.format.handler</i></tt> = default view
65
+ * <tt>prefix/name<b>.mobile</b><i>.locale.format.handler</i></tt> = mobile view
66
+
67
+ #### Auto Fallback
68
+
69
+ When a mobile version of partial view is not available, it will automatically fallback to default (non-mobile) version.
36
70
 
37
71
  ### `mobile?` Helper
38
72
 
39
- Still want to detect mobile device in Controller and View? Use `mobile?` helper. It returns `true` if it thinks the browser is a mobile device, `false` otherwise.
73
+ The `mobile?` helper tells you if currently MobileView switched to mobile version or not. It is available in controller and view.
74
+
75
+ The situation of "switched to mobile view" could be:
76
+
77
+ * accessing with a mobile device browser, or
78
+ * manually switched to mobile view (see [cookie-based switching](#cookie-based-mobile-view-switching) below).
79
+
80
+ Returns `true` if it switched to mobile view, `false` otherwise.
81
+
82
+ Example:
83
+
84
+ ```ruby
85
+ # in controller
86
+ @text += "Hello! Mobile" if mobile?
87
+ ```
88
+
89
+ ```erb
90
+ <%# in view %>
91
+ <%= render_advertisement unless mobile? %>
92
+ ```
93
+
94
+ #### Mounted Engine
40
95
 
41
96
  To use `mobile?` helper in a mounted engine, for example, [Rails Cell](https://github.com/apotonick/cells), simply include the `MobileView::ControllerAdditions` module:
42
97
 
43
98
  ```ruby
44
99
  class PostCell < Cell::Rails
45
100
  include MobileView::ControllerAdditions
101
+ has_mobile_view
46
102
  end
47
103
  ```
48
104
 
@@ -50,21 +106,31 @@ end
50
106
 
51
107
  By setting `mobile` cookie, you can force it to load mobile views. This is helpful when debugging the app in desktop browsers, or allowing user to switch to mobile version manually.
52
108
 
109
+ The cookie injection of manual switching should happen **before** `has_mobile_view` is invoked.
110
+
53
111
  Example:
54
112
 
55
113
  ```ruby
56
114
  class AppliactionController < ActionController::Base
57
115
  before_filter :manual_mobile_switching
58
116
 
117
+ has_mobile_view
118
+
59
119
  protected
60
120
  # Detects `_mobile_view` parameter.
61
- # If it is any value other than 0, turn on mobile view,
62
- # otherwise, turn off mobile view.
63
- def manual_mobile_switching
64
- if params[:_mobile_view] != "0"
65
- cookies["mobile"] = true # or anything other than falsy value
66
- else
67
- cookies.delete "mobile" # remove `mobile' cookie
121
+ #
122
+ # If it is 'yes', force turn on mobile view by setting cookie mobile=1;
123
+ # if it is 'no', force turn off mobile view by setting cookie mobile=0;
124
+ # if it is 'auto', remove mobile cookie and fallback to User-Agent mode;
125
+ # otherwise, no effect.
126
+ def mobile_switching
127
+ case params[:_mobile_view]
128
+ when 'yes'
129
+ force_mobile!
130
+ when 'no'
131
+ force_non_mobile!
132
+ when 'auto'
133
+ dismiss_mobile_forcing!
68
134
  end
69
135
  end
70
136
  end
@@ -76,6 +142,17 @@ end
76
142
 
77
143
  According to the algorithm of [Rack::MobileDetect](https://github.com/talison/rack-mobile-detect/), iPad will be seen as a Mobile Device. This may be a fail assumption if you want to show your desktop website to iPad and tablet users. This can (may) be resolved by replacing mobile device detection logic or add more tablet-specific methods.
78
144
 
145
+ Workaround: Force switch to non-mobile version when client is iPad:
146
+
147
+ ```ruby
148
+ class AppliactionController < ActionController::Base
149
+ before_filter :force_non_mobile!, :if => :ipad?
150
+
151
+ # still have to invoke has_mobile_view after force_mobile!
152
+ has_mobile_view
153
+ end
154
+ ```
155
+
79
156
  ## References
80
157
 
81
158
  * [#269 Template Inheritance - RailsCasts](http://railscasts.com/episodes/269-template-inheritance)
@@ -84,10 +161,15 @@ According to the algorithm of [Rack::MobileDetect](https://github.com/talison/ra
84
161
 
85
162
  ## Changelog
86
163
 
164
+ ### 0.3.0
165
+
166
+ * [breaking] Fron now on you have to invoke `has_mobile_view` in controller explicitly.
167
+ * Wrap cookie-based switching into a bunch of helper methods for code readability.
168
+
87
169
  ### 0.2.0
88
170
 
89
171
  * Support cookie-based switching
90
- * Rename helper `mobile_device?` to `mobile?` since it now not only detects mobile device, but also accepts cookie-based switching.
172
+ * [breaking] Rename helper `mobile_device?` to `mobile?` since it now not only detects mobile device, but also accepts cookie-based switching.
91
173
  * Ability to have other mounted engines use `mobile?` helper. (Inspired from [Devise](https://github.com/plataformatec/devise/blob/v2.1.2/lib/devise/controllers/helpers.rb))
92
174
 
93
175
  ### 0.1.0
@@ -95,7 +177,7 @@ According to the algorithm of [Rack::MobileDetect](https://github.com/talison/ra
95
177
  :birthday: First Release
96
178
 
97
179
  * `*.mobile` view template auto-overriding
98
- * Automatically mobile device detection
180
+ * Automatic mobile device detection
99
181
  * `mobile_device?` helper for controller and view
100
182
 
101
183
  ## License
@@ -1,20 +1,60 @@
1
+ require 'mobile_view/forced_switching'
2
+
1
3
  module MobileView
2
4
  module ControllerAdditions
3
5
  extend ActiveSupport::Concern
4
6
 
5
- included do
7
+ include MobileView::ForcedSwitching::ControllerAdditions
8
+
9
+ included do |base|
10
+ base.extend ClassMethods
11
+
6
12
  # make mobile? method a view helper too
7
13
  helper_method :mobile?
14
+ end
15
+
16
+ module ClassMethods
17
+ # Install MobileView into a Controller.
18
+ #
19
+ # By invoking this method, a view-path resolver will be prepended
20
+ # before all the existing resolvers, immediately. If you're going
21
+ # to force mobile switching (see {ForcedSwitching}),
22
+ # then you have to invoke them before +has_mobile_view+.
23
+ #
24
+ # == Example
25
+ #
26
+ # Present non-mobile version to iPad users:
27
+ #
28
+ # class ApplicationController < ActionController::Base
29
+ # before_filter :force_non_mobile!, :if => :ipad?
30
+ #
31
+ # has_mobile_view
32
+ # end
33
+ #
34
+ def has_mobile_view
8
35
 
9
- # find mobile view templates first, if mobile device is detected.
10
- before_filter do
11
- prepend_view_path(MobileView.resolver) if mobile?
36
+ # find mobile view templates first, if mobile device is detected.
37
+ self.before_filter do
38
+ prepend_view_path(MobileView.resolver) if mobile?
39
+ end
12
40
  end
13
41
  end
14
42
 
15
43
  protected
44
+ # Test if currently MobileView uses mobile version of view templates.
45
+ #
46
+ # Situations of "use mobile version" is be determined by the following algorithm:
47
+ #
48
+ # 1. If using {ForcedSwitching::ControllerAdditions forced switching}, then test if force switched to mobile version.
49
+ # 2. Otherwise, automatically test by User-Agent (done by {https://github.com/talison/rack-mobile-detect Rack::MobileDetect}).
50
+ #
51
+ # @return {Boolean}
16
52
  def mobile?
17
- cookies[:mobile].present? || request.headers["X_MOBILE_DEVICE"].present?
53
+ if mobile_forcing?
54
+ return forced_mobile?
55
+ else
56
+ return request.headers["X_MOBILE_DEVICE"].present?
57
+ end
18
58
  end
19
59
  end
20
60
  end
@@ -0,0 +1,107 @@
1
+ module MobileView
2
+ module ForcedSwitching
3
+ # The name of cookie which remembers forced mobile view switching.
4
+ COOKIE_NAME = "mobile"
5
+
6
+ # Value of the cookie to represent "forced to mobile view".
7
+ FORCE_MOBILE_VALUE = "1"
8
+
9
+ # Value of the cookie to represent "forced to non-mobile view".
10
+ FORCE_NON_MOBILE_VALUE = "0"
11
+
12
+ # These controller additions are helpers that you can use
13
+ # in your controller to test if currently it is forcely (manually) switched to mobile
14
+ # version, or non-mobile version.
15
+ #
16
+ # By force-switching to either version, MobileView will take it at the top
17
+ # precedence, and ignore the ascending methods (e.g. by User-Agent).
18
+ #
19
+ # Force-switching should happen before {MobileView::ControllerAdditions::ClassMethods#has_mobile_view has_mobile_view}
20
+ # is invoked. See {MobileView::ControllerAdditions::ClassMethods#has_mobile_view has_mobile_view}'s example.
21
+ #
22
+ # They will be automatically included into the controller
23
+ # that includes {MobileView::ControllerAdditions}. You don't need to
24
+ # <tt>include</tt> this module manually.
25
+ #
26
+ # TODO: Unit Testing
27
+ #
28
+ # == Examples
29
+ #
30
+ # === Use as before filters
31
+ #
32
+ # class AppliactionController < ActionController::Base
33
+ # before_filter :force_mobile!, :if => :ie6?
34
+ # before_filter :force_non_mobile!, :if => :ipad?
35
+ # has_mobile_view
36
+ # end
37
+ #
38
+ # === Manual Mobile Switching
39
+ #
40
+ # A user can manually switch to mobile version or non-mobile version by specifying `_mobile_view` parameter.
41
+ #
42
+ # For example:
43
+ #
44
+ # * <tt>?_mobile_view=yes</tt> switches to mobile view.
45
+ # * <tt>?_mobile_view=no</tt> switches to non-mobile view.
46
+ # * <tt>?_mobile_view=auto</tt> don't force switch; determine by User-Agent.
47
+ #
48
+ # To accomplish the logic above:
49
+ #
50
+ # class AppliactionController < ActionController::Base
51
+ # before_filter :manual_mobile_switching
52
+ #
53
+ # has_mobile_view
54
+ #
55
+ # protected
56
+ #
57
+ # # Detects `_mobile_view` parameter.
58
+ # #
59
+ # # If it is 'yes', force turn on mobile view by setting cookie mobile=1;
60
+ # # if it is 'no', force turn off mobile view by setting cookie mobile=0;
61
+ # # if it is 'auto', remove mobile cookie and fallback to User-Agent mode;
62
+ # # otherwise, no effect.
63
+ # def mobile_switching
64
+ # case params[:_mobile_view]
65
+ # when 'yes'
66
+ # force_mobile!
67
+ # when 'no'
68
+ # force_non_mobile!
69
+ # when 'auto'
70
+ # dismiss_mobile_forcing!
71
+ # end
72
+ # end
73
+ # end
74
+ module ControllerAdditions
75
+ protected
76
+ # Force switch to mobile view.
77
+ def force_mobile!
78
+ cookies[COOKIE_NAME] = FORCE_MOBILE_VALUE
79
+ end
80
+
81
+ # Force switch to non-mobile (default) view.
82
+ def force_non_mobile!
83
+ cookies[COOKIE_NAME] = FORCE_NON_MOBILE_VALUE
84
+ end
85
+
86
+ # Dismiss any forced switching, and fallback to other methods like User-Agent sniffing
87
+ def dismiss_mobile_forcing!
88
+ cookies.delete COOKIE_NAME
89
+ end
90
+
91
+ # Test if currently forced to mobile or non-mobile view.
92
+ def mobile_forcing?
93
+ forced_mobile? || forced_non_mobile?
94
+ end
95
+
96
+ # Test if currently forced to mobile view.
97
+ def forced_mobile?
98
+ cookies[COOKIE_NAME] == FORCE_MOBILE_VALUE
99
+ end
100
+
101
+ # Test if currently forced to non-mobile view.
102
+ def forced_non_mobile?
103
+ cookies[COOKIE_NAME] == FORCE_NON_MOBILE_VALUE
104
+ end
105
+ end
106
+ end
107
+ end
@@ -1,7 +1,41 @@
1
1
  module MobileView
2
+
3
+ # Resolver for mobile view templates; search for +*.mobile+ templates.
4
+ #
5
+ # When invoking {ControllerAdditions::ClassMethods#has_mobile_view has_mobile_view}, and MobileView thinks
6
+ # it should choose mobile version first, this resolver will be prepended to the
7
+ # existing +view_paths+, makes it become the first precedence of template searching.
8
+ #
9
+ # Searching is done by finding templates with the following pattern, which is same as
10
+ # the convension of template file naming rule, except for +.mobile+ injection:
11
+ #
12
+ # prefix/action.mobile.locale.formats.handlers
13
+ # ^^^^^^^
14
+ # hard-coded
15
+ #
16
+ # Pattern is modified from {https://github.com/rails/rails/blob/v3.2.9/actionpack/lib/action_view/template/resolver.rb#L106 ActionView::PathResolver}
17
+ #
18
+ # == Algorithm Explanation
19
+ #
20
+ # For example, when there exists the following two templates:
21
+ #
22
+ # * posts/show.html.erb
23
+ # * posts/show.mobile.html.erb
24
+ #
25
+ # When MobileView thinks currently it is switched to mobile version (see {ControllerAdditions#mobile?}),
26
+ # then a {Resolver} will be prepended to the search pathes,
27
+ # and Rails will get +posts/show.mobile.html.erb+ as the first available template.
28
+ # When MobileView thinks it is not switched to mobile version, then this {Resolver} will not be
29
+ # prepended, and Rails will search for templates as usual.
30
+ #
31
+ # Since Rails's template searching is implemented by auto-fallback strategy,
32
+ # if a corresponding +*.mobile+ doesn't exist, then this {Resolver}
33
+ # will return nothing, and Rails will automatically use other resolvers.
34
+ #
2
35
  class Resolver < ::ActionView::FileSystemResolver
3
- # pattern is modified from ActionView
4
- # https://github.com/rails/rails/blob/v3.2.9/actionpack/lib/action_view/template/resolver.rb#L106
36
+ # <tt>prefix/action<b>.mobile</b>.locale.formats.handlers</tt>
37
+ #
38
+ # Example: <tt>posts/show<b>.mobile</b>.html.erb</tt>
5
39
  MOBILE_PATTERN = ":prefix/:action.mobile{.:locale,}{.:formats,}{.:handlers,}"
6
40
 
7
41
  def initialize(path)
@@ -1,3 +1,3 @@
1
1
  module MobileView
2
- VERSION = "0.2.0"
2
+ VERSION = "0.3.0"
3
3
  end
@@ -1,3 +1,5 @@
1
1
  class ApplicationController < ActionController::Base
2
2
  protect_from_forgery
3
+
4
+ has_mobile_view
3
5
  end
@@ -8,9 +8,9 @@ def use_iphone
8
8
  page.driver.headers = { "User-Agent" => IPHONE_USER_AGNET }
9
9
  end
10
10
 
11
- def set_mobile_cookie
11
+ def set_mobile_cookie(value)
12
12
  # depends on poltergeist
13
- page.driver.set_cookie "mobile", true
13
+ page.driver.set_cookie "mobile", value
14
14
  end
15
15
 
16
16
  describe "View Template Overriding" do
@@ -51,9 +51,9 @@ describe "View Template Overriding" do
51
51
  end
52
52
  end
53
53
 
54
- context "when client provides mobile cookie" do
54
+ context "when client set cookie mobile=1" do
55
55
  before :each do
56
- set_mobile_cookie
56
+ set_mobile_cookie("1")
57
57
  end
58
58
 
59
59
  it "renders mobile-specific action view template" do
@@ -88,6 +88,43 @@ describe "View Template Overriding" do
88
88
  end
89
89
  end
90
90
 
91
+ context "when client is mobile browser and set cookie mobile=0" do
92
+ before :each do
93
+ set_mobile_cookie("0")
94
+ end
95
+
96
+ it "renders default action view template" do
97
+ visit "/pages/action_view"
98
+
99
+ page.should have_selector('#default-view')
100
+ end
101
+
102
+ it "renders default partial view template" do
103
+ visit "/pages/partial_view"
104
+
105
+ page.should have_selector('#default-partial-view')
106
+ end
107
+
108
+ it "renders default layout view template" do
109
+ visit "/pages/layout_template"
110
+
111
+ page.should have_selector('body.desktop')
112
+ end
113
+
114
+ it "renders default view template when there is no mobile-specific overriding" do
115
+ visit "/pages/default"
116
+
117
+ page.should have_selector('#hello-world')
118
+ end
119
+
120
+ it "renders general content" do
121
+ visit "/pages/conditional"
122
+
123
+ page.should have_selector('#non-mobile-device')
124
+ page.should have_content('Hi Desktop!')
125
+ end
126
+ end
127
+
91
128
  context "when client is not mobile browser" do
92
129
  it "renders default action view template" do
93
130
  visit "/pages/action_view"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: mobile_view
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.3.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-12-22 00:00:00.000000000 Z
12
+ date: 2012-12-27 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: rails
@@ -101,6 +101,7 @@ extra_rdoc_files: []
101
101
  files:
102
102
  - .gitignore
103
103
  - .rspec
104
+ - .yardopts
104
105
  - Gemfile
105
106
  - Gemfile.lock
106
107
  - README.markdown
@@ -108,6 +109,7 @@ files:
108
109
  - lib/mobile_view.rb
109
110
  - lib/mobile_view/controller_additions.rb
110
111
  - lib/mobile_view/engine.rb
112
+ - lib/mobile_view/forced_switching.rb
111
113
  - lib/mobile_view/resolver.rb
112
114
  - lib/mobile_view/version.rb
113
115
  - lib/tasks/mobile_view_tasks.rake
@@ -170,7 +172,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
170
172
  version: '0'
171
173
  segments:
172
174
  - 0
173
- hash: 232344584105567251
175
+ hash: 2535113380593733321
174
176
  required_rubygems_version: !ruby/object:Gem::Requirement
175
177
  none: false
176
178
  requirements:
@@ -179,7 +181,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
179
181
  version: '0'
180
182
  segments:
181
183
  - 0
182
- hash: 232344584105567251
184
+ hash: 2535113380593733321
183
185
  requirements: []
184
186
  rubyforge_project:
185
187
  rubygems_version: 1.8.24