effective_bootstrap 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.
Files changed (335) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +93 -0
  4. data/app/assets/icons/activity.svg +13 -0
  5. data/app/assets/icons/airplay.svg +14 -0
  6. data/app/assets/icons/alert-circle.svg +15 -0
  7. data/app/assets/icons/alert-octagon.svg +15 -0
  8. data/app/assets/icons/alert-triangle.svg +15 -0
  9. data/app/assets/icons/align-center.svg +16 -0
  10. data/app/assets/icons/align-justify.svg +16 -0
  11. data/app/assets/icons/align-left.svg +16 -0
  12. data/app/assets/icons/align-right.svg +16 -0
  13. data/app/assets/icons/anchor.svg +15 -0
  14. data/app/assets/icons/aperture.svg +19 -0
  15. data/app/assets/icons/arrow-down-circle.svg +15 -0
  16. data/app/assets/icons/arrow-down-left.svg +14 -0
  17. data/app/assets/icons/arrow-down-right.svg +14 -0
  18. data/app/assets/icons/arrow-down.svg +14 -0
  19. data/app/assets/icons/arrow-left-circle.svg +15 -0
  20. data/app/assets/icons/arrow-left.svg +14 -0
  21. data/app/assets/icons/arrow-right-circle.svg +15 -0
  22. data/app/assets/icons/arrow-right.svg +14 -0
  23. data/app/assets/icons/arrow-up-circle.svg +15 -0
  24. data/app/assets/icons/arrow-up-left.svg +14 -0
  25. data/app/assets/icons/arrow-up-right.svg +14 -0
  26. data/app/assets/icons/arrow-up.svg +14 -0
  27. data/app/assets/icons/at-sign.svg +14 -0
  28. data/app/assets/icons/award.svg +14 -0
  29. data/app/assets/icons/bar-chart-2.svg +15 -0
  30. data/app/assets/icons/bar-chart.svg +15 -0
  31. data/app/assets/icons/battery-charging.svg +15 -0
  32. data/app/assets/icons/battery.svg +14 -0
  33. data/app/assets/icons/bell-off.svg +14 -0
  34. data/app/assets/icons/bell.svg +13 -0
  35. data/app/assets/icons/bluetooth.svg +13 -0
  36. data/app/assets/icons/bold.svg +14 -0
  37. data/app/assets/icons/book-open.svg +14 -0
  38. data/app/assets/icons/book.svg +14 -0
  39. data/app/assets/icons/bookmark.svg +13 -0
  40. data/app/assets/icons/box.svg +15 -0
  41. data/app/assets/icons/briefcase.svg +14 -0
  42. data/app/assets/icons/calendar.svg +16 -0
  43. data/app/assets/icons/camera-off.svg +14 -0
  44. data/app/assets/icons/camera.svg +14 -0
  45. data/app/assets/icons/cast.svg +14 -0
  46. data/app/assets/icons/check-circle.svg +14 -0
  47. data/app/assets/icons/check-square.svg +14 -0
  48. data/app/assets/icons/check.svg +13 -0
  49. data/app/assets/icons/chevron-down.svg +13 -0
  50. data/app/assets/icons/chevron-left.svg +13 -0
  51. data/app/assets/icons/chevron-right.svg +13 -0
  52. data/app/assets/icons/chevron-up.svg +13 -0
  53. data/app/assets/icons/chevrons-down.svg +14 -0
  54. data/app/assets/icons/chevrons-left.svg +14 -0
  55. data/app/assets/icons/chevrons-right.svg +14 -0
  56. data/app/assets/icons/chevrons-up.svg +14 -0
  57. data/app/assets/icons/chrome.svg +17 -0
  58. data/app/assets/icons/circle.svg +13 -0
  59. data/app/assets/icons/clipboard.svg +14 -0
  60. data/app/assets/icons/clock.svg +14 -0
  61. data/app/assets/icons/cloud-drizzle.svg +19 -0
  62. data/app/assets/icons/cloud-lightning.svg +14 -0
  63. data/app/assets/icons/cloud-off.svg +14 -0
  64. data/app/assets/icons/cloud-rain.svg +16 -0
  65. data/app/assets/icons/cloud-snow.svg +19 -0
  66. data/app/assets/icons/cloud.svg +13 -0
  67. data/app/assets/icons/code.svg +14 -0
  68. data/app/assets/icons/codepen.svg +17 -0
  69. data/app/assets/icons/command.svg +13 -0
  70. data/app/assets/icons/compass.svg +14 -0
  71. data/app/assets/icons/copy.svg +14 -0
  72. data/app/assets/icons/corner-down-left.svg +14 -0
  73. data/app/assets/icons/corner-down-right.svg +14 -0
  74. data/app/assets/icons/corner-left-down.svg +14 -0
  75. data/app/assets/icons/corner-left-up.svg +14 -0
  76. data/app/assets/icons/corner-right-down.svg +14 -0
  77. data/app/assets/icons/corner-right-up.svg +14 -0
  78. data/app/assets/icons/corner-up-left.svg +14 -0
  79. data/app/assets/icons/corner-up-right.svg +14 -0
  80. data/app/assets/icons/cpu.svg +22 -0
  81. data/app/assets/icons/credit-card.svg +14 -0
  82. data/app/assets/icons/crop.svg +14 -0
  83. data/app/assets/icons/crosshair.svg +17 -0
  84. data/app/assets/icons/database.svg +15 -0
  85. data/app/assets/icons/delete.svg +15 -0
  86. data/app/assets/icons/disc.svg +14 -0
  87. data/app/assets/icons/dollar-sign.svg +14 -0
  88. data/app/assets/icons/download-cloud.svg +15 -0
  89. data/app/assets/icons/download.svg +15 -0
  90. data/app/assets/icons/droplet.svg +13 -0
  91. data/app/assets/icons/edit-2.svg +13 -0
  92. data/app/assets/icons/edit-3.svg +14 -0
  93. data/app/assets/icons/edit.svg +14 -0
  94. data/app/assets/icons/external-link.svg +15 -0
  95. data/app/assets/icons/eye-off.svg +14 -0
  96. data/app/assets/icons/eye.svg +14 -0
  97. data/app/assets/icons/facebook.svg +13 -0
  98. data/app/assets/icons/fast-forward.svg +14 -0
  99. data/app/assets/icons/feather.svg +15 -0
  100. data/app/assets/icons/file-minus.svg +15 -0
  101. data/app/assets/icons/file-plus.svg +16 -0
  102. data/app/assets/icons/file-text.svg +17 -0
  103. data/app/assets/icons/file.svg +14 -0
  104. data/app/assets/icons/film.svg +20 -0
  105. data/app/assets/icons/filter.svg +13 -0
  106. data/app/assets/icons/flag.svg +14 -0
  107. data/app/assets/icons/folder-minus.svg +14 -0
  108. data/app/assets/icons/folder-plus.svg +15 -0
  109. data/app/assets/icons/folder.svg +13 -0
  110. data/app/assets/icons/git-branch.svg +16 -0
  111. data/app/assets/icons/git-commit.svg +15 -0
  112. data/app/assets/icons/git-merge.svg +15 -0
  113. data/app/assets/icons/git-pull-request.svg +16 -0
  114. data/app/assets/icons/github.svg +13 -0
  115. data/app/assets/icons/gitlab.svg +13 -0
  116. data/app/assets/icons/globe.svg +15 -0
  117. data/app/assets/icons/grid.svg +16 -0
  118. data/app/assets/icons/hard-drive.svg +16 -0
  119. data/app/assets/icons/hash.svg +16 -0
  120. data/app/assets/icons/headphones.svg +14 -0
  121. data/app/assets/icons/heart.svg +13 -0
  122. data/app/assets/icons/help-circle.svg +15 -0
  123. data/app/assets/icons/home.svg +14 -0
  124. data/app/assets/icons/image.svg +15 -0
  125. data/app/assets/icons/inbox.svg +14 -0
  126. data/app/assets/icons/info.svg +15 -0
  127. data/app/assets/icons/instagram.svg +15 -0
  128. data/app/assets/icons/italic.svg +15 -0
  129. data/app/assets/icons/layers.svg +15 -0
  130. data/app/assets/icons/layout.svg +15 -0
  131. data/app/assets/icons/life-buoy.svg +19 -0
  132. data/app/assets/icons/link-2.svg +14 -0
  133. data/app/assets/icons/link.svg +14 -0
  134. data/app/assets/icons/linkedin.svg +15 -0
  135. data/app/assets/icons/list.svg +18 -0
  136. data/app/assets/icons/loader.svg +20 -0
  137. data/app/assets/icons/lock.svg +14 -0
  138. data/app/assets/icons/log-in.svg +15 -0
  139. data/app/assets/icons/log-out.svg +15 -0
  140. data/app/assets/icons/mail.svg +14 -0
  141. data/app/assets/icons/map-pin.svg +14 -0
  142. data/app/assets/icons/map.svg +15 -0
  143. data/app/assets/icons/maximize-2.svg +16 -0
  144. data/app/assets/icons/maximize.svg +13 -0
  145. data/app/assets/icons/menu.svg +15 -0
  146. data/app/assets/icons/message-circle.svg +13 -0
  147. data/app/assets/icons/message-square.svg +13 -0
  148. data/app/assets/icons/mic-off.svg +17 -0
  149. data/app/assets/icons/mic.svg +16 -0
  150. data/app/assets/icons/minimize-2.svg +16 -0
  151. data/app/assets/icons/minimize.svg +13 -0
  152. data/app/assets/icons/minus-circle.svg +14 -0
  153. data/app/assets/icons/minus-square.svg +14 -0
  154. data/app/assets/icons/minus.svg +13 -0
  155. data/app/assets/icons/monitor.svg +15 -0
  156. data/app/assets/icons/moon.svg +13 -0
  157. data/app/assets/icons/more-horizontal.svg +15 -0
  158. data/app/assets/icons/more-vertical.svg +15 -0
  159. data/app/assets/icons/move.svg +18 -0
  160. data/app/assets/icons/music.svg +14 -0
  161. data/app/assets/icons/navigation-2.svg +13 -0
  162. data/app/assets/icons/navigation.svg +13 -0
  163. data/app/assets/icons/octagon.svg +13 -0
  164. data/app/assets/icons/package.svg +16 -0
  165. data/app/assets/icons/paperclip.svg +13 -0
  166. data/app/assets/icons/pause-circle.svg +15 -0
  167. data/app/assets/icons/pause.svg +14 -0
  168. data/app/assets/icons/percent.svg +15 -0
  169. data/app/assets/icons/phone-call.svg +13 -0
  170. data/app/assets/icons/phone-forwarded.svg +15 -0
  171. data/app/assets/icons/phone-incoming.svg +15 -0
  172. data/app/assets/icons/phone-missed.svg +15 -0
  173. data/app/assets/icons/phone-off.svg +14 -0
  174. data/app/assets/icons/phone-outgoing.svg +15 -0
  175. data/app/assets/icons/phone.svg +13 -0
  176. data/app/assets/icons/pie-chart.svg +14 -0
  177. data/app/assets/icons/play-circle.svg +14 -0
  178. data/app/assets/icons/play.svg +13 -0
  179. data/app/assets/icons/plus-circle.svg +15 -0
  180. data/app/assets/icons/plus-square.svg +15 -0
  181. data/app/assets/icons/plus.svg +14 -0
  182. data/app/assets/icons/pocket.svg +14 -0
  183. data/app/assets/icons/power.svg +14 -0
  184. data/app/assets/icons/printer.svg +15 -0
  185. data/app/assets/icons/radio.svg +14 -0
  186. data/app/assets/icons/refresh-ccw.svg +15 -0
  187. data/app/assets/icons/refresh-cw.svg +15 -0
  188. data/app/assets/icons/repeat.svg +16 -0
  189. data/app/assets/icons/rewind.svg +14 -0
  190. data/app/assets/icons/rotate-ccw.svg +14 -0
  191. data/app/assets/icons/rotate-cw.svg +14 -0
  192. data/app/assets/icons/rss.svg +15 -0
  193. data/app/assets/icons/save.svg +15 -0
  194. data/app/assets/icons/scissors.svg +17 -0
  195. data/app/assets/icons/search.svg +14 -0
  196. data/app/assets/icons/send.svg +14 -0
  197. data/app/assets/icons/server.svg +16 -0
  198. data/app/assets/icons/settings.svg +14 -0
  199. data/app/assets/icons/share-2.svg +17 -0
  200. data/app/assets/icons/share.svg +15 -0
  201. data/app/assets/icons/shield-off.svg +15 -0
  202. data/app/assets/icons/shield.svg +13 -0
  203. data/app/assets/icons/shopping-bag.svg +15 -0
  204. data/app/assets/icons/shopping-cart.svg +15 -0
  205. data/app/assets/icons/shuffle.svg +17 -0
  206. data/app/assets/icons/sidebar.svg +14 -0
  207. data/app/assets/icons/skip-back.svg +14 -0
  208. data/app/assets/icons/skip-forward.svg +14 -0
  209. data/app/assets/icons/slack.svg +17 -0
  210. data/app/assets/icons/slash.svg +14 -0
  211. data/app/assets/icons/sliders.svg +21 -0
  212. data/app/assets/icons/smartphone.svg +14 -0
  213. data/app/assets/icons/speaker.svg +15 -0
  214. data/app/assets/icons/spinner.svg +1 -0
  215. data/app/assets/icons/square.svg +13 -0
  216. data/app/assets/icons/star.svg +13 -0
  217. data/app/assets/icons/stop-circle.svg +14 -0
  218. data/app/assets/icons/sun.svg +21 -0
  219. data/app/assets/icons/sunrise.svg +20 -0
  220. data/app/assets/icons/sunset.svg +20 -0
  221. data/app/assets/icons/tablet.svg +22 -0
  222. data/app/assets/icons/tag.svg +14 -0
  223. data/app/assets/icons/target.svg +15 -0
  224. data/app/assets/icons/terminal.svg +14 -0
  225. data/app/assets/icons/thermometer.svg +13 -0
  226. data/app/assets/icons/thumbs-down.svg +13 -0
  227. data/app/assets/icons/thumbs-up.svg +13 -0
  228. data/app/assets/icons/toggle-left.svg +14 -0
  229. data/app/assets/icons/toggle-right.svg +14 -0
  230. data/app/assets/icons/trash-2.svg +16 -0
  231. data/app/assets/icons/trash.svg +14 -0
  232. data/app/assets/icons/trending-down.svg +14 -0
  233. data/app/assets/icons/trending-up.svg +14 -0
  234. data/app/assets/icons/triangle.svg +13 -0
  235. data/app/assets/icons/truck.svg +16 -0
  236. data/app/assets/icons/tv.svg +14 -0
  237. data/app/assets/icons/twitter.svg +13 -0
  238. data/app/assets/icons/type.svg +15 -0
  239. data/app/assets/icons/umbrella.svg +13 -0
  240. data/app/assets/icons/underline.svg +14 -0
  241. data/app/assets/icons/unlock.svg +14 -0
  242. data/app/assets/icons/upload-cloud.svg +16 -0
  243. data/app/assets/icons/upload.svg +15 -0
  244. data/app/assets/icons/user-check.svg +15 -0
  245. data/app/assets/icons/user-minus.svg +15 -0
  246. data/app/assets/icons/user-plus.svg +16 -0
  247. data/app/assets/icons/user-x.svg +16 -0
  248. data/app/assets/icons/user.svg +14 -0
  249. data/app/assets/icons/users.svg +16 -0
  250. data/app/assets/icons/video-off.svg +14 -0
  251. data/app/assets/icons/video.svg +14 -0
  252. data/app/assets/icons/voicemail.svg +15 -0
  253. data/app/assets/icons/volume-1.svg +14 -0
  254. data/app/assets/icons/volume-2.svg +14 -0
  255. data/app/assets/icons/volume-x.svg +15 -0
  256. data/app/assets/icons/volume.svg +13 -0
  257. data/app/assets/icons/watch.svg +15 -0
  258. data/app/assets/icons/wifi-off.svg +19 -0
  259. data/app/assets/icons/wifi.svg +16 -0
  260. data/app/assets/icons/wind.svg +13 -0
  261. data/app/assets/icons/x-circle.svg +15 -0
  262. data/app/assets/icons/x-square.svg +15 -0
  263. data/app/assets/icons/x.svg +14 -0
  264. data/app/assets/icons/zap-off.svg +16 -0
  265. data/app/assets/icons/zap.svg +13 -0
  266. data/app/assets/icons/zoom-in.svg +16 -0
  267. data/app/assets/icons/zoom-out.svg +15 -0
  268. data/app/assets/javascripts/effective_bootstrap.js +9 -0
  269. data/app/assets/javascripts/effective_bootstrap/base.js.coffee +37 -0
  270. data/app/assets/javascripts/effective_date/initialize.js.coffee +4 -0
  271. data/app/assets/javascripts/effective_date/input.js +5 -0
  272. data/app/assets/javascripts/effective_datetime/bootstrap-datetimepicker.js +2637 -0
  273. data/app/assets/javascripts/effective_datetime/initialize.js.coffee +4 -0
  274. data/app/assets/javascripts/effective_datetime/input.js +5 -0
  275. data/app/assets/javascripts/effective_datetime/moment.js +4535 -0
  276. data/app/assets/javascripts/effective_datetime/overrides.js.coffee +38 -0
  277. data/app/assets/javascripts/effective_datetime/turbolinks.js.coffee +5 -0
  278. data/app/assets/javascripts/effective_phone/initialize.js.coffee +4 -0
  279. data/app/assets/javascripts/effective_phone/input.js +2 -0
  280. data/app/assets/javascripts/effective_phone/jquery.maskedInput.js +182 -0
  281. data/app/assets/javascripts/effective_price/input.js.coffee +34 -0
  282. data/app/assets/javascripts/effective_select/initialize.js.coffee +45 -0
  283. data/app/assets/javascripts/effective_select/input.js +3 -0
  284. data/app/assets/javascripts/effective_select/overrides.js.coffee +45 -0
  285. data/app/assets/javascripts/effective_select/select2.js +5746 -0
  286. data/app/assets/javascripts/effective_time/initialize.js.coffee +4 -0
  287. data/app/assets/javascripts/effective_time/input.js +5 -0
  288. data/app/assets/stylesheets/effective_bootstrap.scss +8 -0
  289. data/app/assets/stylesheets/effective_bootstrap/base.scss +11 -0
  290. data/app/assets/stylesheets/effective_bootstrap/icons.scss +27 -0
  291. data/app/assets/stylesheets/effective_date/input.scss +1 -0
  292. data/app/assets/stylesheets/effective_datetime/bootstrap-datetimepicker.scss +374 -0
  293. data/app/assets/stylesheets/effective_datetime/input.scss +2 -0
  294. data/app/assets/stylesheets/effective_datetime/overrides.scss +28 -0
  295. data/app/assets/stylesheets/effective_select/bootstrap-theme.css +564 -0
  296. data/app/assets/stylesheets/effective_select/input.scss +3 -0
  297. data/app/assets/stylesheets/effective_select/overrides.scss +92 -0
  298. data/app/assets/stylesheets/effective_select/select2.css +484 -0
  299. data/app/assets/stylesheets/effective_time/input.scss +1 -0
  300. data/app/helpers/effective_bootstrap_helper.rb +52 -0
  301. data/app/helpers/effective_form_builder_helper.rb +23 -0
  302. data/app/helpers/effective_icons_helper.rb +46 -0
  303. data/app/models/effective/access_denied.rb +17 -0
  304. data/app/models/effective/form_builder.rb +100 -0
  305. data/app/models/effective/form_input.rb +319 -0
  306. data/app/models/effective/form_inputs/check_box.rb +62 -0
  307. data/app/models/effective/form_inputs/checks.rb +73 -0
  308. data/app/models/effective/form_inputs/collection_input.rb +144 -0
  309. data/app/models/effective/form_inputs/date_field.rb +22 -0
  310. data/app/models/effective/form_inputs/datetime_field.rb +53 -0
  311. data/app/models/effective/form_inputs/email_field.rb +15 -0
  312. data/app/models/effective/form_inputs/error_field.rb +47 -0
  313. data/app/models/effective/form_inputs/form_group.rb +26 -0
  314. data/app/models/effective/form_inputs/number_field.rb +6 -0
  315. data/app/models/effective/form_inputs/password_field.rb +24 -0
  316. data/app/models/effective/form_inputs/phone_field.rb +34 -0
  317. data/app/models/effective/form_inputs/price_field.rb +37 -0
  318. data/app/models/effective/form_inputs/radios.rb +74 -0
  319. data/app/models/effective/form_inputs/select.rb +87 -0
  320. data/app/models/effective/form_inputs/static_field.rb +20 -0
  321. data/app/models/effective/form_inputs/submit.rb +42 -0
  322. data/app/models/effective/form_inputs/text_area.rb +11 -0
  323. data/app/models/effective/form_inputs/text_field.rb +6 -0
  324. data/app/models/effective/form_inputs/time_field.rb +22 -0
  325. data/app/models/effective/form_inputs/url_field.rb +15 -0
  326. data/app/views/effective/style_guide/__fields.html.haml +42 -0
  327. data/app/views/effective/style_guide/__inline_fields.html.haml +4 -0
  328. data/app/views/effective/style_guide/_effective_form_with.html.haml +23 -0
  329. data/app/views/effective/style_guide/_feather_icons.html.haml +9 -0
  330. data/config/effective_bootstrap.rb +24 -0
  331. data/lib/effective_bootstrap.rb +31 -0
  332. data/lib/effective_bootstrap/engine.rb +11 -0
  333. data/lib/effective_bootstrap/version.rb +3 -0
  334. data/lib/generators/effective_bootstrap/install_generator.rb +14 -0
  335. metadata +419 -0
@@ -0,0 +1 @@
1
+ @import '../effective_datetime/input';
@@ -0,0 +1,52 @@
1
+ # Boostrap4 Helpers
2
+
3
+ module EffectiveBootstrapHelper
4
+ # Nav links and dropdowns
5
+ # Automatically puts in the 'active' class based on request path
6
+
7
+ # %ul.navbar-nav
8
+ # = nav_link_to 'Sign In', new_user_session_path
9
+ # = nav_dropdown 'Settings' do
10
+ # = nav_link_to 'Account Settings', user_settings_path
11
+ # = nav_dropdown_divider
12
+ # = nav_link_to 'Sign In', new_user_session_path, method: :delete
13
+ def nav_link_to(label, path, opts = {})
14
+ if @_nav_mode == :dropdown # We insert dropdown-items
15
+ return link_to(label, path, merge_class_key(opts, 'dropdown-item'))
16
+ end
17
+
18
+ # Regular nav link item
19
+ content_tag(:li, class: (request.fullpath.include?(path) ? 'nav-item active' : 'nav-item')) do
20
+ link_to(label, path, merge_class_key(opts, 'nav-link'))
21
+ end
22
+ end
23
+
24
+ def nav_dropdown(label, right: false, link_class: [], list_class: [], &block)
25
+ raise 'expected a block' unless block_given?
26
+
27
+ id = "dropdown-#{''.object_id}"
28
+
29
+ content_tag(:li, class: 'nav-item dropdown') do
30
+ content_tag(:a, class: 'nav-link dropdown-toggle', href: '#', id: id, role: 'button', 'data-toggle': 'dropdown', 'aria-haspopup': true, 'aria-expanded': false) do
31
+ label.html_safe
32
+ end + content_tag(:div, class: (right ? 'dropdown-menu dropdown-menu-right' : 'dropdown-menu'), 'aria-labelledby': id) do
33
+ @_nav_mode = :dropdown; yield; @_nav_mode = nil
34
+ end
35
+ end
36
+ end
37
+
38
+ def nav_divider
39
+ content_tag(:div, '', class: 'dropdown-divider')
40
+ end
41
+
42
+ def merge_class_key(hash, value)
43
+ return { :class => value } unless hash.kind_of?(Hash)
44
+
45
+ if hash[:class].present?
46
+ hash.merge!(:class => "#{hash[:class]} #{value}")
47
+ else
48
+ hash.merge!(:class => value)
49
+ end
50
+ end
51
+
52
+ end
@@ -0,0 +1,23 @@
1
+ module EffectiveFormBuilderHelper
2
+ def effective_form_with(**options, &block)
3
+ options[:class] = [options[:class], 'needs-validation', ('form-inline' if options[:layout] == :inline)].compact.join(' ')
4
+
5
+ without_error_proc do
6
+ form_with(**options.merge(builder: Effective::FormBuilder, html: { novalidate: true, onsubmit: 'return EffectiveBootstrap.validate(this);' }), &block)
7
+ end
8
+ end
9
+
10
+ private
11
+
12
+ # Disables a .fields_with_errors wrapping div when
13
+ def without_error_proc
14
+ original = ActionView::Base.field_error_proc
15
+
16
+ begin
17
+ ActionView::Base.field_error_proc = proc { |input, _| input }; yield
18
+ ensure
19
+ ActionView::Base.field_error_proc = original
20
+ end
21
+ end
22
+
23
+ end
@@ -0,0 +1,46 @@
1
+ module EffectiveIconsHelper
2
+
3
+ def icon(svg, options = {})
4
+ svg = svg.to_s.chomp('.svg')
5
+
6
+ options.reverse_merge!(nocomment: true)
7
+ options[:class] = [options[:class], "eb-icon eb-icon-#{svg}"].compact.join(' ')
8
+
9
+ inline_svg(svg + '.svg', options)
10
+ end
11
+
12
+ def icon_to(svg, url, options = {})
13
+ link_to(icon(svg), url, options)
14
+ end
15
+
16
+ def show_icon_to(path, options = {})
17
+ icon_to('eye-open', path, { title: 'Show' }.merge(options))
18
+ end
19
+
20
+ def edit_icon_to(path, options = {})
21
+ icon_to('edit', path, { title: 'Edit' }.merge(options))
22
+ end
23
+
24
+ def destroy_icon_to(path, options = {})
25
+ defaults = { title: 'Destroy', data: { method: :delete, confirm: 'Delete this item?' } }
26
+ icon_to('trash', path, defaults.merge(options))
27
+ end
28
+
29
+ def settings_icon_to(path, options = {})
30
+ icon_to('cog', path, { title: 'Settings' }.merge(options))
31
+ end
32
+
33
+ def ok_icon_to(path, options = {})
34
+ icon_to('ok', path, { title: 'OK' }.merge(options))
35
+ end
36
+
37
+ def approve_icon_to(path, options = {})
38
+ icon_to('ok', path, { title: 'Approve' }.merge(options))
39
+ end
40
+
41
+ def remove_icon_to(path, options = {})
42
+ icon_to('remove', path, { title: 'Remove' }.merge(options))
43
+ end
44
+
45
+ end
46
+
@@ -0,0 +1,17 @@
1
+ unless defined?(Effective::AccessDenied)
2
+ module Effective
3
+ class AccessDenied < StandardError
4
+ attr_reader :action, :subject
5
+
6
+ def initialize(message = nil, action = nil, subject = nil)
7
+ @message = message
8
+ @action = action
9
+ @subject = subject
10
+ end
11
+
12
+ def to_s
13
+ @message || I18n.t(:'unauthorized.default', :default => 'Access Denied')
14
+ end
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,100 @@
1
+ module Effective
2
+ class FormBuilder < ActionView::Helpers::FormBuilder
3
+
4
+ attr_accessor :layout, :template
5
+
6
+ delegate :content_tag, to: :template
7
+
8
+ def initialize(object_name, object, template, options)
9
+ @template = template
10
+ @layout = (options.delete(:layout) || :vertical).to_sym
11
+ super
12
+ end
13
+
14
+ alias_method :super_text_field, :text_field
15
+
16
+ def check_box(name, options = {})
17
+ Effective::FormInputs::CheckBox.new(name, options, builder: self).to_html { super(name, options) }
18
+ end
19
+
20
+ def checks(name, choices = nil, *args)
21
+ options = args.extract_options!.merge!(collection: choices)
22
+ Effective::FormInputs::Checks.new(name, options, builder: self).to_html
23
+ end
24
+
25
+ def date_field(name, options = {})
26
+ Effective::FormInputs::DateField.new(name, options, builder: self).to_html { super(name, options) }
27
+ end
28
+
29
+ def datetime_field(name, options = {})
30
+ Effective::FormInputs::DatetimeField.new(name, options, builder: self).to_html { super(name, options) }
31
+ end
32
+
33
+ def email_field(name, options = {})
34
+ Effective::FormInputs::EmailField.new(name, options, builder: self).to_html { super(name, options) }
35
+ end
36
+
37
+ def error(name = nil, options = {})
38
+ Effective::FormInputs::ErrorField.new(name, options, builder: self).to_html()
39
+ end
40
+ alias_method :errors, :error
41
+
42
+ def form_group(name = nil, options = {}, &block)
43
+ Effective::FormInputs::FormGroup.new(name, options, builder: self).to_html(&block)
44
+ end
45
+
46
+ def number_field(name, options = {})
47
+ Effective::FormInputs::NumberField.new(name, options, builder: self).to_html { super(name, options) }
48
+ end
49
+
50
+ def password_field(name, options = {})
51
+ Effective::FormInputs::PasswordField.new(name, options, builder: self).to_html { super(name, options) }
52
+ end
53
+
54
+ def phone_field(name, options = {})
55
+ Effective::FormInputs::PhoneField.new(name, options, builder: self).to_html { super(name, options) }
56
+ end
57
+ alias_method :tel_field, :phone_field
58
+ alias_method :telephone_field, :phone_field
59
+
60
+ def price_field(name, options = {})
61
+ Effective::FormInputs::PriceField.new(name, options, builder: self).to_html { super(name, options) }
62
+ end
63
+
64
+ def select(name, choices = nil, *args)
65
+ options = args.extract_options!.merge!(collection: choices)
66
+ Effective::FormInputs::Select.new(name, options, builder: self).to_html
67
+ end
68
+
69
+ def submit(name = 'Submit', options = {})
70
+ Effective::FormInputs::Submit.new(name, options, builder: self).to_html { super(name, options) }
71
+ end
72
+
73
+ def static_field(name, options = {}, &block)
74
+ Effective::FormInputs::StaticField.new(name, options, builder: self).to_html(&block)
75
+ end
76
+
77
+ def radios(name, choices = nil, *args)
78
+ options = args.extract_options!.merge!(collection: choices)
79
+ Effective::FormInputs::Radios.new(name, options, builder: self).to_html
80
+ end
81
+
82
+ def text_area(name, options = {})
83
+ Effective::FormInputs::TextArea.new(name, options, builder: self).to_html { super(name, options) }
84
+ end
85
+
86
+ def text_field(name, options = {})
87
+ Effective::FormInputs::TextField.new(name, options, builder: self).to_html { super(name, options) }
88
+ end
89
+
90
+ def time_field(name, options = {})
91
+ Effective::FormInputs::TimeField.new(name, options, builder: self).to_html { super(name, options) }
92
+ end
93
+
94
+ def url_field(name, options = {})
95
+ Effective::FormInputs::UrlField.new(name, options, builder: self).to_html { super(name, options) }
96
+ end
97
+
98
+ end
99
+ end
100
+
@@ -0,0 +1,319 @@
1
+ module Effective
2
+ class FormInput
3
+ attr_accessor :name, :options
4
+
5
+ BLANK = ''.html_safe
6
+
7
+ delegate :object, to: :@builder
8
+ delegate :capture, :content_tag, :link_to, :icon, to: :@template
9
+
10
+ # So this takes in the options for an entire form group.
11
+ def initialize(name, options, builder:, html_options: nil)
12
+ @builder = builder
13
+ @template = builder.template
14
+
15
+ @name = name
16
+ @options = extract_options!(options, html_options: html_options)
17
+ apply_input_options!
18
+ end
19
+
20
+ def input_group_options
21
+ { input_group: { class: 'input-group' }, prepend: false, append: false }
22
+ end
23
+
24
+ def input_html_options
25
+ { class: 'form-control' }
26
+ end
27
+
28
+ def input_js_options
29
+ {}
30
+ end
31
+
32
+ def label_options
33
+ case layout
34
+ when :horizontal
35
+ { class: 'col-sm-2 col-form-label'}
36
+ when :inline
37
+ { class: 'sr-only' }
38
+ else
39
+ { }
40
+ end
41
+ end
42
+
43
+ def feedback_options
44
+ case layout
45
+ when :inline
46
+ false
47
+ else
48
+ { valid: { class: 'valid-feedback' }, invalid: { class: 'invalid-feedback' } }
49
+ end
50
+ end
51
+
52
+ def hint_options
53
+ case layout
54
+ when :inline
55
+ { tag: :small, class: 'text-muted', id: "#{tag_id}_hint" }
56
+ else
57
+ { tag: :small, class: 'form-text text-muted', id: "#{tag_id}_hint" }
58
+ end
59
+ end
60
+
61
+ def wrapper_options
62
+ case layout
63
+ when :horizontal
64
+ { class: 'form-group row' }
65
+ else
66
+ { class: 'form-group' }
67
+ end
68
+ end
69
+
70
+ def to_html(&block)
71
+ wrap(&block)
72
+ end
73
+
74
+ protected
75
+
76
+ def wrap(&block)
77
+ case layout
78
+ when :inline
79
+ build_content(&block)
80
+ when :horizontal
81
+ build_wrapper do
82
+ (build_label.presence || content_tag(:div, '', class: 'col-sm-2')) +
83
+ content_tag(:div, build_content(&block), class: 'col-sm-10')
84
+ end
85
+ else # Vertical
86
+ build_wrapper { build_content(&block) }
87
+ end.html_safe
88
+ end
89
+
90
+ def build_wrapper(&block)
91
+ content_tag(:div, yield, options[:wrapper])
92
+ end
93
+
94
+ def build_content(&block)
95
+ return build_input_group_content(&block) if input_group?
96
+
97
+ if layout == :horizontal
98
+ build_input(&block) + build_feedback + build_hint
99
+ else
100
+ build_label + build_input(&block) + build_feedback + build_hint
101
+ end
102
+ end
103
+
104
+ def build_input_group_content(&block)
105
+ if layout == :horizontal
106
+ build_input_group { build_input(&block) } + build_hint
107
+ else
108
+ build_label + build_input_group { build_input(&block) } + build_hint
109
+ end
110
+ end
111
+
112
+ def build_input_group(&block) # Includes input and feedback
113
+ content_tag(:div, '', options[:input_group][:input_group]) do # Twice here, kind of weird.
114
+ [
115
+ (content_tag(:div, options[:input_group][:prepend], class: 'input-group-prepend') if options[:input_group][:prepend]),
116
+ build_input(&block),
117
+ (content_tag(:div, options[:input_group][:append], class: 'input-group-append') if options[:input_group][:append]),
118
+ build_feedback
119
+ ].compact.join.html_safe
120
+ end
121
+ end
122
+
123
+ def build_label
124
+ return BLANK if options[:label] == false
125
+ return BLANK if name.kind_of?(NilClass)
126
+
127
+ text = (options[:label].delete(:text) || (object.class.human_attribute_name(name) if object) || BLANK).html_safe
128
+
129
+ if options[:input][:id]
130
+ options[:label][:for] = options[:input][:id]
131
+ end
132
+
133
+ @builder.label(name, text, options[:label])
134
+ end
135
+
136
+ def build_input(&block)
137
+ capture(&block)
138
+ end
139
+
140
+ def build_hint
141
+ return BLANK unless options[:hint] && options[:hint][:text]
142
+
143
+ tag = options[:hint].delete(:tag)
144
+ text = options[:hint].delete(:text).html_safe
145
+
146
+ content_tag(tag, text, options[:hint])
147
+ end
148
+
149
+ def build_feedback
150
+ return BLANK if options[:feedback] == false
151
+
152
+ invalid = object.errors[name].to_sentence.presence if object.respond_to?(:errors)
153
+ invalid ||= options[:feedback][:invalid].delete(:text)
154
+ invalid ||= [("can't be blank" if options[:input][:required]), ('must be valid' if validated?(name))].compact.join(' and ')
155
+ invalid ||= 'is invalid'
156
+
157
+ valid = options[:feedback][:valid].delete(:text) || "Look's good!"
158
+
159
+ content_tag(:div, invalid, options[:feedback][:invalid]) +
160
+ content_tag(:div, valid, options[:feedback][:valid])
161
+ end
162
+
163
+ def has_error?(name = nil)
164
+ return false unless object.respond_to?(:errors)
165
+ name ? object.errors[name].present? : object.errors.present?
166
+ end
167
+
168
+ def required?(name)
169
+ return false unless object && name
170
+
171
+ obj = (object.class == Class) ? object : object.class
172
+ return false unless obj.respond_to?(:validators_on)
173
+
174
+ obj.validators_on(name).any? { |v| v.kind_of?(ActiveRecord::Validations::PresenceValidator) }
175
+ end
176
+
177
+ def validated?(name)
178
+ return false unless object && name
179
+
180
+ obj = (object.class == Class) ? object : object.class
181
+ return false unless obj.respond_to?(:validators_on)
182
+
183
+ obj.validators_on(name).any? { |v| !v.kind_of?(ActiveRecord::Validations::PresenceValidator) }
184
+ end
185
+
186
+ def input_group?
187
+ (options[:input_group][:append] || options[:input_group][:prepend]).present?
188
+ end
189
+
190
+ # Used for passwords and to not apply server side feedback
191
+ def reset_feedback?
192
+ return @reset_feedback unless @reset_feedback.nil?
193
+ @reset_feedback = options[:feedback].present? && (options[:feedback].delete(:reset) == true)
194
+ end
195
+
196
+ def value
197
+ object.public_send(name) if object.respond_to?(name)
198
+ end
199
+
200
+ private
201
+
202
+ # Here we split them into { wrapper: {}, label: {}, hint: {}, input: {} }
203
+ # And make sure to keep any additional options on the input: {}
204
+ def extract_options!(options, html_options: nil)
205
+ options.symbolize_keys!
206
+ html_options.symbolize_keys! if html_options
207
+
208
+ # effective_bootstrap specific options
209
+ layout = options.delete(:layout) # Symbol
210
+ wrapper = options.delete(:wrapper) # Hash
211
+ input_group = { append: options.delete(:append), prepend: options.delete(:prepend), input_group: options.delete(:input_group) }.compact
212
+
213
+ feedback = options.delete(:feedback) # Hash
214
+ label = options.delete(:label) # String or Hash
215
+ hint = options.delete(:hint) # String or Hash
216
+
217
+ input_html = options.delete(:input_html) || {} # Hash
218
+ input_js = options.delete(:input_js) || {} # Hash
219
+
220
+ # Every other option goes to input
221
+ @options = input = (html_options || options)
222
+
223
+ # Merge all the default objects, and intialize everything
224
+ wrapper = merge_defaults!(wrapper, wrapper_options)
225
+ input_group = merge_defaults!(input_group, input_group_options)
226
+ feedback = merge_defaults!(feedback, feedback_options)
227
+
228
+ label = merge_defaults!(label, label_options)
229
+ hint = merge_defaults!(hint, hint_options)
230
+
231
+ # Merge input_html: {}, defaults, and add all class: keys together
232
+ input.merge!(input_html.except(:class))
233
+ merge_defaults!(input, input_html_options.except(:class))
234
+ input[:class] = [input[:class], input_html[:class], input_html_options[:class]].compact.join(' ')
235
+
236
+ merge_defaults!(input_js, input_js_options)
237
+
238
+ if input_js.present?
239
+ merge_defaults!(input_js, input_js_options_method_name)
240
+ input['data-input-js-options'] = JSON.generate(input_js)
241
+ end
242
+
243
+ { layout: layout, wrapper: wrapper, input_group: input_group, label: label, hint: hint, input: input, feedback: feedback }
244
+ end
245
+
246
+ def apply_input_options!
247
+ # Server side validation
248
+ if has_error?
249
+ if has_error?(name)
250
+ options[:input][:class] = [options[:input][:class], 'is-invalid'].compact.join(' ')
251
+ elsif reset_feedback?
252
+ # Nothing
253
+ else
254
+ options[:input][:class] = [options[:input][:class], 'is-valid'].compact.join(' ')
255
+ end
256
+ end
257
+
258
+ if required?(name) && (options[:input].delete(:required) != false)
259
+ options[:input][:required] = 'required'
260
+ end
261
+
262
+ if options[:input][:readonly]
263
+ options[:input][:readonly] = 'readonly'
264
+ options[:input][:class] = options[:input][:class].to_s.sub('form-control', 'form-control-plaintext')
265
+ end
266
+
267
+ if options[:hint] && options[:hint][:text] && options[:hint][:id]
268
+ options[:input].reverse_merge!('aria-describedby': options[:hint][:id])
269
+ end
270
+ end
271
+
272
+ def merge_defaults!(obj, defaults)
273
+ defaults = {} if defaults.nil?
274
+
275
+ case obj
276
+ when false
277
+ false
278
+ when nil, true
279
+ defaults
280
+ when String
281
+ defaults.merge(text: obj)
282
+ when Hash
283
+ obj.reverse_merge!(defaults)
284
+ else
285
+ raise 'unexpected object'
286
+ end
287
+ end
288
+
289
+ def layout
290
+ options[:layout] || @builder.layout
291
+ end
292
+
293
+ # https://github.com/rails/rails/blob/master/actionview/lib/action_view/helpers/tags/base.rb#L120
294
+ # Not 100% sure best way to generate this
295
+ def tag_id(index = nil)
296
+ case
297
+ when @builder.object_name.empty?
298
+ sanitized_method_name.dup
299
+ when index
300
+ "#{sanitized_object_name}_#{index}_#{sanitized_method_name}"
301
+ else
302
+ "#{sanitized_object_name}_#{sanitized_method_name}"
303
+ end
304
+ end
305
+
306
+ def sanitized_object_name
307
+ @builder.object_name.to_s.gsub(/\]\[|[^-a-zA-Z0-9:.]/, "_").sub(/_$/, "")
308
+ end
309
+
310
+ def sanitized_method_name
311
+ name.to_s.sub(/\?$/, "")
312
+ end
313
+
314
+ def input_js_options_method_name
315
+ { method_name: "effective_#{self.class.name.split('::').last.underscore.chomp('_field')}" }
316
+ end
317
+
318
+ end
319
+ end