diff --git a/.gitignore b/.gitignore index 32522ff..05f4b9d 100644 --- a/.gitignore +++ b/.gitignore @@ -1,34 +1,17 @@ -*.rbc -*.sassc -.sass-cache -capybara-*.html -.rspec -.rvmrc -/.bundle -/vendor/bundle -/log/* -/tmp/* -/db/*.sqlite3 -/public/system/* -/coverage/ -/spec/tmp/* -**.orig -rerun.txt -pickle-email-*.html -.project -config/initializers/secret_token.rb -*.gem -/test/* - -.ruby-version -.ruby-gemset - -*.swp -*.swo +/.bundle/ +/doc/ +/log/*.log +/pkg/ +/tmp/ +/spec/dummy/db/*.sqlite3 +/spec/dummy/db/*.sqlite3-* +/spec/dummy/log/*.log +/spec/dummy/storage/ +/spec/dummy/tmp/ +.byebug_history -spec/dummy/db/*.sqlite3 -spec/dummy/log/*.log -spec/dummy/tmp/ -tags +/node_modules -.byebug_history +/.ruby-version +/.ruby-gemset +/.nvmrc diff --git a/Changes.md b/Changes.md deleted file mode 100644 index 4738dc9..0000000 --- a/Changes.md +++ /dev/null @@ -1,116 +0,0 @@ -1.2.3 ------------ - -- Fix sorting to mitigate possible SQL-injection and improve tests [Ivan Korunkov] - -1.2.2 ------------ - -- Remove duplicated href key from config template #146 [nfilzi] -- Replace deprecated .any? with .present? #143 [AakLak] -- Development environment update #140 [mizinsky] -- Fix sanitize_params method #137 [mizinsky] -- Enable to configure global remote option and it to affects sortable helper #131 [kitabatake] -- Kaminari update [mizinsky] -- Update Readme for Rails >= 5.1 Users [mizinsky] - -1.2.1 ------------ - -- Allow to render outside of controllers [bval] -- Documentation fixes [blackcofla] -- Use id.to_json so integers and uuids will both work [sevgibson] -- Fix popover in bootstrap 4 [sevgibson] -- Fix Kaminari #num_pages deprecation warning [tylerhunt] -- Add support for Turbolinks 5 [wynksaiddestroy] -- Use #empty? for AC::Params [phoffer] -- Fix indentation in some files [boy-papan] - -1.2.0 ------------ - -- Rails 5 support and Kaminari update [akostadinov] -- Better handling of nested controls params -- Fix controls not fading out list. Related to #51 -- Config now includes element templates -- Add ability to pass locals to list view [GeorgeDewar] - -1.1.2 ------------ - -- Some bugfixing: #20, #46, #58 - -1.1.0 ------------ - -- Config profiles -- Remove duplicate href key [wynksaiddestroy] -- API refactoring [GCorbel] -- Feature Specs [GCorbel] -- Avoid smart listing controls double initialization [lacco] -- Turbolinks support [wynksaiddestroy] -- Better form controls handling -- Possibility to specify sort directions - -1.0.0 ------------ - -- JS Events triggered on item actions -- Fix filter resetting -- Fix new item autoshow -- Possibility to pass custom title to default actions -- Confirmation tweaks -- Multiple smart listings isolation -- New sorting architecture (and implicit sorting attributes) -- Controls helper -- Slightly changed item action templates - -0.9.8 ------------ - -- Custom popovers support - -0.9.7 ------------ - -- Some bugfixing -- Fix listing sorting XSS bug -- Add possibility to display new item form by default -- "Save & continue" support - -0.9.6 ------------ - -- Some bugfixing -- Initial setup generator - -0.9.5 ------------ - -- Fix collection counting bug -- Add builtin show action -- Make CSS class and data attribute names generic and customizable (SmartListing.configure) -- Make JavaScript more customizable - -0.9.4 ------------ - -- Possibility to callback action -- Changes in templates - -0.9.3 ------------ - -- Possibility to specify kaminari options -- Possibility to generate views and customize them in the app -- Better custom action handling - -0.9.2 ------------ - -- Add possibility to specify available page sizes in options hash - -0.9.0 ------------ - -- Initial release diff --git a/Gemfile b/Gemfile index b4e2a20..cf07534 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,12 @@ -source "https://rubygems.org" +source 'https://rubygems.org' +git_source(:github) { |repo| "https://github.com/#{repo}.git" } +# Specify your gem's dependencies in smart_listing.gemspec. gemspec + +group :development do + gem 'pg' +end + +# To use a debugger +gem 'byebug', group: [:development, :test] diff --git a/Gemfile.lock b/Gemfile.lock index cab8805..734afe6 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,91 +1,102 @@ PATH remote: . specs: - smart_listing (1.2.2) - coffee-rails - jquery-rails - kaminari (>= 0.17) - rails (>= 3.2) + smart_listing (2.0) + rails (>= 6.0) GEM remote: https://rubygems.org/ specs: - actioncable (5.1.4) - actionpack (= 5.1.4) + actioncable (6.1.3) + actionpack (= 6.1.3) + activesupport (= 6.1.3) nio4r (~> 2.0) - websocket-driver (~> 0.6.1) - actionmailer (5.1.4) - actionpack (= 5.1.4) - actionview (= 5.1.4) - activejob (= 5.1.4) + websocket-driver (>= 0.6.1) + actionmailbox (6.1.3) + actionpack (= 6.1.3) + activejob (= 6.1.3) + activerecord (= 6.1.3) + activestorage (= 6.1.3) + activesupport (= 6.1.3) + mail (>= 2.7.1) + actionmailer (6.1.3) + actionpack (= 6.1.3) + actionview (= 6.1.3) + activejob (= 6.1.3) + activesupport (= 6.1.3) mail (~> 2.5, >= 2.5.4) rails-dom-testing (~> 2.0) - actionpack (5.1.4) - actionview (= 5.1.4) - activesupport (= 5.1.4) - rack (~> 2.0) + actionpack (6.1.3) + actionview (= 6.1.3) + activesupport (= 6.1.3) + rack (~> 2.0, >= 2.0.9) rack-test (>= 0.6.3) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.2) - actionview (5.1.4) - activesupport (= 5.1.4) + rails-html-sanitizer (~> 1.0, >= 1.2.0) + actiontext (6.1.3) + actionpack (= 6.1.3) + activerecord (= 6.1.3) + activestorage (= 6.1.3) + activesupport (= 6.1.3) + nokogiri (>= 1.8.5) + actionview (6.1.3) + activesupport (= 6.1.3) builder (~> 3.1) erubi (~> 1.4) rails-dom-testing (~> 2.0) - rails-html-sanitizer (~> 1.0, >= 1.0.3) - activejob (5.1.4) - activesupport (= 5.1.4) + rails-html-sanitizer (~> 1.1, >= 1.2.0) + activejob (6.1.3) + activesupport (= 6.1.3) globalid (>= 0.3.6) - activemodel (5.1.4) - activesupport (= 5.1.4) - activerecord (5.1.4) - activemodel (= 5.1.4) - activesupport (= 5.1.4) - arel (~> 8.0) - activesupport (5.1.4) + activemodel (6.1.3) + activesupport (= 6.1.3) + activerecord (6.1.3) + activemodel (= 6.1.3) + activesupport (= 6.1.3) + activestorage (6.1.3) + actionpack (= 6.1.3) + activejob (= 6.1.3) + activerecord (= 6.1.3) + activesupport (= 6.1.3) + marcel (~> 0.3.1) + mimemagic (~> 0.3.2) + activesupport (6.1.3) concurrent-ruby (~> 1.0, >= 1.0.2) - i18n (~> 0.7) - minitest (~> 5.1) - tzinfo (~> 1.1) - addressable (2.5.2) - public_suffix (>= 2.0.2, < 4.0) - arel (8.0.0) - autoprefixer-rails (7.2.5) - execjs - bootstrap-sass (3.3.7) - autoprefixer-rails (>= 5.2.1) - sass (>= 3.3.4) - builder (3.2.3) - byebug (9.1.0) - capybara (2.13.0) + i18n (>= 1.6, < 2) + minitest (>= 5.1) + tzinfo (~> 2.0) + zeitwerk (~> 2.3) + addressable (2.7.0) + public_suffix (>= 2.0.2, < 5.0) + builder (3.2.4) + byebug (11.1.3) + capybara (3.35.3) addressable - mime-types (>= 1.16) - nokogiri (>= 1.3.3) - rack (>= 1.0.0) - rack-test (>= 0.5.4) - xpath (~> 2.0) - capybara-webkit (1.14.0) - capybara (>= 2.3.0, < 2.14.0) + mini_mime (>= 0.1.3) + nokogiri (~> 1.8) + rack (>= 1.6.0) + rack-test (>= 0.6.3) + regexp_parser (>= 1.5, < 3.0) + xpath (~> 3.2) + capybara-webkit (1.15.1) + capybara (>= 2.3, < 4.0) json - coderay (1.1.2) - coffee-rails (4.2.2) - coffee-script (>= 2.2.0) - railties (>= 4.0.0) - coffee-script (2.4.1) - coffee-script-source - execjs - coffee-script-source (1.12.2) - concurrent-ruby (1.0.5) - crass (1.0.3) - database_cleaner (1.6.2) - diff-lcs (1.3) - erubi (1.7.0) - execjs (2.7.0) - ffi (1.9.18) + coderay (1.1.3) + concurrent-ruby (1.1.8) + crass (1.0.6) + database_cleaner (2.0.1) + database_cleaner-active_record (~> 2.0.0) + database_cleaner-active_record (2.0.0) + activerecord (>= 5.a) + database_cleaner-core (~> 2.0.0) + database_cleaner-core (2.0.1) + diff-lcs (1.4.4) + erubi (1.10.0) + ffi (1.15.0) formatador (0.2.5) - globalid (0.4.1) + globalid (0.4.2) activesupport (>= 4.2.0) - guard (2.14.2) + guard (2.16.2) formatador (>= 0.2.4) listen (>= 2.7, < 4.0) lumberjack (>= 1.0.12, < 2.0) @@ -99,142 +110,123 @@ GEM guard (~> 2.1) guard-compat (~> 1.1) rspec (>= 2.99.0, < 4.0) - i18n (0.9.3) + i18n (1.8.9) concurrent-ruby (~> 1.0) - jquery-rails (4.3.3) - rails-dom-testing (>= 1, < 3) - railties (>= 4.2.0) - thor (>= 0.14, < 2.0) - json (2.1.0) - kaminari (1.1.1) - activesupport (>= 4.1.0) - kaminari-actionview (= 1.1.1) - kaminari-activerecord (= 1.1.1) - kaminari-core (= 1.1.1) - kaminari-actionview (1.1.1) - actionview - kaminari-core (= 1.1.1) - kaminari-activerecord (1.1.1) - activerecord - kaminari-core (= 1.1.1) - kaminari-core (1.1.1) - listen (3.1.5) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) - ruby_dep (~> 1.2) - loofah (2.1.1) + json (2.5.1) + listen (3.5.0) + rb-fsevent (~> 0.10, >= 0.10.3) + rb-inotify (~> 0.9, >= 0.9.10) + loofah (2.9.0) crass (~> 1.0.2) nokogiri (>= 1.5.9) - lumberjack (1.0.12) - mail (2.7.0) + lumberjack (1.2.8) + mail (2.7.1) mini_mime (>= 0.1.1) - method_source (0.9.0) - mime-types (3.1) - mime-types-data (~> 3.2015) - mime-types-data (3.2016.0521) - mini_mime (1.0.0) - mini_portile2 (2.3.0) - minitest (5.11.2) + marcel (0.3.3) + mimemagic (~> 0.3.2) + method_source (1.0.0) + mimemagic (0.3.7) + nokogiri (~> 1.11.2) + mini_mime (1.0.2) + minitest (5.14.4) nenv (0.3.0) - nio4r (2.3.1) - nokogiri (1.8.1) - mini_portile2 (~> 2.3.0) - notiffany (0.1.1) + nio4r (2.5.7) + nokogiri (1.11.2-x86_64-linux) + racc (~> 1.4) + notiffany (0.1.3) nenv (~> 0.1) shellany (~> 0.0) - pry (0.11.3) - coderay (~> 1.1.0) - method_source (~> 0.9.0) - public_suffix (3.0.1) - rack (2.0.3) - rack-test (0.8.2) + pg (1.2.3) + pry (0.14.0) + coderay (~> 1.1) + method_source (~> 1.0) + public_suffix (4.0.6) + racc (1.5.2) + rack (2.2.3) + rack-test (1.1.0) rack (>= 1.0, < 3) - rails (5.1.4) - actioncable (= 5.1.4) - actionmailer (= 5.1.4) - actionpack (= 5.1.4) - actionview (= 5.1.4) - activejob (= 5.1.4) - activemodel (= 5.1.4) - activerecord (= 5.1.4) - activesupport (= 5.1.4) - bundler (>= 1.3.0) - railties (= 5.1.4) + rails (6.1.3) + actioncable (= 6.1.3) + actionmailbox (= 6.1.3) + actionmailer (= 6.1.3) + actionpack (= 6.1.3) + actiontext (= 6.1.3) + actionview (= 6.1.3) + activejob (= 6.1.3) + activemodel (= 6.1.3) + activerecord (= 6.1.3) + activestorage (= 6.1.3) + activesupport (= 6.1.3) + bundler (>= 1.15.0) + railties (= 6.1.3) sprockets-rails (>= 2.0.0) rails-dom-testing (2.0.3) activesupport (>= 4.2.0) nokogiri (>= 1.6) - rails-html-sanitizer (1.0.3) - loofah (~> 2.0) - railties (5.1.4) - actionpack (= 5.1.4) - activesupport (= 5.1.4) + rails-html-sanitizer (1.3.0) + loofah (~> 2.3) + railties (6.1.3) + actionpack (= 6.1.3) + activesupport (= 6.1.3) method_source rake (>= 0.8.7) - thor (>= 0.18.1, < 2.0) - rake (12.3.0) - rb-fsevent (0.10.2) - rb-inotify (0.9.10) - ffi (>= 0.5.0, < 2) - rspec (3.7.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-core (3.7.1) - rspec-support (~> 3.7.0) - rspec-expectations (3.7.0) + thor (~> 1.0) + rake (13.0.3) + rb-fsevent (0.10.4) + rb-inotify (0.10.1) + ffi (~> 1.0) + regexp_parser (2.1.1) + rspec (3.10.0) + rspec-core (~> 3.10.0) + rspec-expectations (~> 3.10.0) + rspec-mocks (~> 3.10.0) + rspec-core (3.10.1) + rspec-support (~> 3.10.0) + rspec-expectations (3.10.1) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-mocks (3.7.0) + rspec-support (~> 3.10.0) + rspec-mocks (3.10.2) diff-lcs (>= 1.2.0, < 2.0) - rspec-support (~> 3.7.0) - rspec-rails (3.7.2) - actionpack (>= 3.0) - activesupport (>= 3.0) - railties (>= 3.0) - rspec-core (~> 3.7.0) - rspec-expectations (~> 3.7.0) - rspec-mocks (~> 3.7.0) - rspec-support (~> 3.7.0) - rspec-support (3.7.0) - ruby_dep (1.5.0) - sass (3.5.5) - sass-listen (~> 4.0.0) - sass-listen (4.0.0) - rb-fsevent (~> 0.9, >= 0.9.4) - rb-inotify (~> 0.9, >= 0.9.7) + rspec-support (~> 3.10.0) + rspec-rails (5.0.1) + actionpack (>= 5.2) + activesupport (>= 5.2) + railties (>= 5.2) + rspec-core (~> 3.10) + rspec-expectations (~> 3.10) + rspec-mocks (~> 3.10) + rspec-support (~> 3.10) + rspec-support (3.10.2) shellany (0.0.1) - sprockets (3.7.2) + sprockets (4.0.2) concurrent-ruby (~> 1.0) rack (> 1, < 3) - sprockets-rails (3.2.1) + sprockets-rails (3.2.2) actionpack (>= 4.0) activesupport (>= 4.0) sprockets (>= 3.0.0) - sqlite3 (1.3.13) - thor (0.20.0) - thread_safe (0.3.6) - tzinfo (1.2.4) - thread_safe (~> 0.1) - websocket-driver (0.6.5) + thor (1.1.0) + tzinfo (2.0.4) + concurrent-ruby (~> 1.0) + websocket-driver (0.7.3) websocket-extensions (>= 0.1.0) - websocket-extensions (0.1.3) - xpath (2.1.0) - nokogiri (~> 1.3) + websocket-extensions (0.1.5) + xpath (3.2.0) + nokogiri (~> 1.8) + zeitwerk (2.4.2) PLATFORMS - ruby + x86_64-linux DEPENDENCIES - bootstrap-sass byebug - capybara (< 2.14) - capybara-webkit (~> 1.14) + capybara + capybara-webkit database_cleaner guard-rspec + pg rspec-rails smart_listing! - sqlite3 BUNDLED WITH - 1.16.2 + 2.2.15 diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 3215f01..0000000 --- a/Guardfile +++ /dev/null @@ -1,70 +0,0 @@ -# A sample Guardfile -# More info at https://github.com/guard/guard#readme - -## Uncomment and set this to only include directories you want to watch -# directories %w(app lib config test spec features) \ -# .select{|d| Dir.exists?(d) ? d : UI.warning("Directory #{d} does not exist")} - -## Note: if you are using the `directories` clause above and you are not -## watching the project directory ('.'), then you will want to move -## the Guardfile to a watched dir and symlink it back, e.g. -# -# $ mkdir config -# $ mv Guardfile config/ -# $ ln -s config/Guardfile . -# -# and, you'll have to watch "config/Guardfile" instead of "Guardfile" - -# Note: The cmd option is now required due to the increasing number of ways -# rspec may be run, below are examples of the most common uses. -# * bundler: 'bundle exec rspec' -# * bundler binstubs: 'bin/rspec' -# * spring: 'bin/rspec' (This will use spring if running and you have -# installed the spring binstubs per the docs) -# * zeus: 'zeus rspec' (requires the server to be started separately) -# * 'just' rspec: 'rspec' - -guard :rspec, cmd: "bundle exec rspec" do - require "guard/rspec/dsl" - dsl = Guard::RSpec::Dsl.new(self) - - # Feel free to open issues for suggestions and improvements - - # RSpec files - rspec = dsl.rspec - watch(rspec.spec_helper) { rspec.spec_dir } - watch(rspec.spec_support) { rspec.spec_dir } - watch(rspec.spec_files) - - # Ruby files - ruby = dsl.ruby - dsl.watch_spec_files_for(ruby.lib_files) - - # Rails files - rails = dsl.rails(view_extensions: %w(erb haml slim)) - dsl.watch_spec_files_for(rails.app_files) - dsl.watch_spec_files_for(rails.views) - - watch(rails.controllers) do |m| - [ - rspec.spec.call("routing/#{m[1]}_routing"), - rspec.spec.call("controllers/#{m[1]}_controller"), - rspec.spec.call("acceptance/#{m[1]}") - ] - end - - # Rails config changes - watch(rails.spec_helper) { rspec.spec_dir } - watch(rails.routes) { "#{rspec.spec_dir}/routing" } - watch(rails.app_controller) { "#{rspec.spec_dir}/controllers" } - - # Capybara features specs - watch(rails.view_dirs) { |m| rspec.spec.call("features/#{m[1]}") } - watch(rails.layouts) { |m| rspec.spec.call("features/#{m[1]}") } - - # Turnip features and steps - watch(%r{^spec/acceptance/(.+)\.feature$}) - watch(%r{^spec/acceptance/steps/(.+)_steps\.rb$}) do |m| - Dir[File.join("**/#{m[1]}.feature")][0] || "spec/acceptance" - end -end diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 24940c1..0000000 --- a/LICENSE +++ /dev/null @@ -1,20 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2013 Sology (www.sology.eu) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of -the Software, and to permit persons to whom the Software is furnished to do so, -subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS -FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR -COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER -IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN -CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/MIT-LICENSE b/MIT-LICENSE new file mode 100644 index 0000000..698fef6 --- /dev/null +++ b/MIT-LICENSE @@ -0,0 +1,20 @@ +Copyright 2013-2021 Sology + +Permission is hereby granted, free of charge, to any person obtaining +a copy of this software and associated documentation files (the +"Software"), to deal in the Software without restriction, including +without limitation the rights to use, copy, modify, merge, publish, +distribute, sublicense, and/or sell copies of the Software, and to +permit persons to whom the Software is furnished to do so, subject to +the following conditions: + +The above copyright notice and this permission notice shall be +included in all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, +EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND +NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE +LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION +OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION +WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. diff --git a/Rakefile b/Rakefile index 9097223..416b023 100644 --- a/Rakefile +++ b/Rakefile @@ -1,34 +1,8 @@ -begin - require 'bundler/setup' -rescue LoadError - puts 'You must `gem install bundler` and `bundle install` to run rake tasks' -end +require "bundler/setup" -require 'rdoc/task' +APP_RAKEFILE = File.expand_path("spec/dummy/Rakefile", __dir__) +load "rails/tasks/engine.rake" -RDoc::Task.new(:rdoc) do |rdoc| - rdoc.rdoc_dir = 'rdoc' - rdoc.title = 'SmartListing' - rdoc.options << '--line-numbers' - rdoc.rdoc_files.include('README.rdoc') - rdoc.rdoc_files.include('lib/**/*.rb') -end +load "rails/tasks/statistics.rake" -APP_RAKEFILE = File.expand_path("../spec/dummy/Rakefile", __FILE__) -load 'rails/tasks/engine.rake' - - - -Bundler::GemHelper.install_tasks - -require 'rake/testtask' - -Rake::TestTask.new(:test) do |t| - t.libs << 'lib' - t.libs << 'test' - t.pattern = 'test/**/*_test.rb' - t.verbose = false -end - - -task default: :test +require "bundler/gem_tasks" diff --git a/app/assets/config/smart_listing_manifest.js b/app/assets/config/smart_listing_manifest.js new file mode 100644 index 0000000..3a989aa --- /dev/null +++ b/app/assets/config/smart_listing_manifest.js @@ -0,0 +1 @@ +//= link_directory ../stylesheets/smart_listing .css diff --git a/spec/dummy/app/mailers/.keep b/app/assets/images/smart_listing/.keep similarity index 100% rename from spec/dummy/app/mailers/.keep rename to app/assets/images/smart_listing/.keep diff --git a/app/assets/javascripts/smart_listing.coffee.erb b/app/assets/javascripts/smart_listing.coffee.erb deleted file mode 100644 index 26313e0..0000000 --- a/app/assets/javascripts/smart_listing.coffee.erb +++ /dev/null @@ -1,451 +0,0 @@ -# endsWith polyfill -if !String::endsWith - String::endsWith = (search, this_len) -> - if this_len == undefined or this_len > @length - this_len = @length - @substring(this_len - (search.length), this_len) == search - -# Useful when SmartListing target url is different than current one -$.rails.href = (element) -> - element.attr("href") || element.data("<%= SmartListing.config.data_attributes(:href) %>") || window.location.pathname - -class window.SmartListing - class Config - @options: <%= SmartListing.config.dump_json %> - - @merge: (d) -> - $.extend true, @options, d || $("body").data("smart-listing-config") - - @class: (name)-> - @options["constants"]["classes"][name] - - @class_name: (name) -> - ".#{@class(name)}" - - @data_attribute: (name)-> - @options["constants"]["data_attributes"][name] - - @selector: (name)-> - @options["constants"]["selectors"][name] - - @element_template: (name)-> - @options["constants"]["element_templates"][name] - - @bootstrap_commands: (name)-> - @options["constants"]["bootstrap_commands"][name] - - @config: Config - - - constructor: (e) -> - @container = e - @name = @container.attr("id") - @loading = @container.find(SmartListing.config.class_name("loading")) - @content = @container.find(SmartListing.config.class_name("content")) - @status = $("#{SmartListing.config.class_name("status")} [data-#{SmartListing.config.data_attribute("main")}='#{@name}']") - @confirmed = null - @popovers = {} - - @container.on "ajax:before", "#{SmartListing.config.class_name("item_actions")}, #{SmartListing.config.class_name("pagination_container")}", (e) => - @fadeLoading() - - @container.on "ajax:success", (e) => - if $(e.target).is("#{SmartListing.config.class_name("item_actions")} #{SmartListing.config.selector("item_action_destroy")}") - # handle HEAD OK response for deletion request - editable = $(e.target).closest(SmartListing.config.class_name("editable")) - if @container.find(SmartListing.config.class_name("editable")).length == 1 - @reload() - return false - else - editable.remove() - - @container.trigger("smart_listing:destroy", editable) - - @changeItemCount(-1) - @refresh() - - @fadeLoaded() - return false - - @container.on "click", SmartListing.config.selector("edit_cancel"), (event) => - editable = $(event.currentTarget).closest(SmartListing.config.class_name("editable")) - if(editable.length > 0) - # Cancel edit - @cancelEdit(editable) - else - # Cancel new record - @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden")) - @container.find(SmartListing.config.class_name("new_item_action")).removeClass(SmartListing.config.class("hidden")) - - @setAutoshow(false) - false - - @container.on "click", "#{SmartListing.config.class_name("item_actions")} a[data-#{SmartListing.config.data_attribute("confirmation")}]", (event) => - $.fn.smart_listing.confirm $(event.currentTarget), $(event.currentTarget).data(SmartListing.config.data_attribute("confirmation")) - - @container.on "click", "#{SmartListing.config.class_name("item_actions")} a[data-#{SmartListing.config.data_attribute("popover")}]", (event) => - name = $(event.currentTarget).data(SmartListing.config.data_attribute("popover")) - if jQuery.isFunction(@popovers[name]) - @popovers[name]($(event.currentTarget)) - false - - - @container.on "click", "input[type=text]#{SmartListing.config.class_name("autoselect")}", (event) -> - $(this).select() - - @container.on "change", SmartListing.config.class_name("callback"), (event) => - checkbox = $(event.currentTarget) - id = checkbox.closest(SmartListing.config.selector("row")).data(SmartListing.config.data_attribute("id")) - data = {} - data[checkbox.val()] = checkbox.is(":checked") - $.ajax({ - beforeSend: (xhr, settings) -> - xhr.setRequestHeader "accept", "*/*;q=0.5, " + settings.accepts.script - url: @container.data(SmartListing.config.data_attribute("callback_href")), - type: "POST", - data: data, - }) - - fadeLoading: => - $.fn.smart_listing.onLoading(@content, @loading) - - fadeLoaded: => - $.fn.smart_listing.onLoaded(@content, @loading) - - itemCount: => - parseInt(@container.data(SmartListing.config.data_attribute("item_count"))) - - maxCount: => - parseInt(@container.data(SmartListing.config.data_attribute("max_count"))) - - setAutoshow: (v) => - - @container.data(SmartListing.config.data_attribute("autoshow"), v) - - changeItemCount: (value) => - count = @container.data(SmartListing.config.data_attribute("item_count")) + value - @container.data(SmartListing.config.data_attribute("item_count"), count) - @container.find(SmartListing.config.selector("pagination_count")).html(count) - - cancelEdit: (editable) => - if editable.data(SmartListing.config.data_attribute("inline_edit_backup")) - editable.html(editable.data(SmartListing.config.data_attribute("inline_edit_backup"))) - editable.removeClass(SmartListing.config.class("inline_editing")) - editable.removeData(SmartListing.config.data_attribute("inline_edit_backup")) - - # Callback called when record is added/deleted using ajax request - refresh: () => - header = @content.find(SmartListing.config.selector("head")) - footer = @content.find(SmartListing.config.class_name("pagination_per_page")) - no_records = @content.find(SmartListing.config.class_name("no_records")) - - if @itemCount() == 0 - header.hide() - footer.hide() - no_records.show() - else - header.show() - footer.show() - no_records.hide() - - if @maxCount() - if @itemCount() >= @maxCount() - @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden")) - @container.find(SmartListing.config.class_name("new_item_action")).addClass(SmartListing.config.class("hidden")) - else - if @container.data(SmartListing.config.data_attribute("autoshow")) - @container.find(SmartListing.config.class_name("new_item_placeholder")).removeClass(SmartListing.config.class("hidden")) - @container.find(SmartListing.config.class_name("new_item_action")).addClass(SmartListing.config.class("hidden")) - else - @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden")) - @container.find(SmartListing.config.class_name("new_item_action")).removeClass(SmartListing.config.class("hidden")) - - @status.each (index, status) => - $(status).find(SmartListing.config.class_name("limit")).html(@maxCount() - @itemCount()) - if @maxCount() - @itemCount() == 0 - $(status).find(SmartListing.config.class_name("limit_alert")).show() - else - $(status).find(SmartListing.config.class_name("limit_alert")).hide() - - # Trigger AJAX request to reload the list - reload: () => - $.rails.handleRemote(@container) - - params: (value) => - if value - @container.data(SmartListing.config.data_attribute("params"), value) - else - @container.data(SmartListing.config.data_attribute("params")) - - registerPopover: (name, callback) => - @popovers[name] = callback - - editable: (id) => - @container.find("#{SmartListing.config.class_name("editable")}[data-#{SmartListing.config.data_attribute("id")}=#{id}]") - - ################################################################################################# - # Methods executed by rails UJS: - - new_item: (content) => - if !@maxCount() || (@itemCount() < @maxCount()) - new_item_action = @container.find(SmartListing.config.class_name("new_item_action")) - new_item_placeholder = @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden")) - - @container.find(SmartListing.config.class_name("editable")).each (i, v) => - @cancelEdit($(v)) - - new_item_action.addClass(SmartListing.config.class("hidden")) - new_item_placeholder.removeClass(SmartListing.config.class("hidden")) - new_item_placeholder.html(content) - new_item_placeholder.addClass(SmartListing.config.class("inline_editing")) - - @container.trigger("smart_listing:new", new_item_placeholder) - - @fadeLoaded() - - create: (id, success, content) => - new_item_action = @container.find(SmartListing.config.class_name("new_item_action")) - new_item_placeholder = @container.find(SmartListing.config.class_name("new_item_placeholder")) - - if success - new_item_placeholder.addClass(SmartListing.config.class("hidden")) - new_item_action.removeClass(SmartListing.config.class("hidden")) - - new_item = $(SmartListing.config.element_template("row")).addClass(SmartListing.config.class("editable")) - new_item.attr("data-#{SmartListing.config.data_attribute("id")}", id) - new_item.html(content) - - if new_item_placeholder.length != 0 - if new_item_placeholder.data("insert-mode") == "after" - new_item_placeholder.after(new_item) - else - new_item_placeholder.before(new_item) - else - @content.append(new_item) - - @container.trigger("smart_listing:create:success", new_item) - - @changeItemCount(1) - @refresh() - else - new_item_placeholder.html(content) - - @container.trigger("smart_listing:create:fail", new_item_placeholder) - - @fadeLoaded() - - edit: (id, content) => - @container.find(SmartListing.config.class_name("editable")).each (i, v) => - @cancelEdit($(v)) - @container.find(SmartListing.config.class_name("new_item_placeholder")).addClass(SmartListing.config.class("hidden")) - @container.find(SmartListing.config.class_name("new_item_action")).removeClass(SmartListing.config.class("hidden")) - - editable = @editable(id) - editable.data(SmartListing.config.data_attribute("inline_edit_backup"), editable.html()) - editable.html(content) - editable.addClass(SmartListing.config.class("inline_editing")) - - @container.trigger("smart_listing:edit", editable) - - @fadeLoaded() - - update: (id, success, content) => - editable = @editable(id) - if success - editable.removeClass(SmartListing.config.class("inline_editing")) - editable.removeData(SmartListing.config.data_attribute("inline_edit_backup")) - editable.html(content) - - @container.trigger("smart_listing:update:success", editable) - - @refresh() - else - editable.html(content) - - @container.trigger("smart_listing:update:fail", editable) - - @fadeLoaded() - - destroy: (id, destroyed) => - # No need to do anything here, already handled by ajax:success handler - - remove: (id) => - editable = @editable(id) - editable.remove() - - @container.trigger("smart_listing:remove", editable) - - update_list: (content, data) => - @container.data(SmartListing.config.data_attribute("params"), $.extend(@container.data(SmartListing.config.data_attribute("params")), data[SmartListing.config.data_attribute("params")])) - @container.data(SmartListing.config.data_attribute("max_count"), data[SmartListing.config.data_attribute("max_count")]) - @container.data(SmartListing.config.data_attribute("item_count"), data[SmartListing.config.data_attribute("item_count")]) - - @content.html(content) - - @refresh() - @fadeLoaded() - - @container.trigger("smart_listing:update_list", @container) - -$.fn.smart_listing = () -> - map = $(this).map () -> - if !$(this).data(SmartListing.config.data_attribute("main")) - $(this).data(SmartListing.config.data_attribute("main"), new SmartListing($(this))) - $(this).data(SmartListing.config.data_attribute("main")) - if map.length == 1 - map[0] - else - map - -$.fn.smart_listing.observeField = (field, opts = {}) -> - key_timeout = null - last_value = null - options = - onFilled: () -> - onEmpty: () -> - onChange: () -> - options = $.extend(options, opts) - - keyChange = () -> - if field.val().length > 0 - options.onFilled() - else - options.onEmpty() - - if field.val() == last_value && field.val().length != 0 - return - lastValue = field.val() - - options.onChange() - - field.data(SmartListing.config.data_attribute("observed"), true) - - field.bind "keydown", (e) -> - if(key_timeout) - clearTimeout(key_timeout) - - key_timeout = setTimeout(-> - keyChange() - , 400) - -$.fn.smart_listing.showPopover = (elem, body) -> - elem.popover(SmartListing.config.bootstrap_commands("popover_destroy")) - elem.popover(content: body, html: true, trigger: "manual") - elem.popover("show") - -$.fn.smart_listing.showConfirmation = (confirmation_elem, msg, confirm_callback) -> - buildPopover = (confirmation_elem, msg) -> - deletion_popover = $("
").addClass("confirmation_box") - deletion_popover.append($("").html(msg)) - deletion_popover.append($("") - .append($("").html("Yes").addClass("btn btn-danger ").click (event) => - # set @confirmed element and emulate click on icon - editable = $(event.currentTarget).closest(SmartListing.config.class_name("editable")) - confirm_callback(confirmation_elem) - $(confirmation_elem).click() - $(confirmation_elem).popover(SmartListing.config.bootstrap_commands("popover_destroy")) - ) - .append(" ") - .append($("").html("No").addClass("btn btn-small").click (event) => - editable = $(event.currentTarget).closest(SmartListing.config.class_name("editable")) - $(confirmation_elem).popover(SmartListing.config.bootstrap_commands("popover_destroy")) - ) - ) - - $.fn.smart_listing.showPopover confirmation_elem, buildPopover(confirmation_elem, msg) - -$.fn.smart_listing.confirm = (elem, msg) -> - if !elem.data("confirmed") - # We need confirmation - $.fn.smart_listing.showConfirmation elem, msg, (confirm_elem) => - confirm_elem.data("confirmed", true) - false - else - # Confirmed, reset flag and go ahead with deletion - elem.data("confirmed", false) - true - -$.fn.smart_listing.onLoading = (content, loader) -> - content.stop(true).fadeTo(500, 0.2) - loader.show() - loader.stop(true).fadeTo(500, 1) - -$.fn.smart_listing.onLoaded = (content, loader) -> - content.stop(true).fadeTo(500, 1) - loader.stop(true).fadeTo 500, 0, () => - loader.hide() - -$.fn.smart_listing_controls = () -> - reload = (controls) -> - container = $("##{controls.data(SmartListing.config.data_attribute("main"))}") - smart_listing = container.smart_listing() - - # serialize form and merge it with smart listing params - prms = {} - $.each controls.serializeArray(), (i, field) -> - if field.name.endsWith("[]") - field_name = field.name.slice(0, field.name.length - 2) - if Array.isArray(prms[field_name]) - prms[field_name].push field.value - else - prms[field_name] = [field.value] - else - prms[field.name] = field.value - - prms = $.extend(smart_listing.params(), prms) - smart_listing.params(prms) - - smart_listing.fadeLoading() - smart_listing.reload() - - $(this).each () -> - # avoid double initialization - return if $(this).data(SmartListing.config.data_attribute("controls_initialized")) - $(this).data(SmartListing.config.data_attribute("controls_initialized"), true) - - controls = $(this) - smart_listing = $("##{controls.data(SmartListing.config.data_attribute("main"))}") - reset = controls.find(SmartListing.config.class_name("controls_reset")) - - controls.submit -> - # setup smart listing params, reload and don"t actually submit controls form - reload(controls) - false - - controls.find("input, select").change () -> - unless $(this).data(SmartListing.config.data_attribute("observed")) # do not submit controls form when changed field is observed (observing submits form by itself) - reload(controls) - - $.fn.smart_listing_controls.filter(controls.find(SmartListing.config.class_name("filtering"))) - -$.fn.smart_listing_controls.filter = (filter) -> - form = filter.closest("form") - button = form.find(SmartListing.config.selector("filtering_button")) - icon = form.find(SmartListing.config.selector("filtering_icon")) - field = form.find(SmartListing.config.selector("filtering_input")) - - $.fn.smart_listing.observeField(field, - onFilled: -> - icon.removeClass(SmartListing.config.class("filtering_search")) - icon.addClass(SmartListing.config.class("filtering_cancel")) - button.removeClass(SmartListing.config.class("filtering_disabled")) - onEmpty: -> - icon.addClass(SmartListing.config.class("filtering_search")) - icon.removeClass(SmartListing.config.class("filtering_cancel")) - button.addClass(SmartListing.config.class("filtering_disabled")) - onChange: -> - form.submit() - ) - - button.click -> - if field.val().length > 0 - field.val("") - field.trigger("keydown") - return false - -ready = -> - $(SmartListing.config.class_name("main")).smart_listing() - $(SmartListing.config.class_name("controls")).smart_listing_controls() - -$(document).ready ready -$(document).on "page:load turbolinks:load", ready diff --git a/app/assets/stylesheets/smart_listing/application.css b/app/assets/stylesheets/smart_listing/application.css new file mode 100644 index 0000000..0ebd7fe --- /dev/null +++ b/app/assets/stylesheets/smart_listing/application.css @@ -0,0 +1,15 @@ +/* + * This is a manifest file that'll be compiled into application.css, which will include all the files + * listed below. + * + * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets, + * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path. + * + * You're free to add application-wide styles to this file and they'll appear at the bottom of the + * compiled file so the styles you add here take precedence over styles defined in any other CSS/SCSS + * files in this directory. Styles in this file should be added after the last require_* statement. + * It is generally better to create a new file per style scope. + * + *= require_tree . + *= require_self + */ diff --git a/app/controllers/concerns/smart_listing/controller_builder.rb b/app/controllers/concerns/smart_listing/controller_builder.rb new file mode 100644 index 0000000..809db7f --- /dev/null +++ b/app/controllers/concerns/smart_listing/controller_builder.rb @@ -0,0 +1,31 @@ +module SmartListing + module ControllerBuilder + private + + def smart_listing *args + options = args.extract_options! + name = (args[0] || options[:name] || controller_name).to_sym + collection = args[1] || options[:collection] || (defined?(smart_listing_collection) ? smart_listing_collection : nil) + + options = {:config_profile => smart_listing_config_profile}.merge(options) + + @_smart_listings ||= {} + + base = @_smart_listings[name] ||= SmartListing::Base.new(name, collection, options).tap do |base| + base.setup(params, cookies) + end + + Builder.new(base, view_context, options) + end + + def smart_listing_config_profile + :default + end + + def smart_listing_remote + SmartListing::Remote::TagBuilder.new(view_context) + end + + # TODO prepend `smart_listing` view paths + end +end diff --git a/app/controllers/smart_listing/application_controller.rb b/app/controllers/smart_listing/application_controller.rb new file mode 100644 index 0000000..0f9cf8f --- /dev/null +++ b/app/controllers/smart_listing/application_controller.rb @@ -0,0 +1,4 @@ +module SmartListing + class ApplicationController < ActionController::Base + end +end diff --git a/app/helpers/smart_listing/application_helper.rb b/app/helpers/smart_listing/application_helper.rb deleted file mode 100644 index cd1effe..0000000 --- a/app/helpers/smart_listing/application_helper.rb +++ /dev/null @@ -1,5 +0,0 @@ -require 'smart_listing/helper' -module SmartListing - module ApplicationHelper - end -end diff --git a/app/helpers/smart_listing/base_helper.rb b/app/helpers/smart_listing/base_helper.rb new file mode 100644 index 0000000..8846587 --- /dev/null +++ b/app/helpers/smart_listing/base_helper.rb @@ -0,0 +1,82 @@ +module SmartListing + module BaseHelper + def smart_listing name = controller_name, options = {} + name = name.to_sym + + @_smart_listings ||= {} + base = @_smart_listings[name] || SmartListing::Base.new(name, nil, options) + + Builder.new(base, self, options) + end + + def smart_listing_dom_id *args + SmartListing::Builder.dom_id *args + end + + def smart_listing_limit_left name + name = name.to_sym + smart_listing = @smart_listings[name] + + smart_listing.max_count - smart_listing.count + end + + ################################################################################################# + # JS helpers: + + # Updates smart listing + # + # Posible calls: + # smart_listing_update name, options = {} + # smart_listing_update options = {} + def smart_listing_update *args + options = args.extract_options! + name = (args[0] || options[:name] || controller_name).to_sym + smart_listing = @_smart_listings[name] + + # don't update list if params are missing (prevents interfering with other lists) + if params.keys.select{|k| k.include?("smart_listing")}.present? && !params[smart_listing.base_param] + return unless options[:force] + end + + builder = Builder.new(name, smart_listing, self, {}, nil) + render(:partial => 'smart_listing/update_list', :locals => { + :name => smart_listing.name, + :part => smart_listing.partial, + :smart_listing => builder, + :smart_listing_data => { + smart_listing_config.data_attributes(:params) => smart_listing.all_params, + smart_listing_config.data_attributes(:max_count) => smart_listing.max_count, + smart_listing_config.data_attributes(:item_count) => smart_listing.count, + }, + :locals => options[:locals] || {} + }) + end + + # Renders single item (i.e for create, update actions) + # + # Possible calls: + # smart_listing_item name, item_action, object = nil, partial = nil, options = {} + # smart_listing_item item_action, object = nil, partial = nil, options = {} + def smart_listing_item *args + options = args.extract_options! + if [:create, :create_continue, :destroy, :edit, :new, :remove, :update].include?(args[1]) + name = args[0] + item_action = args[1] + object = args[2] + partial = args[3] + else + name = (options[:name] || controller_name).to_sym + item_action = args[0] + object = args[1] + partial = args[2] + end + type = object.class.name.downcase.to_sym if object + id = options[:id] || object.try(:id) + valid = options[:valid] if options.has_key?(:valid) + object_key = options.delete(:object_key) || :object + new = options.delete(:new) + + render(:partial => "smart_listing/item/#{item_action.to_s}", :locals => {:name => name, :id => id, :valid => valid, :object_key => object_key, :object => object, :part => partial, :new => new}) + end + end +end diff --git a/app/helpers/smart_listing/helper.rb b/app/helpers/smart_listing/helper.rb deleted file mode 100644 index 707554a..0000000 --- a/app/helpers/smart_listing/helper.rb +++ /dev/null @@ -1,360 +0,0 @@ -module SmartListing - module Helper - module ControllerExtensions - # Creates new smart listing - # - # Possible calls: - # smart_listing_create name, collection, options = {} - # smart_listing_create options = {} - def smart_listing_create *args - options = args.extract_options! - name = (args[0] || options[:name] || controller_name).to_sym - collection = args[1] || options[:collection] || smart_listing_collection - - view_context = self.respond_to?(:controller) ? controller.view_context : self.view_context - options = {:config_profile => view_context.smart_listing_config_profile}.merge(options) - - list = SmartListing::Base.new(name, collection, options) - list.setup(params, cookies) - - @smart_listings ||= {} - @smart_listings[name] = list - - list.collection - end - - def smart_listing name - @smart_listings[name.to_sym] - end - - def _prefixes - super << 'smart_listing' - end - end - - class Builder - - class_attribute :smart_listing_helpers - - def initialize(smart_listing_name, smart_listing, template, options, proc) - @smart_listing_name, @smart_listing, @template, @options, @proc = smart_listing_name, smart_listing, template, options, proc - end - - def name - @smart_listing_name - end - - def paginate options = {} - if @smart_listing.collection.respond_to? :current_page - @template.paginate @smart_listing.collection, {:remote => @smart_listing.remote?, :param_name => @smart_listing.param_name(:page)}.merge(@smart_listing.kaminari_options) - end - end - - def collection - @smart_listing.collection - end - - # Check if smart list is empty - def empty? - @smart_listing.count == 0 - end - - def pagination_per_page_links options = {} - container_classes = [@template.smart_listing_config.classes(:pagination_per_page)] - container_classes << @template.smart_listing_config.classes(:hidden) if empty? - - per_page_sizes = @smart_listing.page_sizes.clone - per_page_sizes.push(0) if @smart_listing.unlimited_per_page? - - locals = { - :container_classes => container_classes, - :per_page_sizes => per_page_sizes, - } - - @template.render(:partial => 'smart_listing/pagination_per_page_links', :locals => default_locals.merge(locals)) - end - - def pagination_per_page_link page - if @smart_listing.per_page.to_i != page - url = @template.url_for(@smart_listing.params.merge(@smart_listing.all_params(:per_page => page, :page => 1))) - end - - locals = { - :page => page, - :url => url, - } - - @template.render(:partial => 'smart_listing/pagination_per_page_link', :locals => default_locals.merge(locals)) - end - - def sortable title, attribute, options = {} - dirs = options[:sort_dirs] || @smart_listing.sort_dirs || [nil, "asc", "desc"] - - next_index = dirs.index(@smart_listing.sort_order(attribute)).nil? ? 0 : (dirs.index(@smart_listing.sort_order(attribute)) + 1) % dirs.length - - sort_params = { - attribute => dirs[next_index] - } - - locals = { - :order => @smart_listing.sort_order(attribute), - :url => @template.url_for(@smart_listing.params.merge(@smart_listing.all_params(:sort => sort_params))), - :container_classes => [@template.smart_listing_config.classes(:sortable)], - :attribute => attribute, - :title => title, - :remote => @smart_listing.remote? - } - - @template.render(:partial => 'smart_listing/sortable', :locals => default_locals.merge(locals)) - end - - def update options = {} - part = options.delete(:partial) || @smart_listing.partial || @smart_listing_name - - @template.render(:partial => 'smart_listing/update_list', :locals => {:name => @smart_listing_name, :part => part, :smart_listing => self}) - end - - # Renders the main partial (whole list) - def render_list locals = {} - if @smart_listing.partial - @template.render :partial => @smart_listing.partial, :locals => {:smart_listing => self}.merge(locals || {}) - end - end - - # Basic render block wrapper that adds smart_listing reference to local variables - def render options = {}, locals = {}, &block - if locals.empty? - options[:locals] ||= {} - options[:locals].merge!(:smart_listing => self) - else - locals.merge!({:smart_listing => self}) - end - - @template.render options, locals, &block - end - - # Add new item button & placeholder to list - def item_new options = {}, &block - no_records_classes = [@template.smart_listing_config.classes(:no_records)] - no_records_classes << @template.smart_listing_config.classes(:hidden) unless empty? - new_item_button_classes = [] - new_item_button_classes << @template.smart_listing_config.classes(:hidden) if max_count? - - locals = { - :colspan => options.delete(:colspan), - :no_items_classes => no_records_classes, - :no_items_text => options.delete(:no_items_text) || @template.t("smart_listing.msgs.no_items"), - :new_item_button_url => options.delete(:link), - :new_item_button_classes => new_item_button_classes, - :new_item_button_text => options.delete(:text) || @template.t("smart_listing.actions.new"), - :new_item_autoshow => block_given?, - :new_item_content => nil, - } - - unless block_given? - locals[:placeholder_classes] = [@template.smart_listing_config.classes(:new_item_placeholder), @template.smart_listing_config.classes(:hidden)] - locals[:new_item_action_classes] = [@template.smart_listing_config.classes(:new_item_action)] - locals[:new_item_action_classes] << @template.smart_listing_config.classes(:hidden) if !empty? && max_count? - - @template.render(:partial => 'smart_listing/item_new', :locals => default_locals.merge(locals)) - else - locals[:placeholder_classes] = [@template.smart_listing_config.classes(:new_item_placeholder)] - locals[:placeholder_classes] << @template.smart_listing_config.classes(:hidden) if !empty? && max_count? - locals[:new_item_action_classes] = [@template.smart_listing_config.classes(:new_item_action), @template.smart_listing_config.classes(:hidden)] - - locals[:new_item_content] = @template.capture(&block) - @template.render(:partial => 'smart_listing/item_new', :locals => default_locals.merge(locals)) - end - end - - def count - @smart_listing.count - end - - # Check if smart list reached its item max count - def max_count? - return false if @smart_listing.max_count.nil? - @smart_listing.count >= @smart_listing.max_count - end - - private - - def default_locals - {:smart_listing => @smart_listing, :builder => self} - end - end - - def smart_listing_config_profile - defined?(super) ? super : :default - end - - def smart_listing_config - SmartListing.config(smart_listing_config_profile) - end - - # Outputs smart list container - def smart_listing_for name, *args, &block - raise ArgumentError, "Missing block" unless block_given? - name = name.to_sym - options = args.extract_options! - bare = options.delete(:bare) - - builder = Builder.new(name, @smart_listings[name], self, options, block) - - output = "" - - data = {} - data[smart_listing_config.data_attributes(:max_count)] = @smart_listings[name].max_count if @smart_listings[name].max_count && @smart_listings[name].max_count > 0 - data[smart_listing_config.data_attributes(:item_count)] = @smart_listings[name].count - data[smart_listing_config.data_attributes(:href)] = @smart_listings[name].href if @smart_listings[name].href - data[smart_listing_config.data_attributes(:callback_href)] = @smart_listings[name].callback_href if @smart_listings[name].callback_href - data.merge!(options[:data]) if options[:data] - - if bare - output = capture(builder, &block) - else - output = content_tag(:div, :class => smart_listing_config.classes(:main), :id => name, :data => data) do - concat(content_tag(:div, "", :class => smart_listing_config.classes(:loading))) - concat(content_tag(:div, :class => smart_listing_config.classes(:content)) do - concat(capture(builder, &block)) - end) - end - end - - output - end - - def smart_listing_render name = controller_name, *args - options = args.dup.extract_options! - smart_listing_for(name, *args) do |smart_listing| - concat(smart_listing.render_list(options[:locals])) - end - end - - def smart_listing_controls_for name, *args, &block - smart_listing = @smart_listings.try(:[], name) - - classes = [smart_listing_config.classes(:controls), args.first.try(:[], :class)] - - form_tag(smart_listing.try(:href) || {}, :remote => smart_listing.try(:remote?) || true, :method => :get, :class => classes, :data => {smart_listing_config.data_attributes(:main) => name}) do - concat(content_tag(:div, :style => "margin:0;padding:0;display:inline") do - concat(hidden_field_tag("#{smart_listing.try(:base_param)}[_]", 1, :id => nil)) # this forces smart_listing_update to refresh the list - end) - concat(capture(&block)) - end - end - - # Render item action buttons (ie. edit, destroy and custom ones) - def smart_listing_item_actions actions = [] - content_tag(:span) do - actions.each do |action| - next unless action.is_a?(Hash) - - locals = { - :action_if => action.has_key?(:if) ? action[:if] : true, - :url => action.delete(:url), - :icon => action.delete(:icon), - :title => action.delete(:title), - } - - template = nil - action_name = action[:name].to_sym - - case action_name - when :show - locals[:icon] ||= smart_listing_config.classes(:icon_show) - template = 'action_show' - when :edit - locals[:icon] ||= smart_listing_config.classes(:icon_edit) - template = 'action_edit' - when :destroy - locals[:icon] ||= smart_listing_config.classes(:icon_trash) - locals.merge!( - :confirmation => action.delete(:confirmation), - ) - template = 'action_delete' - when :custom - locals.merge!( - :html_options => action, - ) - template = 'action_custom' - end - - locals[:icon] = [locals[:icon], smart_listing_config.classes(:muted)] if !locals[:action_if] - - if template - concat(render(:partial => "smart_listing/#{template}", :locals => locals)) - else - concat(render(:partial => "smart_listing/action_#{action_name}", :locals => {:action => action})) - end - end - end - end - - def smart_listing_limit_left name - name = name.to_sym - smart_listing = @smart_listings[name] - - smart_listing.max_count - smart_listing.count - end - - ################################################################################################# - # JS helpers: - - # Updates smart listing - # - # Posible calls: - # smart_listing_update name, options = {} - # smart_listing_update options = {} - def smart_listing_update *args - options = args.extract_options! - name = (args[0] || options[:name] || controller_name).to_sym - smart_listing = @smart_listings[name] - - # don't update list if params are missing (prevents interfering with other lists) - if params.keys.select{|k| k.include?("smart_listing")}.present? && !params[smart_listing.base_param] - return unless options[:force] - end - - builder = Builder.new(name, smart_listing, self, {}, nil) - render(:partial => 'smart_listing/update_list', :locals => { - :name => smart_listing.name, - :part => smart_listing.partial, - :smart_listing => builder, - :smart_listing_data => { - smart_listing_config.data_attributes(:params) => smart_listing.all_params, - smart_listing_config.data_attributes(:max_count) => smart_listing.max_count, - smart_listing_config.data_attributes(:item_count) => smart_listing.count, - }, - :locals => options[:locals] || {} - }) - end - - # Renders single item (i.e for create, update actions) - # - # Possible calls: - # smart_listing_item name, item_action, object = nil, partial = nil, options = {} - # smart_listing_item item_action, object = nil, partial = nil, options = {} - def smart_listing_item *args - options = args.extract_options! - if [:create, :create_continue, :destroy, :edit, :new, :remove, :update].include?(args[1]) - name = args[0] - item_action = args[1] - object = args[2] - partial = args[3] - else - name = (options[:name] || controller_name).to_sym - item_action = args[0] - object = args[1] - partial = args[2] - end - type = object.class.name.downcase.to_sym if object - id = options[:id] || object.try(:id) - valid = options[:valid] if options.has_key?(:valid) - object_key = options.delete(:object_key) || :object - new = options.delete(:new) - - render(:partial => "smart_listing/item/#{item_action.to_s}", :locals => {:name => name, :id => id, :valid => valid, :object_key => object_key, :object => object, :part => partial, :new => new}) - end - end -end diff --git a/app/helpers/smart_listing/remote/action_helper.rb b/app/helpers/smart_listing/remote/action_helper.rb new file mode 100644 index 0000000..548df6e --- /dev/null +++ b/app/helpers/smart_listing/remote/action_helper.rb @@ -0,0 +1,19 @@ +module SmartListing + module Remote + module ActionHelper + def smart_listing_action_tag(builder_or_base, action, target:, template: nil) + target = SmartListing::Builder.dom_id(builder_or_base, target) + template = action.to_sym == :remove ? "" : "#{template}" + + case builder_or_base.config.global_options[:remote_mode] + when :ujs + %(| <%= smart_listing.sortable "Name", "name"%> | -<%= smart_listing.sortable "Email", "email"%> | -Boolean | -- - - <% smart_listing.collection.each do |user| %> - |
|---|
| Name | -|
|---|---|
| <%= user.name %> | -<%= user.email %> | -
No records!
-<% end %> diff --git a/spec/dummy/app/views/users/_searchable_list.html.erb b/spec/dummy/app/views/users/_searchable_list.html.erb deleted file mode 100644 index 65b6e2b..0000000 --- a/spec/dummy/app/views/users/_searchable_list.html.erb +++ /dev/null @@ -1,21 +0,0 @@ -<% unless smart_listing.empty? %> -| Name | -|
|---|---|
| <%= user.name %> | -<%= user.email %> | -
No records!
-<% end %> diff --git a/spec/dummy/app/views/users/_sortable_list.html.erb b/spec/dummy/app/views/users/_sortable_list.html.erb deleted file mode 100644 index dd44399..0000000 --- a/spec/dummy/app/views/users/_sortable_list.html.erb +++ /dev/null @@ -1,24 +0,0 @@ -<% unless smart_listing.empty? %> -| - <%= smart_listing.sortable "Name", "name" %> - | -- <%= smart_listing.sortable "Email", "email" %> - | - - - <% smart_listing.collection.each do |user| %> -
|---|---|
| <%= user.name %> | -<%= user.email %> | -
No records!
-<% end %> diff --git a/spec/dummy/app/views/users/index.html.erb b/spec/dummy/app/views/users/index.html.erb deleted file mode 100644 index 598ce6f..0000000 --- a/spec/dummy/app/views/users/index.html.erb +++ /dev/null @@ -1 +0,0 @@ -<%= smart_listing_render %> diff --git a/spec/dummy/app/views/users/index.js.erb b/spec/dummy/app/views/users/index.js.erb deleted file mode 100644 index 5d37153..0000000 --- a/spec/dummy/app/views/users/index.js.erb +++ /dev/null @@ -1 +0,0 @@ -<%= smart_listing_update %> diff --git a/spec/dummy/app/views/users/searchable.html.erb b/spec/dummy/app/views/users/searchable.html.erb deleted file mode 100644 index 96b5667..0000000 --- a/spec/dummy/app/views/users/searchable.html.erb +++ /dev/null @@ -1,12 +0,0 @@ -<%= smart_listing_controls_for(:users, {class: 'form-inline text-right'}) do %> -