effective_bootstrap 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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