viewlet 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ .DS_Store
4
+ Gemfile.lock
5
+ pkg/*
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in viewlet.gemspec
4
+ gemspec
data/MIT.LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Dmitriy Kalinin
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,206 @@
1
+ ## Goals
2
+
3
+ * to ease creation of view components
4
+
5
+ Problem: Most likely your site has few similar view structures that
6
+ are repeated throughout the site (e.g. *list* of members, groups, etc.).
7
+ One solution is to refactor such code into a shared partial
8
+ (may be `_list_section.html.haml`) and pass customization options
9
+ via locals hash; however, with this approach it can become quite
10
+ challenging/inelegant to apply customizations.
11
+
12
+ * to organize HTML/JS/CSS files based on a feature rather than file type
13
+
14
+ Problem: As soon as you start extracting reusable view components
15
+ from your pages it becomes weird to have HTML/CSS/JS component files
16
+ spread out in three different directories. Turning your component into a
17
+ gem remedies that problem since gems can have separate `assets`
18
+ directory; however, I don't see a benefit in making every single
19
+ component into a gem, especially when it's application specific.
20
+
21
+ ## Installation
22
+
23
+ ```ruby
24
+ gem "viewlet", :git => "https://github.com/cppforlife/viewlet.git"
25
+ ```
26
+
27
+ ## Usage
28
+
29
+ Let's say we have `GroupsController#show` that lists group members.
30
+ Here is how `show.html.haml` could look:
31
+
32
+ ```haml
33
+ %h1= "Group: #{@group.name}"
34
+ %p= @group.description
35
+
36
+ = viewlet(:list_section) do |s|
37
+ - s.heading "Group members"
38
+ - s.empty_description "No members in this group"
39
+
40
+ - s.collapse_button false
41
+ - s.add_button do
42
+ = link_to "Invite Members", new_group_member_path(@group)
43
+
44
+ - s.items @group.members
45
+
46
+ - s.row_title do |member|
47
+ .name= member.name
48
+ .summary= member.summary
49
+
50
+ - s.row_details do |member|
51
+ = render :partial => "some_other_partial", :locals => {:member => member}
52
+ ```
53
+
54
+ Now let's define list_section viewlet. Viewlets live in `app/viewlets`
55
+ and each one must have at least `<name>.html.haml`.
56
+
57
+ In `app/viewlets/list_section/list_section.html.haml`:
58
+
59
+ ```haml
60
+ .list_section
61
+ %h2
62
+ = heading
63
+
64
+ - if add_button
65
+ %small= add_button
66
+
67
+ - if collapse_button
68
+ %small.collapse_button= link_to "Collapse", "#"
69
+
70
+ - if items.empty?
71
+ - # outputs value regardless being defined as an argument-less block or a plain value
72
+ %p= empty_description
73
+
74
+ - else
75
+ %ul
76
+ - items.each do |item|
77
+ %li{:class => cycle("odd", "even", :name => :list_section)}
78
+ .left= list_section.row_title(item)
79
+
80
+ - # alternative way of capturing block's content
81
+ .right= capture(item, &row_details)
82
+ ```
83
+
84
+ All viewlet options (heading, add_button, etc.) set in `show.html.haml`
85
+ become available in `list_section.html.haml` as local variables. None of
86
+ those options are special and you can make up as many as you want.
87
+
88
+ Note: If there aren't CSS or JS files you want to keep next to your viewlet
89
+ HTML file you don't need to create a directory for each viewlet; simply
90
+ put them in `app/viewlets` e.g. `app/viewlets/list_section.html.haml`.
91
+
92
+ ### CSS & JS
93
+
94
+ You can also add other types of files to `app/viewlets/list_section/`.
95
+ Idea here is that your viewlet is self-contained and
96
+ encapsulates all needed parts - HTML, CSS, and JS.
97
+
98
+ In `app/viewlets/list_section/plugin.css.scss`:
99
+
100
+ ```scss
101
+ .list_section {
102
+ width: 300px;
103
+
104
+ ul {
105
+ margin: 0;
106
+ }
107
+
108
+ li {
109
+ border: 1px solid #ccc;
110
+ margin-bottom: -1px;
111
+ padding: 10px;
112
+ list-style-type: none;
113
+ overflow: hidden;
114
+ }
115
+
116
+ .left {
117
+ float: left;
118
+ }
119
+
120
+ .right {
121
+ float: right;
122
+ }
123
+ }
124
+ ```
125
+
126
+ To include list_section viewlet CSS in your application add
127
+
128
+ *= require list_section/plugin
129
+
130
+ to your `application.css`
131
+
132
+ In `app/viewlets/list_section/plugin.js`:
133
+
134
+ ```javascript
135
+ // Probably define listSection() jQuery plugin
136
+ ```
137
+
138
+ To include list_section viewlet JS in your application add
139
+
140
+ ```javascript
141
+ //= require list_section/plugin
142
+ ```
143
+
144
+ to your `application.js`
145
+
146
+ ## Misc
147
+
148
+ * Let's say we decide to make our list_section viewlet use
149
+ third-party list re-ordering library (e.g. `orderable-list.js`).
150
+ You can add `orderable-list.js` javascript file to
151
+ `app/viewlets/list_section` and require it from `plugin.js`:
152
+
153
+ ```javascript
154
+ //= require ./orderable-list
155
+ ```
156
+
157
+ * Let's say our `plugin.js` defined jQuery plugin `listSection`
158
+ so that in our `application.js` we can do something like this:
159
+
160
+ ```javascript
161
+ $(document).ready(function(){
162
+ $(".list_section").listSection();
163
+ });
164
+ ```
165
+
166
+ This is fine; however, that means that our component is not
167
+ really functional until we add that javascript piece somewhere.
168
+ Alternatively you can put it right after HTML so everytime
169
+ list_section is rendered it will be automatically initialized.
170
+
171
+ For example in `list_section.html.haml`:
172
+
173
+ ```haml
174
+ .list_section{:id => unique_id}
175
+ %h2= heading
176
+ ...
177
+
178
+ - unless defined?(no_script)
179
+ :javascript
180
+ $(document).ready(function(){
181
+ $("##{unique_id}").listSection();
182
+ });
183
+ ```
184
+
185
+ Every viewlet has a predefined local variable `unique_id`
186
+ that could be used as HTML id.
187
+
188
+ * It's trivial to subclass `Viewlet::Base` to add new functionality.
189
+ `class_name` option lets you set custom viewlet class:
190
+
191
+ ```haml
192
+ = viewlet(:list_section, :class_name => "CustomListSectionViewlet") do
193
+ ...
194
+ ```
195
+
196
+ * You do not have to pass in block to `viewlet`:
197
+
198
+ ```haml
199
+ = viewlet(:password_strength)
200
+ ```
201
+
202
+ ## Todo
203
+
204
+ * come up with a better name for main files - *plugin* doesn't sound that good
205
+ * `lib/viewlets/` as fallback viewlet lookup path
206
+ * automatically load custom Viewlet::Base subclass from `some_viewlet/plugin.rb`
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,17 @@
1
+ .tabbable{:id => unique_id}
2
+ %ul.nav.nav-tabs
3
+ - tabs.each_with_index do |tab, i|
4
+ %li{:class => ("active" if i == 0)}
5
+ = link_to tab, "#tab#{i}"
6
+
7
+ .tab-content
8
+ - tabs.each_with_index do |tab, i|
9
+ .tab-pane{:id => "tab#{i}", :class => ("active" if i == 0)}
10
+ %h1= tab
11
+ = send(tab.underscore)
12
+
13
+ - unless defined?(no_script)
14
+ javascript:
15
+ $(document).ready(function(){
16
+ $("##{unique_id}").tab();
17
+ });
@@ -0,0 +1 @@
1
+ // http://twitter.github.com/bootstrap/assets/js/bootstrap-tab.js
@@ -0,0 +1,8 @@
1
+ = viewlet(:bootstrap_tabs) do |t|
2
+ - t.tabs %w(Movies Books)
3
+
4
+ - t.movies do
5
+ %p Bunch of movies
6
+
7
+ - t.books do
8
+ %p Bunch of books
@@ -0,0 +1,48 @@
1
+ require "viewlet/template"
2
+
3
+ module Viewlet
4
+ class Base
5
+ def initialize(name, view)
6
+ @name = name
7
+ @view = view
8
+ @variables = {
9
+ @name.to_sym => self,
10
+ :unique_id => "viewlet_#{rand(36**20).to_s(36)}"
11
+ }
12
+ end
13
+
14
+ def render
15
+ Template.find(@name.to_s).render(@view, @variables)
16
+ end
17
+
18
+ private
19
+
20
+ def method_missing(method, *args, &block)
21
+ is_write_op = if @variables[method].is_a?(Proc)
22
+ block.present?
23
+ else
24
+ args.any? || block.present?
25
+ end
26
+
27
+ send("_#{is_write_op ? :write : :read}_variable", method, *args, &block)
28
+ end
29
+
30
+ def _read_variable(name, *args, &block)
31
+ if @variables[name].is_a?(Proc)
32
+ @view.capture(*args, &@variables[name])
33
+ else
34
+ @variables[name]
35
+ end
36
+ end
37
+
38
+ def _write_variable(name, *args, &block)
39
+ @variables[name] = if block
40
+ # HAML changes argument-less block {|| } into block {|*a| }
41
+ # which makes block.arity to be -1 instead of just 0
42
+ block.arity == -1 ? @view.capture(&block) : block
43
+ else
44
+ args.first
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,15 @@
1
+ module Viewlet
2
+ module Helpers
3
+ def viewlet(name, options={}, &block)
4
+ klass = options[:class_name].try(:constantize) || Base
5
+ viewlet = klass.new(name, self)
6
+
7
+ case block.arity
8
+ when 0 then block.call
9
+ when 1 then block.call(viewlet)
10
+ end if block_given?
11
+
12
+ viewlet.render
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,11 @@
1
+ module Viewlet
2
+ class Railtie < ::Rails::Railtie
3
+ initializer "viewlets.view_helpers" do
4
+ ActionView::Base.send :include, Viewlet::Helpers
5
+ end
6
+
7
+ config.to_prepare do |app|
8
+ Rails.application.config.assets.paths << Rails.root.join("app", "viewlets")
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,31 @@
1
+ module Viewlet
2
+ class Template
3
+ def self.find(name)
4
+ args = [name, "", false, {:locale => [:en], :formats => [:html], :handlers => [:erb, :haml]}, nil]
5
+ template = path_resolver.find_all(*args).first ||
6
+ raise(ActionView::MissingTemplate.new([path_resolver], *args))
7
+
8
+ # Cannot refresh template because it will try to use
9
+ # view's lookup context which will not include app/viewlets dir
10
+ template.virtual_path = nil
11
+
12
+ new(template)
13
+ end
14
+
15
+ def initialize(template)
16
+ @template = template
17
+ end
18
+
19
+ def render(view, variables={})
20
+ @template.locals = variables.keys
21
+ @template.render(view, variables)
22
+ end
23
+
24
+ private
25
+
26
+ def self.path_resolver
27
+ ActionView::FileSystemResolver.new \
28
+ Rails.root.join("app/viewlets"), "{:action/,}:action{.:locale,}{.:formats,}{.:handlers,}"
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,3 @@
1
+ module Viewlet
2
+ VERSION = "0.0.1"
3
+ end
data/lib/viewlet.rb ADDED
@@ -0,0 +1,7 @@
1
+ require "viewlet/version"
2
+ require "viewlet/base"
3
+ require "viewlet/helpers"
4
+ require "viewlet/railtie"
5
+
6
+ module Viewlet
7
+ end
data/viewlet.gemspec ADDED
@@ -0,0 +1,22 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "viewlet/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "viewlet"
7
+ s.version = Viewlet::VERSION
8
+ s.authors = ["Dmitriy Kalinin"]
9
+ s.email = ["cppforlife@gmail.com"]
10
+ s.homepage = "https://github.com/cppforlife/viewlet"
11
+ s.summary = "Rails view components"
12
+ s.description = s.summary
13
+
14
+ s.files = `git ls-files`.split("\n")
15
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
17
+ s.require_paths = ["lib"]
18
+
19
+ # specify any dependencies here; for example:
20
+ # s.add_development_dependency "rspec"
21
+ # s.add_runtime_dependency "rest-client"
22
+ end
metadata ADDED
@@ -0,0 +1,82 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: viewlet
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease: false
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Dmitriy Kalinin
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-07-20 00:00:00 -07:00
19
+ default_executable:
20
+ dependencies: []
21
+
22
+ description: Rails view components
23
+ email:
24
+ - cppforlife@gmail.com
25
+ executables: []
26
+
27
+ extensions: []
28
+
29
+ extra_rdoc_files: []
30
+
31
+ files:
32
+ - .gitignore
33
+ - Gemfile
34
+ - MIT.LICENSE
35
+ - README.md
36
+ - Rakefile
37
+ - examples/bootstrap_tabs/bootstrap_tabs.html.haml
38
+ - examples/bootstrap_tabs/plugin.js
39
+ - examples/bootstrap_tabs/usage.html.haml
40
+ - lib/viewlet.rb
41
+ - lib/viewlet/base.rb
42
+ - lib/viewlet/helpers.rb
43
+ - lib/viewlet/railtie.rb
44
+ - lib/viewlet/template.rb
45
+ - lib/viewlet/version.rb
46
+ - viewlet.gemspec
47
+ has_rdoc: true
48
+ homepage: https://github.com/cppforlife/viewlet
49
+ licenses: []
50
+
51
+ post_install_message:
52
+ rdoc_options: []
53
+
54
+ require_paths:
55
+ - lib
56
+ required_ruby_version: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ hash: 3
62
+ segments:
63
+ - 0
64
+ version: "0"
65
+ required_rubygems_version: !ruby/object:Gem::Requirement
66
+ none: false
67
+ requirements:
68
+ - - ">="
69
+ - !ruby/object:Gem::Version
70
+ hash: 3
71
+ segments:
72
+ - 0
73
+ version: "0"
74
+ requirements: []
75
+
76
+ rubyforge_project:
77
+ rubygems_version: 1.3.7
78
+ signing_key:
79
+ specification_version: 3
80
+ summary: Rails view components
81
+ test_files: []
82
+