rails-bootstrap-widgets 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (49) hide show
  1. data/MIT-LICENSE +20 -0
  2. data/README.md +220 -0
  3. data/Rakefile +40 -0
  4. data/app/assets/javascripts/rails-bootstrap-widgets/application.js +15 -0
  5. data/app/assets/javascripts/rails-bootstrap-widgets/filters.js.coffee +12 -0
  6. data/app/assets/javascripts/rails-bootstrap-widgets/show_modal.js +26 -0
  7. data/app/helpers/rails-bootstrap-widgets/application_helper.rb +4 -0
  8. data/app/helpers/rails-bootstrap-widgets/filters_helper.rb +58 -0
  9. data/app/helpers/rails-bootstrap-widgets/modal_helper.rb +61 -0
  10. data/config/locales/en.yml +15 -0
  11. data/config/locales/ru.yml +15 -0
  12. data/lib/rails-bootstrap-widgets.rb +4 -0
  13. data/lib/rails-bootstrap-widgets/engine.rb +5 -0
  14. data/lib/rails-bootstrap-widgets/version.rb +3 -0
  15. data/lib/tasks/rails-bootstrap-widgets_tasks.rake +4 -0
  16. data/spec/dummy/README.rdoc +261 -0
  17. data/spec/dummy/Rakefile +7 -0
  18. data/spec/dummy/app/assets/javascripts/application.js +15 -0
  19. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  20. data/spec/dummy/app/controllers/application_controller.rb +3 -0
  21. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  22. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  23. data/spec/dummy/config.ru +4 -0
  24. data/spec/dummy/config/application.rb +59 -0
  25. data/spec/dummy/config/boot.rb +10 -0
  26. data/spec/dummy/config/database.yml +25 -0
  27. data/spec/dummy/config/environment.rb +5 -0
  28. data/spec/dummy/config/environments/development.rb +37 -0
  29. data/spec/dummy/config/environments/production.rb +67 -0
  30. data/spec/dummy/config/environments/test.rb +37 -0
  31. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  32. data/spec/dummy/config/initializers/inflections.rb +15 -0
  33. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  34. data/spec/dummy/config/initializers/secret_token.rb +7 -0
  35. data/spec/dummy/config/initializers/session_store.rb +8 -0
  36. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  37. data/spec/dummy/config/locales/en.yml +5 -0
  38. data/spec/dummy/config/routes.rb +4 -0
  39. data/spec/dummy/db/test.sqlite3 +0 -0
  40. data/spec/dummy/log/test.log +47 -0
  41. data/spec/dummy/public/404.html +26 -0
  42. data/spec/dummy/public/422.html +26 -0
  43. data/spec/dummy/public/500.html +25 -0
  44. data/spec/dummy/public/favicon.ico +0 -0
  45. data/spec/dummy/script/rails +6 -0
  46. data/spec/helpers/rails-bootstrap-widgets/filters_helper_spec.rb +50 -0
  47. data/spec/helpers/rails-bootstrap-widgets/modal_helper_spec.rb +75 -0
  48. data/spec/spec_helper.rb +14 -0
  49. metadata +356 -0
@@ -0,0 +1,20 @@
1
+ Copyright 2013 YOURNAME
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.
@@ -0,0 +1,220 @@
1
+ # About [![Build Status](https://travis-ci.org/nepalez/widgets.png?branch=master)](https://travis-ci.org/nepalez/rails-bootstrap-widgets)
2
+
3
+ The project defines a collection of widgets to be used inside views. It also contains some js files to provide additional behavior for html elemets, created by those widgets. Both helpers and js are compatible with Twitter Bootstrap framework.
4
+
5
+ # Installation
6
+
7
+ Add this line to your application's ```Gemfile```:
8
+ ```Ruby
9
+ gem 'rails-bootstrap-widgets'
10
+ ```
11
+ And then execute:
12
+ ```
13
+ $ bundle
14
+ ```
15
+ Or install it yourself as:
16
+ ```Ruby
17
+ $ gem install rails-bootstrap-widgets
18
+ ```
19
+ # Available widgets
20
+ ## List widgets
21
+ This group consists of two widgets to be used in views that contain items lists.
22
+ ### pagination_widget (helper method)
23
+ ```Ruby
24
+ pagination_widget(items, options = {})
25
+ ```
26
+ The widget simply wraps pager (from the ```will-paginate``` gem) into the aside tag with some class.
27
+ #### Example
28
+ ```Ruby
29
+ pagination_widget @items, class: 'pagination-top'
30
+ ```
31
+ This will create
32
+ ```Html
33
+ <aside class="pagination-top">
34
+ <!-- Standard result of 'will_paginate(items, inner_window: 0, outer_window: 0)' call -->
35
+ </aside>
36
+ ```
37
+ ### filters_widget (helper method)
38
+ ```Ruby
39
+ filters_widget(items, options = {})
40
+ ```
41
+ The widgets create html for filtering, ordering and paging a list of items. Provided html will include:
42
+
43
+ 1. Input field to select filter from those are available
44
+ 2. Input field to select order from those are available
45
+ 3. (Hidden) button to reset filter values
46
+ 4. Button to reload the page with new filter values
47
+ 5. Items pager
48
+
49
+ This html code should provide basic functionality without js support.
50
+
51
+ If js support is turned on in a client's browser, jQuery method will show reset button (js required) and hide reload button. It also makes current page to be updated authomatically after new filter or ordering is selected, or reset button clicked.
52
+
53
+ #### Example
54
+ ```Ruby
55
+ filters_widget (
56
+ @items,
57
+ filters: { published: :Published, unpublished: :Unpublished}, # list of filters to select from in a format { key: :name }
58
+ filter: 'published', # key of the current filter to apply to items
59
+ orders: { tree: :Tree, feed: :Feed }, # list of types of items ordering to select from
60
+ order: 'tree' # key of the current order type
61
+ )
62
+ ```
63
+ This will provide html:
64
+ ```HTML
65
+ <form accept-charset="UTF-8" action="/" class="span8" id="filters" method="get">
66
+ <div style="margin:0;padding:0;display:inline">
67
+ <input name="utf8" type="hidden" value="&#x2713;" />
68
+ </div>
69
+ <div class="input-prepend">
70
+ <span class="add-on">
71
+ <i class="icon-filter"></i>
72
+ </span>
73
+ <select id="filter" name="filter">
74
+ <option value="published" selected="selected">Published</option>
75
+ <option value="unpublished">Unpublished</option>
76
+ </select>
77
+ </div>
78
+ <div class="input-prepend">
79
+ <span class="add-on">
80
+ <i class="icon-order"></i>
81
+ </span>
82
+ <select id="order" name="order">
83
+ <option value="tree" selected="selected">Tree</option>
84
+ <option value="feed">Feed</option>
85
+ </select>
86
+ </div>
87
+ <a href="#" class="btn add-on hide" id="reset">
88
+ <i class="icon-reset"></i>
89
+ </a>
90
+ <div class="input-prepend" id="reload">
91
+ <span class="add-on">
92
+ <i class="icon-reload"></i>
93
+ </span>
94
+ <input class="btn" name="commit" type="submit" value="Reload" />
95
+ </div>
96
+ </form>
97
+ <aside class="span4">
98
+ <!-- Standard result of 'will_paginate(items, inner_window: 0, outer_window: 0)' call -->
99
+ </aside>
100
+ ```
101
+ ### Filters js support
102
+ The module adds file ```filters.js.coffee``` to ```app/assets/js/``` to authomatically reload current page after new filter or ordering selected by a user.
103
+
104
+ When current page is reloaded:
105
+
106
+ 1. Filters reload button is removed (reloading became authomatic instead)
107
+ 2. Filters reset button is added (it clears both filter and order field and reload the page with no filter|order set. They can be set by default within a controller)
108
+ 3. Adds reloading the page on selecting new input field value (filter or order)
109
+
110
+ ## Modal widgets and js
111
+
112
+ ### modal_form_widget (helper method)
113
+ ```Ruby
114
+ modal_form_widget(options = {}, &block)
115
+ ```
116
+ The widget puts a list of fields (inputs, selectors etc.) into a modal form window (presented by ```<form id='modal' class='modal fade'...>```)
117
+ #### Details
118
+ ```Ruby
119
+ modal_form_widget (
120
+ title: 'modal window title', # required
121
+ action: 'path to corresponding route', # required
122
+ method: 'request method for the form', # "post" by default, "post", "get", "put" or "delete" extected
123
+ button: 'text on the submit button', # I18n.t("buttons.submit") by default
124
+ cancel: 'text on the cancel form button' # I18n.t("buttons.cancel") by default
125
+ ) do
126
+ "" # html code of form fields
127
+ end
128
+ ```
129
+ #### Example
130
+ ```Ruby
131
+ modal_form_widget(title: 'Title', button: 'Button', method: :put, action: '/some_addr', cancel: "Cancel") { "content" }
132
+ ```
133
+ This will create
134
+ ```Html
135
+ <form accept-charset="UTF-8" action="/some_addr" class="modal fade" data-remote="true" id="modal" method="post">
136
+ <div style="margin:0;padding:0;display:inline">
137
+ <input name="utf8" type="hidden" value="&#x2713;" />
138
+ </div>
139
+ <input id="_method" name="_method" type="hidden" value="put" />
140
+ <header class="modal-header">
141
+ <button aria-hidden="true" class="close" data-dismiss="modal" type="button">&times;</button>
142
+ <h1>Title</h1>
143
+ </header>
144
+ <div class="modal-body">content</div>
145
+ <div class="modal-footer">
146
+ <a href="#" aria-hidden="true" class="btn" data-dismiss="modal">Cancel</a>
147
+ <input class="btn btn-primary" id="submit" name="commit" type="submit" value="Button" />
148
+ </div>
149
+ </form>
150
+ ```
151
+ ### modal_view_widget (helper method)
152
+ ```Ruby
153
+ modal_view_widget(options = {}, &block)
154
+ ```
155
+ The widget puts html code into a modal window (presented by ```<div id='modal' class='modal fade'>```)
156
+ #### Details
157
+ ```Ruby
158
+ modal_view_widget (
159
+ title: 'modal window title', # required
160
+ href: 'path for redirection to details', # required
161
+ redirect: true|false, # whether redirect button should be shown in the modal window footer (true by default),
162
+ button: 'text on the redirect button', # I18n.t("buttons.details") by default
163
+ cancel: 'text on the cancel form button' # I18n.t("buttons.cancel") by default
164
+ ) do
165
+ "" # modal window's html content
166
+ end
167
+ ```
168
+ #### Example
169
+ ```Ruby
170
+ modal_view_widget(title: 'Title', button: 'Button', href: '/some_addr', cancel: "Cancel") { "content" }
171
+ ```
172
+ This will create
173
+ ```Html
174
+ <div class="modal fade" id="modal">
175
+ <header class="modal-header">
176
+ <button aria-hidden="true" class="close" data-dismiss="modal" type="button">&times;</button>
177
+ <h1>Title</h1>
178
+ </header>
179
+ <div class="modal-body">content</div>
180
+ <div class="modal-footer">
181
+ <a href="#" aria-hidden="true" class="btn" data-dismiss="modal">Cancel</a>
182
+ <a href="/some_addr" class="btn btn-primary">Button</a>
183
+ </div>
184
+ </div>
185
+ ```
186
+ ### showModal (js function)
187
+ ```jQuery
188
+ showModal(href, template, width = nil)
189
+ ```
190
+ Provides jQuery method "show_modal" to download and open modal window via ajax.
191
+ #### Arguments
192
+ ```href``` - path to corresponding ajax request (required).
193
+
194
+ *When modal window is loaded, any link with selected ```href``` and ```data-remote='true'``` attribute is converted to modal window call. Direct links (without ```data-remote```) remain unchanged.*
195
+
196
+ *Before another modal window is loaded, the calls are reverted back to ajax links. Next time the modal should be downloaded by ajax again. To prevent excess downloading, use http request caching (see <a href='http://railscasts.com/episodes/321-http-caching'>RailsCast episode #321</a> for example).*
197
+
198
+ ```template``` - path to modal window template (required).
199
+
200
+ *This template will be rendered and incerted by ajax to html - before the closing ```</body>``` tag.*
201
+
202
+ *Before another modal window is loaded, previous modal window is removed. Therefore at any given time the page will contain no more than one modal window*
203
+
204
+ ```width``` - width in pixels for the modal link. If not set, the default bootstrap modal window is rendered.
205
+
206
+ #### Example
207
+ In the ```app/views/posts/show.js.coffee``` you could call:
208
+ ```jQuery
209
+ showModal('<%= post_path(@item) %>', "<%= j render 'modal/show' %>")
210
+ ```
211
+ # Contributing
212
+
213
+ 1. Fork it
214
+ 2. Create your feature branch (git checkout -b my-new-feature)
215
+ 3. Commit your changes (git commit -am 'Add some feature')
216
+ 4. Push to the branch (git push origin my-new-feature)
217
+ 5. Create new Pull Request
218
+
219
+ # License
220
+ This project rocks and uses MIT-LICENSE.
@@ -0,0 +1,40 @@
1
+ #!/usr/bin/env rake
2
+ begin
3
+ require 'bundler/setup'
4
+ rescue LoadError
5
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
6
+ end
7
+ begin
8
+ require 'rdoc/task'
9
+ rescue LoadError
10
+ require 'rdoc/rdoc'
11
+ require 'rake/rdoctask'
12
+ RDoc::Task = Rake::RDocTask
13
+ end
14
+
15
+ RDoc::Task.new(:rdoc) do |rdoc|
16
+ rdoc.rdoc_dir = 'rdoc'
17
+ rdoc.title = 'RailsBootstrapWidgets'
18
+ rdoc.options << '--line-numbers'
19
+ rdoc.rdoc_files.include('README.md')
20
+ rdoc.rdoc_files.include('lib/**/*.rb')
21
+ end
22
+
23
+ APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__)
24
+ load 'rails/tasks/engine.rake'
25
+
26
+
27
+
28
+ Bundler::GemHelper.install_tasks
29
+
30
+ require 'rake/testtask'
31
+
32
+ Rake::TestTask.new(:test) do |t|
33
+ t.libs << 'lib'
34
+ t.libs << 'test'
35
+ t.pattern = 'test/**/*_test.rb'
36
+ t.verbose = false
37
+ end
38
+
39
+
40
+ task :default => :test
@@ -0,0 +1,15 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // the compiled file.
9
+ //
10
+ // WARNING: THE FIRST BLANK LINE MARKS THE END OF WHAT'S TO BE PROCESSED, ANY BLANK LINE SHOULD
11
+ // GO AFTER THE REQUIRES BELOW.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require_tree .
@@ -0,0 +1,12 @@
1
+ # encoding: utf-8
2
+ # removes reload and displays reset button to reset fields via ajax
3
+ jQuery ->
4
+ $(document).ready ->
5
+ $('form#filters #reset').removeClass('hide').removeAttr('href')
6
+ $('form#filters #reload').remove()
7
+ $('form#filters select').change ->
8
+ $('form#filters').submit()
9
+ $('#reset').click ->
10
+ $('form#filters select').find('option:first').attr('selected', 'selected').parent('select');
11
+ $('form#filters select').prop('selectedIndex', -1);
12
+ $('form#filters').submit()
@@ -0,0 +1,26 @@
1
+ var showModal = function(href, template, width) {
2
+
3
+ // close and remove previous modals and their calls
4
+ $('.modal').modal('hide');
5
+ $('.modal').remove();
6
+ $("*[data-toggle='modal']").attr('data-remote', 'true').removeAttr('data-target').removeAttr('data-toggle');
7
+
8
+ // add new modal and its calls
9
+ $('body').append(template);
10
+ $("a[href*='" + href + "'][data-remote='true']").removeAttr('data-remote').attr('data-target', '#modal').attr('data-toggle', 'modal');
11
+
12
+ // lock button after sending a request
13
+ $("#modal").submit(function(){ $('input[type=submit]', this).attr('disabled', 'disabled'); });
14
+
15
+ // set width of the modal
16
+ if (width.length > 0) {
17
+ $('#modal').modal({ backdrop: true, keyboard: true }).css({ width: width, 'margin-left': function () { return -($(this).width() / 2); }});
18
+ }
19
+
20
+ // add vertical scroll
21
+ $('.modal-body').css({ "max-height": function() { return ($(window).height() * 0.55); }});
22
+ $(window).resize(function(){ $('.modal-body').css({ "max-height": function() { return ($(window).height() * 0.5); }}); });
23
+
24
+ // open the new modal
25
+ $('#modal').modal('show');
26
+ }
@@ -0,0 +1,4 @@
1
+ module RailsBootstrapWidgets
2
+ module ApplicationHelper
3
+ end
4
+ end
@@ -0,0 +1,58 @@
1
+ # encoding: utf-8
2
+ module RailsBootstrapWidgets
3
+ module FiltersHelper
4
+
5
+ def pagination_widget(items, options = {})
6
+ if items.present?
7
+ content_tag(:aside, (options[:class].to_s.present? ? { class: options[:class] } : {})) do
8
+ will_paginate(items, inner_window: 0, outer_window: 0).html_safe
9
+ end
10
+ end
11
+ end
12
+
13
+ def filters_widget(items, options = {})
14
+ if (options = _prepare(options)).present?
15
+ form_tag(url_for("/"), method: :get, id: :filters, class: :span8) do
16
+ (_selector(:filter, options[:filters], options[:filter]) << _selector(:order, options[:orders], options[:order]) << _reset << _reload).html_safe
17
+ end
18
+ else
19
+ ""
20
+ end << pagination_widget(items, class: :span4)
21
+ end
22
+
23
+ private
24
+
25
+ def _prepare(options = {})
26
+ _options = {}
27
+ _options.merge!({ filters: options[:filters], filter: options[:filter].to_s }) if _consistent?(options[:filters], options[:filter])
28
+ _options.merge!({ orders: options[:orders], order: options[:order].to_s }) if _consistent?(options[:orders], options[:order])
29
+ return _options
30
+ end
31
+
32
+ def _consistent?(list, item)
33
+ list.class == Hash && item.to_s.present? && list.keys.collect{ |key| key.to_s }.include?(item.to_s)
34
+ end
35
+
36
+ def _selector(type, list, item)
37
+ if list
38
+ content_tag(:div, class: "input-prepend") do
39
+ content_tag(:span, class: "add-on") { "<i class=\"icon-#{ type }\"></i>".html_safe } <<
40
+ select_tag(type, options_for_select(list.to_a.collect{ |i| i.reverse }, item))
41
+ end
42
+ else
43
+ ""
44
+ end
45
+ end
46
+
47
+ def _reset
48
+ link_to("<i class=\"icon-reset\"></i>".html_safe, '#', class: "btn add-on hide", id: :reset)
49
+ end
50
+
51
+ def _reload
52
+ content_tag(:div, id: :reload, class: "input-prepend") do
53
+ content_tag(:span, class: "add-on") { "<i class=\"icon-reload\"></i>".html_safe } <<
54
+ submit_tag(I18n.t("buttons.reload"), class: "btn")
55
+ end
56
+ end
57
+ end
58
+ end
@@ -0,0 +1,61 @@
1
+ # encoding: utf-8
2
+ module RailsBootstrapWidgets
3
+ module ModalHelper
4
+
5
+ def modal_form_widget(options = {}, &block)
6
+ if (options = _prepare(options, :form)) && (content = block_given? ? capture(&block) : nil)
7
+ form_tag(options[:action], method: :post, id: "modal", class: "modal fade", remote: true) do
8
+ (_hidden(options[:method]) << _header(options[:title]) << _body(content) << _footer(:submit, options[:button], options[:cancel])).html_safe
9
+ end
10
+ else
11
+ ''
12
+ end
13
+ end
14
+
15
+ def modal_view_widget(options = {}, &block)
16
+ content = block_given? ? capture(&block) : nil
17
+ if (options = _prepare(options, :view)) && content
18
+ content_tag(:div, id: "modal", class: "modal fade") do
19
+ (_header(options[:title]) << _body(content) << _footer(:button, options[:button], options[:cancel], options[:href], options[:redirect])).html_safe
20
+ end
21
+ else
22
+ ''
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def _prepare(options, type)
29
+ if (options.class == Hash) && %w(form view).include?(type.to_s)
30
+ options[:redirect] = true unless options[:redirect] == false
31
+ options[:method] = 'post' unless %w(get put delete).include?(options[:method].to_s)
32
+ options[:cancel] ||= I18n.t('buttons.cancel')
33
+ options[:button] ||= (type == :form ? I18n.t('buttons.submit') : I18n.t('buttons.details'))
34
+ return options if options[:title].present? && (((type == :view) && options[:href].present?) || ((type == :form) && options[:action].present? && options[:cancel].present?))
35
+ end
36
+ return nil
37
+ end
38
+
39
+ def _hidden(method)
40
+ method.to_s == "post" ? "" : hidden_field_tag(:_method, method.to_s)
41
+ end
42
+
43
+ def _header(title)
44
+ content_tag(:header, class: 'modal-header') do
45
+ button_tag(type: :button, class: :close, "data-dismiss" => :modal, "aria-hidden" => 'true') { "\&times;".html_safe } <<
46
+ content_tag(:h1, title)
47
+ end
48
+ end
49
+
50
+ def _body(content)
51
+ content_tag(:div, class: "modal-body") { content }
52
+ end
53
+
54
+ def _footer(type, button, cancel, href = nil, redirect = true)
55
+ content_tag(:div, class: 'modal-footer') do
56
+ link_to(cancel, "#", class: "btn", "data-dismiss" => "modal", "aria-hidden" => "true") <<
57
+ (type == :submit ? submit_tag(button, id: :submit, class: "btn btn-primary") : (redirect ? link_to(button, href, class: "btn btn-primary") : ""))
58
+ end
59
+ end
60
+ end
61
+ end