├── .editorconfig ├── .gitignore ├── Gemfile ├── Gemfile.lock ├── LICENSE.txt ├── README.md ├── Rakefile ├── VERSION ├── app ├── assets │ ├── images │ │ └── bhf │ │ │ ├── ajax_loader.gif │ │ │ ├── bg.png │ │ │ ├── logo_bhf.svg │ │ │ ├── mooeditable-toolbarbuttons-tango.png │ │ │ ├── pictos.png │ │ │ ├── pictos_2x.png │ │ │ ├── small_ajax_loader.gif │ │ │ ├── small_ajax_loader_h.gif │ │ │ └── wmd-buttons.png │ ├── javascripts │ │ └── bhf │ │ │ ├── MooTools-More-1.6.0.js │ │ │ ├── application.js │ │ │ ├── classes │ │ │ ├── Ajaxify.js │ │ │ ├── ArrayFields.js │ │ │ ├── FormHelper.js │ │ │ ├── MooEditable.js │ │ │ ├── MultipleFields.js │ │ │ ├── Picker.js │ │ │ ├── Picker_Attach.js │ │ │ ├── Picker_Date.js │ │ │ ├── PlatformHelper.js │ │ │ ├── QuickEdit.js │ │ │ ├── QuickEditStack.js │ │ │ ├── Request_bhf.js │ │ │ ├── Setlatlng.js │ │ │ ├── showdown.js │ │ │ └── wmd.js │ │ │ ├── locales │ │ │ ├── Locale.de-DE.js │ │ │ ├── Locale.en-US.js │ │ │ └── Locale.pt-PT.js │ │ │ ├── mootools_ujs.js │ │ │ └── trix.js │ └── stylesheets │ │ └── bhf │ │ ├── MooEditable.scss │ │ ├── application.sass │ │ ├── functions.sass │ │ ├── reset.sass │ │ ├── trix.scss │ │ └── typo.scss ├── controllers │ └── bhf │ │ ├── application_controller.rb │ │ ├── embed_entries_controller.rb │ │ ├── entries_controller.rb │ │ └── pages_controller.rb ├── helpers │ └── bhf │ │ ├── application_helper.rb │ │ ├── entries_helper.rb │ │ ├── frontend_helper.rb │ │ └── pages_helper.rb └── views │ ├── bhf │ ├── _footer.html.haml │ ├── _user.html.haml │ ├── application │ │ └── index.html.haml │ ├── entries │ │ ├── _form.html.haml │ │ ├── _validation_errors.html.haml │ │ ├── edit.html.haml │ │ ├── new.html.haml │ │ └── show.html.haml │ ├── form │ │ ├── belongs_to │ │ │ ├── _radio.html.haml │ │ │ ├── _select.html.haml │ │ │ └── _static.html.haml │ │ ├── column │ │ │ ├── _activestorage_attachment.html.haml │ │ │ ├── _activestorage_attachments.html.haml │ │ │ ├── _array.html.haml │ │ │ ├── _attachment_presenter.html.haml │ │ │ ├── _boolean.html.haml │ │ │ ├── _date.html.haml │ │ │ ├── _hash.html.haml │ │ │ ├── _hidden.html.haml │ │ │ ├── _image_file.html.haml │ │ │ ├── _mappin.html.haml │ │ │ ├── _markdown.html.haml │ │ │ ├── _multiple_fields.html.haml │ │ │ ├── _number.html.haml │ │ │ ├── _password.html.haml │ │ │ ├── _rich_text.html.haml │ │ │ ├── _static.html.haml │ │ │ ├── _string.html.haml │ │ │ ├── _text.html.haml │ │ │ ├── _type.html.haml │ │ │ └── _wysiwyg.html.haml │ │ ├── embeds_many │ │ │ └── _static.html.haml │ │ ├── embeds_one │ │ │ └── _static.html.haml │ │ ├── has_and_belongs_to_many │ │ │ ├── _check_box.html.haml │ │ │ └── _static.html.haml │ │ ├── has_many │ │ │ ├── _check_box.html.haml │ │ │ └── _static.html.haml │ │ └── has_one │ │ │ ├── _select.html.haml │ │ │ └── _static.html.haml │ ├── helper │ │ ├── _definition_item.html.haml │ │ ├── _field_errors.html.haml │ │ ├── _flash.html.haml │ │ ├── _frontend_edit.html.haml │ │ ├── _info.html.haml │ │ ├── _node.html.haml │ │ └── _reflection_node.html.haml │ ├── pages │ │ ├── _platform.html.haml │ │ ├── _search.html.haml │ │ └── show.html.haml │ └── table │ │ ├── belongs_to │ │ └── _default.html.haml │ │ ├── column │ │ ├── _array.html.haml │ │ ├── _boolean.html.haml │ │ ├── _carrierwave.html.haml │ │ ├── _date.html.haml │ │ ├── _empty.html.haml │ │ ├── _extern_link.html.haml │ │ ├── _file.html.haml │ │ ├── _hash.html.haml │ │ ├── _image.html.haml │ │ ├── _number.html.haml │ │ ├── _primary_key.html.haml │ │ ├── _string.html.haml │ │ ├── _text.html.haml │ │ ├── _thumbnail.html.haml │ │ ├── _toggle.html.haml │ │ └── _type.html.haml │ │ ├── embeds_many │ │ └── _default.html.haml │ │ ├── embeds_one │ │ └── _default.html.haml │ │ ├── has_and_belongs_to_many │ │ └── _default.html.haml │ │ ├── has_many │ │ └── _default.html.haml │ │ └── has_one │ │ └── _default.html.haml │ ├── kaminari │ └── bhf │ │ ├── _gap.html.haml │ │ ├── _next_page.html.haml │ │ ├── _page.html.haml │ │ ├── _paginator.html.haml │ │ └── _prev_page.html.haml │ └── layouts │ └── bhf │ ├── application.haml │ └── quick_edit.haml ├── bhf.gemspec ├── config ├── locales │ ├── de.yml │ ├── en.yml │ └── pt.yml └── routes.rb ├── lib ├── bhf.rb ├── bhf │ ├── action_view │ │ ├── form_builder.rb │ │ └── form_options.rb │ ├── active_record │ │ └── base.rb │ ├── controller │ │ └── extension.rb │ ├── mongoid │ │ └── document.rb │ ├── platform │ │ ├── attribute │ │ │ ├── abstract.rb │ │ │ ├── column.rb │ │ │ └── reflection.rb │ │ ├── base.rb │ │ └── pagination.rb │ └── settings │ │ ├── base.rb │ │ ├── platform.rb │ │ └── yaml_parser.rb └── rails │ └── generators │ └── bhf │ └── templates │ └── initializer.rb └── test ├── database.yml └── test_helper.rb /.editorconfig: -------------------------------------------------------------------------------- 1 | # editorconfig.org 2 | root = true 3 | 4 | [*] 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | charset = utf-8 9 | trim_trailing_whitespace = true 10 | insert_final_newline = true 11 | 12 | [*.js] 13 | indent_style = tab 14 | 15 | [*.{md,haml}] 16 | trim_trailing_whitespace = false 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .sass-cache/ 2 | pkg/ 3 | doc/ 4 | .yardoc/ 5 | .bundle/ 6 | **/*.sw* 7 | **/*~ 8 | .ruby-version 9 | .ruby-gemset 10 | .rvmrc 11 | node_modules 12 | /public/packs 13 | /public/packs-test 14 | /public/bhf-packs 15 | /public/bhf-packs-test 16 | /tmp/* 17 | bhf-*.gem 18 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source 'http://rubygems.org' 2 | 3 | gem 'rails', '>= 5.2.2.1' 4 | gem 'sass-rails', '>= 6' 5 | gem 'turbolinks', '>= 2' 6 | gem 'kaminari', '>= 0' 7 | gem 'haml-rails', '>= 0' 8 | 9 | 10 | group :development do 11 | gem 'shoulda', '>= 3' 12 | gem 'rdoc', '>= 3' 13 | gem 'bundler', '>= 1' 14 | gem 'jeweler', '>= 2' 15 | gem 'simplecov', '>= 0' 16 | end 17 | -------------------------------------------------------------------------------- /Gemfile.lock: -------------------------------------------------------------------------------- 1 | GEM 2 | remote: http://rubygems.org/ 3 | specs: 4 | actioncable (6.0.2.1) 5 | actionpack (= 6.0.2.1) 6 | nio4r (~> 2.0) 7 | websocket-driver (>= 0.6.1) 8 | actionmailbox (6.0.2.1) 9 | actionpack (= 6.0.2.1) 10 | activejob (= 6.0.2.1) 11 | activerecord (= 6.0.2.1) 12 | activestorage (= 6.0.2.1) 13 | activesupport (= 6.0.2.1) 14 | mail (>= 2.7.1) 15 | actionmailer (6.0.2.1) 16 | actionpack (= 6.0.2.1) 17 | actionview (= 6.0.2.1) 18 | activejob (= 6.0.2.1) 19 | mail (~> 2.5, >= 2.5.4) 20 | rails-dom-testing (~> 2.0) 21 | actionpack (6.0.2.1) 22 | actionview (= 6.0.2.1) 23 | activesupport (= 6.0.2.1) 24 | rack (~> 2.0, >= 2.0.8) 25 | rack-test (>= 0.6.3) 26 | rails-dom-testing (~> 2.0) 27 | rails-html-sanitizer (~> 1.0, >= 1.2.0) 28 | actiontext (6.0.2.1) 29 | actionpack (= 6.0.2.1) 30 | activerecord (= 6.0.2.1) 31 | activestorage (= 6.0.2.1) 32 | activesupport (= 6.0.2.1) 33 | nokogiri (>= 1.8.5) 34 | actionview (6.0.2.1) 35 | activesupport (= 6.0.2.1) 36 | builder (~> 3.1) 37 | erubi (~> 1.4) 38 | rails-dom-testing (~> 2.0) 39 | rails-html-sanitizer (~> 1.1, >= 1.2.0) 40 | activejob (6.0.2.1) 41 | activesupport (= 6.0.2.1) 42 | globalid (>= 0.3.6) 43 | activemodel (6.0.2.1) 44 | activesupport (= 6.0.2.1) 45 | activerecord (6.0.2.1) 46 | activemodel (= 6.0.2.1) 47 | activesupport (= 6.0.2.1) 48 | activestorage (6.0.2.1) 49 | actionpack (= 6.0.2.1) 50 | activejob (= 6.0.2.1) 51 | activerecord (= 6.0.2.1) 52 | marcel (~> 0.3.1) 53 | activesupport (6.0.2.1) 54 | concurrent-ruby (~> 1.0, >= 1.0.2) 55 | i18n (>= 0.7, < 2) 56 | minitest (~> 5.1) 57 | tzinfo (~> 1.1) 58 | zeitwerk (~> 2.2) 59 | addressable (2.4.0) 60 | builder (3.2.4) 61 | concurrent-ruby (1.1.6) 62 | crass (1.0.6) 63 | descendants_tracker (0.0.4) 64 | thread_safe (~> 0.3, >= 0.3.1) 65 | docile (1.3.2) 66 | erubi (1.9.0) 67 | erubis (2.7.0) 68 | faraday (0.9.2) 69 | multipart-post (>= 1.2, < 3) 70 | ffi (1.11.3) 71 | git (1.5.0) 72 | github_api (0.16.0) 73 | addressable (~> 2.4.0) 74 | descendants_tracker (~> 0.0.4) 75 | faraday (~> 0.8, < 0.10) 76 | hashie (>= 3.4) 77 | mime-types (>= 1.16, < 3.0) 78 | oauth2 (~> 1.0) 79 | globalid (0.4.2) 80 | activesupport (>= 4.2.0) 81 | haml (5.1.2) 82 | temple (>= 0.8.0) 83 | tilt 84 | haml-rails (2.0.1) 85 | actionpack (>= 5.1) 86 | activesupport (>= 5.1) 87 | haml (>= 4.0.6, < 6.0) 88 | html2haml (>= 1.0.1) 89 | railties (>= 5.1) 90 | hashie (4.0.0) 91 | highline (2.0.3) 92 | html2haml (2.2.0) 93 | erubis (~> 2.7.0) 94 | haml (>= 4.0, < 6) 95 | nokogiri (>= 1.6.0) 96 | ruby_parser (~> 3.5) 97 | i18n (1.8.3) 98 | concurrent-ruby (~> 1.0) 99 | jeweler (2.3.9) 100 | builder 101 | bundler 102 | git (>= 1.2.5) 103 | github_api (~> 0.16.0) 104 | highline (>= 1.6.15) 105 | nokogiri (>= 1.5.10) 106 | psych 107 | rake 108 | rdoc 109 | semver2 110 | json (2.3.0) 111 | jwt (2.2.1) 112 | kaminari (1.2.1) 113 | activesupport (>= 4.1.0) 114 | kaminari-actionview (= 1.2.1) 115 | kaminari-activerecord (= 1.2.1) 116 | kaminari-core (= 1.2.1) 117 | kaminari-actionview (1.2.1) 118 | actionview 119 | kaminari-core (= 1.2.1) 120 | kaminari-activerecord (1.2.1) 121 | activerecord 122 | kaminari-core (= 1.2.1) 123 | kaminari-core (1.2.1) 124 | loofah (2.6.0) 125 | crass (~> 1.0.2) 126 | nokogiri (>= 1.5.9) 127 | mail (2.7.1) 128 | mini_mime (>= 0.1.1) 129 | marcel (0.3.3) 130 | mimemagic (~> 0.3.2) 131 | method_source (0.9.2) 132 | mime-types (2.99.3) 133 | mimemagic (0.3.10) 134 | nokogiri (~> 1) 135 | rake 136 | mini_mime (1.0.2) 137 | mini_portile2 (2.8.0) 138 | minitest (5.14.1) 139 | multi_json (1.14.1) 140 | multi_xml (0.6.0) 141 | multipart-post (2.1.1) 142 | nio4r (2.5.2) 143 | nokogiri (1.13.4) 144 | mini_portile2 (~> 2.8.0) 145 | racc (~> 1.4) 146 | oauth2 (1.4.2) 147 | faraday (>= 0.8, < 2.0) 148 | jwt (>= 1.0, < 3.0) 149 | multi_json (~> 1.3) 150 | multi_xml (~> 0.5) 151 | rack (>= 1.2, < 3) 152 | psych (3.1.0) 153 | racc (1.6.0) 154 | rack (2.2.3) 155 | rack-test (1.1.0) 156 | rack (>= 1.0, < 3) 157 | rails (6.0.2.1) 158 | actioncable (= 6.0.2.1) 159 | actionmailbox (= 6.0.2.1) 160 | actionmailer (= 6.0.2.1) 161 | actionpack (= 6.0.2.1) 162 | actiontext (= 6.0.2.1) 163 | actionview (= 6.0.2.1) 164 | activejob (= 6.0.2.1) 165 | activemodel (= 6.0.2.1) 166 | activerecord (= 6.0.2.1) 167 | activestorage (= 6.0.2.1) 168 | activesupport (= 6.0.2.1) 169 | bundler (>= 1.3.0) 170 | railties (= 6.0.2.1) 171 | sprockets-rails (>= 2.0.0) 172 | rails-dom-testing (2.0.3) 173 | activesupport (>= 4.2.0) 174 | nokogiri (>= 1.6) 175 | rails-html-sanitizer (1.3.0) 176 | loofah (~> 2.3) 177 | railties (6.0.2.1) 178 | actionpack (= 6.0.2.1) 179 | activesupport (= 6.0.2.1) 180 | method_source 181 | rake (>= 0.8.7) 182 | thor (>= 0.20.3, < 2.0) 183 | rake (13.0.1) 184 | rdoc (6.3.1) 185 | ruby_parser (3.14.1) 186 | sexp_processor (~> 4.9) 187 | sass-rails (6.0.0) 188 | sassc-rails (~> 2.1, >= 2.1.1) 189 | sassc (2.2.1) 190 | ffi (~> 1.9) 191 | sassc-rails (2.1.2) 192 | railties (>= 4.0.0) 193 | sassc (>= 2.0) 194 | sprockets (> 3.0) 195 | sprockets-rails 196 | tilt 197 | semver2 (3.4.2) 198 | sexp_processor (4.13.0) 199 | shoulda (3.6.0) 200 | shoulda-context (~> 1.0, >= 1.0.1) 201 | shoulda-matchers (~> 3.0) 202 | shoulda-context (1.2.2) 203 | shoulda-matchers (3.1.3) 204 | activesupport (>= 4.0.0) 205 | simplecov (0.17.1) 206 | docile (~> 1.1) 207 | json (>= 1.8, < 3) 208 | simplecov-html (~> 0.10.0) 209 | simplecov-html (0.10.2) 210 | sprockets (4.0.0) 211 | concurrent-ruby (~> 1.0) 212 | rack (> 1, < 3) 213 | sprockets-rails (3.2.1) 214 | actionpack (>= 4.0) 215 | activesupport (>= 4.0) 216 | sprockets (>= 3.0.0) 217 | temple (0.8.2) 218 | thor (1.0.1) 219 | thread_safe (0.3.6) 220 | tilt (2.0.10) 221 | turbolinks (5.2.1) 222 | turbolinks-source (~> 5.2) 223 | turbolinks-source (5.2.0) 224 | tzinfo (1.2.7) 225 | thread_safe (~> 0.1) 226 | websocket-driver (0.7.1) 227 | websocket-extensions (>= 0.1.0) 228 | websocket-extensions (0.1.5) 229 | zeitwerk (2.4.0) 230 | 231 | PLATFORMS 232 | ruby 233 | 234 | DEPENDENCIES 235 | bundler (>= 1) 236 | haml-rails 237 | jeweler (>= 2) 238 | kaminari 239 | rails (>= 5.2.2.1) 240 | rdoc (>= 3) 241 | sass-rails (>= 6) 242 | shoulda (>= 3) 243 | simplecov 244 | turbolinks (>= 2) 245 | 246 | BUNDLED WITH 247 | 1.17.3 248 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 Anton Pawlik 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. 21 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bhf 2 | 3 | ## [Guide and Documentation](https://antpaw.github.io/bhf/) 4 | 5 | ## Contributing to bhf 6 | 7 | * Check out the latest master to make sure the feature hasn't been implemented or the bug hasn't been fixed yet. 8 | * Check out the issue tracker to make sure someone already hasn't requested it and/or contributed it. 9 | * Fork the project. 10 | * Start a feature/bugfix branch. 11 | * Commit and push until you are happy with your contribution. 12 | * Please try not to mess with the Rakefile, version, or history. If you want to have your own version, or is otherwise necessary, that is fine, but please isolate to its own commit so I can cherry-pick around it. 13 | 14 | 15 | ## Roadmap 16 | 17 | * Adding unit tests and integration tests 18 | * Adding bulk edit for the table view (aka platform) 19 | * Manage relations with help of autocomplete inputs 20 | * Move from MooTools to jQuery 21 | 22 | ## Support 23 | 24 |

Click here to lend your support to: bhf and make a donation at pledgie.com !

25 | 26 | 27 | ## Copyright 28 | 29 | Copyright (c) 2014 Anton Pawlik. See LICENSE.txt for 30 | further details. 31 | 32 | 33 | ### Build 34 | 35 | gem install juwelier 36 | rake gemspec:generate 37 | git commit -m 'Regenerate gemspec for version 1.0.0.beta11' -a 38 | gem build bhf.gemspec 39 | gem push bhf-1.0.0.beta11.gem 40 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'rubygems' 4 | require 'bundler' 5 | begin 6 | Bundler.setup(:default, :development) 7 | rescue Bundler::BundlerError => e 8 | $stderr.puts e.message 9 | $stderr.puts "Run `bundle install` to install missing gems" 10 | exit e.status_code 11 | end 12 | require 'rake' 13 | 14 | require 'jeweler' 15 | Jeweler::Tasks.new do |gem| 16 | gem.name = 'bhf' 17 | gem.homepage = 'https://antpaw.github.io/bhf' 18 | gem.licenses = 'MIT' 19 | gem.summary = 'A simple to use Rails-Engine-Gem that offers an admin interface for trusted user.' 20 | gem.description = 'A simple to use Rails-Engine-Gem that offers an admin interface for trusted user. Easy integratable and highly configurable and agnostic. Works with ActiveRecord and Mongoid.' 21 | gem.email = 'anton.pawlik@gmail.com' 22 | gem.authors = ['Anton Pawlik'] 23 | end 24 | Jeweler::RubygemsDotOrgTasks.new 25 | 26 | require 'rake/testtask' 27 | Rake::TestTask.new(:test) do |test| 28 | test.libs << 'lib' << 'test' 29 | test.pattern = 'test/**/test_*.rb' 30 | test.verbose = true 31 | end 32 | 33 | desc "Code coverage detail" 34 | task :simplecov do 35 | ENV['COVERAGE'] = "true" 36 | Rake::Task['test'].execute 37 | end 38 | 39 | task :default => :test 40 | 41 | require 'rdoc/task' 42 | Rake::RDocTask.new do |rdoc| 43 | version = File.exist?('VERSION') ? File.read('VERSION') : "" 44 | 45 | rdoc.rdoc_dir = 'rdoc' 46 | rdoc.title = "bhf #{version}" 47 | rdoc.rdoc_files.include('README*') 48 | rdoc.rdoc_files.include('lib/**/*.rb') 49 | end 50 | -------------------------------------------------------------------------------- /VERSION: -------------------------------------------------------------------------------- 1 | 1.0.0.beta16 2 | -------------------------------------------------------------------------------- /app/assets/images/bhf/ajax_loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpaw/bhf/57d208c064568a0bea48063fd478b271ae0d2474/app/assets/images/bhf/ajax_loader.gif -------------------------------------------------------------------------------- /app/assets/images/bhf/bg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpaw/bhf/57d208c064568a0bea48063fd478b271ae0d2474/app/assets/images/bhf/bg.png -------------------------------------------------------------------------------- /app/assets/images/bhf/logo_bhf.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | logo_bhf 4 | Created with Sketch (http://www.bohemiancoding.com/sketch) 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /app/assets/images/bhf/mooeditable-toolbarbuttons-tango.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpaw/bhf/57d208c064568a0bea48063fd478b271ae0d2474/app/assets/images/bhf/mooeditable-toolbarbuttons-tango.png -------------------------------------------------------------------------------- /app/assets/images/bhf/pictos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpaw/bhf/57d208c064568a0bea48063fd478b271ae0d2474/app/assets/images/bhf/pictos.png -------------------------------------------------------------------------------- /app/assets/images/bhf/pictos_2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpaw/bhf/57d208c064568a0bea48063fd478b271ae0d2474/app/assets/images/bhf/pictos_2x.png -------------------------------------------------------------------------------- /app/assets/images/bhf/small_ajax_loader.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpaw/bhf/57d208c064568a0bea48063fd478b271ae0d2474/app/assets/images/bhf/small_ajax_loader.gif -------------------------------------------------------------------------------- /app/assets/images/bhf/small_ajax_loader_h.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpaw/bhf/57d208c064568a0bea48063fd478b271ae0d2474/app/assets/images/bhf/small_ajax_loader_h.gif -------------------------------------------------------------------------------- /app/assets/images/bhf/wmd-buttons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/antpaw/bhf/57d208c064568a0bea48063fd478b271ae0d2474/app/assets/images/bhf/wmd-buttons.png -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/application.js: -------------------------------------------------------------------------------- 1 | //= require turbolinks 2 | //= require activestorage 3 | //= require ./MooTools-More-1.6.0.js 4 | //= require ./mootools_ujs 5 | //= require ./trix 6 | //= require_tree ./locales/ 7 | //= require_tree ./classes/ 8 | 9 | (function(){ 10 | var lang = document.html.get('lang').split('-')[0]; 11 | lang = (lang === 'en') ? 'en-US' : lang.toLowerCase()+'-'+lang.toUpperCase(); 12 | Locale.use(lang); 13 | 14 | var ajaxNote = new Ajaxify(); 15 | 16 | var editStack = new QuickEditStack({ 17 | permanentQuickEditEvents: { 18 | startRequest: function(){ 19 | var linkElem = this.linkElem; 20 | var rowElem = this.linkElem.getParent('tr'); 21 | if (rowElem) { 22 | linkElem = rowElem; 23 | } 24 | linkElem.addClass('live_edit'); 25 | }, 26 | closed: function(){ 27 | var linkElem = this.linkElem; 28 | var rowElem = this.linkElem.getParent('tr'); 29 | if (rowElem) { 30 | rowElem.addClass('animate'); 31 | setTimeout(function(){ 32 | rowElem.removeClass('animate'); 33 | }, 600); 34 | linkElem = rowElem; 35 | } 36 | setTimeout(function(){ 37 | linkElem.removeClass('live_edit'); 38 | }); 39 | } 40 | } 41 | }); 42 | 43 | var triggerPlatformPagination = function(href, platform, callback){ 44 | window.history.pushState({ turbolinks: true, url: href }, '', href); 45 | ajaxNote.loading(); 46 | new Request.HTML({ 47 | method: 'get', 48 | url: href, 49 | onFailure: function(){ 50 | ajaxNote.failure(); 51 | }, 52 | onSuccess: function (a, b, html) { 53 | platform.innerHTML = html; 54 | if (callback) { 55 | callback.call(); 56 | } 57 | window.fireEvent('platformUpdate', [platform.getParent()]); 58 | } 59 | }).send(); 60 | }; 61 | 62 | var updateElementsAfterQuickEditSuccess = function(eventNames, linkElem, json){ 63 | var parent = linkElem.getParent('.quick_edit_block'); 64 | var entry = linkElem.getParent('.quick_edit_entry'); 65 | var entryTemplate, newEntryInjectArea, relation, parsedTemplate; 66 | if (parent) { 67 | entryTemplate = parent.getElement('.quick_edit_template'); 68 | newEntryInjectArea = parent.getElement('.quick_edit_inject'); 69 | relation = parent.getElement('.relation'); 70 | } 71 | if ( ! entry) { 72 | entry = parent.getElement('.quick_edit_active'); 73 | } 74 | 75 | if (entryTemplate) { 76 | entryTemplate = entryTemplate.clone(); 77 | entryTemplate.getElements('.js_remove_disabled').each(function(elem){ 78 | elem.removeProperty('disabled'); 79 | }); 80 | if (json) { 81 | parsedTemplate = entryTemplate.innerHTML.replace(new RegExp('%7Bobject_id%7D', 'g'), '{object_id}').substitute(json); 82 | } 83 | } 84 | 85 | if (eventNames.contains('successAndAdd')) { 86 | if (newEntryInjectArea && entryTemplate) { 87 | newEntryInjectArea.appendHTML(parsedTemplate); 88 | if (newEntryInjectArea) { 89 | newEntryInjectArea.fireEvent('quickEditEntryAdded'); 90 | } 91 | } 92 | 93 | if (relation) { 94 | if (relation.getPrevious('.empty')) { 95 | relation.getPrevious('.empty').addClass('hide'); 96 | } 97 | if (relation.hasClass('has_one') || relation.hasClass('embeds_one')) { 98 | relation.getNext('.js_add_field').addClass('hide'); 99 | } 100 | } 101 | } 102 | if (eventNames.contains('successAndChange')) { 103 | if (entry && parsedTemplate) { 104 | entry.innerHTML = parsedTemplate; 105 | } 106 | } 107 | if (eventNames.contains('successAndRemove')) { 108 | if (entry) { 109 | entry.dispose(); 110 | } 111 | if (newEntryInjectArea) { 112 | newEntryInjectArea.fireEvent('quickEditEntryRemoved'); 113 | } 114 | } 115 | }; 116 | 117 | var scrollContent = function(){ 118 | editStack.bricksArray.each(function(quickEdit){ 119 | var innerForm = quickEdit.holder.getElement('form'); 120 | if ( ! innerForm) { return; } 121 | var scroll = document.body.scrollTop - 83; 122 | if (scroll + innerForm.getSize().y > document.body.clientHeight) { return; } 123 | quickEdit.holder.setStyle('padding-top', scroll); 124 | }); 125 | }; 126 | 127 | 128 | window.addEvent('bhfDomChunkReady', function(mainScope){ 129 | 130 | var areaSelect = mainScope.getElement('#area_select'); 131 | if (areaSelect) { 132 | areaSelect.addEvent('change', function(){ 133 | location.href = this.value; 134 | }); 135 | } 136 | 137 | var quickEditOptions; 138 | 139 | ajaxNote.setup(); 140 | 141 | var jsForm = new FormHelper(); 142 | mainScope.getElements('.js_bhf_form').each(function(form){ 143 | jsForm.setup(form); 144 | }); 145 | 146 | var sharedQuickEditOptions = { 147 | onFailure: function(){ 148 | ajaxNote.failure(); 149 | }, 150 | onStartRequest: function(){ 151 | ajaxNote.loading(); 152 | }, 153 | onFormInjected: function(){ 154 | scrollContent(); 155 | ajaxNote.success(); 156 | }, 157 | onSave: function(eventNames, linkElem, json){ 158 | updateElementsAfterQuickEditSuccess(eventNames, linkElem, json); 159 | ajaxNote.success(); 160 | }, 161 | onBeforeSubmit: function(){ 162 | ajaxNote.loading(); 163 | jsForm.wysiwyg.each(function(editor){ 164 | editor.saveContent(); 165 | }); 166 | } 167 | }; 168 | 169 | var platforms = mainScope.getElements('.platform'); 170 | var mainForm = mainScope.getElementById('main_form'); 171 | 172 | mainScope.getElements('.quick_edit_select').addEvent('change', function(){ 173 | var parent = this.getParent('.quick_edit_block'); 174 | var optionElem = this.options[this.selectedIndex]; 175 | var editElem = parent.getElement('.js_edit_field'); 176 | if (editElem) { 177 | editElem.href = optionElem.get('data-edit'); 178 | } 179 | var deleteElem = parent.getElement('.js_delete'); 180 | if (deleteElem) { 181 | deleteElem.href = optionElem.get('data-delete'); 182 | } 183 | this.getElements('.quick_edit_active').removeClass('quick_edit_active'); 184 | optionElem.addClass('quick_edit_active'); 185 | }).addEvent('quickEditEntryAdded', function(){ 186 | this.selectedIndex = this.options.length - 1; 187 | this.fireEvent('change'); 188 | }).addEvent('quickEditEntryRemoved', function(){ 189 | this.selectedIndex = 0; 190 | this.fireEvent('change'); 191 | }).fireEvent('change'); 192 | 193 | mainScope.addEvent('click:relay(.js_delete)', function(e){ 194 | e.target.addEvents({ 195 | 'ajax:success': function(){ 196 | updateElementsAfterQuickEditSuccess(['successAndRemove'], e.target); 197 | }, 198 | 'ajax:failure': function(){ 199 | alert(Locale.get('Notifications.failure')); 200 | } 201 | }); 202 | }); 203 | 204 | mainScope.addEvent('click:relay(.js_toggle)', function(e){ 205 | e.target.addEvents({ 206 | 'ajax:success': function(xhr, responseJSON){ 207 | updateElementsAfterQuickEditSuccess(['successAndChange'], e.target, responseJSON); 208 | }, 209 | 'ajax:failure': function(){ 210 | alert(Locale.get('Notifications.failure')); 211 | } 212 | }); 213 | }); 214 | 215 | if (platforms.length) { 216 | quickEditOptions = Object.merge({ 217 | onSuccessAndNext: function(){ 218 | var tr = this.linkElem.getParent('tr'); 219 | var nextTr = tr.getNext('tr'); 220 | 221 | if (nextTr) { 222 | editStack.removeAllStacks(); 223 | editStack.addEditBrick(quickEditOptions, nextTr.getElement('a'), nextTr); 224 | } 225 | else { 226 | var trIndex; 227 | var platform = tr.getParent('.platform'); 228 | var loadMore = platform.getElement('.load_more'); 229 | if (loadMore) { 230 | trIndex = tr.getParent('tbody').getElements('tr').indexOf(tr); 231 | triggerPlatformPagination(loadMore.get('href'), platform, function(){ 232 | platform.getElements('tbody tr').each(function(newTr, index){ 233 | if (trIndex === index) { 234 | nextTr = newTr.getNext('tr'); 235 | 236 | editStack.removeAllStacks(); 237 | editStack.addEditBrick(quickEditOptions, nextTr.getElement('a'), nextTr); 238 | } 239 | }); 240 | }); 241 | } 242 | else { 243 | nextTr = platform.getElements('tbody tr')[0]; 244 | 245 | editStack.removeAllStacks(); 246 | editStack.addEditBrick(quickEditOptions, nextTr.getElement('a'), nextTr); 247 | } 248 | } 249 | } 250 | }, sharedQuickEditOptions); 251 | 252 | 253 | platforms.each(function(p){ 254 | new PlatformHelper(p, { 255 | onPaginationStart: function(link){ 256 | triggerPlatformPagination(link.get('href'), link.getParent('.platform')); 257 | }, 258 | onQuickEditStart: function(link){ 259 | editStack.removeAllStacks(); 260 | editStack.addEditBrick(quickEditOptions, link, link.getParent('tr')); 261 | }, 262 | onSearch: function(){ 263 | ajaxNote.loading(); 264 | }, 265 | onSearchFailure: function(){ 266 | ajaxNote.failure(); 267 | }, 268 | onSearchSuccess: function(request){ 269 | window.fireEvent('platformUpdate', [p.getParent()]); 270 | window.history.pushState({ turbolinks: true, url: request.url }, '', request.url); 271 | } 272 | }); 273 | }); 274 | } 275 | else if (mainForm) { 276 | quickEditOptions = Object.merge({ 277 | onSuccessAndNext: function(){ 278 | var a = this.linkElem; 279 | var li = a.getParent('li'); 280 | if ( ! li) { 281 | this.close(); 282 | return; 283 | } 284 | var holder = li.getNext('li'); 285 | if ( ! holder) { 286 | holder = li.getParent('ul'); 287 | } 288 | 289 | editStack.removeAllStacks(); 290 | editStack.addEditBrick(quickEditOptions, holder.getElement('a')); 291 | } 292 | }, sharedQuickEditOptions); 293 | 294 | mainForm.addEvent('click:relay(.quick_edit)', function(e){ 295 | e.preventDefault(); 296 | editStack.removeAllStacks(); 297 | editStack.addEditBrick(quickEditOptions, this); 298 | }); 299 | } 300 | else if (mainScope.hasClass('quick_edit_holder')) { 301 | quickEditOptions = Object.merge({ 302 | onClosed: function(){ 303 | editStack.removeStack(); 304 | }, 305 | hideNext: true 306 | }, sharedQuickEditOptions); 307 | 308 | mainScope.addEvent('click:relay(.quick_edit)', function(e){ 309 | e.preventDefault(); 310 | editStack.addStack(); 311 | editStack.addEditBrick(quickEditOptions, this); 312 | }); 313 | } 314 | 315 | mainScope.getElements('.js_sortable').each(function(sortableElems){ 316 | new Sortables(sortableElems, { 317 | handle: '.handle', 318 | onStart: function(element){ 319 | element.addClass('dragged'); 320 | }, 321 | onComplete: function(element){ 322 | element.removeClass('dragged'); 323 | new Request({ 324 | method: 'put', 325 | headers: { 326 | 'X-CSRF-Token': rails && rails.csrf && rails.csrf.token 327 | }, 328 | url: sortableElems.get('data-sort-url') 329 | }).send({data: {order: this.serialize()}}); 330 | } 331 | }); 332 | }); 333 | 334 | var fm = document.id('flash_massages'); 335 | if (fm) { 336 | fm.removeClass.delay(10000, fm, 'show'); 337 | } 338 | 339 | mainScope.getElements('.initial_referral').each(function(elem){ 340 | setTimeout(function(){ 341 | elem.removeClass('live_edit'); 342 | setTimeout(function(){ 343 | elem.removeClass('animate'); 344 | }, 600); 345 | }, 500); 346 | }); 347 | 348 | mainScope.getElements('.ninja_file_field').addEvent('change', function(){ 349 | var text = (this.value || '').split('\\'); 350 | var parent = this.getParent(); 351 | var newText = parent.get('data-empty-field-text'); 352 | if (this.value) { 353 | newText = text.getLast() || this.value; 354 | } 355 | parent.getElement('.h_text').set('text', newText); 356 | }); 357 | 358 | mainScope.getElements('.js_submit_form_on_change').addEvent('change', function(e){ 359 | e.target.getParent('form').submit(); 360 | }); 361 | 362 | // picture preview handler on input 363 | mainScope.getElements('.preview_input').addEvent('change', function(e) { 364 | var img = this.getNext('img'); 365 | if (img) { 366 | img.src = URL.createObjectURL(e.target.files[0]); 367 | } 368 | }); 369 | 370 | // picture preview handler on hover 371 | mainScope.getElements('.thumbnail_image').addEvents({ 372 | mousemove: function(e) { 373 | this.getNext('img').setStyles({ 374 | top: e.page.y - 260, 375 | left: e.page.x + 10 376 | }); 377 | }, 378 | mouseenter: function() { 379 | var popup = new Element('img', {'class': 'popped_image', src: this.src}); 380 | popup.inject(this, 'after'); 381 | }, 382 | mouseleave: function() { 383 | mainScope.getElements('.popped_image').destroy(); 384 | } 385 | }); 386 | 387 | ajaxNote.success(); 388 | 389 | 390 | // direct_uploads.js 391 | mainScope.addEventListener("direct-upload:initialize", function (event) { 392 | var target = event.target; 393 | var detail = event.detail; 394 | var id = detail.id; 395 | var file = detail.file; 396 | target.insertAdjacentHTML("beforebegin", 397 | '
' + 398 | '
' + 399 | '' + file.name + '' + 400 | '
' 401 | ); 402 | }); 403 | 404 | mainScope.addEventListener("direct-upload:start", function (event) { 405 | var id = event.detail.id; 406 | var element = document.getElementById('direct-upload-' + id); 407 | element.classList.remove("direct-upload--pending"); 408 | }); 409 | 410 | mainScope.addEventListener("direct-upload:progress", function (event) { 411 | var id = event.detail.id; 412 | var progress = event.detail.progress; 413 | var progressElement = document.getElementById('direct-upload-progress-' + id); 414 | progressElement.style.width = progress + "%"; 415 | }); 416 | 417 | mainScope.addEventListener("direct-upload:error", function (event) { 418 | event.preventDefault(); 419 | var id = event.detail.id; 420 | var error = event.detail.error; 421 | var element = document.getElementById('direct-upload-' + id); 422 | element.classList.add("direct-upload--error"); 423 | element.setAttribute("title", error); 424 | }); 425 | 426 | mainScope.addEventListener("direct-upload:end", function (event) { 427 | var id = event.detail.id; 428 | var element = document.getElementById('direct-upload-' + id); 429 | element.classList.add("direct-upload--complete"); 430 | }); 431 | }); 432 | 433 | 434 | var bodyCallback = function () { 435 | window.fireEvent('bhfDomChunkReady', [document.body]); 436 | }; 437 | var scopeCallback = function (scope) { 438 | window.fireEvent('bhfDomChunkReady', [scope]); 439 | }; 440 | document.addEventListener('turbolinks:request-start', function () { 441 | ajaxNote.loading(); 442 | }); 443 | document.addEventListener('turbolinks:load', bodyCallback); 444 | window.addEvent('platformUpdate', scopeCallback); 445 | window.addEvent('quickEditFormInject', scopeCallback); 446 | }()); 447 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/Ajaxify.js: -------------------------------------------------------------------------------- 1 | var Ajaxify = new Class({ 2 | version: 0.2, 3 | 4 | Implements: [Options, Events], 5 | 6 | options: { 7 | holder: new Element('div#ajax_holder'), 8 | fadeOutDuration: 2000 9 | }, 10 | 11 | initialize: function(_options) { 12 | this.options.events = { 13 | loading: { 14 | name: 'ajax:loading', 15 | text: 'Loading…' 16 | }, 17 | success: { 18 | name: 'ajax:success', 19 | text: 'Changes successfully saved!' 20 | }, 21 | failure: { 22 | name: 'ajax:failure', 23 | text: Locale.get('Notifications.failure') 24 | } 25 | }; 26 | this.setOptions(_options); 27 | }, 28 | 29 | setup: function(){ 30 | this.holder = this.options.holder.inject(document.body); 31 | }, 32 | 33 | applyEvents: function(el){ 34 | el = document.id(el || document.body); 35 | var apply = function(action, callback) { 36 | el.getElements('[data-remote="true"]').addEvent(action, callback); 37 | }; 38 | 39 | apply(this.options.events.loading.name, this.loading.bind(this)); 40 | apply(this.options.events.success.name, this.success.bind(this)); 41 | apply(this.options.events.failure.name, this.failure.bind(this)); 42 | }, 43 | 44 | loading: function(xhr){ 45 | this.setMessage('loading', false); 46 | }, 47 | success: function(xhr){ 48 | this.setMessage('success', true); 49 | }, 50 | failure: function(xhr){ 51 | this.setMessage('failure', false); 52 | }, 53 | 54 | setMessage: function(status, fadeOut) { 55 | this.holder 56 | .set('text', this.options.events[status].text) 57 | .set('class', status); 58 | 59 | if (fadeOut) { 60 | this.holder.addClass('fadeout'); 61 | setTimeout(function(){ 62 | this.holder.erase('class'); 63 | }.bind(this), this.options.fadeOutDuration); 64 | } 65 | } 66 | }); 67 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/ArrayFields.js: -------------------------------------------------------------------------------- 1 | var ArrayFields = new Class({ 2 | version: 0.1, 3 | 4 | // Implements: [Options, Events], 5 | 6 | initialize: function(_object, _options) { 7 | if ( ! _object) { return; } 8 | // this.setOptions(_options); 9 | var elem = _object; 10 | var template = elem.getElement('.array_fields').clone(); 11 | var currentNr = 0; 12 | 13 | new Element('span.plus_button.qe_button.default_form_align', {text: '+'}) 14 | .inject(elem) 15 | .addEvent('click', function(e){ 16 | var holder = template.clone(); 17 | var newInput = (holder.getElement('input') || holder).erase('value'); 18 | var arrayI = newInput.get('name').match(/.+?\[(\d+)\].+/); 19 | if (arrayI && arrayI[1]) { 20 | currentNr += 1; 21 | newInput.set('name', 22 | newInput.get('name') 23 | .replace(/(.+?\[)\d+(\].+)/, '$1'+(parseInt(arrayI[1], 10)+currentNr)+'$2') 24 | ); 25 | } 26 | holder.inject(e.target, 'before'); 27 | window.fireEvent('bhfDomChunkReady', [holder]); 28 | }); 29 | 30 | elem.getParent('form').addEvent('submit', function(){ 31 | elem.getElements('.array_fields').each(function(fieldElem){ 32 | var input = (fieldElem.getElement('input') || fieldElem); 33 | if (input.value) { return; } 34 | input.erase('name'); 35 | }); 36 | return true; 37 | }); 38 | } 39 | }); 40 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/FormHelper.js: -------------------------------------------------------------------------------- 1 | var FormHelper = new Class({ 2 | version: 0.1, 3 | 4 | wysiwyg: [], 5 | 6 | options: { 7 | }, 8 | 9 | // Implements: [Options, Events], 10 | 11 | setup: function(_object, _options) { 12 | if ( ! _object) { return; } 13 | // this.setOptions(_options); 14 | var scope = _object; 15 | 16 | scope.getElements('.wysiwyg').each(function(elem){ 17 | this.wysiwyg.push(elem.mooEditable()); 18 | }.bind(this)); 19 | 20 | scope.getElements('.multiple_field').each(function(elem){ 21 | new MultipleFields(elem); 22 | }); 23 | scope.getElements('.array_holder').each(function(elem){ 24 | new ArrayFields(elem); 25 | }); 26 | 27 | scope.getElements('.picker').each(function(input){ 28 | var options = { 29 | timePicker: true, 30 | format: '%B %d, %Y %H:%M' 31 | }; 32 | if (input.hasClass('date')) { 33 | options = { 34 | timePicker: false, 35 | format: '%B %d, %Y' 36 | }; 37 | } 38 | else if (input.hasClass('time')) { 39 | options = { 40 | pickOnly: 'time', 41 | format: '%H:%M' 42 | }; 43 | } 44 | 45 | var hiddenInput = input.clone(); 46 | if (input.value) { 47 | input.value = new Date().parse(input.value).format(options.format); 48 | } 49 | input.erase('name'); 50 | hiddenInput.set('type', 'hidden').inject(input, 'after'); 51 | 52 | new Picker.Date(input, Object.merge({ 53 | onSelect: function(date){ 54 | hiddenInput.value = date.format('db'); 55 | } 56 | }, options)); 57 | }); 58 | 59 | scope.getElements('.wmd_editor').each(function(mdTa){ 60 | var headline, toggleHtmlPreview, toggleLivePreview, livePreview, htmlPreview; 61 | 62 | var togglePreview = function(e){ 63 | var htmlMode = e.target.hasClass('toggle_html_preview'); 64 | livePreview.toggleClass('hide', htmlMode); 65 | htmlPreview.toggleClass('hide', !htmlMode); 66 | toggleLivePreview.toggleClass('active', !htmlMode); 67 | toggleHtmlPreview.toggleClass('active', htmlMode); 68 | }; 69 | 70 | headline = new Element('h6.preview_switch', {text: 'Preview'}); 71 | 72 | toggleHtmlPreview = new Element('span.toggle_html_preview', {text: 'HTML'}) 73 | .addEvent('click', togglePreview) 74 | .inject(headline); 75 | toggleLivePreview = new Element('span.toggle_live_preview', {text: 'Live'}) 76 | .addEvent('click', togglePreview) 77 | .inject(headline); 78 | 79 | headline.inject(mdTa, 'after'); 80 | 81 | livePreview = new Element('div.wmd-preview.hide').inject(headline, 'after'); 82 | htmlPreview = new Element('div.wmd-output.hide').inject(livePreview, 'after'); 83 | 84 | new WMDEditor({ 85 | input: mdTa, 86 | button_bar: new Element('div').inject(mdTa, 'before'), 87 | preview: livePreview, 88 | output: htmlPreview, 89 | buttons: 'bold italic link image ol ul heading hr undo redo', 90 | modifierKeys: false, 91 | autoFormatting: false 92 | }); 93 | }); 94 | 95 | scope.getElements('.map_data_lat').each(function(lat){ 96 | new Setlatlng(lat); 97 | }); 98 | } 99 | }); 100 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/MultipleFields.js: -------------------------------------------------------------------------------- 1 | var MultipleFields = new Class({ 2 | version: 0.1, 3 | 4 | options: { 5 | spliter: ';' 6 | }, 7 | 8 | // Implements: [Options, Events], 9 | 10 | initialize: function(_object, _options) { 11 | if ( ! _object) { return; } 12 | // this.setOptions(_options); 13 | if (_object.get('data-spliter')) { 14 | this.options.spliter = _object.get('data-spliter'); 15 | } 16 | 17 | var elem = _object; 18 | var template = elem.clone() 19 | .erase('name').erase('id').erase('data-spliter').erase('value') 20 | .addClass('template'); 21 | 22 | elem.set('type', 'hidden'); 23 | 24 | new Element('span.plus_button.qe_button.default_form_align', {text: '+'}) 25 | .inject(elem, 'after') 26 | .addEvent('click', function(e){ 27 | this.addField(elem, template); 28 | }.bind(this)); 29 | 30 | elem.get('value').toString().split(this.options.spliter).each(function(data){ 31 | this.addField(elem, template, data); 32 | }.bind(this)); 33 | }, 34 | 35 | addField: function(elem, template, data){ 36 | template 37 | .clone() 38 | .set('value', data) 39 | .addEvent('change', function(e){ 40 | var values = []; 41 | e.target.getParent('.input').getElements('.template').each(function(template){ 42 | if (template.value.trim()) { 43 | values.push(template.value.trim()); 44 | } 45 | }); 46 | elem.set('value', values.join(this.options.spliter)); 47 | }.bind(this)) 48 | .inject(elem.getParent('.input').getElement('.plus_button'), 'before'); 49 | } 50 | }); 51 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/Picker.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Picker 4 | description: Creates a Picker, which can be used for anything 5 | authors: Arian Stolwijk 6 | requires: [Core/Element.Dimensions, Core/Fx.Tween, Core/Fx.Transitions] 7 | provides: Picker 8 | ... 9 | */ 10 | 11 | 12 | var Picker = new Class({ 13 | 14 | Implements: [Options, Events], 15 | 16 | options: {/* 17 | onShow: function(){}, 18 | onOpen: function(){}, 19 | onHide: function(){}, 20 | onClose: function(){},*/ 21 | 22 | pickerClass: 'datepicker', 23 | inject: null, 24 | animationDuration: 400, 25 | useFadeInOut: true, 26 | positionOffset: {x: 0, y: 0}, 27 | pickerPosition: 'bottom', 28 | draggable: true, 29 | showOnInit: true, 30 | columns: 1, 31 | footer: false 32 | }, 33 | 34 | initialize: function(options){ 35 | this.setOptions(options); 36 | this.constructPicker(); 37 | if (this.options.showOnInit) this.show(); 38 | }, 39 | 40 | constructPicker: function(){ 41 | var options = this.options; 42 | 43 | var picker = this.picker = new Element('div', { 44 | 'class': options.pickerClass, 45 | styles: { 46 | left: 0, 47 | top: 0, 48 | display: 'none', 49 | opacity: 0 50 | } 51 | }).inject(options.inject || document.body); 52 | picker.addClass('column_' + options.columns); 53 | 54 | if (options.useFadeInOut){ 55 | picker.set('tween', { 56 | duration: options.animationDuration, 57 | link: 'cancel' 58 | }); 59 | } 60 | 61 | // Build the header 62 | var header = this.header = new Element('div.header').inject(picker); 63 | 64 | var title = this.title = new Element('div.title').inject(header); 65 | var titleID = this.titleID = 'pickertitle-' + String.uniqueID(); 66 | this.titleText = new Element('div', { 67 | 'role': 'heading', 68 | 'class': 'titleText', 69 | 'id': titleID, 70 | 'aria-live': 'assertive', 71 | 'aria-atomic': 'true' 72 | }).inject(title); 73 | 74 | this.closeButton = new Element('div.closeButton[text=x][role=button]') 75 | .addEvent('click', this.close.pass(false, this)) 76 | .inject(header); 77 | 78 | // Build the body of the picker 79 | var body = this.body = new Element('div.body').inject(picker); 80 | 81 | if (options.footer){ 82 | this.footer = new Element('div.footer').inject(picker); 83 | picker.addClass('footer'); 84 | } 85 | 86 | // oldContents and newContents are used to slide from the old content to a new one. 87 | var slider = this.slider = new Element('div.slider', { 88 | styles: { 89 | position: 'absolute', 90 | top: 0, 91 | left: 0 92 | } 93 | }).set('tween', { 94 | duration: options.animationDuration, 95 | transition: Fx.Transitions.Quad.easeInOut 96 | }).inject(body); 97 | 98 | this.newContents = new Element('div', { 99 | styles: { 100 | position: 'absolute', 101 | top: 0, 102 | left: 0 103 | } 104 | }).inject(slider); 105 | 106 | this.oldContents = new Element('div', { 107 | styles: { 108 | position: 'absolute', 109 | top: 0 110 | } 111 | }).inject(slider); 112 | 113 | this.originalColumns = options.columns; 114 | this.setColumns(options.columns); 115 | 116 | // IFrameShim for select fields in IE 117 | var shim = this.shim = window['IframeShim'] ? new IframeShim(picker) : null; 118 | 119 | // Dragging 120 | if (options.draggable && typeOf(picker.makeDraggable) == 'function'){ 121 | this.dragger = picker.makeDraggable(shim ? { 122 | onDrag: shim.position.bind(shim) 123 | } : null); 124 | picker.setStyle('cursor', 'move'); 125 | } 126 | }, 127 | 128 | open: function(noFx){ 129 | if (this.opened == true) return this; 130 | this.opened = true; 131 | var self = this, 132 | picker = this.picker.setStyle('display', 'block').set('aria-hidden', 'false') 133 | if (this.shim) this.shim.show(); 134 | this.fireEvent('open'); 135 | if (this.options.useFadeInOut && !noFx){ 136 | picker.get('tween').start('opacity', 1).chain(function(){ 137 | self.fireEvent('show'); 138 | this.callChain(); 139 | }); 140 | } else { 141 | picker.setStyle('opacity', 1); 142 | this.fireEvent('show'); 143 | } 144 | return this; 145 | }, 146 | 147 | show: function(){ 148 | return this.open(true); 149 | }, 150 | 151 | close: function(noFx){ 152 | if (this.opened == false) return this; 153 | this.opened = false; 154 | this.fireEvent('close'); 155 | var self = this, picker = this.picker, hide = function(){ 156 | picker.setStyle('display', 'none').set('aria-hidden', 'true'); 157 | if (self.shim) self.shim.hide(); 158 | self.fireEvent('hide'); 159 | }; 160 | if (this.options.useFadeInOut && !noFx){ 161 | picker.get('tween').start('opacity', 0).chain(hide); 162 | } else { 163 | picker.setStyle('opacity', 0); 164 | hide(); 165 | } 166 | return this; 167 | }, 168 | 169 | hide: function(){ 170 | return this.close(true); 171 | }, 172 | 173 | toggle: function(){ 174 | return this[this.opened == true ? 'close' : 'open'](); 175 | }, 176 | 177 | destroy: function(){ 178 | this.picker.destroy(); 179 | if (this.shim) this.shim.destroy(); 180 | }, 181 | 182 | position: function(x, y){ 183 | var offset = this.options.positionOffset, 184 | scroll = document.getScroll(), 185 | size = document.getSize(), 186 | pickersize = this.picker.getSize(); 187 | 188 | if (typeOf(x) == 'element'){ 189 | var element = x, 190 | where = y || this.options.pickerPosition; 191 | 192 | var elementCoords = element.getCoordinates(); 193 | 194 | x = (where == 'left') ? elementCoords.left - pickersize.x 195 | : (where == 'bottom' || where == 'top') ? elementCoords.left 196 | : elementCoords.right 197 | y = (where == 'bottom') ? elementCoords.bottom 198 | : (where == 'top') ? elementCoords.top - pickersize.y 199 | : elementCoords.top; 200 | } 201 | 202 | x += offset.x * ((where && where == 'left') ? -1 : 1); 203 | y += offset.y * ((where && where == 'top') ? -1: 1); 204 | 205 | if ((x + pickersize.x) > (size.x + scroll.x)) x = (size.x + scroll.x) - pickersize.x; 206 | if ((y + pickersize.y) > (size.y + scroll.y)) y = (size.y + scroll.y) - pickersize.y; 207 | if (x < 0) x = 0; 208 | if (y < 0) y = 0; 209 | 210 | this.picker.setStyles({ 211 | left: x, 212 | top: y 213 | }); 214 | if (this.shim) this.shim.position(); 215 | return this; 216 | }, 217 | 218 | setBodySize: function(){ 219 | var bodysize = this.bodysize = this.body.getSize(); 220 | 221 | this.slider.setStyles({ 222 | width: 2 * bodysize.x, 223 | height: bodysize.y 224 | }); 225 | this.oldContents.setStyles({ 226 | left: bodysize.x, 227 | width: bodysize.x, 228 | height: bodysize.y 229 | }); 230 | this.newContents.setStyles({ 231 | width: bodysize.x, 232 | height: bodysize.y 233 | }); 234 | }, 235 | 236 | setColumnContent: function(column, content){ 237 | var columnElement = this.columns[column]; 238 | if (!columnElement) return this; 239 | 240 | var type = typeOf(content); 241 | if (['string', 'number'].contains(type)) columnElement.set('text', content); 242 | else columnElement.empty().adopt(content); 243 | 244 | return this; 245 | }, 246 | 247 | setColumnsContent: function(content, fx){ 248 | var old = this.columns; 249 | this.columns = this.newColumns; 250 | this.newColumns = old; 251 | 252 | content.forEach(function(_content, i){ 253 | this.setColumnContent(i, _content); 254 | }, this); 255 | return this.setContent(null, fx); 256 | }, 257 | 258 | setColumns: function(columns){ 259 | var _columns = this.columns = new Elements, _newColumns = this.newColumns = new Elements; 260 | for (var i = columns; i--;){ 261 | _columns.push(new Element('div.column').addClass('column_' + (columns - i))); 262 | _newColumns.push(new Element('div.column').addClass('column_' + (columns - i))); 263 | } 264 | 265 | var oldClass = 'column_' + this.options.columns, newClass = 'column_' + columns; 266 | this.picker.removeClass(oldClass).addClass(newClass); 267 | 268 | this.options.columns = columns; 269 | return this; 270 | }, 271 | 272 | setContent: function(content, fx){ 273 | if (content) return this.setColumnsContent([content], fx); 274 | 275 | // swap contents so we can fill the newContents again and animate 276 | var old = this.oldContents; 277 | this.oldContents = this.newContents; 278 | this.newContents = old; 279 | this.newContents.empty(); 280 | 281 | this.newContents.adopt(this.columns); 282 | 283 | this.setBodySize(); 284 | 285 | if (fx){ 286 | this.fx(fx); 287 | } else { 288 | this.slider.setStyle('left', 0); 289 | this.oldContents.setStyles({left: 0, opacity: 0}); 290 | this.newContents.setStyles({left: 0, opacity: 1}); 291 | } 292 | return this; 293 | }, 294 | 295 | fx: function(fx){ 296 | var oldContents = this.oldContents, 297 | newContents = this.newContents, 298 | slider = this.slider, 299 | bodysize = this.bodysize; 300 | if (fx == 'right'){ 301 | oldContents.setStyles({left: 0, opacity: 1}); 302 | newContents.setStyles({left: bodysize.x, opacity: 1}); 303 | slider.setStyle('left', 0).tween('left', 0, -bodysize.x); 304 | } else if (fx == 'left'){ 305 | oldContents.setStyles({left: bodysize.x, opacity: 1}); 306 | newContents.setStyles({left: 0, opacity: 1}); 307 | slider.setStyle('left', -bodysize.x).tween('left', -bodysize.x, 0); 308 | } else if (fx == 'fade'){ 309 | slider.setStyle('left', 0); 310 | oldContents.setStyle('left', 0).set('tween', { 311 | duration: this.options.animationDuration / 2 312 | }).tween('opacity', 1, 0).get('tween').chain(function(){ 313 | oldContents.setStyle('left', bodysize.x); 314 | }); 315 | newContents.setStyles({opacity: 0, left: 0}).set('tween', { 316 | duration: this.options.animationDuration 317 | }).tween('opacity', 0, 1); 318 | } 319 | }, 320 | 321 | toElement: function(){ 322 | return this.picker; 323 | }, 324 | 325 | setTitle: function(content, fn){ 326 | if (!fn) fn = Function.from; 327 | this.titleText.empty().adopt( 328 | Array.convert(content).map(function(item, i){ 329 | return typeOf(item) == 'element' 330 | ? item 331 | : new Element('div.column', {text: fn(item, this.options)}).addClass('column_' + (i + 1)); 332 | }, this) 333 | ); 334 | return this; 335 | }, 336 | 337 | setTitleEvent: function(fn){ 338 | this.titleText.removeEvents('click'); 339 | if (fn) this.titleText.addEvent('click', fn); 340 | this.titleText.setStyle('cursor', fn ? 'pointer' : ''); 341 | return this; 342 | } 343 | 344 | }); 345 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/Picker_Attach.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | name: Picker.Attach 4 | description: Adds attach and detach methods to the Picker, to attach it to element events 5 | authors: Arian Stolwijk 6 | requires: [Picker, Core/Element.Event] 7 | provides: Picker.Attach 8 | ... 9 | */ 10 | 11 | 12 | Picker.Attach = new Class({ 13 | 14 | Extends: Picker, 15 | 16 | options: {/* 17 | onAttached: function(event){}, 18 | 19 | toggleElements: null, // deprecated 20 | toggle: null, // When set it deactivate toggling by clicking on the input */ 21 | togglesOnly: true, // set to false to always make calendar popup on input element, if true, it depends on the toggles elements set. 22 | showOnInit: false, // overrides the Picker option 23 | blockKeydown: true 24 | }, 25 | 26 | initialize: function(attachTo, options){ 27 | this.parent(options); 28 | 29 | this.attachedEvents = []; 30 | this.attachedElements = []; 31 | this.toggles = []; 32 | this.inputs = []; 33 | 34 | var documentEvent = function(event){ 35 | if (this.attachedElements.contains(event.target)) return; 36 | this.close(); 37 | }.bind(this); 38 | var document = this.picker.getDocument().addEvent('click', documentEvent); 39 | 40 | var preventPickerClick = function(event){ 41 | event.stopPropagation(); 42 | return false; 43 | }; 44 | this.picker.addEvent('click', preventPickerClick); 45 | 46 | // Support for deprecated toggleElements 47 | if (this.options.toggleElements) this.options.toggle = document.getElements(this.options.toggleElements); 48 | 49 | this.attach(attachTo, this.options.toggle); 50 | }, 51 | 52 | attach: function(attachTo, toggle){ 53 | if (typeOf(attachTo) == 'string') attachTo = document.id(attachTo); 54 | if (typeOf(toggle) == 'string') toggle = document.id(toggle); 55 | 56 | var elements = Array.convert(attachTo), 57 | toggles = Array.convert(toggle), 58 | allElements = [].append(elements).combine(toggles), 59 | self = this; 60 | 61 | var closeEvent = function(event){ 62 | var stopInput = self.options.blockKeydown 63 | && event.type == 'keydown' 64 | && !(['tab', 'esc'].contains(event.key)), 65 | isCloseKey = event.type == 'keydown' 66 | && (['tab', 'esc'].contains(event.key)), 67 | isA = event.target.get('tag') == 'a'; 68 | 69 | if (stopInput || isA) event.preventDefault(); 70 | if (isCloseKey || isA) self.close(); 71 | }; 72 | 73 | var getOpenEvent = function(element){ 74 | return function(event){ 75 | var tag = event.target.get('tag'); 76 | if (tag == 'input' && event.type == 'click' && !element.match(':focus') || (self.opened && self.input == element)) return; 77 | if (tag == 'a') event.stop(); 78 | self.position(element); 79 | self.open(); 80 | self.fireEvent('attached', [event, element]); 81 | }; 82 | }; 83 | 84 | var getToggleEvent = function(open, close){ 85 | return function(event){ 86 | if (self.opened) close(event); 87 | else open(event); 88 | }; 89 | }; 90 | 91 | allElements.each(function(element){ 92 | 93 | // The events are already attached! 94 | if (self.attachedElements.contains(element)) return; 95 | 96 | var events = {}, 97 | tag = element.get('tag'), 98 | openEvent = getOpenEvent(element), 99 | // closeEvent does not have a depency on element 100 | toggleEvent = getToggleEvent(openEvent, closeEvent); 101 | 102 | if (tag == 'input'){ 103 | // Fix in order to use togglers only 104 | if (!self.options.togglesOnly || !toggles.length){ 105 | events = { 106 | focus: openEvent, 107 | click: openEvent, 108 | keydown: closeEvent 109 | }; 110 | } 111 | self.inputs.push(element); 112 | } else { 113 | if (toggles.contains(element)){ 114 | self.toggles.push(element); 115 | events.click = toggleEvent 116 | } else { 117 | events.click = openEvent; 118 | } 119 | } 120 | element.addEvents(events); 121 | self.attachedElements.push(element); 122 | self.attachedEvents.push(events); 123 | }); 124 | return this; 125 | }, 126 | 127 | detach: function(attachTo, toggle){ 128 | if (typeOf(attachTo) == 'string') attachTo = document.id(attachTo); 129 | if (typeOf(toggle) == 'string') toggle = document.id(toggle); 130 | 131 | var elements = Array.convert(attachTo), 132 | toggles = Array.convert(toggle), 133 | allElements = [].append(elements).combine(toggles), 134 | self = this; 135 | 136 | if (!allElements.length) allElements = self.attachedElements; 137 | 138 | allElements.each(function(element){ 139 | var i = self.attachedElements.indexOf(element); 140 | if (i < 0) return; 141 | 142 | var events = self.attachedEvents[i]; 143 | element.removeEvents(events); 144 | delete self.attachedEvents[i]; 145 | delete self.attachedElements[i]; 146 | 147 | var toggleIndex = self.toggles.indexOf(element); 148 | if (toggleIndex != -1) delete self.toggles[toggleIndex]; 149 | 150 | var inputIndex = self.inputs.indexOf(element); 151 | if (toggleIndex != -1) delete self.inputs[inputIndex]; 152 | }); 153 | return this; 154 | }, 155 | 156 | destroy: function(){ 157 | this.detach(); 158 | return this.parent(); 159 | } 160 | 161 | }); 162 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/PlatformHelper.js: -------------------------------------------------------------------------------- 1 | var PlatformHelper = new Class({ 2 | version: 0.1, 3 | 4 | options: { 5 | }, 6 | 7 | Implements: [Options, Events], 8 | 9 | initialize: function(_object, _options) { 10 | if ( ! _object) { return; } 11 | this.setOptions(_options); 12 | var scope = _object; 13 | var _this = this; 14 | 15 | scope.getElements('.pagination a, thead a').addEvent('click', function(e){ 16 | e.preventDefault(); 17 | _this.fireEvent('paginationStart', [this]) 18 | }); 19 | scope.getElements('.search').addEvent('submit', function(e){ 20 | _this.fireEvent('search'); 21 | e.preventDefault(); 22 | var hidden_search = this.getElement('.hidden_search'); 23 | if (hidden_search) { 24 | hidden_search.destroy(); 25 | } 26 | var request = new Request.bhf({ 27 | method: 'get', 28 | url: e.target.get('action'), 29 | onFailure: function(){ 30 | _this.fireEvent('searchFailure'); 31 | }, 32 | onSuccess: function(a, b, html){ 33 | scope.innerHTML = html; 34 | _this.fireEvent('searchSuccess', [request]) 35 | } 36 | }).send({data: e.target}); 37 | }); 38 | scope.addEvent('click:relay(.quick_edit)', function(e){ 39 | e.preventDefault(); 40 | _this.fireEvent('quickEditStart', [this]); 41 | }); 42 | scope.addEvent('click:relay(.action a, .js_toggle)', function(e){ 43 | this.addClass('clicked'); 44 | setTimeout(function(){ 45 | this.removeClass('clicked'); 46 | }.bind(this), 1500); 47 | }); 48 | scope.getElements('.js_delete').addEvent('mouseenter', function(){ 49 | this.getParent('tr').addClass('background_delete'); 50 | }).addEvent('mouseleave', function(){ 51 | this.getParent('tr').removeClass('background_delete'); 52 | }); 53 | } 54 | }); 55 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/QuickEdit.js: -------------------------------------------------------------------------------- 1 | var QuickEdit = new Class({ 2 | version: 0.2, 3 | 4 | options: { 5 | holderParent: 'content', 6 | hideNext: false 7 | }, 8 | 9 | Implements: [Options, Events], 10 | 11 | initialize: function(_options){ 12 | this.setOptions(_options); 13 | this.holder = new Element('div.quick_edit_holder'); 14 | }, 15 | 16 | startEdit: function(element){ 17 | this.linkElem = element; 18 | this.newEntry = this.linkElem.hasClass('js_add_field'); 19 | 20 | this.fireEvent('startRequest'); 21 | this.currentRequest = new Request.HTML({ 22 | method: 'get', 23 | evalScripts: false, 24 | url: element.get('href'), 25 | onFailure: function(invalidForm){ 26 | this.fireEvent('failure'); 27 | }.bind(this), 28 | onSuccess: function(responseTree, responseElements, responseHTML, responseJavaScript){ 29 | this.injectForm(responseHTML); 30 | 31 | var nextElem = this.holder.getElement('.save_and_next'); 32 | if (this.options.hideNext && nextElem) { 33 | nextElem.addClass('hide'); 34 | } 35 | 36 | window.fireEvent('quickEditFormInject', [this.holder]); 37 | }.bind(this) 38 | }).send(); 39 | }, 40 | 41 | submit: function(eventNames){ 42 | var form = this.holder.getElement('form'); 43 | this.fireEvent('beforeSubmit'); 44 | 45 | new Request.JSON({ 46 | method: form.get('method'), 47 | url: form.get('action'), 48 | evalScripts: true, 49 | onRequest: function(){ 50 | this.disableButtons(); 51 | }.bind(this), 52 | onFailure: function(invalidForm){ 53 | this.injectForm(invalidForm.response); 54 | window.fireEvent('quickEditFormInject', [this.holder]); 55 | }.bind(this), 56 | onSuccess: function(json){ 57 | if ( ! eventNames.contains('successAndNext')) { 58 | this.close(); 59 | } 60 | eventNames.each(function(eventName){ 61 | this.fireEvent(eventName, [json]); 62 | }.bind(this)); 63 | this.fireEvent('save', [eventNames, this.linkElem, json]); 64 | }.bind(this) 65 | }).send({data: form}); 66 | }, 67 | 68 | disableButtons: function(){ 69 | this.holder.getElements('.open, .cancel, .save_and_next, .save').set('disabled', 'disabled'); 70 | }, 71 | 72 | close: function(){ 73 | if (this.currentRequest) { 74 | this.currentRequest.cancel(); 75 | } 76 | this.holder.dispose(); 77 | this.fireEvent('closed'); 78 | }, 79 | 80 | injectForm: function(form){ 81 | this.holder.innerHTML = form; 82 | 83 | this.holder.getElements('.open').addEvent('click', function(e){ 84 | e.preventDefault(); 85 | Turbolinks.visit((this.linkElem.getElement('a') || this.linkElem).get('href')); 86 | }.bind(this)); 87 | this.holder.getElements('.cancel').addEvent('click', function(e){ 88 | e.preventDefault(); 89 | this.close(); 90 | }.bind(this)); 91 | this.holder.getElements('.save_and_next').addEvent('click', function(e){ 92 | e.preventDefault(); 93 | this.submit(this.newEntry ? ['successAndAdd'] : ['successAndChange', 'successAndNext']); 94 | }.bind(this)); 95 | this.holder.getElements('.save').addEvent('click', function(e){ 96 | e.preventDefault(); 97 | this.submit(this.newEntry ? ['successAndAdd'] : ['successAndChange']); 98 | }.bind(this)); 99 | this.holder.getElements('form').addEvent('submit', function(e){ 100 | e.preventDefault(); 101 | this.submit(this.newEntry ? ['successAndAdd'] : ['successAndChange']); 102 | }.bind(this)); 103 | 104 | this.holder.inject(document.id(this.options.holderParent)); 105 | 106 | this.fireEvent('formInjected', [this.holder]); 107 | }, 108 | 109 | hide: function(){ 110 | this.holder.addClass('collapsed'); 111 | }, 112 | 113 | show: function(){ 114 | this.holder.removeClass('collapsed'); 115 | } 116 | }); 117 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/QuickEditStack.js: -------------------------------------------------------------------------------- 1 | var QuickEditStack = new Class({ 2 | version: 0.1, 3 | 4 | options: { 5 | }, 6 | bricksArray: [], 7 | bricksIndex: 0, 8 | 9 | Implements: [Options, Events], 10 | 11 | initialize: function(_options) { 12 | this.setOptions(_options); 13 | }, 14 | addEditBrick: function(editOptions, link, linkParent){ 15 | var qi = this.bricksIndex; 16 | var qe = this.bricksArray[qi]; 17 | if ( ! qe) { 18 | qe = new QuickEdit(editOptions); 19 | this.bricksArray.push(qe); 20 | } 21 | 22 | this.bricksArray.each(function(b, i){ 23 | if (i === qi) { 24 | b.show(); 25 | } 26 | else if (i > qi) { 27 | b.close(); 28 | } 29 | }); 30 | 31 | qe.addEvents(this.options.permanentQuickEditEvents); 32 | qe.startEdit(link); 33 | }, 34 | addStack: function(){ 35 | this.bricksIndex = this.bricksIndex + 1; 36 | this.bricksArray.each(function(b){ 37 | b.hide(); 38 | }); 39 | }, 40 | removeStack: function(){ 41 | this.bricksArray[this.bricksIndex] = undefined; 42 | this.bricksIndex = this.bricksIndex - 1; 43 | this.bricksArray = this.bricksArray.clean(); 44 | (this.bricksArray.getLast() || {show: function(){}}).show(); 45 | }, 46 | removeAllStacks: function(){ 47 | this.bricksArray.clean().each(function(b, i){ 48 | b.close(); 49 | }); 50 | this.bricksArray = []; 51 | this.bricksIndex = 0; 52 | } 53 | }); 54 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/Request_bhf.js: -------------------------------------------------------------------------------- 1 | Request.bhf = new Class({ 2 | Extends: Request.HTML, 3 | 4 | send: function(options){ 5 | var progressSupport = ('onprogress' in new Browser.Request); 6 | 7 | if (!this.check(options)) return this; 8 | 9 | this.options.isSuccess = this.options.isSuccess || this.isSuccess; 10 | this.running = true; 11 | 12 | var type = typeOf(options); 13 | if (type == 'string' || type == 'element') options = {data: options}; 14 | 15 | var old = this.options; 16 | options = Object.append({data: old.data, url: old.url, method: old.method}, options); 17 | var data = options.data, url = String(options.url), method = options.method.toLowerCase(); 18 | 19 | switch (typeOf(data)){ 20 | case 'element': data = document.id(data).toQueryString(); break; 21 | case 'object': case 'hash': data = Object.toQueryString(data); 22 | } 23 | 24 | if (this.options.format){ 25 | var format = 'format=' + this.options.format; 26 | data = (data) ? format + '&' + data : format; 27 | } 28 | 29 | if (this.options.emulation && !['get', 'post'].contains(method)){ 30 | var _method = '_method=' + method; 31 | data = (data) ? _method + '&' + data : _method; 32 | method = 'post'; 33 | } 34 | 35 | if (this.options.urlEncoded && ['post', 'put'].contains(method)){ 36 | var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : ''; 37 | this.headers['Content-type'] = 'application/x-www-form-urlencoded' + encoding; 38 | } 39 | 40 | if (!url) url = document.location.pathname; 41 | 42 | var trimPosition = url.lastIndexOf('/'); 43 | if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition); 44 | 45 | if (this.options.noCache) 46 | url += (url.indexOf('?') > -1 ? '&' : '?') + String.uniqueID(); 47 | 48 | if (data && (method == 'get' || method == 'delete')){ 49 | url += (url.indexOf('?') > -1 ? '&' : '?') + data; 50 | data = null; 51 | } 52 | 53 | var xhr = this.xhr; 54 | if (progressSupport){ 55 | xhr.onloadstart = this.loadstart.bind(this); 56 | xhr.onprogress = this.progress.bind(this); 57 | } 58 | 59 | xhr.open(method.toUpperCase(), url, this.options.async, this.options.user, this.options.password); 60 | if (this.options.user && 'withCredentials' in xhr) xhr.withCredentials = true; 61 | 62 | xhr.onreadystatechange = this.onStateChange.bind(this); 63 | 64 | Object.each(this.headers, function(value, key){ 65 | try { 66 | xhr.setRequestHeader(key, value); 67 | } catch (e){ 68 | this.fireEvent('exception', [key, value]); 69 | } 70 | }, this); 71 | 72 | this.fireEvent('request'); 73 | this.url = url; 74 | xhr.send(data); 75 | if (!this.options.async) this.onStateChange(); 76 | else if (this.options.timeout) this.timer = this.timeout.delay(this.options.timeout, this); 77 | return this; 78 | } 79 | }); 80 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/classes/Setlatlng.js: -------------------------------------------------------------------------------- 1 | var Setlatlng = new Class({ 2 | Implements: [Options, Events], 3 | 4 | options: { 5 | 6 | }, 7 | 8 | initialize: function(elem){ 9 | if (window.google) { 10 | this.setup(elem); 11 | } 12 | else { 13 | Setlatlng.GMapsCallback = function(){ 14 | this.setup(elem); 15 | }.bind(this); 16 | Asset.javascript('http://maps.googleapis.com/maps/api/js?sensor=false&callback=Setlatlng.GMapsCallback'); 17 | } 18 | }, 19 | 20 | setup: function(elem){ 21 | var latElem = elem; 22 | var lngElem = elem.getNext('.map_data_lng'); 23 | var setValues = function(lat, lng){ 24 | latElem.value = lat; 25 | lngElem.value = lng; 26 | }; 27 | var center = new google.maps.LatLng( 28 | latElem.value ? latElem.value : latElem.get('data-default-lat'), 29 | lngElem.value ? lngElem.value : latElem.get('data-default-lng') 30 | ); 31 | 32 | var map = new google.maps.Map(new Element('div.map_canvas').inject(latElem, 'before'), { 33 | zoom: 14, 34 | mapTypeId: google.maps.MapTypeId.ROADMAP, 35 | center: center, 36 | mapTypeControl: true, 37 | zoomControl: true, 38 | zoomControlOptions: { 39 | style: google.maps.ZoomControlStyle.SMALL 40 | }, 41 | streetViewControl: false, 42 | panControl: false, 43 | scaleControl: false, 44 | overviewMapControl: false 45 | }); 46 | 47 | var marker = new google.maps.Marker({ 48 | title: 'Location', 49 | draggable: true 50 | }); 51 | 52 | if (latElem.value && lngElem.value) { 53 | marker.setPosition(new google.maps.LatLng(latElem.value, lngElem.value)); 54 | marker.setMap(map); 55 | } 56 | else { 57 | if (navigator) { 58 | navigator.geolocation.getCurrentPosition(function(e){ 59 | map.setCenter(new google.maps.LatLng(e.coords.latitude, e.coords.longitude)); 60 | }); 61 | } 62 | google.maps.event.addListener(map, 'click', function(e){ 63 | marker.setPosition(e.latLng); 64 | marker.setMap(map); 65 | setValues(marker.getPosition().lat(), marker.getPosition().lng()); 66 | google.maps.event.clearListeners(map, 'click'); 67 | }); 68 | } 69 | 70 | google.maps.event.addListener(marker, 'dragend', function(){ 71 | var mPos = marker.getPosition(); 72 | map.panTo(mPos); 73 | setValues(mPos.lat(), mPos.lng()); 74 | }); 75 | } 76 | }); 77 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/locales/Locale.de-DE.js: -------------------------------------------------------------------------------- 1 | Locale.define('de-DE', 'DatePicker', { 2 | select_a_time: 'Wähle eine Zeit', 3 | use_mouse_wheel: 'Mit dem Mausrad kannst du schneller die Werte ändern', 4 | time_confirm_button: 'OK', 5 | apply_range: 'Übernehmen', 6 | cancel: 'Abbrechen', 7 | week: 'W' 8 | }); 9 | 10 | Locale.define('de-DE', 'Notifications', { 11 | failure: 'Etwas ist schief gelaufen!' 12 | }); 13 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/locales/Locale.en-US.js: -------------------------------------------------------------------------------- 1 | Locale.define('en-US', 'DatePicker', { 2 | select_a_time: 'Select a time', 3 | use_mouse_wheel: 'Use the mouse wheel to quickly change value', 4 | time_confirm_button: 'OK', 5 | apply_range: 'Apply', 6 | cancel: 'Cancel', 7 | week: 'Wk' 8 | }); 9 | 10 | 11 | Locale.define('en-US', 'Notifications', { 12 | failure: 'Something went wrong!' 13 | }); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/locales/Locale.pt-PT.js: -------------------------------------------------------------------------------- 1 | Locale.define('pt-PT', 'DatePicker', { 2 | select_a_time: 'Selecione um horário', 3 | use_mouse_wheel: 'Use o scroll do mouse para rapidamente alterar valores', 4 | time_confirm_button: 'OK', 5 | apply_range: 'Aplicar', 6 | cancel: 'Cancelar', 7 | week: 'Sm' 8 | }); 9 | 10 | 11 | Locale.define('pt-PT', 'Notifications', { 12 | failure: 'Algo deu errado!' 13 | }); 14 | -------------------------------------------------------------------------------- /app/assets/javascripts/bhf/mootools_ujs.js: -------------------------------------------------------------------------------- 1 | /* 2 | --- 3 | description: A MooTools driver for the Ruby on Rails 3 unobtrusive JavaScript API. 4 | 5 | license: MIT-style 6 | 7 | authors: 8 | - Kevin Valdek 9 | - Oskar Krawczyk 10 | 11 | requires: 12 | core/1.4: '*' 13 | 14 | provides: 15 | - Rails 3 MooTools driver 16 | 17 | ... 18 | */ 19 | 20 | window.addEvent('domready', function(){ 21 | 22 | rails.csrf = { 23 | token: rails.getCsrf('token'), 24 | param: rails.getCsrf('param') 25 | }; 26 | 27 | rails.applyEvents(); 28 | }); 29 | 30 | (function($){ 31 | 32 | window.rails = { 33 | /** 34 | * If el is passed as argument, events will only be applied to 35 | * elements within el. Otherwise applied to document body. 36 | */ 37 | applyEvents: function(el){ 38 | el = $(el || document); 39 | var apply = function(selector, action, callback){ 40 | el.addEvent(action + ':relay(' + selector + ')', callback); 41 | }; 42 | 43 | apply('form[data-remote="true"]', 'submit', rails.handleRemote); 44 | apply('a[data-remote="true"], input[data-remote="true"]', 'click', rails.handleRemote); 45 | apply('a[data-method][data-remote!=true]', 'click', function(event){ 46 | event.preventDefault(); 47 | if (rails.confirmed(this)){ 48 | var form = Element('form', { 49 | method: 'post', 50 | action: this.get('href'), 51 | styles: { 52 | display: 'none' 53 | } 54 | }).inject(this, 'after'); 55 | 56 | var methodInput = Element('input', { 57 | type: 'hidden', 58 | name: '_method', 59 | value: this.get('data-method') 60 | }); 61 | 62 | var csrfInput = Element('input', { 63 | type: 'hidden', 64 | name: rails.csrf.param, 65 | value: rails.csrf.token 66 | }); 67 | 68 | form.adopt(methodInput, csrfInput).submit(); 69 | } 70 | }); 71 | var noMethodNorRemoteConfirm = ':not([data-method]):not([data-remote=true])[data-confirm]'; 72 | apply('a' + noMethodNorRemoteConfirm + ',' + 'input' + noMethodNorRemoteConfirm, 'click', function(){ 73 | return rails.confirmed(this); 74 | }); 75 | }, 76 | 77 | getCsrf: function(name){ 78 | var meta = document.getElement('meta[name=csrf-' + name + ']'); 79 | return (meta ? meta.get('content') : null); 80 | }, 81 | 82 | confirmed: function(el){ 83 | var confirmMessage = el.get('data-confirm'); 84 | if(confirmMessage && !confirm(confirmMessage)){ 85 | return false; 86 | } 87 | return true; 88 | }, 89 | 90 | disable: function(el){ 91 | var button = el.get('data-disable-with') ? el : el.getElement('[data-disable-with]'); 92 | if (button){ 93 | var enableWith = button.get('value'); 94 | el.addEvent('ajax:complete', function(){ 95 | button.set({ 96 | value: enableWith, 97 | disabled: false 98 | }); 99 | }); 100 | button.set({ 101 | value: button.get('data-disable-with'), 102 | disabled: true 103 | }); 104 | } 105 | }, 106 | 107 | handleRemote: function(event){ 108 | event.preventDefault(); 109 | if(rails.confirmed(this)){ 110 | if (this.get('data-type') === 'json') { 111 | this.request = new Request.RailsJSON(this); 112 | } 113 | else { 114 | this.request = new Request.Rails(this); 115 | } 116 | rails.disable(this); 117 | this.request.send(); 118 | } 119 | } 120 | }; 121 | 122 | var _reqestClassMethods = { 123 | initialize: function(element, options){ 124 | this.el = element; 125 | this.parent(Object.merge({ 126 | method: this.el.get('method') || this.el.get('data-method') || 'get', 127 | url: this.el.get('action') || this.el.get('href') 128 | }, options)); 129 | 130 | if (rails.csrf.token) { 131 | Object.append(this.headers, { 132 | 'X-CSRF-Token': rails.csrf.token 133 | }); 134 | } 135 | 136 | this.addRailsEvents(); 137 | }, 138 | 139 | send: function(options) { 140 | this.el.fireEvent('ajax:before'); 141 | if (this.el.get('tag') === 'form'){ 142 | this.options.data = this.el; 143 | } 144 | this.parent(options); 145 | this.el.fireEvent('ajax:after', this.xhr); 146 | }, 147 | 148 | addRailsEvents: function(){ 149 | this.addEvent('request', function(){ 150 | this.el.fireEvent('ajax:loading', this.xhr); 151 | }); 152 | 153 | this.addEvent('success', function(responseJSON){ 154 | this.el.fireEvent('ajax:success', [this.xhr, responseJSON]); 155 | }); 156 | 157 | this.addEvent('complete', function(){ 158 | this.el.fireEvent('ajax:complete', this.xhr); 159 | this.el.fireEvent('ajax:loaded', this.xhr); 160 | }); 161 | 162 | this.addEvent('failure', function(){ 163 | this.el.fireEvent('ajax:failure', this.xhr); 164 | }); 165 | } 166 | }; 167 | 168 | Request.RailsJSON = new Class(Object.merge({ 169 | Extends: Request.JSON 170 | }, _reqestClassMethods)); 171 | 172 | Request.Rails = new Class(Object.merge({ 173 | Extends: Request 174 | }, _reqestClassMethods)); 175 | 176 | })(document.id); 177 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bhf/MooEditable.scss: -------------------------------------------------------------------------------- 1 | .mooeditable-container{ 2 | position: relative; 3 | border: 2px solid #ddd; 4 | } 5 | 6 | .mooeditable-ui-toolbar{ 7 | color: inherit; 8 | background-color: #eee; 9 | padding: 2px; 10 | } 11 | 12 | .mooeditable-ui-toolbar:after{ 13 | content: "."; 14 | display: block; 15 | height: 0; 16 | clear: both; 17 | visibility: hidden; 18 | } 19 | 20 | .mooeditable-ui-toolbar .toolbar-item, 21 | .mooeditable-ui-toolbar .toolbar-separator{ 22 | display: -moz-inline-box; 23 | display: inline-block; 24 | vertical-align: middle; 25 | } 26 | 27 | .mooeditable-ui-toolbar .mooeditable-ui-button{ 28 | cursor: pointer; 29 | border: 0; 30 | width: auto; 31 | height: auto; 32 | margin: 2px; 33 | padding: 2px; 34 | vertical-align: middle; 35 | color: inherit; 36 | background-color: transparent; 37 | } 38 | * html .mooeditable-ui-toolbar .mooeditable-ui-button{ /* IE6 */ 39 | width: 24px; 40 | overflow: visible; 41 | padding: 2px 4px; 42 | } 43 | 44 | .mooeditable-ui-toolbar .mooeditable-ui-button.active, 45 | .mooeditable-ui-toolbar .mooeditable-ui-button.onActive{ 46 | color: inherit; 47 | background-color: #ddd; 48 | } 49 | 50 | .mooeditable-ui-toolbar .mooeditable-ui-button:hover, 51 | .mooeditable-ui-toolbar .mooeditable-ui-button.hover{ 52 | color: inherit; 53 | background-color: #fff; 54 | } 55 | 56 | .mooeditable-ui-toolbar .mooeditable-ui-button.disabled{ 57 | cursor: default; 58 | } 59 | 60 | .mooeditable-ui-toolbar .mooeditable-ui-button.disabled:hover{ 61 | color: inherit; 62 | background-color: transparent; 63 | } 64 | 65 | .mooeditable-ui-toolbar .mooeditable-ui-button .button-icon{ 66 | display: -moz-inline-box; 67 | display: inline-block; 68 | width: 16px; 69 | height: 16px; 70 | vertical-align: middle; 71 | color: inherit; 72 | background: transparent url(mooeditable-toolbarbuttons-tango.png) no-repeat 0 -8px; 73 | } 74 | 75 | .mooeditable-ui-toolbar .mooeditable-ui-button .button-text{ 76 | display: none; 77 | font-size: 13px; 78 | vertical-align: middle; 79 | } 80 | 81 | .mooeditable-ui-toolbar .mooeditable-ui-button-text .button-icon{ 82 | display: none; 83 | } 84 | .mooeditable-ui-toolbar .mooeditable-ui-button-text .button-text{ 85 | display: inline; 86 | } 87 | 88 | .mooeditable-ui-toolbar .mooeditable-ui-button-icon-text{ 89 | width: auto; 90 | } 91 | .mooeditable-ui-toolbar .mooeditable-ui-button-icon-text .button-text{ 92 | display: inline; 93 | margin-left: 4px; 94 | } 95 | 96 | .mooeditable-ui-toolbar .toolbar-separator{ 97 | width: 1px; 98 | height: 24px; 99 | margin: 2px; 100 | text-indent: -999em; 101 | color: inherit; 102 | background-color: #fafafa; 103 | } 104 | 105 | .mooeditable-ui-toolbar .bold-item .button-icon{ background-position: 0 0; } 106 | .mooeditable-ui-toolbar .createlink-item .button-icon{ background-position: 0 -16px; } 107 | .mooeditable-ui-toolbar .indent-item .button-icon{ background-position: 0 -32px; } 108 | .mooeditable-ui-toolbar .insertorderedlist-item .button-icon{ background-position: 0 -48px; } 109 | .mooeditable-ui-toolbar .insertunorderedlist-item .button-icon{ background-position: 0 -64px; } 110 | .mooeditable-ui-toolbar .italic-item .button-icon{ background-position: 0 -80px; } 111 | .mooeditable-ui-toolbar .outdent-item .button-icon{ background-position: 0 -96px; } 112 | .mooeditable-ui-toolbar .redo-item .button-icon{ background-position: 0 -112px; } 113 | .mooeditable-ui-toolbar .strikethrough-item .button-icon{ background-position: 0 -128px; } 114 | .mooeditable-ui-toolbar .toggleview-item .button-icon{ background-position: 0 -144px; } 115 | .mooeditable-ui-toolbar .underline-item .button-icon{ background-position: 0 -160px; } 116 | .mooeditable-ui-toolbar .undo-item .button-icon{ background-position: 0 -176px; } 117 | .mooeditable-ui-toolbar .unlink-item .button-icon{ background-position: 0 -192px; } 118 | .mooeditable-ui-toolbar .urlimage-item .button-icon{ background-position: 0 -208px; } 119 | 120 | .mooeditable-iframe{ 121 | margin: 0; 122 | padding: 0; 123 | border: 0; 124 | width: 100%; 125 | vertical-align: top; 126 | } 127 | 128 | .mooeditable-textarea{ 129 | margin: 0 !important; 130 | padding: 0 !important; 131 | border: 0 !important; 132 | width: 100% !important; 133 | resize: none !important; /* disable resizable textareas in Webkit */ 134 | outline: 0 !important; /* disable focus ring in Safari */ 135 | } 136 | 137 | .mooeditable-ui-dialog{ 138 | color: inherit; 139 | background-color: #ddd; 140 | position: absolute; 141 | display: block; 142 | cursor: default; 143 | font-size: 12px; 144 | z-index: 100; 145 | width: 100%; 146 | } 147 | 148 | .mooeditable-ui-dialog .dialog-content{ 149 | padding: 4px 10px; 150 | } 151 | 152 | .mooeditable-ui-dialog .dialog-content *{ 153 | vertical-align: middle; 154 | } 155 | 156 | .mooeditable-ui-dialog input{ 157 | margin: 0 8px; 158 | } 159 | 160 | .mooeditable-ui-dialog input.text{ 161 | width: 300px; 162 | } 163 | 164 | .mooeditable-ui-button-overlay{ 165 | color: inherit; 166 | background-color: #ddd; 167 | font-size: 12px; 168 | z-index: 100; 169 | outline: 0; 170 | -moz-outline: 0; 171 | -webkit-outline: 0; 172 | } 173 | 174 | .mooeditable-ui-button-overlay .overlay-content{ 175 | padding: 10px; 176 | } -------------------------------------------------------------------------------- /app/assets/stylesheets/bhf/functions.sass: -------------------------------------------------------------------------------- 1 | =box-model($model: border-box) 2 | box-sizing: $model 3 | -ms-box-sizing: $model 4 | -moz-box-sizing: $model 5 | -webkit-box-sizing: $model 6 | 7 | =transition-duration($sec: 0.5) 8 | transition-duration: $sec + s 9 | -moz-transition-duration: $sec + s 10 | -webkit-transition-duration: $sec + s 11 | =transition-property($prop) 12 | transition-property: $prop 13 | -moz-transition-property: $prop 14 | -webkit-transition-property: $prop 15 | =transition-delay($sec: 0.5) 16 | transition-delay: $sec + s 17 | -moz-transition-delay: $sec + s 18 | -webkit-transition-delay: $sec + s 19 | 20 | =border-radius($radius: 4) 21 | border-radius: $radius + px 22 | -moz-border-radius: $radius + px 23 | 24 | =border-bottom-radius($radius: 4) 25 | border-radius: 0px 26 | border-bottom-right-radius: $radius + px 27 | border-bottom-left-radius: $radius + px 28 | -moz-border-radius: 0px 29 | -moz-border-radius-bottomright: $radius + px 30 | -moz-border-radius-bottomleft: $radius + px 31 | 32 | =box-shadow($x: 0, $y: 0, $blur: 0, $color: #000000) 33 | box-shadow: $x + px $y + px $blur + px $color 34 | -moz-box-shadow: $x + px $y + px $blur + px $color 35 | -webkit-box-shadow: $x + px $y + px $blur + px $color 36 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bhf/reset.sass: -------------------------------------------------------------------------------- 1 | html, body, div, span, applet, object, iframe, 2 | h1, h2, h3, h4, h5, h6, p, blockquote, pre, 3 | a, abbr, acronym, address, big, cite, code, 4 | del, dfn, em, font, img, ins, kbd, q, s, samp, 5 | small, strike, strong, sub, sup, tt, var, 6 | b, u, i, center, 7 | dl, dt, dd, ol, ul, li, 8 | fieldset, form, label, legend, 9 | table, caption, tbody, tfoot, thead, tr, th, td 10 | margin: 0 11 | padding: 0 12 | border: 0 13 | outline: 0 14 | font-size: 100% 15 | vertical-align: baseline 16 | background: transparent 17 | body 18 | line-height: 1 19 | ol, ul 20 | list-style: none 21 | 22 | 23 | // remember to highlight inserts somehow! 24 | ins 25 | text-decoration: none 26 | del 27 | text-decoration: line-through 28 | 29 | 30 | // tables still need 'cellspacing="0"' in the markup 31 | table 32 | border-spacing: 0 33 | -------------------------------------------------------------------------------- /app/assets/stylesheets/bhf/typo.scss: -------------------------------------------------------------------------------- 1 | /* 2 | 960 Grid System ~ Text CSS. 3 | Learn more ~ http://960.gs/ 4 | 5 | Licensed under GPL and MIT. 6 | */ 7 | 8 | /* `Basic HTML 9 | ----------------------------------------------------------------------------------------------------*/ 10 | 11 | textarea, 12 | input, 13 | button, 14 | body { 15 | font: 14px/1.5 "Helvetica Neue", Helvetica, Arial, sans-serif; 16 | } 17 | 18 | a:focus { 19 | outline: 1px dotted; 20 | } 21 | 22 | hr { 23 | border: 0 #C8C8C8 solid; 24 | border-top-width: 1px; 25 | clear: both; 26 | height: 0; 27 | margin-bottom: 20px; 28 | } 29 | 30 | /* `Headings 31 | ----------------------------------------------------------------------------------------------------*/ 32 | 33 | h1 { 34 | font-size: 25px; 35 | } 36 | 37 | h2 { 38 | font-size: 23px; 39 | } 40 | 41 | h3 { 42 | font-size: 21px; 43 | } 44 | 45 | h4 { 46 | font-size: 19px; 47 | } 48 | 49 | h5 { 50 | font-size: 17px; 51 | } 52 | 53 | h6 { 54 | font-size: 15px; 55 | } 56 | 57 | /* `Spacing 58 | ----------------------------------------------------------------------------------------------------*/ 59 | 60 | ol { 61 | list-style: decimal; 62 | } 63 | -------------------------------------------------------------------------------- /app/controllers/bhf/application_controller.rb: -------------------------------------------------------------------------------- 1 | class Bhf::ApplicationController < ActionController::Base 2 | include Bhf::Extension::ApplicationController 3 | protect_from_forgery 4 | end 5 | -------------------------------------------------------------------------------- /app/controllers/bhf/embed_entries_controller.rb: -------------------------------------------------------------------------------- 1 | class Bhf::EmbedEntriesController < Bhf::EntriesController 2 | 3 | def new 4 | @form_url = entry_embed_index_path(@platform.name, @model.get_embedded_parent(params[:entry_id])) 5 | 6 | render 'bhf/entries/new', ({layout: 'bhf/quick_edit'} if @quick_edit) || {} 7 | end 8 | 9 | def edit 10 | @form_url = entry_embed_path(@platform.name, @model.get_embedded_parent(params[:entry_id]), @object) 11 | 12 | render 'bhf/entries/edit', ({layout: 'bhf/quick_edit'} if @quick_edit) || {} 13 | end 14 | 15 | def create 16 | before_save 17 | if @object.save 18 | manage_many_to_many 19 | after_save 20 | 21 | if @quick_edit 22 | render json: object_to_bhf_hash, status: :ok 23 | else 24 | redirect_after_save(notice: set_message('update.success', @model)) 25 | end 26 | else 27 | @form_url = entry_embed_index_path(@platform.name, @model.get_embedded_parent(params[:entry_id])) 28 | 29 | r_settings = {status: :unprocessable_entity} 30 | if @quick_edit 31 | r_settings[:layout] = 'bhf/quick_edit' 32 | r_settings[:formats] = [:html] 33 | end 34 | render 'bhf/entries/new', r_settings 35 | end 36 | end 37 | 38 | def update 39 | before_save 40 | if @object.update(@permited_params) 41 | manage_many_to_many 42 | after_save 43 | 44 | if @quick_edit 45 | render json: object_to_bhf_hash, status: :ok 46 | else 47 | redirect_after_save(notice: set_message('update.success', @model)) 48 | end 49 | else 50 | @form_url = entry_embed_path(@platform.name, @model.get_embedded_parent(params[:entry_id]), @object) 51 | 52 | r_settings = {status: :unprocessable_entity} 53 | if @quick_edit 54 | r_settings[:layout] = 'bhf/quick_edit' 55 | r_settings[:formats] = [:html] 56 | end 57 | render 'bhf/entries/edit', r_settings 58 | end 59 | end 60 | 61 | private 62 | 63 | def load_object 64 | @object = @model.bhf_find_embed(params[:entry_id], params[:id]) 65 | @object.assign_attributes(@permited_params) if @object and @permited_params 66 | after_load 67 | end 68 | 69 | def load_new_object 70 | @object = @model.bhf_new_embed(params[:entry_id], @permited_params) 71 | after_load 72 | end 73 | 74 | def redirect_after_save(flash) 75 | if params[:return_to_new] 76 | redirect_to new_entry_embed_path(@platform.name, @model.get_embedded_parent(params[:entry_id])), flash 77 | else 78 | redirect_back_or_default(edit_entry_embed_path(@platform.name, @model.get_embedded_parent(params[:entry_id]), @object), flash) 79 | end 80 | end 81 | 82 | end 83 | -------------------------------------------------------------------------------- /app/controllers/bhf/entries_controller.rb: -------------------------------------------------------------------------------- 1 | class Bhf::EntriesController < Bhf::ApplicationController 2 | before_action :load_platform, :load_model, :set_page, :set_quick_edit 3 | before_action :load_object, except: [:create, :new, :sort] 4 | before_action :load_new_object, only: [:create, :new] 5 | 6 | def new 7 | @form_url = entries_path(@platform.name) 8 | 9 | render layout: 'bhf/quick_edit' if @quick_edit 10 | end 11 | 12 | def edit 13 | render file: 'public/404.html', layout: false and return unless @object 14 | 15 | @form_url = entry_path(@platform.name, @object) 16 | 17 | render layout: 'bhf/quick_edit' if @quick_edit 18 | end 19 | 20 | def show 21 | render file: 'public/404.html', layout: false and return unless @object 22 | 23 | respond_to do |format| 24 | format.html 25 | format.json { render json: @object } 26 | end 27 | end 28 | 29 | def create 30 | before_save 31 | if @object.save 32 | manage_relations 33 | after_save 34 | 35 | if @quick_edit 36 | render json: object_to_bhf_hash, status: :ok 37 | else 38 | redirect_after_save(notice: set_message('create.success', @model), referral_entry: {id: @object.id, platform: @platform.name}) 39 | end 40 | else 41 | @form_url = entries_path(@platform.name) 42 | 43 | r_settings = {status: :unprocessable_entity} 44 | if @quick_edit 45 | r_settings[:layout] = 'bhf/quick_edit' 46 | r_settings[:formats] = [:html] 47 | end 48 | render :new, r_settings 49 | end 50 | end 51 | 52 | def update 53 | render file: 'public/404.html', layout: false and return unless @object 54 | 55 | before_save 56 | if @object.update(@permited_params) 57 | manage_relations 58 | after_save 59 | 60 | if @quick_edit 61 | render json: object_to_bhf_hash, status: :ok 62 | else 63 | redirect_after_save(notice: set_message('update.success', @model), referral_entry: {id: @object.id, platform: @platform.name}) 64 | end 65 | else 66 | @form_url = entry_path(@platform.name, @object) 67 | 68 | r_settings = {status: :unprocessable_entity} 69 | if @quick_edit 70 | r_settings[:layout] = 'bhf/quick_edit' 71 | r_settings[:formats] = [:html] 72 | end 73 | render :edit, r_settings 74 | end 75 | end 76 | 77 | def duplicate 78 | new_record = @object.dup 79 | new_record.before_bhf_duplicate(@object) if new_record.respond_to?(:before_bhf_duplicate) 80 | if new_record.save 81 | new_record.after_bhf_duplicate(@object) if new_record.respond_to?(:after_bhf_duplicate) 82 | redirect_to(page_url(@platform.page_name, anchor: "#{@platform.name}_platform"), notice: set_message('duplicate.success', @model), flash: {referral_entry: {id: new_record.id, platform: @platform.name}}) 83 | else 84 | redirect_to(page_url(@platform.page_name, anchor: "#{@platform.name}_platform"), notice: set_message('duplicate.error', @model)) 85 | end 86 | end 87 | 88 | def sort 89 | params[:order].each do |order| 90 | @model. 91 | find(order[1].gsub("_#{@platform.name}", '')). 92 | update_attribute(@platform.sortable_property, order[0].to_i) 93 | end 94 | 95 | head :ok 96 | end 97 | 98 | def destroy 99 | object = @object.destroy 100 | if @quick_edit 101 | respond_to do |f| 102 | f.json { render status: :ok, json: object } 103 | end 104 | else 105 | redirect_back_or_default(page_url(@platform.page_name, anchor: "#{@platform.name}_platform"), notice: set_message('destory.success', @model)) 106 | end 107 | end 108 | 109 | private 110 | 111 | def object_to_bhf_hash 112 | extra_data = { 113 | to_bhf_s: @object.to_bhf_s, 114 | object_id: @object.send(@object.class.bhf_primary_key).to_s 115 | } 116 | extra_data.merge!(@object.to_bhf_hash) if @object.respond_to?(:to_bhf_hash) 117 | 118 | @platform.columns.each_with_object(extra_data) do |column, hash| 119 | respond_to_column = @object.respond_to?(column.name) 120 | column_value = @object.send(column.name) if respond_to_column 121 | custom_link = send(@platform.custom_link, platform: @platform.name, id: @object) if @platform.custom_link 122 | if column.display_type == :toggle 123 | query = {} 124 | query[@platform.model_name] = {} 125 | query[@platform.model_name][column.name] = !column_value 126 | custom_link = entry_path(@platform.name, @object, query) 127 | end 128 | edit_link = @platform.hide_edit ? "##{@object.id}_#{@platform.name}" : edit_entry_path(@platform.name, @object) 129 | partial_name = if respond_to_column && ! column.type_ignore_emtpy? && column_value.blank? 130 | 'column/empty' 131 | else 132 | "#{column.macro}/#{column.display_type}" 133 | end 134 | hash[column.name] = render_to_string( 135 | partial: "bhf/table/#{column.macro}/#{column.display_type}", 136 | formats: [:html], 137 | locals: { 138 | object: @object, column_value: column_value, 139 | link: (custom_link ? custom_link : edit_link), 140 | add_quick_link: (!custom_link && @platform.table_quick_edit) 141 | } 142 | ) 143 | end 144 | end 145 | 146 | def load_platform 147 | @platform = find_platform(params[:platform]) 148 | end 149 | 150 | def load_model 151 | @model = @platform.model 152 | if params[@platform.model_name.to_sym] 153 | @permited_params = params.require(@platform.model_name.to_sym).permit! 154 | end 155 | end 156 | 157 | def load_object 158 | @object = @model.unscoped.find(params[:id]) rescue nil 159 | @object.assign_attributes(@permited_params) if @object and @permited_params 160 | after_load 161 | end 162 | 163 | def load_new_object 164 | @object = @model.new(@permited_params) 165 | after_load 166 | end 167 | 168 | def manage_many_to_many 169 | return unless params[:has_and_belongs_to_many] 170 | params[:has_and_belongs_to_many].each_pair do |relation, ids| 171 | next if ids.nil? || ids.empty? 172 | reflection = @model.reflections[relation] 173 | relation_array = @object.send(relation) 174 | reflection.klass.unscoped.find(ids.keys).each do |relation_obj| 175 | has_relation = relation_array.include?(relation_obj) 176 | 177 | if ids[relation_obj.send(relation_obj.class.bhf_primary_key).to_s].blank? 178 | if has_relation 179 | relation_array.delete(relation_obj) 180 | end 181 | else 182 | if ! has_relation 183 | relation_array << relation_obj 184 | end 185 | end 186 | end 187 | end 188 | end 189 | 190 | def manage_has_one 191 | return unless params[:has_one] 192 | object_id = @object.send(@model.bhf_primary_key) 193 | params[:has_one].each_pair do |relation, id| 194 | reflection = @model.reflections[relation] 195 | reflection.klass.where(reflection.foreign_key, object_id).each do |ref_object| 196 | next if ref_object.send(reflection.klass.bhf_primary_key) == id 197 | ref_object.update_attribute(reflection.foreign_key, nil) 198 | end 199 | unless id.blank? 200 | ref_object = reflection.klass.find(id) 201 | next if ref_object.send(reflection.foreign_key) == object_id 202 | ref_object.update_attribute(reflection.foreign_key, object_id) 203 | end 204 | end 205 | end 206 | 207 | def manage_has_many 208 | return unless params[:has_many] 209 | object_id = @object.send(@model.bhf_primary_key) 210 | params[:has_many].each_pair do |relation, ids| 211 | next if ids.nil? || ids.empty? 212 | reflection = @model.reflections[relation] 213 | preset_ids = @object.send(relation).collect do |object| 214 | object.send(object.class.bhf_primary_key).to_s 215 | end 216 | @object.send(relation).klass.find(ids.keys).each do |relation_obj| 217 | fk = relation_obj.class.reflections[reflection.inverse_of.name.to_s].foreign_key 218 | relation_obj_id_as_string = relation_obj.send(relation_obj.class.bhf_primary_key).to_s 219 | 220 | if ids[relation_obj_id_as_string].blank? 221 | if preset_ids.include?(relation_obj_id_as_string) 222 | relation_obj.update_attribute(fk, nil) 223 | end 224 | else 225 | if ! preset_ids.include?(relation_obj_id_as_string) 226 | relation_obj.update_attribute(fk, object_id) 227 | end 228 | end 229 | end 230 | end 231 | end 232 | 233 | def manage_relations 234 | manage_many_to_many 235 | manage_has_many 236 | manage_has_one 237 | end 238 | 239 | def after_load 240 | @object.send(@platform.hooks(:after_load)) if @platform.hooks(:after_load) 241 | end 242 | 243 | def before_save 244 | @object.send(@platform.hooks(:before_save), params) if @platform.hooks(:before_save) 245 | end 246 | 247 | def after_save 248 | @object.send(@platform.hooks(:after_save), params) if @platform.hooks(:after_save) 249 | end 250 | 251 | def set_page 252 | @page = @platform.page_name 253 | end 254 | 255 | def set_quick_edit 256 | @quick_edit = request.xhr? 257 | end 258 | 259 | def redirect_after_save(flash) 260 | if params[:return_to_edit] 261 | redirect_to edit_entry_path(@platform.name, @object), flash 262 | elsif params[:return_to_new] 263 | redirect_to new_entry_path(@platform.name), flash 264 | else 265 | redirect_back_or_default(page_url(@platform.page_name, anchor: "#{@platform.name}_platform"), flash) 266 | end 267 | end 268 | end 269 | -------------------------------------------------------------------------------- /app/controllers/bhf/pages_controller.rb: -------------------------------------------------------------------------------- 1 | class Bhf::PagesController < Bhf::ApplicationController 2 | before_action :set_page 3 | 4 | def show 5 | @edit_params = {} 6 | unless platform_options = @settings.content_for_page(@page) 7 | raise Exception.new("Page '#{@page}' could not be found") 8 | end 9 | 10 | if request.xhr? 11 | params.each do |key, value| 12 | return render_platform(key) if value.is_a?(ActionController::Parameters) && value[:page] != nil 13 | end 14 | end 15 | 16 | @platforms = platform_options.each_with_object([]) do |opts, obj| 17 | platform = find_platform(opts.keys[0]) 18 | 19 | next if platform.table_hide? 20 | paginate_platform_objects(platform) 21 | obj << platform 22 | end 23 | end 24 | 25 | private 26 | 27 | def set_page 28 | @page = params[:page] 29 | end 30 | 31 | def render_platform(platform_name) 32 | platform = find_platform(platform_name) 33 | 34 | paginate_platform_objects(platform) 35 | 36 | render layout: false, partial: 'platform', locals: {platform: platform} 37 | end 38 | 39 | def paginate_platform_objects(platform) 40 | p = (params[platform.name] || {}) 41 | page = 1 42 | unless p[:page].blank? 43 | page = p[:page].to_i 44 | end 45 | 46 | per_page = platform.pagination.offset_per_page 47 | unless p[:per_page].blank? 48 | per_page = p[:per_page].to_i 49 | end 50 | 51 | page_params = { page: page, per_page: per_page } 52 | 53 | @edit_params[platform.name] = page_params 54 | 55 | platform.get_objects(p, page_params) 56 | end 57 | 58 | end 59 | -------------------------------------------------------------------------------- /app/helpers/bhf/application_helper.rb: -------------------------------------------------------------------------------- 1 | module Bhf 2 | module ApplicationHelper 3 | 4 | def show_t(platform) 5 | t("bhf.helpers.entry.models.#{platform.model_name}.show", platform_title: platform.title_singular.titleize, default: t('bhf.helpers.entry.show')).html_safe 6 | end 7 | 8 | def new_t(platform) 9 | t("bhf.helpers.entry.models.#{platform.model_name}.new", platform_title: platform.title_singular.titleize, default: t('bhf.helpers.entry.new')).html_safe 10 | end 11 | 12 | def duplicate_t(platform) 13 | t("bhf.helpers.entry.models.#{platform.model_name}.duplicate", platform_title: platform.title_singular.titleize, default: t('bhf.helpers.entry.duplicate')).html_safe 14 | end 15 | 16 | def edit_t(platform) 17 | t("bhf.helpers.entry.models.#{platform.model_name}.edit", platform_title: platform.title_singular.titleize, default: t('bhf.helpers.entry.edit')).html_safe 18 | end 19 | 20 | def delete_t(platform) 21 | t("bhf.helpers.entry.models.#{platform.model_name}.delete", platform_title: platform.title_singular.titleize, default: t('bhf.helpers.entry.delete')).html_safe 22 | end 23 | 24 | def find_smallest_size_url_for_file(file) 25 | if file.exists?(:thumb) 26 | file.url(:thumb) 27 | elsif file.exists?(:medium) 28 | file.url(:medium) 29 | else 30 | file.url 31 | end 32 | end 33 | 34 | def type_is_image?(type) 35 | Bhf.configuration.image_types.include?(type) 36 | end 37 | 38 | def bhf_main_logo(area) 39 | Bhf.configuration.logo.call(area) 40 | end 41 | 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /app/helpers/bhf/entries_helper.rb: -------------------------------------------------------------------------------- 1 | module Bhf 2 | module EntriesHelper 3 | include Bhf::ActionView::FormOptions 4 | 5 | def node(f, field, &block) 6 | render partial: 'bhf/helper/node', locals: {f: f, field: field, input: with_output_buffer(&block)} 7 | end 8 | 9 | def definition_item(object, column, &block) 10 | render partial: 'bhf/helper/definition_item', locals: {object: object, column: column, content: with_output_buffer(&block)} 11 | end 12 | 13 | def reflection_node(f, field, &block) 14 | return if !f.object.respond_to?(field.reflection.name) || (field.form_type == :static && f.object.new_record? && f.object.send(field.reflection.name).blank?) 15 | render partial: 'bhf/helper/reflection_node', locals: { 16 | f: f, field: field, input: with_output_buffer(&block) 17 | } 18 | end 19 | 20 | def reflection_title(f, field, count = 2) 21 | title = f.object.class.human_attribute_name(field.reflection.name) 22 | if field.link 23 | title = t("bhf.platforms.#{field.link.name}.title", count: count, default: title) 24 | end 25 | title 26 | end 27 | 28 | end 29 | end 30 | -------------------------------------------------------------------------------- /app/helpers/bhf/frontend_helper.rb: -------------------------------------------------------------------------------- 1 | module Bhf 2 | module FrontendHelper 3 | 4 | def bhf_edit(object, options = {}, &block) 5 | return unless session[Bhf.configuration.session_auth_name.to_s] == true 6 | 7 | options[:platform_name] ||= object.class.to_s.pluralize.downcase 8 | 9 | if object.respond_to?(:'bhf_can_edit?', true) 10 | return unless object.bhf_can_edit?(options) 11 | end 12 | 13 | area = if options[:area] 14 | options[:area] 15 | elsif object.respond_to?(:bhf_area, true) 16 | object.bhf_area(options) 17 | end 18 | 19 | render partial: 'bhf/helper/frontend_edit', locals: { area: area, platform_name: options[:platform_name], object: object, block: (with_output_buffer(&block) if block_given?)} 20 | end 21 | 22 | def bhf_main_logo(area) 23 | Bhf.configuration.logo.call(area) 24 | end 25 | 26 | end 27 | end 28 | -------------------------------------------------------------------------------- /app/helpers/bhf/pages_helper.rb: -------------------------------------------------------------------------------- 1 | module Bhf 2 | module PagesHelper 3 | 4 | def get_value(key, p) 5 | return unless p.is_a?(Hash) 6 | 7 | return p[key[0]][key[1]] if key.is_a?(Array) && p[key[0]].is_a?(Hash) && p[key[0]][key[1]].is_a?(String) #omg 8 | 9 | p[key] if p[key] && p[key].is_a?(String) 10 | end 11 | 12 | def current_order_path(order_by, platform_name) 13 | params_platfrom = params[platform_name] ? params[platform_name].clone : ActionController::Parameters.new({}) 14 | params_platfrom.permit! 15 | 16 | if params_platfrom['order'] == order_by && params_platfrom['direction'] != 'desc' 17 | params_platfrom['direction'] = 'desc' 18 | else 19 | params_platfrom['direction'] = 'asc' 20 | end 21 | 22 | params_platfrom['order'] = order_by 23 | 24 | url_for platform_name => params_platfrom 25 | end 26 | 27 | def order_class(order_by, platform_name) 28 | params_platfrom = params[platform_name] ? params[platform_name] : {} 29 | return unless params_platfrom['order'] == order_by 30 | 31 | params_platfrom['direction'] == 'desc' ? 'sorted desc' : 'sorted asc' 32 | end 33 | 34 | end 35 | end 36 | -------------------------------------------------------------------------------- /app/views/bhf/_footer.html.haml: -------------------------------------------------------------------------------- 1 | - if @start_time 2 | %p.state= t('bhf.footer.state', time: (Time.now-@start_time).round(2)).html_safe 3 | 4 | %p.info= t('bhf.footer.info').html_safe 5 | 6 | %p.copy= t('bhf.footer.copy').html_safe 7 | -------------------------------------------------------------------------------- /app/views/bhf/_user.html.haml: -------------------------------------------------------------------------------- 1 | - if defined?(current_account) && current_account 2 | %p.user 3 | = t('bhf.helpers.user.login.as', user: current_account.to_bhf_s).html_safe 4 | - if main_app.respond_to?(Bhf.configuration.logout_path) 5 | = link_to t('bhf.helpers.user.logout.link'), main_app.send(Bhf.configuration.logout_path), method: :delete, class: :alt_button 6 | -------------------------------------------------------------------------------- /app/views/bhf/application/index.html.haml: -------------------------------------------------------------------------------- 1 | .content_box 2 | %ul.basic_info_list 3 | - @settings.pages.each do |page| 4 | %li= link_to t("bhf.pages.navigation.#{page}", default: page.titleize), page_path(page) 5 | -------------------------------------------------------------------------------- /app/views/bhf/entries/_form.html.haml: -------------------------------------------------------------------------------- 1 | - html = { id: (:main_form unless @quick_edit), multipart: (!@quick_edit && @platform.has_file_upload?), class: 'js_bhf_form' } 2 | = form_for @object, url: @form_url, builder: Bhf::ActionView::FormBuilder, html: html do |f| 3 | 4 | - if f.object.errors.any? 5 | = render partial: 'bhf/entries/validation_errors', locals: {f: f} 6 | 7 | - @platform.fields.each do |field| 8 | = render partial: "bhf/form/#{field.macro}/#{field.form_type}", locals: {f: f, field: field} 9 | 10 | - unless @quick_edit 11 | 12 | - if params[:return_to] 13 | = hidden_field :return, :to, name: :return_to, value: params[:return_to] 14 | 15 | .node 16 | .label 17 | .input 18 | - unless (f.object.new_record? && @platform.hide_create) || (!f.object.new_record? && @platform.hide_edit) 19 | = f.submit t('bhf.helpers.form.buttons.save', platform_title: @platform.title_singular) 20 | - unless @platform.hide_edit 21 | = f.submit t('bhf.helpers.form.buttons.and_edit'), name: :return_to_edit, class: 'alt_button' 22 | - unless @platform.hide_create 23 | = f.submit t('bhf.helpers.form.buttons.and_add'), name: :return_to_new, class: 'alt_button' 24 | 25 | = link_to t('bhf.helpers.entry.cancel'), (params[:return_to] ? params[:return_to] : page_path(@platform.page_name)), class: 'cancel', data: ({turbolinks: false} if params[:return_to]) 26 | -------------------------------------------------------------------------------- /app/views/bhf/entries/_validation_errors.html.haml: -------------------------------------------------------------------------------- 1 | .error_explanation 2 | %h5= t 'bhf.helpers.validation.header', error: pluralize(f.object.errors.count, t('bhf.helpers.validation.error')), name: f.object.class.model_name.human 3 | %ul 4 | - f.object.errors.each do |name, message| 5 | %li= f.error_label name, message 6 | -------------------------------------------------------------------------------- /app/views/bhf/entries/edit.html.haml: -------------------------------------------------------------------------------- 1 | %h3= edit_t @platform 2 | = render 'bhf/entries/form' 3 | -------------------------------------------------------------------------------- /app/views/bhf/entries/new.html.haml: -------------------------------------------------------------------------------- 1 | %h3= new_t @platform 2 | = render 'bhf/entries/form' 3 | -------------------------------------------------------------------------------- /app/views/bhf/entries/show.html.haml: -------------------------------------------------------------------------------- 1 | %h3= show_t @platform 2 | - if @platform.definitions.any? 3 | .dl.content_box 4 | - @platform.definitions.each do |column| 5 | - column_value = @object.send(column.name) 6 | - if ! column_value.blank? 7 | = definition_item @object, column do 8 | = render(partial: "bhf/table/#{column.macro}/#{column.show_type}", 9 | locals: {column_value: column_value, 10 | object: @object, link: false, add_quick_link: false}) 11 | 12 | - if @platform.show_extra_fields and @platform.show_extra_fields.any? 13 | .content_box 14 | - @platform.show_extra_fields.each do |name| 15 | = render(partial: "bhf/table/column/#{name}", locals: {name: name, object: @object, link: false, add_quick_link: false}) 16 | -------------------------------------------------------------------------------- /app/views/bhf/form/belongs_to/_radio.html.haml: -------------------------------------------------------------------------------- 1 | - data_source ||= field.link ? field.link.get_objects : field.reflection.klass.all 2 | - fk = field.db_name 3 | = reflection_node f, field do 4 | .quick_edit_block 5 | %ul.relation.quick_edit_inject 6 | - data_source.each do |obj| 7 | %li.quick_edit_entry 8 | = f.radio_button fk, obj.id, id: "belongs_to_#{fk}_#{obj.id}" 9 | - if field.link && ! field.link.hide_edit 10 | = link_to obj.to_bhf_s, edit_entry_path(field.link.name, obj), class: 'js_edit_field qe_button quick_edit' 11 | - else 12 | = label_tag "belongs_to_#{fk}_#{obj.id}", obj.to_bhf_s 13 | - if field.link && ! field.link.hide_delete 14 | = link_to '×'.html_safe, entry_path(field.link.name, obj), method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 15 | 16 | .quick_edit_template{type: 'text/template'} 17 | %li 18 | = f.radio_button fk, '{object_id}', id: "belongs_to_#{fk}_{object_id}", class: 'js_remove_disabled', disabled: true 19 | - if field.link && ! field.link.hide_edit 20 | = link_to '{to_bhf_s}', edit_entry_path(field.link.name, '{object_id}'), class: 'js_edit_field qe_button quick_edit' 21 | - else 22 | = label_tag "belongs_to_#{fk}_#{obj.id}", obj.to_bhf_s 23 | - if field.link && ! field.link.hide_delete 24 | = link_to '×'.html_safe, entry_path(field.link.name, '{object_id}'), method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 25 | 26 | 27 | - if field.link && ! field.link.hide_create 28 | = link_to '+', new_entry_path(field.link.name), class: 'js_add_field plus_button qe_button quick_edit' 29 | -------------------------------------------------------------------------------- /app/views/bhf/form/belongs_to/_select.html.haml: -------------------------------------------------------------------------------- 1 | - data_source ||= field.link ? field.link.get_objects : field.reflection.klass.all 2 | - fk = field.db_name 3 | .node 4 | .label= f.label fk, reflection_title(f, field, 1) 5 | .input.select_input.quick_edit_block 6 | - if field.link 7 | .quick_edit_template{type: 'text/template'} 8 | = options_for_select([['{to_bhf_s}', '{object_id}', {data: {edit: edit_entry_path(field.link.name, '{object_id}'), delete: entry_path(field.link.name, '{object_id}')}}]]) 9 | - select_options ||= options_from_collection_for_select_with_html_attrs(data_source, :id, :to_bhf_s, f.object.send(fk), Proc.new {|obj| {data: {edit: edit_entry_path(field.link.name, obj), delete: entry_path(field.link.name, obj)}}}) 10 | - else 11 | - select_options ||= options_from_collection_for_select(data_source, :id, :to_bhf_s, f.object.send(fk)) 12 | 13 | = f.select fk, select_options, {include_blank: true}, {class: ('quick_edit_inject quick_edit_select' if field.link)} 14 | = render partial: 'bhf/helper/field_errors', locals: {f: f, field: fk} 15 | 16 | - if field.link && ! field.link.hide_edit 17 | = link_to edit_t(field.link), '', class: 'js_edit_field icon edit quick_edit' 18 | - if field.link && ! field.link.hide_delete 19 | = link_to '×'.html_safe, '', method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 20 | - if field.link && ! field.link.hide_create 21 | = link_to '+', new_entry_path(field.link.name), class: 'js_add_field plus_button qe_button quick_edit' 22 | -------------------------------------------------------------------------------- /app/views/bhf/form/belongs_to/_static.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'bhf/form/has_one/static', locals: {ref_object: f.object.send(field.reflection.name), field: field, f: f} 2 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_activestorage_attachment.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | - file = f.object.send(field.name) 3 | - if file.present? 4 | = render partial: 'bhf/form/column/attachment_presenter', locals: {attachment: file.attachment} 5 | 6 | - if f.object.respond_to?(:"#{field.name}_destroy") 7 | .file_delete 8 | = f.check_box "#{field.name}_destroy" 9 | = f.label "#{field.name}_destroy", t('bhf.helpers.file.delete') 10 | 11 | = f.file_field field.name, direct_upload: true, data: {:'direct-upload-url' => main_app.rails_direct_uploads_url} 12 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_activestorage_attachments.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | - f.object.send(field.name).attachments.each do |attachment| 3 | - if attachment.present? 4 | = render partial: 'bhf/form/column/attachment_presenter', locals: {attachment: attachment} 5 | - if f.object.respond_to?(:"#{field.name}_destroy") 6 | .file_delete 7 | = f.check_box "#{field.name}_destroy" 8 | = f.label "#{field.name}_destroy", t('bhf.helpers.file.delete') 9 | 10 | =# f.file_field field.name, direct_upload: true, data: {:'direct-upload-url' => main_app.rails_direct_uploads_url} 11 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_array.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | .array_holder 3 | = f.fields_for field.name do |x| 4 | - if f.object.send(field.name).blank? 5 | = x.text_field nil, class: 'array_fields', id: nil 6 | - else 7 | - f.object.send(field.name).each do |entry| 8 | = x.text_field nil, value: entry, class: 'array_fields', id: nil 9 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_attachment_presenter.html.haml: -------------------------------------------------------------------------------- 1 | - if attachment.present? 2 | - file_blob_url = main_app.route_for(:rails_service_blob, attachment.blob.signed_id, attachment.blob.filename) 3 | - if attachment.blob.image? 4 | = link_to image_tag(main_app.route_for(:rails_representation, attachment.variant(resize: "200>")), class: 'uploaded_image'), file_blob_url 5 | - else 6 | = link_to attachment.blob.filename.to_s, file_blob_url, class: 'uploaded_file' 7 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_boolean.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.check_box field.name 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_date.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.text_field field.name, class: "picker #{field.type}", value: f.object.send(field.name) ? l(f.object.send(field.name), format: t("bhf.helpers.input.date_format.#{field.type}")) : nil 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_hash.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.object.send(field.name).inspect 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_hidden.html.haml: -------------------------------------------------------------------------------- 1 | = f.hidden_field field.name 2 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_image_file.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.file_field field.name, placeholder: t('bhf.helpers.form.file_field.select'), class: 'preview_input' 3 | = image_tag(f.object.send(field.name), width: 400) 4 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_mappin.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.hidden_field :lat, class: 'map_data_lat', data: {default_lat: 42.6259285} 3 | = f.hidden_field :lng, class: 'map_data_lng', data: {default_lng: -70.6519888} 4 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_markdown.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.text_area field.name, class: ('wmd_editor' unless @quick_edit) 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_multiple_fields.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.text_field field.name, class: 'multiple_field', data: {spliter: (f.object.send("#{field.name}_spliter") if f.object.respond_to?(:"#{field.name}_spliter"))} 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_number.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.text_field field.name, class: 'number' 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_password.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.password_field field.name 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_rich_text.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.rich_text_area field.name, class: 'trix_input trix-content' 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_static.html.haml: -------------------------------------------------------------------------------- 1 | - if value = f.object.send(field.name) 2 | - return if @quick_edit and field.display_type == :primary_key 3 | = node f, field do 4 | - if field.display_type == :primary_key 5 | %strong= value 6 | - elsif field.display_type == :date 7 | = l value, format: :long 8 | - else 9 | = value 10 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_string.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.text_field field.name 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_text.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.text_area field.name 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_type.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | - base = f.object.class.respond_to?(:base_class) ? f.object.class.base_class : f.object.class 3 | - list = base.descendants.map do |x| 4 | - [x.to_s.sub(base.to_s,'').titleize, x.to_s] 5 | = f.select :type, list 6 | -------------------------------------------------------------------------------- /app/views/bhf/form/column/_wysiwyg.html.haml: -------------------------------------------------------------------------------- 1 | = node f, field do 2 | = f.text_area field.name, class: 'wysiwyg' 3 | -------------------------------------------------------------------------------- /app/views/bhf/form/embeds_many/_static.html.haml: -------------------------------------------------------------------------------- 1 | = reflection_node f, field do 2 | - ref_objects ||= f.object.send(field.reflection.name) 3 | - is_one = field.macro == :embeds_one 4 | .quick_edit_block 5 | %p.empty{class: ('hide' unless ref_objects.blank?)}= t 'bhf.helpers.entry.empty', count: (is_one ? 1 : 2) 6 | %ul.relation.quick_edit_inject{class: field.macro} 7 | - ref_objects.each do |ref_object| 8 | %li.quick_edit_entry 9 | - if field.link && ! field.link.hide_edit 10 | = link_to ref_object.to_bhf_s, edit_entry_embed_path(field.link.name, f.object, ref_object), class: 'js_edit_field qe_button quick_edit' 11 | - else 12 | = ref_object.to_bhf_s 13 | - if field.link && ! field.link.hide_delete 14 | = link_to '×'.html_safe, entry_embed_path(field.link.name, f.object, ref_object), method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 15 | 16 | .quick_edit_template{type: 'text/template'} 17 | %li.quick_edit_entry 18 | - if field.link && ! field.link.hide_edit 19 | = link_to '{to_bhf_s}', edit_entry_embed_path(field.link.name, f.object, '{object_id}'), class: 'js_edit_field qe_button quick_edit' 20 | - else 21 | = '{to_bhf_s}' 22 | - if field.link && ! field.link.hide_delete 23 | = link_to '×'.html_safe, entry_embed_path(field.link.name, f.object, '{object_id}'), method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 24 | 25 | - if ! is_one && field.link && ! field.link.hide_create 26 | = link_to '+', new_entry_embed_path(field.link.name, f.object), class: 'js_add_field plus_button qe_button quick_edit default_form_align' 27 | -------------------------------------------------------------------------------- /app/views/bhf/form/embeds_one/_static.html.haml: -------------------------------------------------------------------------------- 1 | - ref_object ||= f.object.send(field.reflection.name) 2 | - if ref_object 3 | = render partial: 'bhf/form/embeds_many/static', locals: {ref_objects: [ref_object], field: field, f: f} 4 | -------------------------------------------------------------------------------- /app/views/bhf/form/has_and_belongs_to_many/_check_box.html.haml: -------------------------------------------------------------------------------- 1 | - data_source ||= field.link ? field.link.get_objects : field.reflection.klass.all 2 | - check_box_type ||= :many_to_many_check_box 3 | = reflection_node f, field do 4 | .quick_edit_block 5 | %ul.relation.quick_edit_inject 6 | - data_source.each do |ref_object| 7 | %li.quick_edit_entry 8 | = f.send(check_box_type, ref_object, field.reflection.name, params, field.link && ! field.link.hide_edit) 9 | - if field.link && ! field.link.hide_edit 10 | = link_to ref_object.to_bhf_s, edit_entry_path(field.link.name, ref_object), class: 'js_edit_field qe_button quick_edit' 11 | - if field.link && ! field.link.hide_delete 12 | = link_to '×'.html_safe, entry_path(field.link.name, ref_object), method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 13 | 14 | .quick_edit_template{type: 'text/template'} 15 | %li.quick_edit_entry 16 | = f.send(check_box_type, OpenStruct.new(to_bhf_s: '{to_bhf_s}'), field.reflection.name, params, field.link && ! field.link.hide_edit, true, "{object_id}", {class: 'js_remove_disabled', disabled: true}).gsub('_object_id_', '{object_id}').html_safe # fix for id attr 17 | - if field.link && ! field.link.hide_edit 18 | = link_to '{to_bhf_s}', edit_entry_path(field.link.name, '{object_id}'), class: 'js_edit_field qe_button quick_edit' 19 | - if field.link && ! field.link.hide_delete 20 | = link_to '×'.html_safe, entry_path(field.link.name, '{object_id}'), method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 21 | 22 | - if field.link && ! field.link.hide_create 23 | = link_to '+', new_entry_path(field.link.name), class: 'js_add_field plus_button qe_button quick_edit default_form_align' 24 | -------------------------------------------------------------------------------- /app/views/bhf/form/has_and_belongs_to_many/_static.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'bhf/form/has_many/static', locals: {ref_objects: f.object.send(field.reflection.name), field: field, f: f} 2 | -------------------------------------------------------------------------------- /app/views/bhf/form/has_many/_check_box.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: "bhf/form/has_and_belongs_to_many/check_box", locals: {f: f, field: field, check_box_type: :has_many_check_box} 2 | -------------------------------------------------------------------------------- /app/views/bhf/form/has_many/_static.html.haml: -------------------------------------------------------------------------------- 1 | = reflection_node f, field do 2 | - ref_objects ||= f.object.send(field.reflection.name) 3 | - is_one = (field.macro == :has_one || field.macro == :belongs_to) 4 | .quick_edit_block 5 | %p.empty{class: ('hide' unless ref_objects.blank?)}= t 'bhf.helpers.entry.empty', count: (is_one ? 1 : 2) 6 | %ul.relation.quick_edit_inject{class: field.macro} 7 | - ref_objects.each do |ref_object| 8 | %li.quick_edit_entry 9 | - if field.link && ! field.link.hide_edit 10 | = link_to ref_object.to_bhf_s, edit_entry_path(field.link.name, ref_object), class: 'js_edit_field qe_button quick_edit' 11 | - else 12 | = ref_object.to_bhf_s 13 | - if field.link && ! field.link.hide_delete 14 | = link_to '×'.html_safe, entry_path(field.link.name, ref_object), method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 15 | 16 | .quick_edit_template{type: 'text/template'} 17 | %li.quick_edit_entry 18 | - if field.link && ! field.link.hide_edit 19 | = link_to '{to_bhf_s}', edit_entry_path(field.link.name, '{object_id}'), class: 'js_edit_field qe_button quick_edit' 20 | - else 21 | = '{to_bhf_s}' 22 | - if field.link && ! field.link.hide_delete 23 | = link_to '×'.html_safe, entry_path(field.link.name, '{object_id}'), method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 24 | 25 | - if ! is_one && field.link && ! field.link.hide_create 26 | = link_to '+', new_entry_path(field.link.name), class: 'js_add_field plus_button qe_button quick_edit default_form_align' 27 | -------------------------------------------------------------------------------- /app/views/bhf/form/has_one/_select.html.haml: -------------------------------------------------------------------------------- 1 | - data_source ||= field.link ? field.link.get_objects : field.reflection.klass.all 2 | - fk = field.reflection.name 3 | - ref_obj = f.object.send(fk) 4 | - ref_obj_id = ref_obj ? ref_obj.send(field.reflection.klass.bhf_primary_key) : nil 5 | .node 6 | .label= f.label fk, reflection_title(f, field, 1) 7 | .input.select_input.quick_edit_block 8 | - if field.link 9 | .quick_edit_template{type: 'text/template'} 10 | = options_for_select([['{to_bhf_s}', '{object_id}', {data: {edit: edit_entry_path(field.link.name, '{object_id}'), delete: entry_path(field.link.name, '{object_id}')}}]]) 11 | - select_options ||= options_from_collection_for_select_with_html_attrs(data_source, :id, :to_bhf_s, ref_obj_id, Proc.new {|obj| {data: {edit: edit_entry_path(field.link.name, obj), delete: entry_path(field.link.name, obj)}}}) 12 | - else 13 | - select_options ||= options_from_collection_for_select(data_source, :id, :to_bhf_s, ref_obj_id) 14 | 15 | = select :has_one, fk, select_options, {include_blank: true}, {class: ('quick_edit_inject quick_edit_select' if field.link)} 16 | = render partial: 'bhf/helper/field_errors', locals: {f: f, field: fk} 17 | 18 | - if field.link && ! field.link.hide_edit 19 | = link_to edit_t(field.link), '', class: 'js_edit_field icon edit quick_edit' 20 | - if field.link && ! field.link.hide_delete 21 | = link_to '×'.html_safe, '', method: :delete, class: 'qe_delete js_delete', remote: true, data: {confirm: t('bhf.helpers.promts.confirm', platform_title: field.link.title_singular)} 22 | - if field.link && ! field.link.hide_create 23 | = link_to '+', new_entry_path(field.link.name), class: 'js_add_field plus_button qe_button quick_edit' 24 | -------------------------------------------------------------------------------- /app/views/bhf/form/has_one/_static.html.haml: -------------------------------------------------------------------------------- 1 | - ref_object ||= f.object.send(field.reflection.name) 2 | - if ref_object 3 | = render partial: 'bhf/form/has_many/static', locals: {ref_objects: [ref_object], field: field, f: f} 4 | -------------------------------------------------------------------------------- /app/views/bhf/helper/_definition_item.html.haml: -------------------------------------------------------------------------------- 1 | %p 2 | %span.dt #{object.class.human_attribute_name(column.name)}: 3 | %span.dd{class: [column.macro, column.show_type], data: {column_name: column.name}} 4 | = content 5 | -------------------------------------------------------------------------------- /app/views/bhf/helper/_field_errors.html.haml: -------------------------------------------------------------------------------- 1 | - if f.field_has_errors?(field) 2 | %ul.errors 3 | - f.field_errors(field).each do |error| 4 | %li= error 5 | -------------------------------------------------------------------------------- /app/views/bhf/helper/_flash.html.haml: -------------------------------------------------------------------------------- 1 | - f = {notice: (flash[:notice] if flash[:notice]), error: (flash[:error] if flash[:error])}.reject {|k, v| v.blank?} 2 | - unless f.blank? 3 | #flash_massages.show 4 | %ul 5 | - f.each do |type, msg| 6 | %li{class: type}= msg.to_s.html_safe 7 | -------------------------------------------------------------------------------- /app/views/bhf/helper/_frontend_edit.html.haml: -------------------------------------------------------------------------------- 1 | - link_title = t('bhf.helpers.entry.edit', platform_title: object.to_bhf_s) 2 | 3 | = link_to bhf.edit_entry_url(platform_name, object, {return_to: request.original_url}.merge(area ? {bhf_area: area} : {})), class: 'bhf_edit', data: {:'no-turbolink' => true}, title: link_title do 4 | = block ? block : link_title 5 | -------------------------------------------------------------------------------- /app/views/bhf/helper/_info.html.haml: -------------------------------------------------------------------------------- 1 | - unless info.blank? 2 | %p.info= info.html_safe 3 | -------------------------------------------------------------------------------- /app/views/bhf/helper/_node.html.haml: -------------------------------------------------------------------------------- 1 | .node{class: field.form_type} 2 | .label= f.label field.name 3 | .input 4 | = input 5 | = render partial: 'bhf/helper/field_errors', locals: {f: f, field: field.name} 6 | = render partial: 'bhf/helper/info', locals: {info: field.info} 7 | -------------------------------------------------------------------------------- /app/views/bhf/helper/_reflection_node.html.haml: -------------------------------------------------------------------------------- 1 | .node 2 | .label 3 | %p= reflection_title(f, field) 4 | .input 5 | = input 6 | = render partial: 'bhf/helper/field_errors', locals: {f: f, field: field.reflection.name} 7 | = render partial: 'bhf/helper/info', locals: {info: field.info} 8 | -------------------------------------------------------------------------------- /app/views/bhf/pages/_platform.html.haml: -------------------------------------------------------------------------------- 1 | - platform.pagination.template = self 2 | - referral_id = nil 3 | - if flash[:referral_entry] && referral_entry = flash[:referral_entry].clone.symbolize_keys 4 | - referral_id = (referral_entry[:platform] == platform.name && referral_entry[:id]) 5 | 6 | - if platform.custom_partial 7 | = render partial: "bhf/platform/#{platform.custom_partial}", locals: {platform: platform, referral_id: referral_id} 8 | - else 9 | - cached_partials = {} 10 | - local_names = [:column_value, :object, :link, :add_quick_link] 11 | %table.data_table{id: "#{platform.name}_platform", class: (platform.custom_columns? ? :custom_columns : :default_columns)} 12 | %caption 13 | %h4= platform.title.titleize 14 | - unless platform.hide_create 15 | %p.create= link_to new_t(platform), new_entry_path(platform.name), class: :alt_button 16 | - unless platform.custom_partial 17 | = render partial: 'search', locals: {platform: platform} if platform.search? 18 | 19 | %thead 20 | %tr 21 | - if platform.sortable 22 | %th.drag 23 | - platform.columns.each do |column| 24 | %th{class: "#{column.macro} #{column.display_type} #{order_class(column.db_name, platform.name)}"} 25 | = link_to_if column.reorderble, column.title.titleize, current_order_path(column.db_name, platform.name) 26 | %th.action 27 | 28 | %tfoot 29 | %tr 30 | - colspan_1 = platform.columns_count+1 31 | - if platform.custom_footer 32 | - colspan_1 = (platform.columns_count / 2.to_f).ceil 33 | - colspan_2 = (platform.columns_count / 2.to_f).floor 34 | %td{colspan: colspan_1} 35 | = platform.pagination.create(platform) unless platform.sortable 36 | - if platform.custom_footer 37 | %td.custom_footer_td{colspan: colspan_2} 38 | = render partial: "bhf/platform/#{platform.custom_footer}", locals: { platform: platform } 39 | 40 | %tbody.quick_edit_block{class: ('js_sortable' if platform.sortable), data: {sort_url: sort_entries_path(platform.name)}} 41 | - if platform.objects.any? 42 | - platform.objects.each do |object| 43 | - edit_link = platform.hide_edit ? "##{object.id}_#{platform.name}" : edit_entry_path(platform.name, object) 44 | - custom_link = send(platform.custom_link, platform: platform.name, id: object) if platform.custom_link 45 | %tr.quick_edit_entry{id: "#{object.id}_#{platform.name}", class: ('animate live_edit initial_referral' if referral_id == object.id)} 46 | - if platform.sortable 47 | %td.drag 48 | %span.handle.icon   49 | - platform.columns.each do |column| 50 | - respond_to_column = object.respond_to?(column.name) 51 | - column_value = object.send(column.name) if respond_to_column 52 | - if column.display_type == :toggle 53 | - query = {} 54 | - query[platform.model_name] = {} 55 | - query[platform.model_name][column.name] = !column_value 56 | - custom_link = entry_path(platform.name, object, query) 57 | - css_classes = [column.macro, column.display_type] 58 | - if column.display_type == :primary_key && column_value.to_s.length > 6 59 | - css_classes << :key_long 60 | %td{class: css_classes} 61 | - partial_name = if respond_to_column && ! column.type_ignore_emtpy? && column_value.blank? 62 | - 'column/empty' 63 | - else 64 | - "#{column.macro}/#{column.display_type}" 65 | - cached_partials[partial_name] ||= lookup_context.find_template(partial_name, ['bhf/table'], true, local_names) 66 | - locals = { column_value: column_value, object: object, 67 | link: (custom_link ? custom_link : edit_link), 68 | add_quick_link: (!custom_link && platform.table_quick_edit) } 69 | = cached_partials[partial_name].render(self, locals) 70 | -# render(partial: "bhf/table/#{column.macro}/#{column.display_type}", locals: {column_value: column_value, object: object}) 71 | 72 | %td.action{class: ('wider' if platform.show_duplicate && !platform.hide_edit && !platform.hide_delete)} 73 | - if platform.show_duplicate 74 | - dt = duplicate_t(platform) 75 | = link_to dt, duplicate_entry_path(platform.name, object), class: 'icon duplicate', method: :post, title: dt 76 | - unless platform.hide_edit 77 | = link_to edit_t(platform), edit_link, class: 'icon edit' 78 | - unless platform.hide_delete 79 | = link_to delete_t(platform), entry_path(platform.name, object), method: :delete, remote: true, class: 'icon delete js_delete', data: {type: :json, confirm: t('bhf.helpers.promts.confirm', platform_title: platform.title_singular.titleize)} 80 | - else 81 | %td.no_entries{colspan: platform.columns_count} 82 | = t 'bhf.pagination.info', name: platform.title.titleize, count: 0 83 | - unless platform.hide_create 84 | = link_to new_t(platform), new_entry_path(platform.name) 85 | 86 | %template.quick_edit_template{type: 'text/template'} 87 | %tr.quick_edit_entry{id: "{object_id}_#{platform.name}"} 88 | - edit_link = edit_entry_path(platform.name, '{object_id}') 89 | - if platform.sortable 90 | %td.drag 91 | %span.handle.icon   92 | - platform.columns.each do |column| 93 | - css_classes = [column.macro, column.display_type] 94 | %td{class: css_classes} 95 | = "{#{column.name}}" 96 | 97 | %td.action{class: ('wider' if platform.show_duplicate && !platform.hide_edit && !platform.hide_delete)} 98 | - if platform.show_duplicate 99 | - dt = duplicate_t(platform) 100 | = link_to dt, duplicate_entry_path(platform.name, '{object_id}'), class: 'icon duplicate', method: :post, title: dt 101 | - unless platform.hide_edit 102 | = link_to edit_t(platform), edit_link, class: 'icon edit' 103 | - unless platform.hide_delete 104 | = link_to delete_t(platform), entry_path(platform.name, '{object_id}'), method: :delete, remote: true, class: 'icon delete js_delete', data: {type: :json, confirm: t('bhf.helpers.promts.confirm', platform_title: platform.title_singular.titleize)} 105 | -------------------------------------------------------------------------------- /app/views/bhf/pages/_search.html.haml: -------------------------------------------------------------------------------- 1 | = form_for platform.name, html: {method: :get, class: :search} do |f| 2 | - p = params[platform.name] 3 | = f.fields_for :search do |s| 4 | 5 | - if platform.scopes 6 | = f.select :scope, options_from_collection_for_select(platform.scopes, :value, :name, (p && p[:scope])), {}, class: 'js_submit_form_on_change' 7 | 8 | %p.info= platform.pagination.info platform 9 | 10 | - if platform.search_field? 11 | = s.text_field :text, value: get_value([:search, :text], p), placeholder: t('bhf.helpers.searchform.placeholder') 12 | 13 | - if p && p[:search].is_a?(Hash) 14 | - p[:search].each do |key, value| 15 | = s.hidden_field key, value: value, id: nil, class: (:hidden_search if key == 'text') 16 | 17 | - if platform.custom_search 18 | = render partial: "bhf/platform/#{platform.custom_search}", locals: { platform: platform, f: f, s: s, p: p } 19 | 20 | = f.hidden_field :per_page, value: get_value(:per_page, p) 21 | = f.hidden_field :order, value: get_value(:order, p) 22 | = f.hidden_field :direction, value: get_value(:direction, p) 23 | -------------------------------------------------------------------------------- /app/views/bhf/pages/show.html.haml: -------------------------------------------------------------------------------- 1 | - @platforms.each do |platform| 2 | .platform= render partial: 'platform', locals: {platform: platform} 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/belongs_to/_default.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'bhf/table/has_one/default', locals: {column_value: column_value, object: object, link: link, add_quick_link: add_quick_link} 2 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_array.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | - if column_value.is_a?(String) 3 | = column_value 4 | - else 5 | = column_value[0..5].join(', ') 6 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_boolean.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | = t("bhf.helpers.boolean.#{!!column_value}") 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_carrierwave.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | - value = column_value.respond_to?(:midem) && column_value.midem || column_value 3 | = image_tag value, alt: column_value, class: 'thumbnail_image' 4 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_date.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | - if column_value.is_a?(String) 3 | = column_value 4 | - else 5 | = l(column_value, format: :short) 6 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_empty.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | = ' '.html_safe 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_extern_link.html.haml: -------------------------------------------------------------------------------- 1 | = link_to column_value, title: column_value, target: '_blank' do 2 | %strong= t('bhf.helpers.link', link: column_value).html_safe 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_file.html.haml: -------------------------------------------------------------------------------- 1 | = link_to column_value, column_value 2 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_hash.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | = column_value.inspect 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_image.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | = image_tag column_value, alt: column_value, class: 'preview_image' 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_number.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | = column_value 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_primary_key.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | %strong= column_value 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_string.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | = column_value 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_text.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | = sanitize(column_value.to_s, tags: %w(strong em)) 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_thumbnail.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | = image_tag column_value, alt: column_value, class: 'thumbnail_image' 3 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_toggle.html.haml: -------------------------------------------------------------------------------- 1 | - if link 2 | = link_to t('bhf.helpers.entry.toggle'), link, method: :patch, remote: true, class: ['icon toggle js_toggle', ('h_true' if column_value)], data: {type: :json} 3 | - else 4 | = t("bhf.helpers.boolean.#{!!column_value}") 5 | -------------------------------------------------------------------------------- /app/views/bhf/table/column/_type.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | - base = object.class.respond_to?(:base_class) ? object.class.base_class : object.class 3 | = column_value.sub(base.to_s, '').titleize 4 | -------------------------------------------------------------------------------- /app/views/bhf/table/embeds_many/_default.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'bhf/table/has_many/default', locals: {column_value: column_value, object: object, link: link, add_quick_link: add_quick_link} 2 | -------------------------------------------------------------------------------- /app/views/bhf/table/embeds_one/_default.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'bhf/table/has_one/default', locals: {column_value: column_value, object: object, link: link, add_quick_link: add_quick_link} 2 | -------------------------------------------------------------------------------- /app/views/bhf/table/has_and_belongs_to_many/_default.html.haml: -------------------------------------------------------------------------------- 1 | = render partial: 'bhf/table/has_many/default', locals: {column_value: column_value, object: object, link: link, add_quick_link: add_quick_link} 2 | -------------------------------------------------------------------------------- /app/views/bhf/table/has_many/_default.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | - if column_value.is_a?(String) 3 | = column_value 4 | - else 5 | = column_value.collect(&:to_bhf_s).join(', ') 6 | -------------------------------------------------------------------------------- /app/views/bhf/table/has_one/_default.html.haml: -------------------------------------------------------------------------------- 1 | = content_tag link ? :a : :span, class: ('quick_edit' if add_quick_link), href: (link if link) do 2 | - if column_value 3 | - if column_value.is_a?(String) 4 | = column_value 5 | - else 6 | = column_value.to_bhf_s 7 | -------------------------------------------------------------------------------- /app/views/kaminari/bhf/_gap.html.haml: -------------------------------------------------------------------------------- 1 | .gap= t('bhf.pagination.truncate') 2 | -------------------------------------------------------------------------------- /app/views/kaminari/bhf/_next_page.html.haml: -------------------------------------------------------------------------------- 1 | = link_to_unless current_page.last?, t('bhf.pagination.next_label'), url, remote: remote do |name| 2 | %span= name 3 | -------------------------------------------------------------------------------- /app/views/kaminari/bhf/_page.html.haml: -------------------------------------------------------------------------------- 1 | = link_to_unless page.current?, page, url, remote: remote, class: 'page_number' do |name| 2 | %em= name 3 | -------------------------------------------------------------------------------- /app/views/kaminari/bhf/_paginator.html.haml: -------------------------------------------------------------------------------- 1 | = paginator.render do 2 | = prev_page_tag 3 | 4 | - each_page do |page| 5 | - if page.left_outer? || page.right_outer? || page.inside_window? 6 | = page_tag page 7 | - elsif !page.was_truncated? 8 | = gap_tag 9 | 10 | = next_page_tag 11 | -------------------------------------------------------------------------------- /app/views/kaminari/bhf/_prev_page.html.haml: -------------------------------------------------------------------------------- 1 | = link_to_unless current_page.first?, t('bhf.pagination.previous_label'), url, remote: remote do |name| 2 | %span= name 3 | -------------------------------------------------------------------------------- /app/views/layouts/bhf/application.haml: -------------------------------------------------------------------------------- 1 | !!! 2 | %html{lang: I18n.locale} 3 | %head 4 | %meta{charset: 'utf-8'} 5 | %meta{name: 'turbolinks-cache-control', content: 'no-cache'} 6 | %title= @title 7 | = csrf_meta_tags 8 | %link{href: "/favicon.ico?#{@app_title.to_s.downcase}", rel: 'icon', type: 'image/x-icon'} 9 | - Bhf.configuration.css.each do |css| 10 | = stylesheet_link_tag css 11 | - Bhf.configuration.js.each do |js| 12 | = javascript_include_tag js 13 | 14 | %body 15 | 16 | = render partial: 'bhf/helper/flash', locals: {flash: flash} 17 | %header 18 | %h1 19 | - logo = bhf_main_logo(params[:bhf_area]) 20 | 21 | = link_to image_tag(logo), @root_link ? @root_link : main_app.root_url, data: {turbolinks: false} 22 | 23 | - if @areas && @areas.length > 1 24 | = select_tag :area_select, options_from_collection_for_select(@areas, 'path', 'name', main_app.bhf_path(params[:bhf_area])), class: 'icon' 25 | 26 | - if @settings && @settings.pages.any? 27 | %nav 28 | %ul{class: ('shrink_space' if @settings.pages.count > 7)} 29 | - @settings.pages.each do |page| 30 | %li{class: ('active' if @page == page)} 31 | = link_to t("bhf.pages.navigation.#{page}", default: page.titleize), page_path(page) 32 | 33 | #content 34 | #main 35 | = yield 36 | 37 | %footer 38 | = render 'bhf/user' 39 | = render 'bhf/footer' 40 | -------------------------------------------------------------------------------- /app/views/layouts/bhf/quick_edit.haml: -------------------------------------------------------------------------------- 1 | .quick_edit_helper 2 | %a.alt_button.cancel= t('bhf.quick_edit.buttons.cancel') 3 | - unless @platform.table_hide? 4 | %a.alt_button.open= t('bhf.quick_edit.buttons.open') 5 | = yield 6 | .button.group 7 | %button.save{class: ('create' if @object.new_record?)}= @object.new_record? ? t('bhf.quick_edit.buttons.create') : t('bhf.quick_edit.buttons.save') 8 | - unless @object.new_record? 9 | %button.save_and_next.icon{title: t('bhf.quick_edit.buttons.save_and_next')}= t('bhf.quick_edit.buttons.save_and_next') 10 | -------------------------------------------------------------------------------- /bhf.gemspec: -------------------------------------------------------------------------------- 1 | # Generated by jeweler 2 | # DO NOT EDIT THIS FILE DIRECTLY 3 | # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec' 4 | # -*- encoding: utf-8 -*- 5 | # stub: bhf 1.0.0.beta16 ruby lib 6 | 7 | Gem::Specification.new do |s| 8 | s.name = "bhf".freeze 9 | s.version = "1.0.0.beta16" 10 | 11 | s.required_rubygems_version = Gem::Requirement.new("> 1.3.1".freeze) if s.respond_to? :required_rubygems_version= 12 | s.require_paths = ["lib".freeze] 13 | s.authors = ["Anton Pawlik".freeze] 14 | s.date = "2023-04-11" 15 | s.description = "A simple to use Rails-Engine-Gem that offers an admin interface for trusted user. Easy integratable and highly configurable and agnostic. Works with ActiveRecord and Mongoid.".freeze 16 | s.email = "anton.pawlik@gmail.com".freeze 17 | s.extra_rdoc_files = [ 18 | "LICENSE.txt", 19 | "README.md" 20 | ] 21 | s.files = [ 22 | ".editorconfig", 23 | "Gemfile", 24 | "Gemfile.lock", 25 | "LICENSE.txt", 26 | "README.md", 27 | "Rakefile", 28 | "VERSION", 29 | "app/assets/images/bhf/ajax_loader.gif", 30 | "app/assets/images/bhf/bg.png", 31 | "app/assets/images/bhf/logo_bhf.svg", 32 | "app/assets/images/bhf/mooeditable-toolbarbuttons-tango.png", 33 | "app/assets/images/bhf/pictos.png", 34 | "app/assets/images/bhf/pictos_2x.png", 35 | "app/assets/images/bhf/small_ajax_loader.gif", 36 | "app/assets/images/bhf/small_ajax_loader_h.gif", 37 | "app/assets/images/bhf/wmd-buttons.png", 38 | "app/assets/javascripts/bhf/MooTools-More-1.6.0.js", 39 | "app/assets/javascripts/bhf/application.js", 40 | "app/assets/javascripts/bhf/classes/Ajaxify.js", 41 | "app/assets/javascripts/bhf/classes/ArrayFields.js", 42 | "app/assets/javascripts/bhf/classes/FormHelper.js", 43 | "app/assets/javascripts/bhf/classes/MooEditable.js", 44 | "app/assets/javascripts/bhf/classes/MultipleFields.js", 45 | "app/assets/javascripts/bhf/classes/Picker.js", 46 | "app/assets/javascripts/bhf/classes/Picker_Attach.js", 47 | "app/assets/javascripts/bhf/classes/Picker_Date.js", 48 | "app/assets/javascripts/bhf/classes/PlatformHelper.js", 49 | "app/assets/javascripts/bhf/classes/QuickEdit.js", 50 | "app/assets/javascripts/bhf/classes/QuickEditStack.js", 51 | "app/assets/javascripts/bhf/classes/Request_bhf.js", 52 | "app/assets/javascripts/bhf/classes/Setlatlng.js", 53 | "app/assets/javascripts/bhf/classes/showdown.js", 54 | "app/assets/javascripts/bhf/classes/wmd.js", 55 | "app/assets/javascripts/bhf/locales/Locale.de-DE.js", 56 | "app/assets/javascripts/bhf/locales/Locale.en-US.js", 57 | "app/assets/javascripts/bhf/locales/Locale.pt-PT.js", 58 | "app/assets/javascripts/bhf/mootools_ujs.js", 59 | "app/assets/javascripts/bhf/trix.js", 60 | "app/assets/stylesheets/bhf/MooEditable.scss", 61 | "app/assets/stylesheets/bhf/application.sass", 62 | "app/assets/stylesheets/bhf/functions.sass", 63 | "app/assets/stylesheets/bhf/reset.sass", 64 | "app/assets/stylesheets/bhf/trix.scss", 65 | "app/assets/stylesheets/bhf/typo.scss", 66 | "app/controllers/bhf/application_controller.rb", 67 | "app/controllers/bhf/embed_entries_controller.rb", 68 | "app/controllers/bhf/entries_controller.rb", 69 | "app/controllers/bhf/pages_controller.rb", 70 | "app/helpers/bhf/application_helper.rb", 71 | "app/helpers/bhf/entries_helper.rb", 72 | "app/helpers/bhf/frontend_helper.rb", 73 | "app/helpers/bhf/pages_helper.rb", 74 | "app/views/bhf/_footer.html.haml", 75 | "app/views/bhf/_user.html.haml", 76 | "app/views/bhf/application/index.html.haml", 77 | "app/views/bhf/entries/_form.html.haml", 78 | "app/views/bhf/entries/_validation_errors.html.haml", 79 | "app/views/bhf/entries/edit.html.haml", 80 | "app/views/bhf/entries/new.html.haml", 81 | "app/views/bhf/entries/show.html.haml", 82 | "app/views/bhf/form/belongs_to/_radio.html.haml", 83 | "app/views/bhf/form/belongs_to/_select.html.haml", 84 | "app/views/bhf/form/belongs_to/_static.html.haml", 85 | "app/views/bhf/form/column/_activestorage_attachment.html.haml", 86 | "app/views/bhf/form/column/_activestorage_attachments.html.haml", 87 | "app/views/bhf/form/column/_array.html.haml", 88 | "app/views/bhf/form/column/_attachment_presenter.html.haml", 89 | "app/views/bhf/form/column/_boolean.html.haml", 90 | "app/views/bhf/form/column/_date.html.haml", 91 | "app/views/bhf/form/column/_hash.html.haml", 92 | "app/views/bhf/form/column/_hidden.html.haml", 93 | "app/views/bhf/form/column/_image_file.html.haml", 94 | "app/views/bhf/form/column/_mappin.html.haml", 95 | "app/views/bhf/form/column/_markdown.html.haml", 96 | "app/views/bhf/form/column/_multiple_fields.html.haml", 97 | "app/views/bhf/form/column/_number.html.haml", 98 | "app/views/bhf/form/column/_password.html.haml", 99 | "app/views/bhf/form/column/_rich_text.html.haml", 100 | "app/views/bhf/form/column/_static.html.haml", 101 | "app/views/bhf/form/column/_string.html.haml", 102 | "app/views/bhf/form/column/_text.html.haml", 103 | "app/views/bhf/form/column/_type.html.haml", 104 | "app/views/bhf/form/column/_wysiwyg.html.haml", 105 | "app/views/bhf/form/embeds_many/_static.html.haml", 106 | "app/views/bhf/form/embeds_one/_static.html.haml", 107 | "app/views/bhf/form/has_and_belongs_to_many/_check_box.html.haml", 108 | "app/views/bhf/form/has_and_belongs_to_many/_static.html.haml", 109 | "app/views/bhf/form/has_many/_check_box.html.haml", 110 | "app/views/bhf/form/has_many/_static.html.haml", 111 | "app/views/bhf/form/has_one/_select.html.haml", 112 | "app/views/bhf/form/has_one/_static.html.haml", 113 | "app/views/bhf/helper/_definition_item.html.haml", 114 | "app/views/bhf/helper/_field_errors.html.haml", 115 | "app/views/bhf/helper/_flash.html.haml", 116 | "app/views/bhf/helper/_frontend_edit.html.haml", 117 | "app/views/bhf/helper/_info.html.haml", 118 | "app/views/bhf/helper/_node.html.haml", 119 | "app/views/bhf/helper/_reflection_node.html.haml", 120 | "app/views/bhf/pages/_platform.html.haml", 121 | "app/views/bhf/pages/_search.html.haml", 122 | "app/views/bhf/pages/show.html.haml", 123 | "app/views/bhf/table/belongs_to/_default.html.haml", 124 | "app/views/bhf/table/column/_array.html.haml", 125 | "app/views/bhf/table/column/_boolean.html.haml", 126 | "app/views/bhf/table/column/_carrierwave.html.haml", 127 | "app/views/bhf/table/column/_date.html.haml", 128 | "app/views/bhf/table/column/_empty.html.haml", 129 | "app/views/bhf/table/column/_extern_link.html.haml", 130 | "app/views/bhf/table/column/_file.html.haml", 131 | "app/views/bhf/table/column/_hash.html.haml", 132 | "app/views/bhf/table/column/_image.html.haml", 133 | "app/views/bhf/table/column/_number.html.haml", 134 | "app/views/bhf/table/column/_primary_key.html.haml", 135 | "app/views/bhf/table/column/_string.html.haml", 136 | "app/views/bhf/table/column/_text.html.haml", 137 | "app/views/bhf/table/column/_thumbnail.html.haml", 138 | "app/views/bhf/table/column/_toggle.html.haml", 139 | "app/views/bhf/table/column/_type.html.haml", 140 | "app/views/bhf/table/embeds_many/_default.html.haml", 141 | "app/views/bhf/table/embeds_one/_default.html.haml", 142 | "app/views/bhf/table/has_and_belongs_to_many/_default.html.haml", 143 | "app/views/bhf/table/has_many/_default.html.haml", 144 | "app/views/bhf/table/has_one/_default.html.haml", 145 | "app/views/kaminari/bhf/_gap.html.haml", 146 | "app/views/kaminari/bhf/_next_page.html.haml", 147 | "app/views/kaminari/bhf/_page.html.haml", 148 | "app/views/kaminari/bhf/_paginator.html.haml", 149 | "app/views/kaminari/bhf/_prev_page.html.haml", 150 | "app/views/layouts/bhf/application.haml", 151 | "app/views/layouts/bhf/quick_edit.haml", 152 | "bhf.gemspec", 153 | "config/locales/de.yml", 154 | "config/locales/en.yml", 155 | "config/locales/pt.yml", 156 | "config/routes.rb", 157 | "lib/bhf.rb", 158 | "lib/bhf/action_view/form_builder.rb", 159 | "lib/bhf/action_view/form_options.rb", 160 | "lib/bhf/active_record/base.rb", 161 | "lib/bhf/controller/extension.rb", 162 | "lib/bhf/mongoid/document.rb", 163 | "lib/bhf/platform/attribute/abstract.rb", 164 | "lib/bhf/platform/attribute/column.rb", 165 | "lib/bhf/platform/attribute/reflection.rb", 166 | "lib/bhf/platform/base.rb", 167 | "lib/bhf/platform/pagination.rb", 168 | "lib/bhf/settings/base.rb", 169 | "lib/bhf/settings/platform.rb", 170 | "lib/bhf/settings/yaml_parser.rb", 171 | "lib/rails/generators/bhf/templates/initializer.rb", 172 | "test/database.yml", 173 | "test/test_helper.rb" 174 | ] 175 | s.homepage = "https://antpaw.github.io/bhf".freeze 176 | s.licenses = ["MIT".freeze] 177 | s.rubygems_version = "3.0.9".freeze 178 | s.summary = "A simple to use Rails-Engine-Gem that offers an admin interface for trusted user.".freeze 179 | 180 | if s.respond_to? :specification_version then 181 | s.specification_version = 4 182 | 183 | if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then 184 | s.add_runtime_dependency(%q.freeze, [">= 5.2.2.1"]) 185 | s.add_runtime_dependency(%q.freeze, [">= 6"]) 186 | s.add_runtime_dependency(%q.freeze, [">= 2"]) 187 | s.add_runtime_dependency(%q.freeze, [">= 0"]) 188 | s.add_runtime_dependency(%q.freeze, [">= 0"]) 189 | s.add_development_dependency(%q.freeze, [">= 3"]) 190 | s.add_development_dependency(%q.freeze, [">= 3"]) 191 | s.add_development_dependency(%q.freeze, [">= 1"]) 192 | s.add_development_dependency(%q.freeze, [">= 2"]) 193 | s.add_development_dependency(%q.freeze, [">= 0"]) 194 | else 195 | s.add_dependency(%q.freeze, [">= 5.2.2.1"]) 196 | s.add_dependency(%q.freeze, [">= 6"]) 197 | s.add_dependency(%q.freeze, [">= 2"]) 198 | s.add_dependency(%q.freeze, [">= 0"]) 199 | s.add_dependency(%q.freeze, [">= 0"]) 200 | s.add_dependency(%q.freeze, [">= 3"]) 201 | s.add_dependency(%q.freeze, [">= 3"]) 202 | s.add_dependency(%q.freeze, [">= 1"]) 203 | s.add_dependency(%q.freeze, [">= 2"]) 204 | s.add_dependency(%q.freeze, [">= 0"]) 205 | end 206 | else 207 | s.add_dependency(%q.freeze, [">= 5.2.2.1"]) 208 | s.add_dependency(%q.freeze, [">= 6"]) 209 | s.add_dependency(%q.freeze, [">= 2"]) 210 | s.add_dependency(%q.freeze, [">= 0"]) 211 | s.add_dependency(%q.freeze, [">= 0"]) 212 | s.add_dependency(%q.freeze, [">= 3"]) 213 | s.add_dependency(%q.freeze, [">= 3"]) 214 | s.add_dependency(%q.freeze, [">= 1"]) 215 | s.add_dependency(%q.freeze, [">= 2"]) 216 | s.add_dependency(%q.freeze, [">= 0"]) 217 | end 218 | end 219 | 220 | -------------------------------------------------------------------------------- /config/locales/de.yml: -------------------------------------------------------------------------------- 1 | de: 2 | bhf: 3 | page_title: "%{title} – Admin" 4 | areas: 5 | page_title: "%{area} – %{title} – Admin" 6 | helpers: 7 | boolean: 8 | 'true': 'ja' 9 | 'false': 'nein' 10 | link: 'Link ⬀' 11 | form: 12 | file_field: 13 | select: Datei wählen 14 | buttons: 15 | save: '%{platform_title} speichern' 16 | and_edit: und editieren 17 | and_add: und anlegen 18 | searchform: 19 | placeholder: Suche 20 | promts: 21 | confirm: 'Wollen Sie diesen %{platform_title}-Eintrag löschen?' 22 | entry: 23 | show: "%{platform_title} anzeigen" 24 | new: "%{platform_title} anlegen" 25 | duplicate: "%{platform_title} duplizieren" 26 | toggle: "umschalten" 27 | edit: "%{platform_title} editieren" 28 | delete: "%{platform_title} löschen" 29 | empty: Leer 30 | cancel: Abbrechen 31 | user: 32 | login: 33 | success: Login war erfolgreich 34 | error: Konnte nicht eingeloggen 35 | as: 'Sie sind als %{user} eingeloggt' 36 | logout: 37 | link: Ausloggen 38 | activated: 39 | success: "%{model} wurde erfolgreich aktiviert" 40 | error: "%{model} Fehler" 41 | validation: 42 | header: 'Durch diese Fehler konnte "%{name}" nicht gespeichert werden:' 43 | error: Fehler 44 | file: 45 | delete: Datei löschen 46 | reselect: Bitte die Datei noch mal auswählen 47 | image: 48 | delete: Bild löschen 49 | 50 | quick_edit: 51 | buttons: 52 | cancel: Abbrechen 53 | open: Öffnen 54 | save: Speichern 55 | create: Erstellen 56 | save_and_next: Speichern und weiter zum nächsten Eintrag gehen 57 | 58 | pagination: 59 | previous_label: « Vorheriges 60 | next_label: Nächstes » 61 | truncate: … 62 | load_more: Mehr anzeigen 63 | load_less: Weniger anzeigen 64 | entries: Einträge 65 | info: 66 | default: "Es werden %{offset_start} - %{offset_end} von %{count} %{name} angezeigt" 67 | other: "Es werden alle %{count} %{name} angezeigt" 68 | one: "Es wird 1 %{name} angezeigt" 69 | zero: "Es wurde nichts gefunden" 70 | 71 | footer: 72 | state: 'Seite wurde in ~%{time} Sekunden erstellt' 73 | copy: 'Gemacht von @antpaw' 74 | info: 'Mehr Informationen auf GitHub' 75 | 76 | attributes: 77 | _id: 'id' 78 | 79 | activerecord: 80 | notices: 81 | messages: 82 | duplicate: 83 | success: "%{model} wurde erfolgreich dupliziert" 84 | error: "%{model} Fehler" 85 | create: 86 | success: "%{model} wurde erfolgreich erstellt" 87 | error: "%{model} Fehler" 88 | update: 89 | success: "%{model} wurde erfolgreich editiert" 90 | error: "%{model} Fehler" 91 | destory: 92 | success: "%{model} wurde erfolgreich gelöscht" 93 | error: "%{model} Fehler" 94 | -------------------------------------------------------------------------------- /config/locales/en.yml: -------------------------------------------------------------------------------- 1 | en: 2 | bhf: 3 | page_title: "%{title} – Admin" 4 | areas: 5 | page_title: "%{area} – %{title} – Admin" 6 | helpers: 7 | boolean: 8 | 'true': 'yes' 9 | 'false': 'no' 10 | link: Link ⬀ 11 | form: 12 | file_field: 13 | select: Choose file 14 | buttons: 15 | save: 'Save %{platform_title}' 16 | and_edit: and edit 17 | and_add: and add another 18 | searchform: 19 | placeholder: Search 20 | promts: 21 | confirm: 'Do you want to delete this %{platform_title}-entry?' 22 | entry: 23 | new: "New %{platform_title}" 24 | show: "Show %{platform_title}" 25 | duplicate: "Duplicate %{platform_title}" 26 | toggle: "Toggle" 27 | edit: "Edit %{platform_title}" 28 | delete: "Delete %{platform_title}" 29 | empty: Empty 30 | cancel: Cancel 31 | user: 32 | login: 33 | success: Login was successful 34 | error: "Can't login" 35 | as: 'You are logged in as %{user}' 36 | logout: 37 | link: Logout 38 | activated: 39 | success: "%{model} was activated successfully" 40 | error: "%{model} error" 41 | validation: 42 | header: '%{error} prohibited this "%{name}" from being saved:' 43 | error: error 44 | file: 45 | delete: Delete file 46 | reselect: Please reselect the file 47 | image: 48 | delete: Delete image 49 | input: 50 | date_format: 51 | timestamp: '%Y-%m-%d %H:%M' 52 | datetime: '%Y-%m-%d %H:%M' 53 | date: '%Y-%m-%d' 54 | time: '%H:%M' 55 | quick_edit: 56 | buttons: 57 | cancel: Cancel 58 | open: Open 59 | save: Save 60 | create: Create 61 | save_and_next: Save and go to the next entry 62 | 63 | pagination: 64 | previous_label: « Previous 65 | next_label: Next » 66 | truncate: … 67 | load_more: Show more entries 68 | load_less: Show less entries 69 | entries: entries 70 | info: 71 | default: "Displaying %{offset_start} - %{offset_end} of %{count} %{name} in total" 72 | other: "Displaying all %{count} %{name}" 73 | one: "Displaying 1 %{name}" 74 | zero: "No %{name} found" 75 | 76 | footer: 77 | state: 'Page rendered in ~%{time} seconds' 78 | copy: 'Made by @antpaw' 79 | info: 'More information on GitHub' 80 | 81 | attributes: 82 | _id: 'id' 83 | 84 | activerecord: 85 | notices: 86 | messages: 87 | duplicate: 88 | success: "%{model} was duplicated successfully" 89 | error: "%{model} error" 90 | create: 91 | success: "%{model} was created successfully" 92 | error: "%{model} error" 93 | update: 94 | success: "%{model} was updated successfully" 95 | error: "%{model} error" 96 | destory: 97 | success: "%{model} was deleted successfully" 98 | error: "%{model} error" 99 | -------------------------------------------------------------------------------- /config/locales/pt.yml: -------------------------------------------------------------------------------- 1 | pt: 2 | bhf: 3 | page_title: "%{title} – Admin" 4 | areas: 5 | page_title: "%{area} – %{title} – Admin" 6 | helpers: 7 | boolean: 8 | 'true': 'sim' 9 | 'false': 'não' 10 | link: Link ⬀ 11 | form: 12 | file_field: 13 | select: Escolher arquivo 14 | buttons: 15 | save: 'Salvar %{platform_title}' 16 | and_edit: e editar 17 | and_add: e adicionar outro 18 | searchform: 19 | placeholder: Buscar 20 | promts: 21 | confirm: 'Você quer deletar o registro: %{platform_title}?' 22 | entry: 23 | new: "Criar %{platform_title}" 24 | show: "Exibir %{platform_title}" 25 | duplicate: "Duplicar %{platform_title}" 26 | edit: "Editar %{platform_title}" 27 | delete: "Deletar %{platform_title}" 28 | empty: Esvaziar 29 | cancel: Cancelar 30 | user: 31 | login: 32 | success: Login efetuado com sucesso 33 | error: "Login não pode ser feito" 34 | as: 'Você está logado como %{user}' 35 | logout: 36 | link: Sair 37 | activated: 38 | success: "%{model} foi ativado com sucesso" 39 | error: "Erro ao ativar %{model}" 40 | validation: 41 | header: '%{error} impediu esse "%{name}" de ser salvo:' 42 | error: erro 43 | file: 44 | delete: Deletar arquivo 45 | reselect: "Por favor, selecione novamente o arquivo" 46 | image: 47 | delete: Deletar imagem 48 | input: 49 | date_format: 50 | timestamp: '%d-%m-%Y %H:%M' 51 | datetime: '%d-%m-%Y %H:%M' 52 | date: '%d-%m-%Y' 53 | time: '%H:%M' 54 | quick_edit: 55 | buttons: 56 | cancel: Cancelar 57 | open: Abrir 58 | save: Salvar 59 | create: Criar 60 | save_and_next: Salvar e ir para o próximo registro 61 | 62 | pagination: 63 | previous_label: « Anterior 64 | next_label: Próximo » 65 | truncate: … 66 | load_more: Exibir mais registros 67 | load_less: Exibir menos registros 68 | entries: registros 69 | info: 70 | default: "Exibindo %{offset_start} - %{offset_end} de %{count} %{name} no total" 71 | other: "Exibindo todos %{count} %{name}" 72 | one: "Exibindo 1 %{name}" 73 | zero: "Nenhum(a) %{name} encontrado" 74 | 75 | footer: 76 | state: 'Página renderizada em ~%{time} segundos' 77 | copy: 'Feito por @antpaw' 78 | info: 'Mais informações no GitHub' 79 | 80 | attributes: 81 | _id: 'id' 82 | 83 | activerecord: 84 | notices: 85 | messages: 86 | duplicate: 87 | success: "%{model} duplicado com sucesso" 88 | error: "Erro ao duplicar %{model}" 89 | create: 90 | success: "%{model} criado com sucesso" 91 | error: "Erro ao criar %{model}" 92 | update: 93 | success: "%{model} atualizado com sucesso" 94 | error: "Erro ao atualizar %{model}" 95 | destory: 96 | success: "%{model} deletado com sucesso" 97 | error: "Erro ao deletar %{model}" 98 | -------------------------------------------------------------------------------- /config/routes.rb: -------------------------------------------------------------------------------- 1 | Bhf::Engine.routes.draw do 2 | 3 | root to: 'application#index' 4 | 5 | get 'page/:page', to: 'pages#show', as: :page 6 | 7 | scope ':platform' do 8 | resources :entries, except: [:index] do 9 | put :sort, on: :collection 10 | post :duplicate, on: :member 11 | 12 | resources :embed_entries, except: [:index], as: :embed 13 | end 14 | end 15 | 16 | end 17 | -------------------------------------------------------------------------------- /lib/bhf.rb: -------------------------------------------------------------------------------- 1 | require 'haml' 2 | require 'turbolinks' 3 | require 'kaminari' 4 | 5 | module Bhf 6 | class Engine < Rails::Engine 7 | 8 | isolate_namespace Bhf 9 | 10 | initializer 'bhf.helper' do 11 | ActiveSupport.on_load :action_controller do 12 | helper Bhf::FrontendHelper 13 | end 14 | end 15 | 16 | initializer 'bhf.model_hooks' do 17 | ActiveSupport.on_load :active_record do 18 | include Bhf::ActiveRecord::Base 19 | end 20 | ActiveSupport.on_load :mongoid do 21 | include Bhf::Mongoid::Document 22 | end 23 | end 24 | 25 | initializer 'bhf.assets.precompile' do |app| 26 | app.config.assets.precompile += %w( bhf/application.css bhf/application.js bhf/logo_bhf.svg ) 27 | end 28 | 29 | end 30 | 31 | 32 | def self.configuration 33 | @configuration ||= Bhf::Configuration.new 34 | end 35 | def self.configure 36 | yield configuration 37 | end 38 | class Configuration 39 | include ActiveSupport::Configurable 40 | 41 | config_accessor(:logo) { lambda { |area| 'bhf/logo_bhf.svg' }} 42 | config_accessor(:on_login_fail) { :root_url } 43 | config_accessor(:logout_path) { :logout_path } 44 | config_accessor(:session_auth_name) { :is_admin } 45 | config_accessor(:session_account_id) { :admin_account_id } 46 | config_accessor(:account_model) { 'User' } 47 | config_accessor(:account_model_find_method) { 'find' } 48 | config_accessor(:css) { ['bhf/application'] } 49 | config_accessor(:js) { ['bhf/application'] } 50 | config_accessor(:abstract_settings) { [] } 51 | config_accessor(:image_types) { 52 | ['image/jpeg', 'image/pjpeg', 'image/jpg', 'image/png', 53 | 'image/tif', 'image/gif'] 54 | } 55 | end 56 | end 57 | 58 | require 'bhf/settings/base' 59 | require 'bhf/settings/platform' 60 | require 'bhf/settings/yaml_parser' 61 | require 'bhf/platform/base' 62 | require 'bhf/platform/pagination' 63 | require 'bhf/platform/attribute/abstract' 64 | require 'bhf/platform/attribute/column' 65 | require 'bhf/platform/attribute/reflection' 66 | require 'bhf/active_record/base' 67 | require 'bhf/mongoid/document' 68 | require 'bhf/action_view/form_options' 69 | require 'bhf/action_view/form_builder' 70 | require 'bhf/controller/extension' 71 | -------------------------------------------------------------------------------- /lib/bhf/action_view/form_builder.rb: -------------------------------------------------------------------------------- 1 | module Bhf::ActionView 2 | class FormBuilder < ActionView::Helpers::FormBuilder 3 | include ActionView::Helpers::FormTagHelper 4 | 5 | def error_label(name, message) 6 | label name, "#{object.class.human_attribute_name(name)} #{message}" 7 | end 8 | 9 | def field_errors(field) 10 | object.errors[field.to_sym] 11 | end 12 | 13 | def field_has_errors?(field) 14 | field_errors(field).any? 15 | end 16 | 17 | def many_to_many_or_has_many_check_box(mm, obj, ref_name, params, hide_label = false, checked = false, bhf_primary_key = nil, extra_html_attrs = {}) 18 | bhf_primary_key ||= obj.send(obj.class.bhf_primary_key).to_s 19 | unless checked 20 | checked = if params[mm] && params[mm][ref_name] 21 | params[mm][ref_name][bhf_primary_key] != '' 22 | else 23 | object.send(ref_name).include?(obj) 24 | end 25 | end 26 | 27 | html = hidden_field_tag("#{mm}[#{ref_name}][#{bhf_primary_key}]", '', extra_html_attrs.merge(id: "hidden_#{mm}_#{ref_name}_#{bhf_primary_key}")) 28 | html = html+' '+check_box_tag("#{mm}[#{ref_name}][#{bhf_primary_key}]", bhf_primary_key, checked, extra_html_attrs) 29 | html = html+' '+label_tag("#{mm}_#{ref_name}_#{bhf_primary_key}", obj.to_bhf_s) unless hide_label 30 | 31 | html 32 | end 33 | 34 | def many_to_many_check_box(obj, ref_name, params, hide_label = false, checked = false, bhf_primary_key = nil, extra_html_attrs = {}) 35 | many_to_many_or_has_many_check_box(:has_and_belongs_to_many, obj, ref_name, params, hide_label, checked, bhf_primary_key, extra_html_attrs) 36 | end 37 | 38 | def has_many_check_box(obj, ref_name, params, hide_label = false, checked = false, bhf_primary_key = nil, extra_html_attrs = {}) 39 | many_to_many_or_has_many_check_box(:has_many, obj, ref_name, params, hide_label, checked, bhf_primary_key, extra_html_attrs) 40 | end 41 | 42 | end 43 | end 44 | -------------------------------------------------------------------------------- /lib/bhf/action_view/form_options.rb: -------------------------------------------------------------------------------- 1 | module Bhf::ActionView 2 | module FormOptions 3 | 4 | def option_groups_from_collection_for_select_with_html_attrs(collection, group_method, group_label_method, option_key_method, option_value_method, selected_key, html_attrs) 5 | collection.map do |group| 6 | option_tags = options_from_collection_for_select_with_html_attrs( 7 | group.send(group_method), option_key_method, option_value_method, selected_key, html_attrs) 8 | 9 | content_tag(:optgroup, option_tags, label: group.send(group_label_method)) 10 | end.join.html_safe 11 | end 12 | 13 | def options_from_collection_for_select_with_html_attrs(collection, value_method, text_method, selected, html_attrs) 14 | options = collection.map do |element| 15 | [ 16 | value_for_collection(element, text_method), value_for_collection(element, value_method), option_html_attributes(element), 17 | html_attrs.call(element) 18 | ] 19 | end 20 | selected, disabled = extract_selected_and_disabled(selected) 21 | select_deselect = { 22 | selected: extract_values_from_collection(collection, value_method, selected), 23 | disabled: extract_values_from_collection(collection, value_method, disabled) 24 | } 25 | 26 | options_for_select(options, select_deselect) 27 | end 28 | 29 | end 30 | end 31 | -------------------------------------------------------------------------------- /lib/bhf/active_record/base.rb: -------------------------------------------------------------------------------- 1 | module Bhf::ActiveRecord 2 | module Base 3 | 4 | extend ActiveSupport::Concern 5 | 6 | def to_bhf_s 7 | return title if self.respond_to? :title 8 | return name if self.respond_to? :name 9 | return headline if self.respond_to? :headline 10 | 11 | if self.respond_to?(:attributes) 12 | return title if attributes['title'] 13 | return name if attributes['name'] 14 | klass_name = if self.class.respond_to?(:model_name) 15 | self.class.model_name.human 16 | else 17 | self.class.to_s.humanize 18 | end 19 | return "#{klass_name} ID: #{send(self.class.primary_key)}" if attributes[self.class.primary_key] 20 | end 21 | 22 | self.to_s.humanize 23 | end 24 | 25 | module ClassMethods 26 | def bhf_default_search(search_params) 27 | return where([]) if (search_term = search_params['text']).blank? 28 | where_statement = [] 29 | columns_hash.each_pair do |name, props| 30 | is_number = search_term.to_i.to_s == search_term || search_term.to_f.to_s == search_term 31 | 32 | if props.type == :string || props.type == :text 33 | where_statement << "LOWER(#{name}) LIKE LOWER('%#{search_term}%')" 34 | elsif props.type == :integer && is_number 35 | where_statement << "#{name} = #{search_term.to_i}" 36 | elsif props.type == :float && is_number 37 | where_statement << "#{name} = #{search_term.to_f}" 38 | end 39 | end 40 | 41 | where(where_statement.join(' OR ')) 42 | end 43 | 44 | def bhf_attribute_method?(column_name) 45 | column_names.include?(column_name) 46 | end 47 | 48 | def bhf_primary_key 49 | primary_key 50 | end 51 | 52 | def bhf_embedded? 53 | false 54 | end 55 | end 56 | 57 | end 58 | end 59 | -------------------------------------------------------------------------------- /lib/bhf/controller/extension.rb: -------------------------------------------------------------------------------- 1 | module Bhf 2 | module Extension 3 | module ApplicationController 4 | extend ActiveSupport::Concern 5 | 6 | included do 7 | include Instance 8 | 9 | before_action :init_time 10 | before_action :check_admin_account, :setup_current_account, 11 | :load_settings, :set_title, :set_areas 12 | 13 | helper_method :current_account 14 | layout 'bhf/application' 15 | end 16 | 17 | module Instance 18 | def index 19 | 20 | end 21 | 22 | protected 23 | 24 | def check_admin_account 25 | if session[Bhf.configuration.session_auth_name.to_s] == true 26 | return true 27 | end 28 | redirect_to(main_app.send(Bhf.configuration.on_login_fail.to_sym), 29 | error: t('bhf.helpers.user.login.error')) and return false 30 | end 31 | 32 | def setup_current_account 33 | if session[Bhf.configuration.session_account_id] 34 | @current_account = 35 | Bhf.configuration.account_model.classify.constantize.send( 36 | Bhf.configuration.account_model_find_method.to_sym, 37 | session[Bhf.configuration.session_account_id.to_s] 38 | ) 39 | # => User.find(current_account.id) 40 | end 41 | end 42 | 43 | def current_account 44 | @current_account 45 | end 46 | 47 | def get_account_roles(area = nil) 48 | return unless current_account 49 | 50 | if area 51 | if current_account.respond_to?(:bhf_area_roles) 52 | return current_account.bhf_area_roles(area).collect(&:identifier) 53 | end 54 | end 55 | 56 | if current_account.respond_to?(:bhf_roles) 57 | current_account.bhf_roles.collect(&:identifier) 58 | end 59 | end 60 | 61 | def load_settings 62 | yaml_parser = Bhf::Settings::YAMLParser.new(get_account_roles( 63 | params[:bhf_area]), params[:bhf_area]) 64 | @settings = 65 | Bhf::Settings::Base.new(yaml_parser.settings_hash, current_account) 66 | end 67 | 68 | def set_title 69 | @app_title = Rails.application.class.to_s.split('::').first 70 | 71 | @title = if params[:bhf_area] 72 | t("bhf.areas.page_title.#{params[:bhf_area]}", 73 | area: params[:bhf_area], 74 | title: @app_title, 75 | default: t('bhf.areas.page_title', 76 | title: @app_title, 77 | area: t("bhf.areas.links.#{params[:bhf_area]}", 78 | default: params[:bhf_area]) 79 | ) 80 | ) 81 | else 82 | t('bhf.page_title', title: @app_title) 83 | end.html_safe 84 | end 85 | 86 | def set_areas 87 | @areas = [] 88 | if current_account and current_account.respond_to?(:bhf_areas) 89 | current_account.bhf_areas.each do |area| 90 | selected = params[:bhf_area] == area.identifier 91 | @areas << OpenStruct.new( 92 | name: area.to_bhf_s, 93 | selected: selected, 94 | path: main_app.bhf_path(area.identifier) 95 | ) 96 | @root_link = area.link if selected 97 | end 98 | end 99 | end 100 | 101 | def set_message(type, model = nil) 102 | key = model && ActiveModel::Naming.singular(model) 103 | I18n.t("bhf.activerecord.notices.models.#{key}.#{type}", 104 | model: model.model_name.human, 105 | default: I18n.t("activerecord.notices.messages.#{type}")) 106 | end 107 | 108 | def init_time 109 | @start_time = Time.zone.now 110 | end 111 | 112 | def find_platform(platform_name) 113 | Bhf::Platform::Base.new(@settings.find_platform_settings(platform_name)) 114 | end 115 | 116 | def redirect_back_or_default(default, msg) 117 | redirect_to(params[:return_to] || default, flash: msg) 118 | end 119 | end 120 | end 121 | end 122 | end 123 | -------------------------------------------------------------------------------- /lib/bhf/mongoid/document.rb: -------------------------------------------------------------------------------- 1 | module Bhf::Mongoid 2 | module Document 3 | 4 | extend ActiveSupport::Concern 5 | 6 | class Field 7 | attr_reader :type, :name 8 | 9 | def initialize(mongoid_field) 10 | @name = mongoid_field.name 11 | @type = mongoid_field.type.to_s.downcase.gsub('mongoid::', '').to_sym 12 | @type = :primary_key if @type == :'bson::objectid' 13 | end 14 | end 15 | 16 | class Reflection 17 | attr_reader :name, :macro, :klass, :foreign_key 18 | 19 | def initialize(mongoid_field) 20 | @name = mongoid_field.name 21 | @klass = mongoid_field.class_name.constantize 22 | @foreign_key = mongoid_field.key 23 | @macro = mongoid_field.class.name.demodulize.underscore.to_sym 24 | # @macro = case mongoid_field.macro 25 | # when :references_and_referenced_in_many 26 | # :has_and_belongs_to_many 27 | # when :references_many 28 | # :has_many 29 | # when :references_one 30 | # :has_one 31 | # when :referenced_in 32 | # :belongs_to 33 | # else 34 | # mongoid_field.macro 35 | # end 36 | end 37 | end 38 | 39 | def to_bhf_s 40 | return title if self.respond_to? :title 41 | return name if self.respond_to? :name 42 | 43 | if self.respond_to?(:attributes) 44 | return title if attributes['title'] 45 | return name if attributes['name'] 46 | klass_name = if self.class.respond_to?(:model_name) 47 | self.class.model_name.human 48 | else 49 | self.class.to_s.humanize 50 | end 51 | return "#{klass_name} ID: #{send(self.class.bhf_primary_key)}" if attributes[self.class.bhf_primary_key] 52 | end 53 | 54 | self.to_s.humanize 55 | end 56 | 57 | module ClassMethods 58 | def columns_hash 59 | c = {} 60 | fields.each_pair do |key, meta| 61 | next if meta.options[:metadata] 62 | next if key == '_type' 63 | c[key] = Field.new(meta) 64 | end 65 | c 66 | end 67 | 68 | def reflections 69 | c = {} 70 | relations.each do |key, meta| 71 | next if meta.is_a?(Mongoid::Association::Embedded::EmbeddedIn) 72 | c[key] = Reflection.new(meta) 73 | end 74 | c 75 | end 76 | 77 | def bhf_attribute_method?(column_name) 78 | attribute_method?(column_name) 79 | end 80 | 81 | def bhf_primary_key 82 | '_id' 83 | end 84 | 85 | def order(a) 86 | order_by(a) 87 | end 88 | 89 | def reorder(a) 90 | order_by(a) 91 | end 92 | 93 | def bhf_default_search(search_params) 94 | return self if (search_term = search_params['text']).blank? 95 | 96 | arr = [] 97 | columns_hash.each_pair do |column, props| 98 | is_number = search_term.to_i.to_s == search_term || search_term.to_f.to_s == search_term 99 | 100 | if props.type == :primary_key 101 | arr << {props.name.to_sym => search_term} 102 | elsif props.type == :string || props.type == :text 103 | arr << {props.name.to_sym => /#{search_term}/i} 104 | elsif props.type == :integer && is_number 105 | arr << {props.name.to_sym => search_term.to_i} 106 | elsif props.type == :float && is_number 107 | arr << {props.name.to_sym => search_term.to_f} 108 | end 109 | 110 | end 111 | self.or(arr) 112 | end 113 | 114 | def get_embedded_parent(parent_id, &block) 115 | relations.each do |key, meta| 116 | next unless meta.is_a?(Mongoid::Association::Embedded::EmbeddedIn) 117 | parent = meta.class_name.constantize 118 | parent = parent.find(parent_id) rescue nil 119 | 120 | if parent 121 | return block.call(parent, meta) if block_given? 122 | return parent 123 | end 124 | end 125 | end 126 | 127 | def bhf_new_embed(parent_id, params = nil) 128 | get_embedded_parent parent_id do |parent, meta| 129 | key_name = if meta.inverse_of? 130 | meta.inverse_of 131 | else 132 | meta.inverse_foreign_key.pluralize 133 | end.to_s 134 | if parent.relations[key_name] and parent.relations[key_name].is_a?(Mongoid::Association::Embedded::EmbedsOne) 135 | parent.send("build_#{key_name}", params) 136 | else 137 | parent.send(key_name).build(params) 138 | end 139 | end 140 | end 141 | 142 | def bhf_find_embed(parent_id, ref_id) 143 | get_embedded_parent parent_id do |parent, meta| 144 | key_name = if meta.inverse_of? 145 | meta.inverse_of 146 | else 147 | meta.inverse_foreign_key.pluralize 148 | end.to_s 149 | relation = parent.send(key_name) 150 | if parent.relations[key_name].is_a?(Mongoid::Association::Embedded::EmbedsOne) 151 | relation 152 | else 153 | relation.find(ref_id) 154 | end 155 | end 156 | end 157 | 158 | def bhf_embedded? 159 | embedded? 160 | end 161 | end 162 | 163 | end 164 | end 165 | -------------------------------------------------------------------------------- /lib/bhf/platform/attribute/abstract.rb: -------------------------------------------------------------------------------- 1 | module Bhf::Platform::Attribute 2 | class Abstract 3 | 4 | attr_reader :name, :title, :info 5 | 6 | def initialize(options) 7 | @name = options[:name] 8 | @title = options[:title] 9 | @info = options[:info] 10 | 11 | @options_form_type = options[:form_type].to_sym if options[:form_type] 12 | @options_display_type = options[:display_type].to_sym if options[:display_type] 13 | @options_show_type = options[:show_type].to_sym if options[:show_type] 14 | 15 | @link_platform_settings = options[:link] unless options[:link].blank? 16 | end 17 | 18 | def macro 19 | :column 20 | end 21 | 22 | def form_type 23 | @options_form_type || @name 24 | end 25 | 26 | def display_type 27 | @options_display_type || @name 28 | end 29 | 30 | def type_ignore_emtpy? 31 | false 32 | end 33 | 34 | def show_type 35 | @options_show_type || display_type || @name 36 | end 37 | 38 | def link 39 | return unless @link_platform_settings 40 | @link ||= Bhf::Platform::Base.new(@link_platform_settings) 41 | end 42 | 43 | def reflection 44 | false 45 | end 46 | 47 | def db_name 48 | name 49 | end 50 | 51 | def reorderble 52 | false 53 | end 54 | 55 | end 56 | end 57 | -------------------------------------------------------------------------------- /lib/bhf/platform/attribute/column.rb: -------------------------------------------------------------------------------- 1 | module Bhf::Platform::Attribute 2 | class Column 3 | 4 | attr_reader :name, :title, :info, :type 5 | 6 | def initialize(props, options = {}) 7 | @name = props.name 8 | @title = options[:title] 9 | @info = options[:info] 10 | @type = props.type 11 | 12 | @options_form_type = options[:form_type].to_sym if options[:form_type] 13 | @options_display_type = options[:display_type].to_sym if options[:display_type] 14 | @options_show_type = options[:show_type].to_sym if options[:show_type] 15 | 16 | @pk = options[:primary_key] 17 | end 18 | 19 | def macro 20 | :column 21 | end 22 | 23 | def form_type 24 | return @options_form_type if @options_form_type 25 | 26 | if name == @pk || name == 'updated_at' || name == 'created_at' 27 | :static 28 | elsif name == 'type' 29 | :type 30 | else 31 | supported_types(@type) 32 | end 33 | end 34 | 35 | def display_type 36 | return @options_display_type if @options_display_type 37 | 38 | if name == @pk 39 | :primary_key 40 | elsif name == 'type' 41 | :type 42 | else 43 | supported_types(@type) 44 | end 45 | end 46 | 47 | def type_ignore_emtpy? 48 | display_type == :boolean || display_type == :toggle 49 | end 50 | 51 | def show_type 52 | @options_show_type || display_type 53 | end 54 | 55 | def reflection 56 | false 57 | end 58 | 59 | def db_name 60 | name 61 | end 62 | 63 | def reorderble 64 | true 65 | end 66 | 67 | private 68 | 69 | def supported_types(check_type) 70 | if [:boolean, :toggle, :text, :array, :hash].include?(check_type) 71 | check_type 72 | elsif type_sym = group_types(check_type) 73 | type_sym 74 | else 75 | :string 76 | end 77 | end 78 | 79 | def group_types(type_sym) 80 | return :date if [:date, :datetime, :timestamp, :time, :year].include?(type_sym) 81 | return :number if [:integer, :float].include?(type_sym) 82 | return :file if type_sym == :file 83 | end 84 | 85 | end 86 | end 87 | -------------------------------------------------------------------------------- /lib/bhf/platform/attribute/reflection.rb: -------------------------------------------------------------------------------- 1 | module Bhf::Platform::Attribute 2 | class Reflection 3 | 4 | attr_reader :name, :title, :info 5 | 6 | def initialize(reflection, options = {}) 7 | @name = reflection.name.to_s 8 | @title = options[:title] 9 | @info = options[:info] 10 | @reflection = reflection 11 | 12 | @options_form_type = options[:form_type].to_sym if options[:form_type] 13 | @options_display_type = options[:display_type].to_sym if options[:display_type] 14 | @options_show_type = options[:show_type].to_sym if options[:show_type] 15 | 16 | @link_platform_settings = options[:link] unless options[:link].blank? 17 | 18 | @reorderble = options[:reorderble] 19 | end 20 | 21 | def macro 22 | return :has_and_belongs_to_many if @reflection.macro == :has_many && @reflection.options[:through] 23 | @reflection.macro 24 | end 25 | 26 | def form_type 27 | type 28 | end 29 | 30 | def display_type 31 | @options_display_type || :default 32 | end 33 | 34 | def type_ignore_emtpy? 35 | false 36 | end 37 | 38 | def show_type 39 | @options_show_type || display_type 40 | end 41 | 42 | def type 43 | return @options_form_type if @options_form_type 44 | 45 | if macro == :has_and_belongs_to_many or macro == :has_many 46 | :check_box 47 | elsif macro == :belongs_to or macro == :has_one 48 | :select 49 | else 50 | :static 51 | end 52 | end 53 | 54 | def reflection 55 | @reflection 56 | end 57 | 58 | def db_name 59 | @reflection.foreign_key 60 | end 61 | 62 | def reorderble 63 | @reorderble 64 | end 65 | 66 | def link 67 | return unless @link_platform_settings 68 | @link ||= Bhf::Platform::Base.new(@link_platform_settings) 69 | end 70 | 71 | end 72 | end 73 | -------------------------------------------------------------------------------- /lib/bhf/platform/base.rb: -------------------------------------------------------------------------------- 1 | module Bhf::Platform 2 | class Base 3 | attr_reader :name, :objects, :page_name, :title, :title_zero, :title_singular 4 | 5 | def initialize(options) 6 | @objects = [] 7 | 8 | @name = options.name 9 | @data = options.hash 10 | @settings = options.settings_base 11 | @user = @settings.user 12 | 13 | t_model_path = "activerecord.models.#{model.model_name.to_s.downcase}" 14 | model_name = I18n.t(t_model_path, count: 2, default: @name.pluralize.capitalize) 15 | @title = I18n.t("bhf.platforms.#{@name}.title", count: 2, default: model_name) 16 | model_name = I18n.t(t_model_path, count: 1, default: @name.singularize.capitalize) 17 | @title_singular = I18n.t("bhf.platforms.#{@name}.title", count: 1, default: model_name) 18 | model_name = I18n.t(t_model_path, count: 0, default: @name.singularize.capitalize) 19 | @title_zero = I18n.t("bhf.platforms.#{@name}.title", count: 0, default: model_name) 20 | 21 | @page_name = options.page_name 22 | end 23 | 24 | 25 | def pagination 26 | @pagination ||= Bhf::Platform::Pagination.new(entries_per_page) 27 | end 28 | 29 | def get_objects(options = {}, paginate_options = nil) 30 | if user_scope? and @user 31 | chain = @user.send(table_value(:user_scope).to_sym) 32 | else 33 | chain = model 34 | if options[:scope].present? 35 | chain = chain.send options[:scope] 36 | elsif data_source 37 | chain = chain.send data_source 38 | end 39 | end 40 | 41 | if options[:order].present? and options[:direction].present? 42 | chain = chain.reorder("#{options[:order]} #{options[:direction]}") 43 | end 44 | 45 | if search? && options[:search].present? 46 | chain = do_search(chain, options[:search]) 47 | end 48 | 49 | if paginate_options && !sortable 50 | chain = chain.page(paginate_options[:page]).per(paginate_options[:per_page]) 51 | elsif chain == model 52 | chain = chain.all 53 | end 54 | 55 | @objects = chain 56 | end 57 | 58 | def model 59 | @model ||= if @data['model'] 60 | @data['model'] 61 | else 62 | @name 63 | end.classify.constantize 64 | end 65 | 66 | def model_name 67 | ActiveModel::Naming.singular(model) 68 | end 69 | 70 | def table_hide? 71 | table_value(:hide) == true || model.bhf_embedded? 72 | end 73 | 74 | def remove_excludes(attrs, excludes) 75 | return attrs unless excludes 76 | attrs.each_with_object([]) do |attribute, obj| 77 | next if excludes.include?(attribute.name.to_s) 78 | obj << attribute 79 | end 80 | end 81 | 82 | def fields 83 | return @fields if @fields 84 | 85 | tmp = default_attrs(form_value(:display), attributes) 86 | if sortable and ! form_value(:display) 87 | tmp = remove_excludes(tmp, [sortable_property.to_s]) 88 | end 89 | 90 | @fields = remove_excludes(tmp, form_value(:exclude)) 91 | end 92 | 93 | def columns 94 | return @columns if @columns 95 | 96 | tmp = default_attrs(table_columns, attributes[0..5]) 97 | if sortable and ! table_columns 98 | tmp = remove_excludes(tmp, [sortable_property.to_s]) 99 | end 100 | 101 | @columns = remove_excludes(tmp, table_value(:exclude)) 102 | end 103 | 104 | def definitions 105 | return @definitions if @definitions 106 | 107 | tmp = default_attrs(show_value(:display) || show_value(:definitions), attributes) 108 | @definitions = remove_excludes(tmp, show_value(:exclude)) 109 | end 110 | 111 | def has_file_upload? 112 | return true if form_value(:multipart) == true 113 | 114 | fields.each do |field| 115 | return true if field.form_type == :file 116 | end 117 | false 118 | end 119 | 120 | 121 | 122 | 123 | 124 | 125 | def search? 126 | table_value(:search) != false 127 | end 128 | 129 | def search_field? 130 | table_value(:search_field) != false 131 | end 132 | 133 | def custom_search 134 | table_value(:custom_search) 135 | end 136 | 137 | def custom_footer 138 | table_value(:custom_footer) 139 | end 140 | 141 | def table_columns 142 | table_value(:display) || table_value(:columns) 143 | end 144 | 145 | def custom_columns? 146 | table_columns.is_a?(Array) 147 | end 148 | 149 | def user_scope? 150 | @user && table_value(:user_scope) 151 | end 152 | 153 | def table 154 | @data['table'] 155 | end 156 | 157 | def show 158 | @data['show'] 159 | end 160 | 161 | def form 162 | @data['form'] 163 | end 164 | 165 | def hooks(method) 166 | if @data['hooks'] && @data['hooks'][method.to_s] 167 | @data['hooks'][method.to_s].to_sym 168 | end 169 | end 170 | 171 | def sortable 172 | table_value 'sortable' 173 | end 174 | 175 | def sortable_property 176 | (@data['sortable_property'] || :position).to_sym 177 | end 178 | 179 | def columns_count 180 | columns.count + (sortable ? 2 : 1) 181 | end 182 | 183 | def hide_edit 184 | table_value 'hide_edit' 185 | end 186 | 187 | def hide_create 188 | table_value('hide_create') || table_value('hide_new') || table_value('hide_add') 189 | end 190 | 191 | def show_duplicate 192 | table_value 'show_duplicate' 193 | end 194 | 195 | def hide_delete 196 | table_value('hide_delete') || table_value('hide_destroy') 197 | end 198 | 199 | def custom_link 200 | table_value 'custom_link' 201 | end 202 | 203 | def table_quick_edit 204 | table_value 'quick_edit' 205 | end 206 | 207 | def custom_partial 208 | table_value 'partial' 209 | end 210 | 211 | def read_data_source 212 | table_value(:source) || table_value(:scope) 213 | end 214 | 215 | def data_source 216 | temp_scope = read_data_source 217 | return unless temp_scope 218 | return temp_scope[0] if temp_scope.is_a?(Array) 219 | temp_scope 220 | end 221 | 222 | def scopes 223 | temp_scope = read_data_source 224 | return unless temp_scope.is_a?(Array) and temp_scope.count > 1 225 | temp_scope.each_with_object([]) do |s, array| 226 | array << OpenStruct.new( 227 | name: I18n.t("bhf.platforms.#{@name}.scopes.#{s}", default: s), 228 | value: s 229 | ) 230 | end 231 | end 232 | 233 | def show_extra_fields 234 | show_value(:extra_fields) 235 | end 236 | 237 | def entries_per_page 238 | table_value(:per_page) 239 | end 240 | 241 | 242 | 243 | 244 | private 245 | 246 | def do_search(chain, search_params) 247 | if table_value(:search) 248 | chain.send table_value(:search), search_params 249 | else 250 | chain.bhf_default_search(search_params) 251 | end 252 | end 253 | 254 | def find_platform_settings_for_link(link_name) 255 | if form_value(:links, link_name) != false 256 | if form_value(:links, link_name) 257 | @settings.find_platform_settings(form_value(:links, link_name)) 258 | else 259 | @settings.find_platform_settings(link_name.to_s.pluralize) || @settings.find_platform_settings(link_name.to_s.singularize) 260 | end 261 | end 262 | end 263 | 264 | def default_attrs(attrs, d_attrs) 265 | return d_attrs unless attrs 266 | 267 | attrs.each_with_object([]) do |name, obj| 268 | obj << ( 269 | attributes.select{ |field| name == field.name }[0] || 270 | Bhf::Platform::Attribute::Abstract.new(default_attribute_options(name).merge({ 271 | name: name, 272 | link: find_platform_settings_for_link(name) 273 | })) 274 | ) 275 | end 276 | end 277 | 278 | def attributes 279 | return @attributes if @attributes 280 | 281 | all = {} 282 | 283 | model.columns_hash.each_pair do |name, props| 284 | next if sortable && name == sortable_property 285 | all[name] = Bhf::Platform::Attribute::Column.new(props, default_attribute_options(name).merge({ 286 | primary_key: model.bhf_primary_key 287 | })) 288 | end 289 | 290 | model.reflections.each_pair do |name, props| 291 | fk = props.foreign_key 292 | all[name] = Bhf::Platform::Attribute::Reflection.new(props, default_attribute_options(name).merge({ 293 | link: find_platform_settings_for_link(name), 294 | reorderble: model.bhf_attribute_method?(fk) 295 | })) 296 | 297 | if all.has_key?(fk) and fk != name.to_s 298 | all.delete(fk) 299 | end 300 | end 301 | 302 | @attributes = default_sort(all) 303 | end 304 | 305 | def default_sort(attrs) 306 | id = [] 307 | headlines = [] 308 | static_dates = [] 309 | output = [] 310 | 311 | attrs.each_pair do |key, value| 312 | if key == model.bhf_primary_key 313 | id << value 314 | elsif key == 'title' || key == 'name' || key == 'headline' 315 | headlines << value 316 | elsif key == 'created_at' || key == 'updated_at' 317 | static_dates << value 318 | else 319 | output << value 320 | end 321 | end 322 | 323 | id + headlines + output.sort_by(&:name) + static_dates 324 | end 325 | 326 | def default_attribute_options(name) 327 | { 328 | title: model.human_attribute_name(name), 329 | form_type: form_value(:types, name), 330 | display_type: table_value(:types, name), 331 | show_type: show_value(:types, name), 332 | info: I18n.t("bhf.platforms.#{@name}.infos.#{name}", default: '') 333 | } 334 | end 335 | 336 | 337 | 338 | def form_value(key, attribute = nil) 339 | lookup_value form, key, attribute 340 | end 341 | 342 | def table_value(key, attribute = nil) 343 | lookup_value table, key, attribute 344 | end 345 | 346 | def show_value(key, attribute = nil) 347 | lookup_value show, key, attribute 348 | end 349 | 350 | def lookup_value(main_key, key, attribute = nil) 351 | if main_key 352 | if attribute == nil 353 | main_key[key.to_s] 354 | elsif main_key[key.to_s] 355 | main_key[key.to_s][attribute.to_s] 356 | end 357 | end 358 | end 359 | 360 | end 361 | end 362 | -------------------------------------------------------------------------------- /lib/bhf/platform/pagination.rb: -------------------------------------------------------------------------------- 1 | Kaminari::Helpers::Tag.class_eval do 2 | def page_url_for(page) 3 | if @param_name.is_a?(Array) 4 | @params[@param_name[0]] = (@params[@param_name[0]] || {}).merge(@param_name[1] => (page <= 1 ? 1 : page)) 5 | return @template.url_for @params 6 | end 7 | 8 | @template.url_for @params.merge(@param_name => (page <= 1 ? 1 : page)) 9 | end 10 | end 11 | # TODO: monitor https://github.com/amatsuda/kaminari/issues/542#issuecomment-40294388 12 | 13 | module Bhf::Platform 14 | 15 | class Pagination 16 | 17 | attr_accessor :template 18 | attr_reader :offset_per_page, :offset_to_add 19 | 20 | def initialize(offset_per_page = 10, offset_to_add = 5) 21 | @offset_per_page = offset_per_page || 10 22 | @offset_to_add = offset_to_add 23 | end 24 | 25 | def create(platform) 26 | platform_params = template.params[platform.name] || {} 27 | 28 | links = if !(page_links = template.paginate(platform.objects, 29 | theme: 'bhf', 30 | param_name: [platform.name, :page], 31 | params: template.params.permit! 32 | )).blank? 33 | "#{load_more(platform)} #{page_links}" 34 | elsif platform.objects.total_pages == 1 && platform.objects.size > @offset_to_add 35 | load_less(platform) 36 | end 37 | 38 | if links 39 | template.content_tag(:div, links.html_safe, {class: 'pagination'}) 40 | end 41 | end 42 | 43 | def info(platform, options = {}) 44 | collection = platform.objects 45 | 46 | if collection.respond_to?(:num_pages) and collection.num_pages > 1 47 | I18n.t('bhf.pagination.info.default', { 48 | name: platform.title, 49 | count: collection.total_count, 50 | offset_start: collection.offset_value + 1, 51 | offset_end: collection.offset_value + collection.limit_value 52 | }) 53 | else 54 | count = collection.size 55 | platform_title = case count 56 | when 0 57 | platform.title 58 | when 1 59 | platform.title_singular 60 | else 61 | platform.title 62 | end 63 | 64 | I18n.t('bhf.pagination.info', 65 | count: count, 66 | name: platform_title 67 | ) 68 | end.html_safe 69 | end 70 | 71 | def load_more(platform, attributes = {}, plus = true) 72 | platform_params = template.params[platform.name] || {} 73 | load_offset = @offset_per_page 74 | load_offset = platform_params[:per_page].to_i if platform_params[:per_page] 75 | if plus 76 | load_offset += @offset_to_add 77 | else 78 | load_offset -= @offset_to_add 79 | end 80 | 81 | platform_params.delete(:page) 82 | platform_params[:per_page] = load_offset 83 | 84 | direction = plus ? 'more' : 'less' 85 | 86 | parsed_paramas = { 87 | bhf_area: template.params[:bhf_area], 88 | page: template.params[:page] 89 | }.merge(platform.name.to_sym => platform_params) 90 | template.link_to( 91 | I18n.t("bhf.pagination.load_#{direction}"), 92 | template.page_path(platform.page_name, parsed_paramas), 93 | attributes.merge(class: "load_#{direction}") 94 | ) 95 | end 96 | 97 | def load_less(platform, attributes = {}) 98 | load_more(platform, attributes, false) 99 | end 100 | 101 | end 102 | end 103 | -------------------------------------------------------------------------------- /lib/bhf/settings/base.rb: -------------------------------------------------------------------------------- 1 | module Bhf::Settings 2 | 3 | class Base 4 | 5 | attr_accessor :user 6 | 7 | def initialize(settings_hash, user = nil) 8 | @settings_hash = settings_hash 9 | @user = user 10 | 11 | t = pages.each_with_object([]) do |page, obj| 12 | content_for_page(page).each do |platform| 13 | obj << platform.keys.flatten 14 | end 15 | end.flatten! 16 | if t.nil? 17 | raise Exception.new("No bhf pages found") 18 | end 19 | if t.uniq.length != t.length 20 | raise Exception.new("Platforms with identical names: '#{t.detect{ |e| t.count(e) > 1 }}'") 21 | end 22 | end 23 | 24 | def pages 25 | @pages ||= @settings_hash['pages'].each_with_object([]) do |page, obj| 26 | if page.is_a?(String) 27 | page = {page => nil} 28 | end 29 | obj << page.keys[0] 30 | end 31 | end 32 | 33 | def content_for_page(selected_page) 34 | @settings_hash['pages'].each do |page| 35 | page = {page => nil} if page.is_a?(String) 36 | 37 | if selected_page == page.keys[0] 38 | return page.values.flatten 39 | end 40 | end 41 | nil 42 | end 43 | 44 | def find_platform_settings(platform_name) 45 | pages.each do |page| 46 | content_for_page(page).each do |platform_hash| 47 | if platform_hash.keys[0] == platform_name.to_s 48 | return Bhf::Settings::Platform.new(platform_hash, page, self) 49 | end 50 | end 51 | end 52 | nil 53 | end 54 | 55 | end 56 | 57 | end 58 | -------------------------------------------------------------------------------- /lib/bhf/settings/platform.rb: -------------------------------------------------------------------------------- 1 | module Bhf::Settings 2 | 3 | class Platform 4 | 5 | attr_reader :name, :data, :page_name, :hash, :settings_base 6 | 7 | def initialize(settings_hash, page_name, settings_base) 8 | if settings_hash.is_a?(String) 9 | settings_hash = {settings_hash => nil} 10 | end 11 | @name = settings_hash.keys[0] 12 | @hash = settings_hash.values[0] || {} 13 | 14 | @settings_base = settings_base 15 | @page_name = page_name 16 | end 17 | 18 | end 19 | 20 | end 21 | -------------------------------------------------------------------------------- /lib/bhf/settings/yaml_parser.rb: -------------------------------------------------------------------------------- 1 | module Bhf::Settings 2 | 3 | class YAMLParser 4 | 5 | attr_accessor :settings_hash 6 | 7 | def initialize(roles_array, area = nil) 8 | roles_settings = roles_yml(roles_array, area) 9 | 10 | if Bhf.configuration.abstract_settings.any? 11 | tmp_pages = get_settings_array(Bhf.configuration.abstract_settings)['pages'] 12 | abstract_platform_settings = tmp_pages.each_with_object({}) do |abstract_pages, hash| 13 | abstract_pages.each do |abstract_page| 14 | abstract_page[1].each do |abstract_platform| 15 | hash.merge!(abstract_platform) 16 | end 17 | end 18 | end 19 | 20 | roles_settings['pages'].each_with_index do |pages, i_1| 21 | pages.each_pair do |key_1, page| 22 | page.each_with_index do |platform, i_2| 23 | platform.each_pair do |key_2, v| 24 | value = v.is_a?(Hash) ? v : {} 25 | abstract_platform_key = if value['extend_abstract'] 26 | value['extend_abstract'] 27 | elsif abstract_platform_settings[key_2] 28 | key_2 29 | end 30 | next unless abstract_platform_key 31 | roles_settings['pages'][i_1][key_1][i_2][key_2] = abstract_platform_settings[abstract_platform_key].deep_merge(value) 32 | end 33 | end 34 | end 35 | end 36 | end 37 | 38 | @settings_hash = roles_settings 39 | end 40 | 41 | def get_settings_array(array, dir = nil) 42 | array.each_with_object({'pages' => []}) do |file_name, tmp_settings_hash| 43 | absolut_path = false 44 | file_path = if dir 45 | "#{dir}/#{file_name}" 46 | else 47 | if file_name[0,1] == '/' 48 | absolut_path = true 49 | file_name 50 | else 51 | "/#{file_name}" 52 | end 53 | end 54 | pages = load_yml(file_path, absolut_path)['pages'] 55 | tmp_settings_hash['pages'] += pages if pages 56 | end 57 | end 58 | 59 | def roles_yml(roles = nil, area = nil) 60 | area_dir = "/#{area}" if area.present? 61 | if roles.is_a?(Array) 62 | files = get_settings_array(roles, area_dir) 63 | 64 | # find the same pages and merge them 65 | merged_files = {'pages' => []} 66 | files['pages'].each do |pages| 67 | merged = false 68 | pages.each_pair do |key, page| 69 | merged_files['pages'].each do |m_page| 70 | if m_page.include?(key) 71 | merged = true 72 | m_page[key] = m_page[key] + page 73 | end 74 | end 75 | end 76 | unless merged 77 | merged_files['pages'] << pages 78 | end 79 | end 80 | merged_files 81 | else 82 | load_yml(area.present? ? "/#{area}/bhf" : nil) 83 | end 84 | end 85 | 86 | def load_yml(suffix = nil, absolut_path = false) 87 | file_path = if absolut_path 88 | "#{suffix}.yml" 89 | else 90 | "config/bhf#{suffix}.yml" 91 | end 92 | YAML::load(IO.read(file_path)) 93 | end 94 | 95 | end 96 | 97 | end 98 | -------------------------------------------------------------------------------- /lib/rails/generators/bhf/templates/initializer.rb: -------------------------------------------------------------------------------- 1 | Bhf.configure do |config| 2 | # config.css << 'bhf' 3 | # config.js << 'bhf' 4 | # config.abstract_settings << 'abstract' 5 | 6 | # config.logo = lambda { |area| 'my_logo.svg' } 7 | # config.on_login_fail = :root_url 8 | # config.logout_path = :logout_path 9 | 10 | # config.session_account_id = :admin_account_id 11 | # config.session_auth_name = :is_admin 12 | # config.account_model = 'User' 13 | # config.account_model_find_method = 'find' 14 | end 15 | -------------------------------------------------------------------------------- /test/database.yml: -------------------------------------------------------------------------------- 1 | mysql: 2 | adapter: mysql 3 | host: 127.0.0.1 4 | database: bhf_test 5 | username: root 6 | password: 7 | encoding: utf8 8 | pool: 5 9 | timeout: 5000 10 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | ENV["RAILS_ENV"] = "test" 2 | 3 | require 'test/unit' 4 | require 'rubygems' 5 | require 'yaml' 6 | require 'active_record' 7 | require 'mysql' 8 | 9 | require 'app/models/bhf/widget.rb' 10 | 11 | def bhf_widget( fixture_name ) 12 | id = @@fixtures['bhf_widget'][ fixture_name.to_s ][ 'id' ] 13 | Bhf::Widget.find( id ) 14 | end 15 | 16 | def load_schema 17 | config = YAML::load( IO.read( File.dirname(__FILE__) + '/database.yml') ) 18 | 19 | # Manually initialize the database 20 | conn = Mysql.real_connect( config['mysql']['host'], config['mysql']['username'], config['mysql']['password'] ) 21 | conn.query( "CREATE DATABASE IF NOT EXISTS #{config['mysql']['database']}" ) 22 | 23 | ActiveRecord::Base.establish_connection( config['mysql'] ) 24 | ActiveRecord::Base.connection() 25 | 26 | load(File.dirname(__FILE__) + "/../lib/rails/generators/bhf/templates/schema.rb") 27 | 28 | @@fixtures = {} 29 | 30 | load_fixture( 'bhf_widget' ) 31 | end 32 | 33 | def load_fixture( table ) 34 | @@fixtures[ table ] = {} 35 | fixture = YAML::load( IO.read( File.dirname(__FILE__) + "/fixtures/#{table}.yml") ) 36 | @@fixtures[ table ] = fixture 37 | 38 | klass = class_eval table.titleize.gsub(/ /, '::') 39 | 40 | fixture.each do |record_name, record| 41 | record.each do |column, value| 42 | if ( match = column.match(/(.*)_id/) ) 43 | fixture_reference = "bhf_" + match[1].pluralize 44 | if value.is_a? Symbol 45 | r = class_eval "#{fixture_reference}( '#{value}' )" 46 | record[ column ] = r.id 47 | end 48 | end 49 | end 50 | 51 | r = klass.create( record ) 52 | @@fixtures[ table ][ record_name ][ 'id' ] = r.id 53 | end 54 | 55 | end 56 | --------------------------------------------------------------------------------