presenting 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/LICENSE +20 -0
- data/README +10 -0
- data/Rakefile +22 -0
- data/app/assets/javascript/grid.js +0 -0
- data/app/assets/javascript/search.js +13 -0
- data/app/assets/stylesheets/details-color.css +7 -0
- data/app/assets/stylesheets/details.css +10 -0
- data/app/assets/stylesheets/form.css +1 -0
- data/app/assets/stylesheets/grid-color.css +71 -0
- data/app/assets/stylesheets/grid.css +64 -0
- data/app/assets/stylesheets/search-color.css +16 -0
- data/app/assets/stylesheets/search.css +45 -0
- data/app/controllers/presentation/assets_controller.rb +42 -0
- data/app/views/presentations/_details.erb +11 -0
- data/app/views/presentations/_field_search.erb +14 -0
- data/app/views/presentations/_form.erb +20 -0
- data/app/views/presentations/_grid.erb +70 -0
- data/app/views/presentations/_search.erb +7 -0
- data/config/routes.rb +3 -0
- data/lib/presentation/base.rb +48 -0
- data/lib/presentation/details.rb +19 -0
- data/lib/presentation/field_search.rb +65 -0
- data/lib/presentation/form.rb +149 -0
- data/lib/presentation/grid.rb +160 -0
- data/lib/presentation/search.rb +9 -0
- data/lib/presenting/attribute.rb +51 -0
- data/lib/presenting/configurable.rb +10 -0
- data/lib/presenting/defaults.rb +10 -0
- data/lib/presenting/field_set.rb +26 -0
- data/lib/presenting/form_helpers.rb +51 -0
- data/lib/presenting/helpers.rb +110 -0
- data/lib/presenting/sanitize.rb +19 -0
- data/lib/presenting/search.rb +185 -0
- data/lib/presenting/sorting.rb +87 -0
- data/lib/presenting/view.rb +3 -0
- data/lib/presenting.rb +9 -0
- data/rails/init.rb +12 -0
- data/test/assets_test.rb +58 -0
- data/test/attribute_test.rb +61 -0
- data/test/configurable_test.rb +20 -0
- data/test/details_test.rb +68 -0
- data/test/field_search_test.rb +102 -0
- data/test/field_set_test.rb +46 -0
- data/test/form_test.rb +287 -0
- data/test/grid_test.rb +219 -0
- data/test/helpers_test.rb +72 -0
- data/test/presenting_test.rb +15 -0
- data/test/rails/app/controllers/application_controller.rb +15 -0
- data/test/rails/app/controllers/users_controller.rb +36 -0
- data/test/rails/app/helpers/application_helper.rb +3 -0
- data/test/rails/app/helpers/users_helper.rb +2 -0
- data/test/rails/app/models/user.rb +2 -0
- data/test/rails/app/views/layouts/application.html.erb +15 -0
- data/test/rails/app/views/users/index.html.erb +10 -0
- data/test/rails/app/views/users/new.html.erb +2 -0
- data/test/rails/app/views/users/show.html.erb +1 -0
- data/test/rails/config/boot.rb +109 -0
- data/test/rails/config/database.yml +17 -0
- data/test/rails/config/environment.rb +13 -0
- data/test/rails/config/environments/development.rb +17 -0
- data/test/rails/config/environments/production.rb +24 -0
- data/test/rails/config/environments/test.rb +22 -0
- data/test/rails/config/locales/en.yml +5 -0
- data/test/rails/config/routes.rb +5 -0
- data/test/rails/db/development.sqlite3 +0 -0
- data/test/rails/db/migrate/20090213085444_create_users.rb +13 -0
- data/test/rails/db/migrate/20090213085607_populate_users.rb +13 -0
- data/test/rails/db/schema.rb +23 -0
- data/test/rails/db/test.sqlite3 +0 -0
- data/test/rails/log/development.log +858 -0
- data/test/rails/public/404.html +30 -0
- data/test/rails/public/422.html +30 -0
- data/test/rails/public/500.html +33 -0
- data/test/rails/public/javascripts/application.js +2 -0
- data/test/rails/public/javascripts/jquery.livequery.min.js +11 -0
- data/test/rails/public/javascripts/prototype.js +4320 -0
- data/test/rails/script/console +3 -0
- data/test/rails/script/dbconsole +3 -0
- data/test/rails/script/destroy +3 -0
- data/test/rails/script/generate +3 -0
- data/test/rails/script/plugin +3 -0
- data/test/rails/script/runner +3 -0
- data/test/rails/script/server +3 -0
- data/test/sanitize_test.rb +15 -0
- data/test/search_conditions_test.rb +137 -0
- data/test/search_test.rb +30 -0
- data/test/sorting_test.rb +63 -0
- data/test/test_helper.rb +66 -0
- metadata +217 -0
    
        data/LICENSE
    ADDED
    
    | @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            Copyright (c) 2008 Lance Ivy
         | 
| 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.
         | 
    
        data/README
    ADDED
    
    | @@ -0,0 +1,10 @@ | |
| 1 | 
            +
            Presenting
         | 
| 2 | 
            +
            ==========
         | 
| 3 | 
            +
            Bringing together my work on ActiveScaffold and Components into standardized, reusable view macros.
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            Performance
         | 
| 6 | 
            +
            ===========
         | 
| 7 | 
            +
            I don't see why it wouldn't be fast. But if anyone "discovers":http://rpm.newrelic.com a "problem":http://ruby-prof.rubyforge.org/, we can go from there. Why worry early?
         | 
| 8 | 
            +
             | 
| 9 | 
            +
             | 
| 10 | 
            +
            Copyright (c) 2008 Lance Ivy, released under the MIT license
         | 
    
        data/Rakefile
    ADDED
    
    | @@ -0,0 +1,22 @@ | |
| 1 | 
            +
            require 'rake'
         | 
| 2 | 
            +
            require 'rake/testtask'
         | 
| 3 | 
            +
            require 'rake/rdoctask'
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            desc 'Default: run unit tests.'
         | 
| 6 | 
            +
            task :default => :test
         | 
| 7 | 
            +
             | 
| 8 | 
            +
            desc 'Test the presenting plugin.'
         | 
| 9 | 
            +
            Rake::TestTask.new(:test) do |t|
         | 
| 10 | 
            +
              t.libs << 'lib'
         | 
| 11 | 
            +
              t.pattern = 'test/**/*_test.rb'
         | 
| 12 | 
            +
              t.verbose = true
         | 
| 13 | 
            +
            end
         | 
| 14 | 
            +
             | 
| 15 | 
            +
            desc 'Generate documentation for the presenting plugin.'
         | 
| 16 | 
            +
            Rake::RDocTask.new(:rdoc) do |rdoc|
         | 
| 17 | 
            +
              rdoc.rdoc_dir = 'rdoc'
         | 
| 18 | 
            +
              rdoc.title    = 'Presenting'
         | 
| 19 | 
            +
              rdoc.options << '--line-numbers' << '--inline-source'
         | 
| 20 | 
            +
              rdoc.rdoc_files.include('README')
         | 
| 21 | 
            +
              rdoc.rdoc_files.include('lib/**/*.rb')
         | 
| 22 | 
            +
            end
         | 
| 
            File without changes
         | 
| @@ -0,0 +1,13 @@ | |
| 1 | 
            +
            $('.presentation-search .compact fieldset label').livequery(function() {
         | 
| 2 | 
            +
              var label = $(this);
         | 
| 3 | 
            +
              var field = label.siblings('input');
         | 
| 4 | 
            +
              if (!field[0] || field.attr('type') == 'checkbox') return;
         | 
| 5 | 
            +
              
         | 
| 6 | 
            +
              label.addClass('overlabel');
         | 
| 7 | 
            +
              
         | 
| 8 | 
            +
              var hide_label = function() { label.css('text-indent', '-1000px') };
         | 
| 9 | 
            +
              var show_label = function() { this.value || label.css('text-indent', '0px') };
         | 
| 10 | 
            +
              
         | 
| 11 | 
            +
              $(field).focus(hide_label).blur(show_label).each(hide_label).each(show_label);
         | 
| 12 | 
            +
              $(label).click(function() {field.focus()});
         | 
| 13 | 
            +
            });
         | 
| @@ -0,0 +1 @@ | |
| 1 | 
            +
            .presentation-form {}
         | 
| @@ -0,0 +1,71 @@ | |
| 1 | 
            +
            .presentation-grid a {
         | 
| 2 | 
            +
              color: #06c;
         | 
| 3 | 
            +
            }
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            .presentation-grid {
         | 
| 6 | 
            +
              font-family: sans-serif;
         | 
| 7 | 
            +
              background-color: #fff;
         | 
| 8 | 
            +
            }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            .presentation-grid caption {  
         | 
| 11 | 
            +
              background-color: transparent;
         | 
| 12 | 
            +
            }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            .presentation-grid thead {
         | 
| 15 | 
            +
              background-color: transparent;
         | 
| 16 | 
            +
            }
         | 
| 17 | 
            +
             | 
| 18 | 
            +
            .presentation-grid thead th {
         | 
| 19 | 
            +
              border-bottom: 2px solid #aaa;
         | 
| 20 | 
            +
              font-weight: normal;
         | 
| 21 | 
            +
              color: #666;
         | 
| 22 | 
            +
              background-color: transparent;
         | 
| 23 | 
            +
            }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            .presentation-grid tbody tr.even {
         | 
| 26 | 
            +
              background-color: #e6f2ff;
         | 
| 27 | 
            +
              border-bottom: 1px solid #c5dbf7;
         | 
| 28 | 
            +
              border-top: 1px solid #c5dbf7;
         | 
| 29 | 
            +
            }
         | 
| 30 | 
            +
             | 
| 31 | 
            +
            .presentation-grid tbody td.sorted {
         | 
| 32 | 
            +
              background-color: #f5f5f5;
         | 
| 33 | 
            +
            }
         | 
| 34 | 
            +
             | 
| 35 | 
            +
            .presentation-grid tbody tr.even td.sorted {
         | 
| 36 | 
            +
              background-color: #c5dbf7;
         | 
| 37 | 
            +
            }
         | 
| 38 | 
            +
             | 
| 39 | 
            +
            .presentation-grid tfoot .pagination {
         | 
| 40 | 
            +
              margin-top: 0.5em;
         | 
| 41 | 
            +
            }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            .presentation-grid tfoot .pagination a {
         | 
| 44 | 
            +
              text-decoration: none;
         | 
| 45 | 
            +
            }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            .presentation-grid tfoot .pagination .prev_page,
         | 
| 48 | 
            +
            .presentation-grid tfoot .pagination .next_page {
         | 
| 49 | 
            +
              padding: 0.1em 0.5em;
         | 
| 50 | 
            +
              background-color: #eee;
         | 
| 51 | 
            +
              border: 1px solid #e5e5e5;
         | 
| 52 | 
            +
            }
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            .presentation-grid tfoot .pagination a.prev_page:hover,
         | 
| 55 | 
            +
            .presentation-grid tfoot .pagination a.next_page:hover {
         | 
| 56 | 
            +
              background-color: #e6f2ff;
         | 
| 57 | 
            +
              border-color: #c5dbf7;
         | 
| 58 | 
            +
            }
         | 
| 59 | 
            +
             | 
| 60 | 
            +
            .presentation-grid tfoot .pagination .prev_page {
         | 
| 61 | 
            +
              margin-right: 0.7em;
         | 
| 62 | 
            +
            }
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            .presentation-grid tfoot .pagination .next_page {
         | 
| 65 | 
            +
              margin-left: 0.7em;
         | 
| 66 | 
            +
            }
         | 
| 67 | 
            +
             | 
| 68 | 
            +
            .presentation-grid tfoot .pagination .current {
         | 
| 69 | 
            +
              padding: 0.1em 0.5em;
         | 
| 70 | 
            +
              border: 1px solid #e5e5e5;
         | 
| 71 | 
            +
            }
         | 
| @@ -0,0 +1,64 @@ | |
| 1 | 
            +
            /** cellspacing **/
         | 
| 2 | 
            +
            .presentation-grid table,
         | 
| 3 | 
            +
            .presentation-grid td,
         | 
| 4 | 
            +
            .presentation-grid th {
         | 
| 5 | 
            +
              border: 0;
         | 
| 6 | 
            +
              border-collapse: collapse;
         | 
| 7 | 
            +
            }
         | 
| 8 | 
            +
             | 
| 9 | 
            +
            .presentation-grid table {
         | 
| 10 | 
            +
              width: 100%;
         | 
| 11 | 
            +
            }
         | 
| 12 | 
            +
             | 
| 13 | 
            +
            .presentation-grid caption {
         | 
| 14 | 
            +
              text-align: left;
         | 
| 15 | 
            +
              font-weight: bold;
         | 
| 16 | 
            +
              padding-left: 0.2em;
         | 
| 17 | 
            +
            }
         | 
| 18 | 
            +
             | 
| 19 | 
            +
            .presentation-grid th {
         | 
| 20 | 
            +
              padding: 0.2em 0.5em 0em 0.2em;
         | 
| 21 | 
            +
              text-align: left;
         | 
| 22 | 
            +
              vertical-align: bottom;
         | 
| 23 | 
            +
            }
         | 
| 24 | 
            +
             | 
| 25 | 
            +
            .presentation-grid th a {
         | 
| 26 | 
            +
              text-decoration: none;
         | 
| 27 | 
            +
            }
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            .presentation-grid td {
         | 
| 30 | 
            +
              padding: 0.4em 0.5em 0.4em 0.2em;
         | 
| 31 | 
            +
              vertical-align: top;
         | 
| 32 | 
            +
            }
         | 
| 33 | 
            +
             | 
| 34 | 
            +
            .presentation-grid ul.actions {
         | 
| 35 | 
            +
              overflow: auto;
         | 
| 36 | 
            +
              zoom: 1;
         | 
| 37 | 
            +
              float: right;
         | 
| 38 | 
            +
              list-style: none;
         | 
| 39 | 
            +
              margin: 0;
         | 
| 40 | 
            +
              padding: 0;
         | 
| 41 | 
            +
            }
         | 
| 42 | 
            +
             | 
| 43 | 
            +
            .presentation-grid ul.actions li {
         | 
| 44 | 
            +
              float: left;
         | 
| 45 | 
            +
            }
         | 
| 46 | 
            +
             | 
| 47 | 
            +
            .presentation-grid ul.actions li a {
         | 
| 48 | 
            +
              font-weight: normal;
         | 
| 49 | 
            +
              display: block;
         | 
| 50 | 
            +
              padding: 0em 0.3em;
         | 
| 51 | 
            +
              text-decoration: none;
         | 
| 52 | 
            +
            }
         | 
| 53 | 
            +
             | 
| 54 | 
            +
            .presentation-grid ul.actions li a:hover {
         | 
| 55 | 
            +
              text-decoration: underline;
         | 
| 56 | 
            +
            }
         | 
| 57 | 
            +
             | 
| 58 | 
            +
            .presentation-grid ul.actions li a {
         | 
| 59 | 
            +
              outline: none;
         | 
| 60 | 
            +
            }
         | 
| 61 | 
            +
             | 
| 62 | 
            +
            .presentation-grid caption ul.actions {
         | 
| 63 | 
            +
              margin-right: 0.5em;
         | 
| 64 | 
            +
            }
         | 
| @@ -0,0 +1,16 @@ | |
| 1 | 
            +
            .presentation-search label {
         | 
| 2 | 
            +
              color: #666;
         | 
| 3 | 
            +
            }
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            .presentation-search fieldset {
         | 
| 6 | 
            +
              border: 1px solid #c5dbf7;
         | 
| 7 | 
            +
              background-color: #e6f2ff;
         | 
| 8 | 
            +
              padding: 2px;
         | 
| 9 | 
            +
            }
         | 
| 10 | 
            +
             | 
| 11 | 
            +
            .presentation-search fieldset.submit {
         | 
| 12 | 
            +
              border: 0px;
         | 
| 13 | 
            +
              padding: 0px;
         | 
| 14 | 
            +
              background-color: transparent;
         | 
| 15 | 
            +
            }
         | 
| 16 | 
            +
             | 
| @@ -0,0 +1,45 @@ | |
| 1 | 
            +
            .presentation-search {
         | 
| 2 | 
            +
              overflow: hidden;
         | 
| 3 | 
            +
              zoom: 1;
         | 
| 4 | 
            +
            }
         | 
| 5 | 
            +
             | 
| 6 | 
            +
            .presentation-search fieldset {
         | 
| 7 | 
            +
              border: 0;
         | 
| 8 | 
            +
              margin: 0 0.5em 1em 0;
         | 
| 9 | 
            +
              padding: 0;
         | 
| 10 | 
            +
              float: left;
         | 
| 11 | 
            +
              width: 145px;
         | 
| 12 | 
            +
            }
         | 
| 13 | 
            +
             | 
| 14 | 
            +
            .presentation-search fieldset label {
         | 
| 15 | 
            +
              font-weight: normal;
         | 
| 16 | 
            +
              margin: 0;
         | 
| 17 | 
            +
              padding: 0;
         | 
| 18 | 
            +
              text-transform: none;
         | 
| 19 | 
            +
            }
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            .presentation-search fieldset input,
         | 
| 22 | 
            +
            .presentation-search fieldset select {
         | 
| 23 | 
            +
              padding: 0px;
         | 
| 24 | 
            +
              margin: 0px;
         | 
| 25 | 
            +
            }
         | 
| 26 | 
            +
             | 
| 27 | 
            +
            /* for overlabels (requires javascript) */
         | 
| 28 | 
            +
             | 
| 29 | 
            +
            .presentation-search .compact label {
         | 
| 30 | 
            +
              display: inline;
         | 
| 31 | 
            +
            }
         | 
| 32 | 
            +
             | 
| 33 | 
            +
            .presentation-search .compact fieldset {
         | 
| 34 | 
            +
              position: relative;
         | 
| 35 | 
            +
              width: auto;
         | 
| 36 | 
            +
            }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            .presentation-search .compact fieldset label.overlabel {
         | 
| 39 | 
            +
              display: block;
         | 
| 40 | 
            +
              position: absolute;
         | 
| 41 | 
            +
              top: 0.2em;
         | 
| 42 | 
            +
              left: 0.5em;
         | 
| 43 | 
            +
              z-index: 1;
         | 
| 44 | 
            +
              cursor: text;
         | 
| 45 | 
            +
            }
         | 
| @@ -0,0 +1,42 @@ | |
| 1 | 
            +
            class Presentation::AssetsController < ActionController::Base
         | 
| 2 | 
            +
              # TODO: consider packaging a minifier so we get the perfect solution: a cached, minified bundle of assets
         | 
| 3 | 
            +
              caches_page :stylesheet, :javascript
         | 
| 4 | 
            +
             | 
| 5 | 
            +
              def stylesheet
         | 
| 6 | 
            +
                dir = asset_path(:stylesheets)
         | 
| 7 | 
            +
                sheet = params[:id].split(',').collect{ |id| File.read("#{dir}/#{id}.css") }.join("\n")
         | 
| 8 | 
            +
                
         | 
| 9 | 
            +
                respond_to do |type|
         | 
| 10 | 
            +
                  type.css {render :text => sheet}
         | 
| 11 | 
            +
                end
         | 
| 12 | 
            +
              end
         | 
| 13 | 
            +
              
         | 
| 14 | 
            +
              # TODO: bundle unobtrusive javascripts that can add behavior.
         | 
| 15 | 
            +
              # - jquery vs prototype (i'll develop jquery, and wait for prototype contributions)
         | 
| 16 | 
            +
              # - ajax links with html response
         | 
| 17 | 
            +
              # - - inline
         | 
| 18 | 
            +
              # - - modal dialog
         | 
| 19 | 
            +
              # - ajax links with js response
         | 
| 20 | 
            +
              # - ajax links with no response
         | 
| 21 | 
            +
              # - inline editing
         | 
| 22 | 
            +
              # - "dirty" form awareness
         | 
| 23 | 
            +
              # - tabbed forms
         | 
| 24 | 
            +
              # - tooltips for extended information (e.g. column description or truncated field text)
         | 
| 25 | 
            +
              # - basic form validation
         | 
| 26 | 
            +
              # - - required fields
         | 
| 27 | 
            +
              # TODO: tests for ujs
         | 
| 28 | 
            +
              def javascript
         | 
| 29 | 
            +
                dir = asset_path(:javascript)
         | 
| 30 | 
            +
                script = params[:id].split(',').collect{ |id| File.read("#{dir}/#{id}.js") }.join("\n")
         | 
| 31 | 
            +
                
         | 
| 32 | 
            +
                respond_to do |type|
         | 
| 33 | 
            +
                  type.js {render :text => script}
         | 
| 34 | 
            +
                end
         | 
| 35 | 
            +
              end
         | 
| 36 | 
            +
              
         | 
| 37 | 
            +
              protected
         | 
| 38 | 
            +
              
         | 
| 39 | 
            +
              def asset_path(type)
         | 
| 40 | 
            +
                File.join(File.dirname(__FILE__), '..', '..', 'assets', type.to_s)
         | 
| 41 | 
            +
              end
         | 
| 42 | 
            +
            end
         | 
| @@ -0,0 +1,11 @@ | |
| 1 | 
            +
            <div class="presentation-details">
         | 
| 2 | 
            +
              <% if @details.title %>
         | 
| 3 | 
            +
              <h3><%= @details.title %></h3>
         | 
| 4 | 
            +
              <% end %>
         | 
| 5 | 
            +
              <dl>
         | 
| 6 | 
            +
                <% @details.fields.each do |field| %>
         | 
| 7 | 
            +
                  <dt><%= field.name %></dt>
         | 
| 8 | 
            +
                  <dd><%= present(field.value_from(@details.presentable), :h => field.sanitize?) %></dd>
         | 
| 9 | 
            +
                <% end %>
         | 
| 10 | 
            +
              </dl>
         | 
| 11 | 
            +
            </div>
         | 
| @@ -0,0 +1,14 @@ | |
| 1 | 
            +
            <div class="presentation-search">
         | 
| 2 | 
            +
              <% form_tag @search.url, :method => :get, :class => ("compact" if @search.compact) do %>
         | 
| 3 | 
            +
                <% @search.fields.each do |field| %>
         | 
| 4 | 
            +
                  <fieldset>
         | 
| 5 | 
            +
                    <label><%= field.name %></label>
         | 
| 6 | 
            +
                    <%= present(field, "#{field.type}_search", :name => "search[#{field.param}][value]") %>
         | 
| 7 | 
            +
                  </fieldset>
         | 
| 8 | 
            +
                <% end %>
         | 
| 9 | 
            +
                <fieldset class="submit">
         | 
| 10 | 
            +
                  <input type="submit" value="Search" class="submit" />
         | 
| 11 | 
            +
                  <%= link_to 'reset', @search.url %>
         | 
| 12 | 
            +
                </fieldset>
         | 
| 13 | 
            +
              <% end %>
         | 
| 14 | 
            +
            </div>
         | 
| @@ -0,0 +1,20 @@ | |
| 1 | 
            +
            <% form_for @form.presentable, :url => @form.url, :method => @form.method, :html => @form.html do |f| %>
         | 
| 2 | 
            +
              <% @form.groups.each do |group| %>
         | 
| 3 | 
            +
                <fieldset>
         | 
| 4 | 
            +
                  <% unless group.name.blank? %>
         | 
| 5 | 
            +
                    <legend><%= group.name %></legend>
         | 
| 6 | 
            +
                  <% end %>
         | 
| 7 | 
            +
                  <% group.fields.each do |field| %>
         | 
| 8 | 
            +
                    <% if field.type.to_sym == :hidden %>
         | 
| 9 | 
            +
                      <%= f.present(field) %>
         | 
| 10 | 
            +
                    <% else %>
         | 
| 11 | 
            +
                      <div class="field">
         | 
| 12 | 
            +
                        <%= f.label field.name, field.label %>
         | 
| 13 | 
            +
                        <%= f.present(field) %>
         | 
| 14 | 
            +
                      </div>
         | 
| 15 | 
            +
                    <% end %>
         | 
| 16 | 
            +
                  <% end %>
         | 
| 17 | 
            +
                </fieldset>
         | 
| 18 | 
            +
              <% end %>
         | 
| 19 | 
            +
              <%= f.submit @form.button %>
         | 
| 20 | 
            +
            <% end %>
         | 
| @@ -0,0 +1,70 @@ | |
| 1 | 
            +
            <div id="<%= @grid.id %>" class="presentation-grid">
         | 
| 2 | 
            +
              <table>
         | 
| 3 | 
            +
                <% unless @grid.title.blank? and @grid.links.empty? %>
         | 
| 4 | 
            +
                  <caption>
         | 
| 5 | 
            +
                    <% unless @grid.links.empty? %>
         | 
| 6 | 
            +
                      <ul class="actions">
         | 
| 7 | 
            +
                        <% @grid.links.each do |link| %>
         | 
| 8 | 
            +
                          <li><%= link %></li>
         | 
| 9 | 
            +
                        <% end %>
         | 
| 10 | 
            +
                      </ul>
         | 
| 11 | 
            +
                    <% end %>
         | 
| 12 | 
            +
                    
         | 
| 13 | 
            +
                    <%= @grid.title %>
         | 
| 14 | 
            +
                  </caption>
         | 
| 15 | 
            +
                <% end %>
         | 
| 16 | 
            +
                <thead>
         | 
| 17 | 
            +
                  <tr>
         | 
| 18 | 
            +
                    <% @grid.fields.each do |field| %>
         | 
| 19 | 
            +
                      <th class="<%= field.id %>">
         | 
| 20 | 
            +
                        <% if field.sortable? %>
         | 
| 21 | 
            +
                          <%= link_to field.name, field.sorted_url(request), :class => "sortable" %>
         | 
| 22 | 
            +
                        <% else %>
         | 
| 23 | 
            +
                          <%= field.name %>
         | 
| 24 | 
            +
                        <% end %>
         | 
| 25 | 
            +
                      </th>
         | 
| 26 | 
            +
                    <% end %>
         | 
| 27 | 
            +
                    <% unless @grid.record_links.empty? %>
         | 
| 28 | 
            +
                      <th></th>
         | 
| 29 | 
            +
                    <% end %>
         | 
| 30 | 
            +
                  </tr>
         | 
| 31 | 
            +
                </thead>
         | 
| 32 | 
            +
                <tbody>
         | 
| 33 | 
            +
                  <% if @grid.presentable.blank? %>
         | 
| 34 | 
            +
                    <tr>
         | 
| 35 | 
            +
                      <td colspan="<%= @grid.colspan %>">
         | 
| 36 | 
            +
                        No records found.
         | 
| 37 | 
            +
                      </td>
         | 
| 38 | 
            +
                    </tr>
         | 
| 39 | 
            +
                  <% else %>
         | 
| 40 | 
            +
                    <% @grid.presentable.each do |record| %>
         | 
| 41 | 
            +
                      <tr class="<%= cycle('odd', 'even') %>">
         | 
| 42 | 
            +
                        <% @grid.fields.each do |field| %>
         | 
| 43 | 
            +
                          <td class="<%= [field.id, ('sorted' if field.is_sorted?(request))].compact.join(' ') %>">
         | 
| 44 | 
            +
                            <%= present(field.value_from(record), :h => field.sanitize?) %>
         | 
| 45 | 
            +
                          </td>
         | 
| 46 | 
            +
                        <% end %>
         | 
| 47 | 
            +
                        <% unless @grid.record_links.empty? %>
         | 
| 48 | 
            +
                          <td>
         | 
| 49 | 
            +
                            <ul class="actions">
         | 
| 50 | 
            +
                              <% @grid.record_links.each do |link| %>
         | 
| 51 | 
            +
                                <li><%= link.call(record) %></li>
         | 
| 52 | 
            +
                              <% end %>
         | 
| 53 | 
            +
                            </ul>
         | 
| 54 | 
            +
                          </td>
         | 
| 55 | 
            +
                        <% end %>
         | 
| 56 | 
            +
                      </tr>
         | 
| 57 | 
            +
                    <% end %>
         | 
| 58 | 
            +
                  <% end %>
         | 
| 59 | 
            +
                </tbody>
         | 
| 60 | 
            +
                <% if @grid.paginate? %>
         | 
| 61 | 
            +
                  <tfoot>
         | 
| 62 | 
            +
                    <tr>
         | 
| 63 | 
            +
                      <td colspan="<%= @grid.colspan %>">
         | 
| 64 | 
            +
                        <%= will_paginate @grid.presentable %>
         | 
| 65 | 
            +
                      </td>
         | 
| 66 | 
            +
                    </tr>
         | 
| 67 | 
            +
                  </tfoot>
         | 
| 68 | 
            +
                <% end %>
         | 
| 69 | 
            +
              </table>
         | 
| 70 | 
            +
            </div>
         | 
    
        data/config/routes.rb
    ADDED
    
    
| @@ -0,0 +1,48 @@ | |
| 1 | 
            +
            module Presentation
         | 
| 2 | 
            +
              class Base
         | 
| 3 | 
            +
                include Presenting::Configurable
         | 
| 4 | 
            +
                
         | 
| 5 | 
            +
                def render
         | 
| 6 | 
            +
                  view.render :partial => "presentations/#{self.class.to_s.split('::').last.underscore}"
         | 
| 7 | 
            +
                end
         | 
| 8 | 
            +
                
         | 
| 9 | 
            +
                attr_accessor :presentable
         | 
| 10 | 
            +
                
         | 
| 11 | 
            +
                ##
         | 
| 12 | 
            +
                ## begin ActionView compat
         | 
| 13 | 
            +
                ##
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                attr_accessor :controller # not strictly for compat, but it makes the compat easy
         | 
| 16 | 
            +
                
         | 
| 17 | 
            +
                def request
         | 
| 18 | 
            +
                  controller.request
         | 
| 19 | 
            +
                end
         | 
| 20 | 
            +
                
         | 
| 21 | 
            +
                def url_for(*args)
         | 
| 22 | 
            +
                  controller.url_for(*args)
         | 
| 23 | 
            +
                end
         | 
| 24 | 
            +
                
         | 
| 25 | 
            +
                def params
         | 
| 26 | 
            +
                  request.parameters
         | 
| 27 | 
            +
                end
         | 
| 28 | 
            +
                
         | 
| 29 | 
            +
                ## end ActionView compat
         | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                protected
         | 
| 32 | 
            +
                
         | 
| 33 | 
            +
                # what the presentation is called in its templates
         | 
| 34 | 
            +
                def iname
         | 
| 35 | 
            +
                  :presentation
         | 
| 36 | 
            +
                end
         | 
| 37 | 
            +
                
         | 
| 38 | 
            +
                # a reference to the view
         | 
| 39 | 
            +
                def view #:nodoc:
         | 
| 40 | 
            +
                  @view ||= Presenting::View.new(ActionController::Base.view_paths, assigns_for_view, self)
         | 
| 41 | 
            +
                end
         | 
| 42 | 
            +
                
         | 
| 43 | 
            +
                def assigns_for_view
         | 
| 44 | 
            +
                  {iname => self, :_request => request}
         | 
| 45 | 
            +
                end
         | 
| 46 | 
            +
                
         | 
| 47 | 
            +
              end
         | 
| 48 | 
            +
            end
         | 
| @@ -0,0 +1,19 @@ | |
| 1 | 
            +
            module Presentation
         | 
| 2 | 
            +
              # TODO: abstract what's common between Record and Grid into a shared module or reusable objects or something
         | 
| 3 | 
            +
              class Details < Grid
         | 
| 4 | 
            +
                def iname; :details end
         | 
| 5 | 
            +
                
         | 
| 6 | 
            +
                # The display title for this presentation. Will default based on the id.
         | 
| 7 | 
            +
                attr_accessor :title
         | 
| 8 | 
            +
                
         | 
| 9 | 
            +
                def fields=(args)
         | 
| 10 | 
            +
                  args.each do |field|
         | 
| 11 | 
            +
                    self.fields << field
         | 
| 12 | 
            +
                  end
         | 
| 13 | 
            +
                end
         | 
| 14 | 
            +
                
         | 
| 15 | 
            +
                def fields
         | 
| 16 | 
            +
                  @fields ||= Presenting::FieldSet.new(Presenting::Attribute, :name, :value)
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
              end
         | 
| 19 | 
            +
            end
         | 
| @@ -0,0 +1,65 @@ | |
| 1 | 
            +
            module Presentation
         | 
| 2 | 
            +
              class FieldSearch < Search
         | 
| 3 | 
            +
                # This method supports the configuration-on-initialization paradigm. It makes:
         | 
| 4 | 
            +
                #   present = Presentation::FieldSearch.new(:fields => [
         | 
| 5 | 
            +
                #     {:a => {:type => :list, :options => %w(foo bar baz)}},
         | 
| 6 | 
            +
                #     :b,
         | 
| 7 | 
            +
                #     {:c => :boolean}
         | 
| 8 | 
            +
                #   ])
         | 
| 9 | 
            +
                #
         | 
| 10 | 
            +
                # equivalent to:
         | 
| 11 | 
            +
                #
         | 
| 12 | 
            +
                #   present = Presentation::FieldSearch.new
         | 
| 13 | 
            +
                #   present.fields << {:a => {:type => :list, :options => %w(foo bar baz)}}
         | 
| 14 | 
            +
                #   present.fields << :b
         | 
| 15 | 
            +
                #   present.fields << {:c => :boolean}
         | 
| 16 | 
            +
                def fields=(args)
         | 
| 17 | 
            +
                  args.each do |field|
         | 
| 18 | 
            +
                    self.fields << field
         | 
| 19 | 
            +
                  end
         | 
| 20 | 
            +
                end
         | 
| 21 | 
            +
                
         | 
| 22 | 
            +
                def fields
         | 
| 23 | 
            +
                  @fields ||= Presenting::FieldSet.new(Field, :param, :type)
         | 
| 24 | 
            +
                end
         | 
| 25 | 
            +
                
         | 
| 26 | 
            +
                def compact
         | 
| 27 | 
            +
                  @compact != false
         | 
| 28 | 
            +
                end
         | 
| 29 | 
            +
                attr_writer :compact
         | 
| 30 | 
            +
                
         | 
| 31 | 
            +
                class Field
         | 
| 32 | 
            +
                  include Presenting::Configurable
         | 
| 33 | 
            +
                  
         | 
| 34 | 
            +
                  # the display name of the field.
         | 
| 35 | 
            +
                  attr_reader :name
         | 
| 36 | 
            +
                  def name=(val)
         | 
| 37 | 
            +
                    @name = val.is_a?(Symbol) ? val.to_s.titleize : val
         | 
| 38 | 
            +
                  end
         | 
| 39 | 
            +
                  
         | 
| 40 | 
            +
                  # the parameter name of the field. note that this is not necessarily the name of a database column or record attribute.
         | 
| 41 | 
            +
                  # it simply needs to be recognized as a "field" by the controller logic.
         | 
| 42 | 
            +
                  attr_reader :param
         | 
| 43 | 
            +
                  def param=(val)
         | 
| 44 | 
            +
                    self.name ||= val
         | 
| 45 | 
            +
                    @param = val.to_s.underscore.downcase.gsub(' ', '_')
         | 
| 46 | 
            +
                  end
         | 
| 47 | 
            +
                  
         | 
| 48 | 
            +
                  # the type of search interface for this field. supported options:
         | 
| 49 | 
            +
                  # * :text     (default)
         | 
| 50 | 
            +
                  # * :checkbox
         | 
| 51 | 
            +
                  # * :time     [planned]
         | 
| 52 | 
            +
                  # * :list     [planned]
         | 
| 53 | 
            +
                  def type
         | 
| 54 | 
            +
                    @type ||= :text
         | 
| 55 | 
            +
                  end
         | 
| 56 | 
            +
                  attr_writer :type
         | 
| 57 | 
            +
                  
         | 
| 58 | 
            +
                  # extra options for the field.
         | 
| 59 | 
            +
                  def options
         | 
| 60 | 
            +
                    @options ||= {}
         | 
| 61 | 
            +
                  end
         | 
| 62 | 
            +
                  attr_writer :options
         | 
| 63 | 
            +
                end
         | 
| 64 | 
            +
              end
         | 
| 65 | 
            +
            end
         |