togo 0.3.1 → 0.4.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/bin/togo-admin +5 -5
- data/lib/togo/admin/admin.rb +45 -3
- data/lib/togo/admin/helpers.rb +0 -0
- data/lib/togo/admin/public/css/screen.css +43 -3
- data/lib/togo/admin/public/js/edit.js +35 -0
- data/lib/togo/admin/public/js/index.js +17 -0
- data/lib/togo/admin/public/js/togo.js +372 -1
- data/lib/togo/admin/views/edit.erb +3 -2
- data/lib/togo/admin/views/index.erb +11 -2
- data/lib/togo/dispatch/dispatch.rb +17 -7
- data/lib/togo/model/model.rb +50 -3
- data/lib/togo/model/types/belongs_to.erb +48 -12
- data/lib/togo/model/types/datetime.erb +1 -1
- data/lib/togo/model/types/has_n.erb +41 -10
- metadata +7 -5
    
        data/bin/togo-admin
    CHANGED
    
    | @@ -1,7 +1,7 @@ | |
| 1 1 | 
             
            #!/usr/bin/env ruby
         | 
| 2 2 |  | 
| 3 3 | 
             
            require 'optparse'
         | 
| 4 | 
            -
            files_to_require =  | 
| 4 | 
            +
            files_to_require = %w( dm-core dm-serializer )
         | 
| 5 5 |  | 
| 6 6 | 
             
            config = {
         | 
| 7 7 | 
             
              :environment => :development,
         | 
| @@ -18,9 +18,10 @@ OptionParser.new{|opt| | |
| 18 18 | 
             
                puts "Usage: togo-admin [options]"
         | 
| 19 19 | 
             
                puts "Options: "
         | 
| 20 20 | 
             
                puts "\t-p [port]: which port to run on"
         | 
| 21 | 
            -
                puts "\t- | 
| 21 | 
            +
                puts "\t-h [host]: which host to run on"
         | 
| 22 22 | 
             
                puts "\t-e [environment]: which environment to boot in"
         | 
| 23 | 
            -
                puts "\t-r [file]:  | 
| 23 | 
            +
                puts "\t-r [file]: specify a file to require before running"
         | 
| 24 | 
            +
                puts "\t-a [handler]: which handler to run the server with (e.g. Thin, Webrick, etc)"
         | 
| 24 25 | 
             
                puts "\t--help: what you're seeing"
         | 
| 25 26 | 
             
                puts "TIP: To load code automatically before running togo-admin, create a file called togo-admin-config.rb."
         | 
| 26 27 | 
             
                exit
         | 
| @@ -42,5 +43,4 @@ config.merge!({ | |
| 42 43 | 
             
              :reloader => Togo::TogoReloader
         | 
| 43 44 | 
             
            })
         | 
| 44 45 |  | 
| 45 | 
            -
            Togo::Admin. | 
| 46 | 
            -
            Togo::Admin.run!
         | 
| 46 | 
            +
            Togo::Admin.run!(config)
         | 
    
        data/lib/togo/admin/admin.rb
    CHANGED
    
    | @@ -1,9 +1,30 @@ | |
| 1 | 
            -
             | 
| 2 | 
            -
             | 
| 1 | 
            +
            module Helpers
         | 
| 2 | 
            +
              
         | 
| 3 | 
            +
              def paging_links(page, count, qs = {})
         | 
| 4 | 
            +
                prev_link, next_link = 'Previous', 'Next'
         | 
| 5 | 
            +
                if not qs.blank?
         | 
| 6 | 
            +
                  qs = qs.keys.collect{|k|
         | 
| 7 | 
            +
                    [k,escape(qs[k])].join('=') if not qs[k].blank?
         | 
| 8 | 
            +
                  }.compact.join('&')
         | 
| 9 | 
            +
                  qs = nil if qs.blank?
         | 
| 10 | 
            +
                end
         | 
| 11 | 
            +
                  
         | 
| 12 | 
            +
                if not page == 1
         | 
| 13 | 
            +
                  prev_link = "<a href=\"?p=#{[page-1, qs].compact.join('&')}\" rel=\"previous\">#{prev_link}</a>"
         | 
| 14 | 
            +
                end
         | 
| 15 | 
            +
                if not page == count and count > 1
         | 
| 16 | 
            +
                  next_link = "<a href=\"?p=#{[page+1, qs].compact.join('&')}\" rel=\"next\">#{next_link}</a>"
         | 
| 17 | 
            +
                end
         | 
| 18 | 
            +
                [prev_link, next_link]
         | 
| 19 | 
            +
              end
         | 
| 20 | 
            +
             | 
| 21 | 
            +
            end
         | 
| 3 22 |  | 
| 4 23 | 
             
            module Togo
         | 
| 5 24 | 
             
              class Admin < Dispatch
         | 
| 6 25 |  | 
| 26 | 
            +
                include Helpers
         | 
| 27 | 
            +
             | 
| 7 28 | 
             
                before do
         | 
| 8 29 | 
             
                  @model = Togo.const_get(params[:model]) if params[:model]
         | 
| 9 30 | 
             
                end
         | 
| @@ -13,7 +34,14 @@ module Togo | |
| 13 34 | 
             
                end
         | 
| 14 35 |  | 
| 15 36 | 
             
                get '/:model' do
         | 
| 16 | 
            -
                  @ | 
| 37 | 
            +
                  @q = params[:q] || ''
         | 
| 38 | 
            +
                  @p = (params[:p] || 1).to_i
         | 
| 39 | 
            +
                  @limit = 50
         | 
| 40 | 
            +
                  @offset = @limit*(@p-1)
         | 
| 41 | 
            +
                  @count = (@q.blank? ? @model.all : @model.search(:q => @q)).size
         | 
| 42 | 
            +
                  @page_count = @count == 0 ? 1 : (@count.to_f/@limit.to_f).ceil
         | 
| 43 | 
            +
                  @criteria = {:limit => @limit, :offset => @offset}
         | 
| 44 | 
            +
                  @content = @q.blank? ? @model.all(@criteria) : @model.search(@criteria.merge(:q => @q))
         | 
| 17 45 | 
             
                  erb :index
         | 
| 18 46 | 
             
                end
         | 
| 19 47 |  | 
| @@ -63,6 +91,20 @@ module Togo | |
| 63 91 | 
             
                  end
         | 
| 64 92 | 
             
                end
         | 
| 65 93 |  | 
| 94 | 
            +
                get '/search/:model' do
         | 
| 95 | 
            +
                  @limit = params[:limit] || 10
         | 
| 96 | 
            +
                  @offset = params[:offset] || 0
         | 
| 97 | 
            +
                  @q = params[:q] || ''
         | 
| 98 | 
            +
                  @count = (@q.blank? ? @model.all : @model.search(:q => @q)).size
         | 
| 99 | 
            +
                  @criteria = {:offset => @offset, :limit => @limit}
         | 
| 100 | 
            +
                  if params[:ids]
         | 
| 101 | 
            +
                    @items = @model.all(@criteria.merge(:id => params[:ids].split(',').map(&:to_i)))
         | 
| 102 | 
            +
                  else
         | 
| 103 | 
            +
                    @items = @model.search(@criteria.merge(:q => @q))
         | 
| 104 | 
            +
                  end
         | 
| 105 | 
            +
                  {:count => @count, :results => @items}.to_json
         | 
| 106 | 
            +
                end
         | 
| 107 | 
            +
             | 
| 66 108 | 
             
              end
         | 
| 67 109 |  | 
| 68 110 | 
             
              # Subclass Rack Reloader to call DataMapper.auto_upgrade! on file reload
         | 
| 
            File without changes
         | 
| @@ -71,7 +71,7 @@ body { | |
| 71 71 | 
             
                position: absolute;
         | 
| 72 72 | 
             
                right: 0;
         | 
| 73 73 | 
             
                z-index: 3;
         | 
| 74 | 
            -
                width:  | 
| 74 | 
            +
                width: 220px;
         | 
| 75 75 | 
             
                top: 0;
         | 
| 76 76 | 
             
            }
         | 
| 77 77 | 
             
            #main #search-form fieldset {
         | 
| @@ -80,6 +80,12 @@ body { | |
| 80 80 | 
             
            #main #search-form input[type=text] {
         | 
| 81 81 | 
             
                width: 190px;
         | 
| 82 82 | 
             
            }
         | 
| 83 | 
            +
            #main #paging {
         | 
| 84 | 
            +
                position: absolute;
         | 
| 85 | 
            +
                right: 240px;
         | 
| 86 | 
            +
                z-index: 3;
         | 
| 87 | 
            +
                top: 22px;
         | 
| 88 | 
            +
            }
         | 
| 83 89 | 
             
            #main h1 a {
         | 
| 84 90 | 
             
                text-decoration: none;
         | 
| 85 91 | 
             
                color: #4076c7;
         | 
| @@ -105,7 +111,8 @@ body { | |
| 105 111 | 
             
                border-bottom: 1px solid #CECECE;
         | 
| 106 112 | 
             
                background: #FFF;
         | 
| 107 113 | 
             
            }
         | 
| 108 | 
            -
            #main table td.checkbox | 
| 114 | 
            +
            #main table td.checkbox,
         | 
| 115 | 
            +
            #main table th.checkbox {
         | 
| 109 116 | 
             
                width: 10px;
         | 
| 110 117 | 
             
                text-align: center;
         | 
| 111 118 | 
             
                padding-right: 0;
         | 
| @@ -154,11 +161,44 @@ body { | |
| 154 161 | 
             
            #main form fieldset.has_n  {
         | 
| 155 162 | 
             
                padding-right: 20px;
         | 
| 156 163 | 
             
            }
         | 
| 164 | 
            +
            #main form fieldset.belongs_to input[type=text],
         | 
| 165 | 
            +
            #main form fieldset.has_n input[type=text] {
         | 
| 166 | 
            +
                width: 190px;
         | 
| 167 | 
            +
            }
         | 
| 168 | 
            +
            #main form fieldset.belongs_to label,
         | 
| 169 | 
            +
            #main form fieldset.has_n label {
         | 
| 170 | 
            +
                float: left;
         | 
| 171 | 
            +
            }
         | 
| 172 | 
            +
            #main form fieldset.belongs_to div.search,
         | 
| 173 | 
            +
            #main form fieldset.has_n div.search {
         | 
| 174 | 
            +
                display: none;
         | 
| 175 | 
            +
                width: 100%;
         | 
| 176 | 
            +
                text-align: right;
         | 
| 177 | 
            +
            }
         | 
| 178 | 
            +
            #main form fieldset.belongs_to div.paging,
         | 
| 179 | 
            +
            #main form fieldset.has_n div.paging {
         | 
| 180 | 
            +
                float: right;
         | 
| 181 | 
            +
                margin-left: 10px;
         | 
| 182 | 
            +
                line-height: 30px;
         | 
| 183 | 
            +
            }
         | 
| 184 | 
            +
            #main form fieldset.belongs_to .checkbox,
         | 
| 185 | 
            +
            #main form fieldset.has_n .checkbox {
         | 
| 186 | 
            +
                display: none;
         | 
| 187 | 
            +
            }
         | 
| 188 | 
            +
            #main form fieldset.belongs_to .active,
         | 
| 189 | 
            +
            #main form fieldset.has_n .active {
         | 
| 190 | 
            +
                display: table-cell;
         | 
| 191 | 
            +
            }
         | 
| 157 192 | 
             
            #main form fieldset table th,
         | 
| 158 193 | 
             
            #main form fieldset table td {
         | 
| 159 194 | 
             
                padding: 5px;
         | 
| 160 195 | 
             
            }
         | 
| 161 | 
            -
             | 
| 196 | 
            +
            #main form fieldset table tfoot td {
         | 
| 197 | 
            +
                background: #DDD;
         | 
| 198 | 
            +
            }
         | 
| 199 | 
            +
            #main form fieldset table tfoot td button {
         | 
| 200 | 
            +
                float: right;
         | 
| 201 | 
            +
            }
         | 
| 162 202 | 
             
            div.actions {
         | 
| 163 203 | 
             
                position: absolute;
         | 
| 164 204 | 
             
                bottom: 0;
         | 
| @@ -0,0 +1,35 @@ | |
| 1 | 
            +
            var hasModified = false;
         | 
| 2 | 
            +
            function saveBeforeLeaving(e) {
         | 
| 3 | 
            +
                return 'You have made changes to this item. Are you sure you want to leave before saving?';
         | 
| 4 | 
            +
            }
         | 
| 5 | 
            +
            function checkForModifiedText(e) {
         | 
| 6 | 
            +
                if ((e.target.nodeName == 'INPUT' && e.target.getAttribute('type') == 'text') ||
         | 
| 7 | 
            +
                    e.target.nodeName == 'TEXTAREA') {
         | 
| 8 | 
            +
                    console.log('modified');
         | 
| 9 | 
            +
                    hasModified = (e.target.value != e.target.defaultValue);
         | 
| 10 | 
            +
                    setBeforeLeaving();
         | 
| 11 | 
            +
                }
         | 
| 12 | 
            +
            }
         | 
| 13 | 
            +
            function checkForModifiedInputs(e) {
         | 
| 14 | 
            +
                if (e.target.nodeName == 'SELECT' ||
         | 
| 15 | 
            +
                    (e.target.nodeName == 'INPUT' && e.target.getAttribute('type') == 'checkbox') ||
         | 
| 16 | 
            +
                    (e.target.nodeName == 'INPUT' && e.target.getAttribute('type') == 'radio')) {
         | 
| 17 | 
            +
                    console.log('modified');
         | 
| 18 | 
            +
                    hasModified = true;
         | 
| 19 | 
            +
                    setBeforeLeaving();
         | 
| 20 | 
            +
                }
         | 
| 21 | 
            +
            }
         | 
| 22 | 
            +
            function setBeforeLeaving() {
         | 
| 23 | 
            +
                if (hasModified) {
         | 
| 24 | 
            +
                    listen('beforeunload', window, saveBeforeLeaving);
         | 
| 25 | 
            +
                } else {
         | 
| 26 | 
            +
                    unlisten('beforeunload', window, saveBeforeLeaving);
         | 
| 27 | 
            +
                }
         | 
| 28 | 
            +
            }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            listen('keyup', window, checkForModifiedText);
         | 
| 31 | 
            +
            listen('click', window, checkForModifiedInputs);
         | 
| 32 | 
            +
            listen('submit', el('edit-form'), function() {
         | 
| 33 | 
            +
                hasModified = false;
         | 
| 34 | 
            +
                setBeforeLeaving();
         | 
| 35 | 
            +
            });
         | 
| @@ -1,6 +1,20 @@ | |
| 1 1 | 
             
            var app = (function() {
         | 
| 2 2 |  | 
| 3 3 | 
             
                var selectedItems = [];
         | 
| 4 | 
            +
                var searchInput = el('search-form').getElementsByTagName('input')[0];
         | 
| 5 | 
            +
                var searchInputDefaultValue = 'Search...';
         | 
| 6 | 
            +
             | 
| 7 | 
            +
                var searchInputHandler = function(action) {
         | 
| 8 | 
            +
                    return function(e) {
         | 
| 9 | 
            +
                        console.log(e);
         | 
| 10 | 
            +
                        if (this.value == '') {
         | 
| 11 | 
            +
                            this.value = searchInputDefaultValue;
         | 
| 12 | 
            +
                        }
         | 
| 13 | 
            +
                        if (this.value == searchInputDefaultValue && action == 'focus') {
         | 
| 14 | 
            +
                            this.value = '';
         | 
| 15 | 
            +
                        }
         | 
| 16 | 
            +
                    };
         | 
| 17 | 
            +
                };
         | 
| 4 18 |  | 
| 5 19 | 
             
                var handleMultiSelect = function(e) {
         | 
| 6 20 | 
             
                    var id = e.target.getAttribute('id').split('_')[1];
         | 
| @@ -22,6 +36,9 @@ var app = (function() { | |
| 22 36 | 
             
                    el('delete-list').value = selectedItems.join(',');
         | 
| 23 37 | 
             
                };
         | 
| 24 38 |  | 
| 39 | 
            +
                listen('focus', searchInput, searchInputHandler('focus'));
         | 
| 40 | 
            +
                listen('blur', searchInput, searchInputHandler('blur'));
         | 
| 41 | 
            +
             | 
| 25 42 | 
             
                var c = el('list-table').getElementsByTagName('input');
         | 
| 26 43 | 
             
                for (var i = 0; i < c.length; i++) {
         | 
| 27 44 | 
             
                    if (c[i].getAttribute('type') == 'checkbox') {
         | 
| @@ -1,3 +1,374 @@ | |
| 1 1 | 
             
            function el(node) {
         | 
| 2 2 | 
             
                return document.getElementById(node);
         | 
| 3 | 
            -
            }
         | 
| 3 | 
            +
            }
         | 
| 4 | 
            +
             | 
| 5 | 
            +
            function hasClass(node, name) {
         | 
| 6 | 
            +
                if (!node || !node.className) { return false; }
         | 
| 7 | 
            +
                return node.className.split(' ').some(function (n) { return n === name; });
         | 
| 8 | 
            +
            }
         | 
| 9 | 
            +
             | 
| 10 | 
            +
            function addClass(node, name) {
         | 
| 11 | 
            +
                if (!node) { return; }
         | 
| 12 | 
            +
                if (typeof(node.length) == 'undefined') { node = [node]; }
         | 
| 13 | 
            +
                for (var i = 0; i < node.length; i++) {
         | 
| 14 | 
            +
                    if (!hasClass(node[i], name)) {
         | 
| 15 | 
            +
                        var curClasses = node[i].className.split(' ');
         | 
| 16 | 
            +
                        curClasses.push(name);
         | 
| 17 | 
            +
                        node[i].className = curClasses.join(' ');
         | 
| 18 | 
            +
                    }
         | 
| 19 | 
            +
                }
         | 
| 20 | 
            +
            };
         | 
| 21 | 
            +
             | 
| 22 | 
            +
            function removeClass(node, name) {
         | 
| 23 | 
            +
                if (!node) { return; }
         | 
| 24 | 
            +
                if (typeof(node.length) == 'undefined') { node = [node]; }
         | 
| 25 | 
            +
                for (var i = 0; i < node.length; i++) {
         | 
| 26 | 
            +
                    node[i].className = node[i].className.replace(new RegExp("(\\s" + name + ")|(" + name + "\\s)|" + name), "");
         | 
| 27 | 
            +
                }
         | 
| 28 | 
            +
            }
         | 
| 29 | 
            +
             | 
| 30 | 
            +
            function toggleClass(node,name) {
         | 
| 31 | 
            +
                if (hasClass(node,name)) {
         | 
| 32 | 
            +
                    removeClass(node,name);
         | 
| 33 | 
            +
                } else {
         | 
| 34 | 
            +
                    addClass(node,name);
         | 
| 35 | 
            +
                }
         | 
| 36 | 
            +
            }
         | 
| 37 | 
            +
             | 
| 38 | 
            +
            var parseJSON = function(json) {
         | 
| 39 | 
            +
                if (window.JSON) {
         | 
| 40 | 
            +
                    parseJSON = function(json) {
         | 
| 41 | 
            +
                        return JSON.parse(json);
         | 
| 42 | 
            +
                    };
         | 
| 43 | 
            +
                } else {
         | 
| 44 | 
            +
                    parseJSON = function(json) {
         | 
| 45 | 
            +
                        return eval("(" + json + ")");
         | 
| 46 | 
            +
                    };
         | 
| 47 | 
            +
                }
         | 
| 48 | 
            +
                return parseJSON(json);
         | 
| 49 | 
            +
            };
         | 
| 50 | 
            +
             | 
| 51 | 
            +
            var ajaxObject = function() {
         | 
| 52 | 
            +
                if (window.ActiveXObject) {
         | 
| 53 | 
            +
                    ajaxObject = function() {
         | 
| 54 | 
            +
                        return new ActiveXObject('Microsoft.XMLHTTP');
         | 
| 55 | 
            +
                    };
         | 
| 56 | 
            +
                } else {
         | 
| 57 | 
            +
                    ajaxObject = function() {
         | 
| 58 | 
            +
                        return new XMLHttpRequest();
         | 
| 59 | 
            +
                    };
         | 
| 60 | 
            +
                }
         | 
| 61 | 
            +
                return ajaxObject();
         | 
| 62 | 
            +
            }
         | 
| 63 | 
            +
             | 
| 64 | 
            +
            var ajax = function(opts) {
         | 
| 65 | 
            +
                var req = ajaxObject();
         | 
| 66 | 
            +
                req.onreadystatechange = function() {
         | 
| 67 | 
            +
                    switch (req.readyState) {
         | 
| 68 | 
            +
                      case 1:
         | 
| 69 | 
            +
                        if (typeof opts.loading == 'function') { opts.loading(); }
         | 
| 70 | 
            +
                        break;
         | 
| 71 | 
            +
                      case 2:
         | 
| 72 | 
            +
                        if (typeof opts.loaded == 'function') { opts.loaded(); }
         | 
| 73 | 
            +
                        break;
         | 
| 74 | 
            +
                      case 4:
         | 
| 75 | 
            +
                        var res = req.responseText;
         | 
| 76 | 
            +
                        switch (req.status) {
         | 
| 77 | 
            +
                          case 200: if (typeof opts.success == 'function') { opts.success(res); } break;
         | 
| 78 | 
            +
                          case 404: if (typeof opts.error == 'function') { opts.error(); } break;
         | 
| 79 | 
            +
                          case 500: if (typeof opts.error == 'function') { opts.error(); } break;
         | 
| 80 | 
            +
                        };
         | 
| 81 | 
            +
                        if (typeof opts.complete == 'function') { opts.complete(); }
         | 
| 82 | 
            +
                        break;
         | 
| 83 | 
            +
                    };
         | 
| 84 | 
            +
                };
         | 
| 85 | 
            +
                req.open((opts.type || 'get').toUpperCase(),opts.url,true);
         | 
| 86 | 
            +
                req.send(null);
         | 
| 87 | 
            +
            };
         | 
| 88 | 
            +
             | 
| 89 | 
            +
            var listen = function(evnt, elem, func) {
         | 
| 90 | 
            +
                if (elem.addEventListener) {
         | 
| 91 | 
            +
                    listen = function(evnt, elem, func) {
         | 
| 92 | 
            +
                        elem.addEventListener(evnt,func,false);
         | 
| 93 | 
            +
                    };
         | 
| 94 | 
            +
                } else if (elem.attachEvent) {
         | 
| 95 | 
            +
                    listen = function(evnt, elem, func) {
         | 
| 96 | 
            +
                        elem.attachEvent('on' + evnt, func);
         | 
| 97 | 
            +
                    };
         | 
| 98 | 
            +
                }
         | 
| 99 | 
            +
                listen(evnt,elem,func);
         | 
| 100 | 
            +
            };
         | 
| 101 | 
            +
             | 
| 102 | 
            +
            var unlisten = function(evnt, elem, func) {
         | 
| 103 | 
            +
                if (elem.removeEventListener) {
         | 
| 104 | 
            +
                    unlisten = function(evnt, elem, func) {
         | 
| 105 | 
            +
                        elem.removeEventListener(evnt,func,false);
         | 
| 106 | 
            +
                    };
         | 
| 107 | 
            +
                } else if (elem.attachEvent) {
         | 
| 108 | 
            +
                    unlisten = function(evnt, elem, func) {
         | 
| 109 | 
            +
                        elem.detachEvent('on' + evnt, func);
         | 
| 110 | 
            +
                    };
         | 
| 111 | 
            +
                }
         | 
| 112 | 
            +
                unlisten(evnt,elem,func);
         | 
| 113 | 
            +
            };
         | 
| 114 | 
            +
             | 
| 115 | 
            +
            var getElementsByClassName = function(classname, el) {
         | 
| 116 | 
            +
                if (document.getElementsByClassName) {
         | 
| 117 | 
            +
                    getElementsByClassName = function(classname, el) {
         | 
| 118 | 
            +
                        if (!el) { el = document; }
         | 
| 119 | 
            +
                        return el.getElementsByClassName(classname);
         | 
| 120 | 
            +
                    };
         | 
| 121 | 
            +
                } else {
         | 
| 122 | 
            +
                    getElementsByClassName = function(classname, el) {
         | 
| 123 | 
            +
                        var classElements = new Array();
         | 
| 124 | 
            +
                        if (!el) { el = document; }
         | 
| 125 | 
            +
                        var els = el.getElementsByTagName('*');
         | 
| 126 | 
            +
                        var elsLen = els.length;
         | 
| 127 | 
            +
                        var pattern = new RegExp("(^|\\s)"+classname+"(\\s|$)");
         | 
| 128 | 
            +
                        for (var i = 0, j = 0; i < elsLen; i++) {
         | 
| 129 | 
            +
                            if ( pattern.test(els[i].className) ) {
         | 
| 130 | 
            +
                                classElements[j] = els[i];
         | 
| 131 | 
            +
                                j++;
         | 
| 132 | 
            +
                            }
         | 
| 133 | 
            +
                        }
         | 
| 134 | 
            +
                        return classElements;
         | 
| 135 | 
            +
                    };
         | 
| 136 | 
            +
                }
         | 
| 137 | 
            +
                return getElementsByClassName(classname, el);
         | 
| 138 | 
            +
            };
         | 
| 139 | 
            +
             | 
| 140 | 
            +
            var AssociationManager = function(opts) {
         | 
| 141 | 
            +
                this.relationship = opts.relationship;
         | 
| 142 | 
            +
                this.name = opts.name;
         | 
| 143 | 
            +
                this.model = opts.model;
         | 
| 144 | 
            +
                this.fields = opts.fields;
         | 
| 145 | 
            +
                this.count = opts.count;
         | 
| 146 | 
            +
                this.pageCount = Math.ceil(this.count/10);
         | 
| 147 | 
            +
                this.pageNumber = 1;
         | 
| 148 | 
            +
                this.relatedIds = {};
         | 
| 149 | 
            +
             | 
| 150 | 
            +
                for (var i = 0, len = opts.related_ids.length; i < len; i++) {
         | 
| 151 | 
            +
                    this.relatedIds[opts.related_ids[i]] = true;
         | 
| 152 | 
            +
                }
         | 
| 153 | 
            +
             | 
| 154 | 
            +
                this.fieldset = el('property-' + this.name);
         | 
| 155 | 
            +
             | 
| 156 | 
            +
                this.pageNumberField = getElementsByClassName('page-number',this.fieldset)[0];
         | 
| 157 | 
            +
                this.pageCountField = getElementsByClassName('page-count',this.fieldset)[0];
         | 
| 158 | 
            +
             | 
| 159 | 
            +
                this.search = getElementsByClassName('search', this.fieldset)[0];
         | 
| 160 | 
            +
                this.searchInput = this.search.getElementsByTagName('input')[0];
         | 
| 161 | 
            +
                this.defaultSearchValue = this.searchInput.value;
         | 
| 162 | 
            +
             | 
| 163 | 
            +
                listen('keydown',this.search,this.searchHandler(this));
         | 
| 164 | 
            +
                listen('focus', this.searchInput,this.searchInputHandler(this, 'focus'));
         | 
| 165 | 
            +
                listen('blur', this.searchInput,this.searchInputHandler(this, 'blur'));
         | 
| 166 | 
            +
             | 
| 167 | 
            +
                this.table = this.fieldset.getElementsByTagName('table')[0];
         | 
| 168 | 
            +
                this.tbody = this.table.getElementsByTagName('tbody')[0];
         | 
| 169 | 
            +
                this.associatedCountDisplay = getElementsByClassName('associated-count-display',this.table)[0];
         | 
| 170 | 
            +
                this.associatedCount = getElementsByClassName('associated-count',this.table)[0];
         | 
| 171 | 
            +
             | 
| 172 | 
            +
                listen('click',this.table,this.tableHandler(this));
         | 
| 173 | 
            +
                this.modifyButton = getElementsByClassName('association-modify', this.table)[0];
         | 
| 174 | 
            +
                listen('click',this.modifyButton,this.modifyHandler(this));
         | 
| 175 | 
            +
             | 
| 176 | 
            +
                this.paging = getElementsByClassName('paging',this.fieldset)[0];
         | 
| 177 | 
            +
                listen('click',this.paging,this.pagingHandler(this));
         | 
| 178 | 
            +
             | 
| 179 | 
            +
                this.selectorType = this.relationship == 'belongs_to' ? 'radio' : 'checkbox';
         | 
| 180 | 
            +
             | 
| 181 | 
            +
                this.states = {
         | 
| 182 | 
            +
                    MODIFY: 'modify',
         | 
| 183 | 
            +
                    SEARCH: 'search',
         | 
| 184 | 
            +
                    PAGING: 'paging'
         | 
| 185 | 
            +
                };
         | 
| 186 | 
            +
            };
         | 
| 187 | 
            +
             | 
| 188 | 
            +
            // Shows/hides all checkboxes table cells, called when 'modify' is clicked
         | 
| 189 | 
            +
            AssociationManager.prototype.toggleCheckboxes = function() {
         | 
| 190 | 
            +
                var checkboxes = getElementsByClassName('checkbox', this.table),
         | 
| 191 | 
            +
                    func = (this.currentState == this.states.MODIFY) ? addClass : removeClass;
         | 
| 192 | 
            +
                for (var i = 0, len = checkboxes.length; i < len; i++) {
         | 
| 193 | 
            +
                    func(checkboxes[i],'active');
         | 
| 194 | 
            +
                }
         | 
| 195 | 
            +
            };
         | 
| 196 | 
            +
             | 
| 197 | 
            +
            // Adjust the list of related content IDs, called after clicking 'done'
         | 
| 198 | 
            +
            AssociationManager.prototype.adjustRelatedList = function() {
         | 
| 199 | 
            +
                var selected = [];
         | 
| 200 | 
            +
                for (k in this.relatedIds) {
         | 
| 201 | 
            +
                    if (this.relatedIds[k] == true) {
         | 
| 202 | 
            +
                        selected.push(k);
         | 
| 203 | 
            +
                    }
         | 
| 204 | 
            +
                }
         | 
| 205 | 
            +
                getElementsByClassName('related_ids', this.fieldset)[0].value = selected.join(',');
         | 
| 206 | 
            +
                var handler = this;
         | 
| 207 | 
            +
                ajax({
         | 
| 208 | 
            +
                    url: "/search/" + this.model + "?ids=" + selected.slice(0,10).join(','),
         | 
| 209 | 
            +
                    success: function(data) {
         | 
| 210 | 
            +
                        handler.searchResultsHandler(handler)(data);
         | 
| 211 | 
            +
                        handler.count = selected.length;
         | 
| 212 | 
            +
                        if (selected.length < 10) {
         | 
| 213 | 
            +
                            handler.associatedCount.innerHTML = 0;
         | 
| 214 | 
            +
                            handler.associatedCountDisplay.style.display = 'none';
         | 
| 215 | 
            +
                        } else {
         | 
| 216 | 
            +
                            handler.associatedCount.innerHTML = selected.length-10;
         | 
| 217 | 
            +
                            handler.associatedCountDisplay.style.display = 'block';
         | 
| 218 | 
            +
                        }
         | 
| 219 | 
            +
                    }
         | 
| 220 | 
            +
                });
         | 
| 221 | 
            +
            };
         | 
| 222 | 
            +
             | 
| 223 | 
            +
            AssociationManager.prototype.setToModifyState = function() {
         | 
| 224 | 
            +
                console.log('setToModifyState');
         | 
| 225 | 
            +
                ajax({
         | 
| 226 | 
            +
                    url: "/search/" + this.model,
         | 
| 227 | 
            +
                    success: this.searchResultsHandler(this)
         | 
| 228 | 
            +
                });
         | 
| 229 | 
            +
            };
         | 
| 230 | 
            +
             | 
| 231 | 
            +
            AssociationManager.prototype.addLinkToValue = function(id, value, linkIt) {
         | 
| 232 | 
            +
                if (!linkIt) { return value; }
         | 
| 233 | 
            +
                return '<a href="/edit/' + this.model + '/' + id + '">' + value + '</a>';
         | 
| 234 | 
            +
            };
         | 
| 235 | 
            +
             | 
| 236 | 
            +
            AssociationManager.prototype.generateRow = function(data) {
         | 
| 237 | 
            +
                var out = '<tr><td class="checkbox active">' +
         | 
| 238 | 
            +
                    '<input type="' + this.selectorType + '" value="' + data.id + '" id="selection_' + this.model + '_' + data.id + '"';
         | 
| 239 | 
            +
                if (this.relatedIds[data.id]) {
         | 
| 240 | 
            +
                    out += ' checked="checked"';
         | 
| 241 | 
            +
                }
         | 
| 242 | 
            +
                if (this.selectorType == 'radio') {
         | 
| 243 | 
            +
                    out += ' name=' + this.model + '_' + this.name + '_selection';
         | 
| 244 | 
            +
                }
         | 
| 245 | 
            +
                out += ' /></td>';
         | 
| 246 | 
            +
                var linkIt = true;
         | 
| 247 | 
            +
                for (var i = 0, len = this.fields.length; i < len; i++) {
         | 
| 248 | 
            +
                    if (i == 1) { linkIt = false; }
         | 
| 249 | 
            +
                    out += '<td>' + this.addLinkToValue(data.id, (data[this.fields[i]] == null ? '-' : data[this.fields[i]]), linkIt) + '</td>';
         | 
| 250 | 
            +
                }
         | 
| 251 | 
            +
                out += '</tr>';
         | 
| 252 | 
            +
                return out;
         | 
| 253 | 
            +
            };
         | 
| 254 | 
            +
             | 
| 255 | 
            +
            AssociationManager.prototype.clearTable = function() {
         | 
| 256 | 
            +
                while (this.tbody.hasChildNodes()) {
         | 
| 257 | 
            +
                    this.tbody.removeChild(this.tbody.lastChild);
         | 
| 258 | 
            +
                }
         | 
| 259 | 
            +
            };
         | 
| 260 | 
            +
             | 
| 261 | 
            +
            // Handles clicking next/previous on paging
         | 
| 262 | 
            +
            AssociationManager.prototype.pagingHandler = function(handler) {
         | 
| 263 | 
            +
                return function(e) {
         | 
| 264 | 
            +
                    e.preventDefault();
         | 
| 265 | 
            +
                    if (e.target.nodeName == 'A') {
         | 
| 266 | 
            +
                        var q = '';
         | 
| 267 | 
            +
                        if (handler.searchInput.value != handler.defaultSearchValue) {
         | 
| 268 | 
            +
                            q = handler.searchInput.value;
         | 
| 269 | 
            +
                        }
         | 
| 270 | 
            +
                        var direction = e.target.getAttribute('rel') == 'next' ? 1 : -1;
         | 
| 271 | 
            +
                        handler.pageNumber = handler.pageNumber-(direction*-1);
         | 
| 272 | 
            +
                        if (handler.pageNumber <= 0) {
         | 
| 273 | 
            +
                            handler.pageNumber = 1;
         | 
| 274 | 
            +
                        } else if (handler.pageNumber > handler.pageCount) {
         | 
| 275 | 
            +
                            handler.pageNumber = handler.pageCount;
         | 
| 276 | 
            +
                        }
         | 
| 277 | 
            +
                        handler.pageNumberField.innerHTML = handler.pageNumber;
         | 
| 278 | 
            +
                        var offset = (handler.pageNumber-1)*10;
         | 
| 279 | 
            +
                        ajax({
         | 
| 280 | 
            +
                            url: "/search/" + handler.model + "?q=" + q + "&limit=10&offset=" + offset,
         | 
| 281 | 
            +
                            success: handler.searchResultsHandler(handler)
         | 
| 282 | 
            +
                        });
         | 
| 283 | 
            +
                    }
         | 
| 284 | 
            +
                };
         | 
| 285 | 
            +
            };
         | 
| 286 | 
            +
             | 
| 287 | 
            +
            // Handles clicks on the table, catching checkbox clicks
         | 
| 288 | 
            +
            AssociationManager.prototype.tableHandler = function(handler) {
         | 
| 289 | 
            +
                return function(e) {
         | 
| 290 | 
            +
                    if (e.target.getAttribute('type') == 'checkbox') {
         | 
| 291 | 
            +
                        handler.relatedIds[e.target.value] = e.target.checked;
         | 
| 292 | 
            +
                    }
         | 
| 293 | 
            +
                    if (e.target.getAttribute('type') == 'radio') {
         | 
| 294 | 
            +
                        handler.relatedIds = {};
         | 
| 295 | 
            +
                        handler.relatedIds[e.target.value] = e.target.checked;
         | 
| 296 | 
            +
                        console.log(handler.relatedIds);
         | 
| 297 | 
            +
                    }
         | 
| 298 | 
            +
                };
         | 
| 299 | 
            +
            };
         | 
| 300 | 
            +
             | 
| 301 | 
            +
            // Handlers clicking on the "modify" button on association table
         | 
| 302 | 
            +
            AssociationManager.prototype.modifyHandler = function(handler) {
         | 
| 303 | 
            +
                return function(e) {
         | 
| 304 | 
            +
                    e.preventDefault();
         | 
| 305 | 
            +
                    switch (handler.currentState) {
         | 
| 306 | 
            +
                      case handler.states.MODIFY:
         | 
| 307 | 
            +
                        handler.modifyButton.innerHTML = 'Modify';
         | 
| 308 | 
            +
                        handler.search.style.display = 'none';
         | 
| 309 | 
            +
                        handler.associatedCountDisplay.style.display = 'table-cell';
         | 
| 310 | 
            +
                        handler.currentState = null;
         | 
| 311 | 
            +
                        handler.pageNumber = 1;
         | 
| 312 | 
            +
                        handler.searchInput.value = handler.defaultSearchValue;
         | 
| 313 | 
            +
                        handler.adjustRelatedList();
         | 
| 314 | 
            +
                        break;
         | 
| 315 | 
            +
                    default:
         | 
| 316 | 
            +
                        handler.modifyButton.innerHTML = 'Done';
         | 
| 317 | 
            +
                        handler.search.style.display = 'block';
         | 
| 318 | 
            +
                        handler.associatedCountDisplay.style.display = 'none';
         | 
| 319 | 
            +
                        handler.currentState = handler.states.MODIFY;
         | 
| 320 | 
            +
                        handler.setToModifyState();
         | 
| 321 | 
            +
                    }
         | 
| 322 | 
            +
                };
         | 
| 323 | 
            +
            };
         | 
| 324 | 
            +
             | 
| 325 | 
            +
            // Handles search results coming back from paging and searching
         | 
| 326 | 
            +
            AssociationManager.prototype.searchResultsHandler = function(handler) {
         | 
| 327 | 
            +
                return function(data) {
         | 
| 328 | 
            +
                    data = parseJSON(data);
         | 
| 329 | 
            +
                    handler.clearTable();
         | 
| 330 | 
            +
                    handler.count = data.count;
         | 
| 331 | 
            +
                    handler.pageCount = Math.ceil(handler.count/10);
         | 
| 332 | 
            +
                    handler.pageCountField.innerHTML = handler.pageCount;
         | 
| 333 | 
            +
                    handler.pageNumberField.innerHTML = handler.pageNumber;
         | 
| 334 | 
            +
                    console.log(data);
         | 
| 335 | 
            +
                    if (data.results.length > 0) {
         | 
| 336 | 
            +
                        var out = [];
         | 
| 337 | 
            +
                        for (var i = 0, len = data.results.length; i < len; i++) {
         | 
| 338 | 
            +
                            out.push(handler.generateRow(data.results[i]));
         | 
| 339 | 
            +
                        }
         | 
| 340 | 
            +
                        handler.tbody.innerHTML = out.join('');
         | 
| 341 | 
            +
                    } else {
         | 
| 342 | 
            +
                    }
         | 
| 343 | 
            +
                    handler.toggleCheckboxes();
         | 
| 344 | 
            +
                };
         | 
| 345 | 
            +
            };
         | 
| 346 | 
            +
             | 
| 347 | 
            +
            // Handlers search box input
         | 
| 348 | 
            +
            AssociationManager.prototype.searchHandler = function(handler) {
         | 
| 349 | 
            +
                return function(e) {
         | 
| 350 | 
            +
                    if (e.keyCode == 13) {
         | 
| 351 | 
            +
                        e.preventDefault();
         | 
| 352 | 
            +
                        var q = handler.searchInput.value;
         | 
| 353 | 
            +
                        handler.pageNumber = 1;
         | 
| 354 | 
            +
                        var offset = (handler.pageNumber-1)*10;
         | 
| 355 | 
            +
                        ajax({
         | 
| 356 | 
            +
                            url: "/search/" + handler.model + "?q=" + q + "&limit=10&offset=" + offset,
         | 
| 357 | 
            +
                            success: handler.searchResultsHandler(handler)
         | 
| 358 | 
            +
                        });
         | 
| 359 | 
            +
                        return false;
         | 
| 360 | 
            +
                    }
         | 
| 361 | 
            +
                    return true;
         | 
| 362 | 
            +
                };
         | 
| 363 | 
            +
            };
         | 
| 364 | 
            +
             | 
| 365 | 
            +
            AssociationManager.prototype.searchInputHandler = function(handler, action) {
         | 
| 366 | 
            +
                return function(e) {
         | 
| 367 | 
            +
                    if (this.value == '') {
         | 
| 368 | 
            +
                        this.value = handler.defaultSearchValue;
         | 
| 369 | 
            +
                    }
         | 
| 370 | 
            +
                    if (this.value == handler.defaultSearchValue && action == 'focus') {
         | 
| 371 | 
            +
                        this.value = '';
         | 
| 372 | 
            +
                    }
         | 
| 373 | 
            +
                };
         | 
| 374 | 
            +
            };
         | 
| @@ -1,9 +1,9 @@ | |
| 1 1 | 
             
            <h1><a href="/<%= @model.name %>"><%= @model.display_name %></a> / Edit</h1>
         | 
| 2 | 
            -
            <form action="/update/<%= @model.name %>/<%= @content.id %>" method="post">
         | 
| 2 | 
            +
            <form action="/update/<%= @model.name %>/<%= @content.id %>" method="post" id="edit-form">
         | 
| 3 3 | 
             
              <div class="wrapper">
         | 
| 4 4 | 
             
                <% if @errors %><div id="save_errors" class="errors"><%= @errors %></div><% end %>
         | 
| 5 5 | 
             
                <% @model.form_properties.each do |p| %>
         | 
| 6 | 
            -
                <fieldset id=" | 
| 6 | 
            +
                <fieldset id="property-<%= p.name %>" class="<%= @model.field_class_for(p) %>">
         | 
| 7 7 | 
             
                  <%= @model.form_for(p,@content) %>
         | 
| 8 8 | 
             
                </fieldset>
         | 
| 9 9 | 
             
                <% end %>
         | 
| @@ -18,3 +18,4 @@ | |
| 18 18 | 
             
                <button type="submit" id="delete-button">Delete</button>
         | 
| 19 19 | 
             
              </form>
         | 
| 20 20 | 
             
            </div>
         | 
| 21 | 
            +
            <script type="text/javascript" src="/js/edit.js"></script>
         | 
| @@ -1,7 +1,16 @@ | |
| 1 1 | 
             
            <h1><%= @model.display_name %></h1>
         | 
| 2 | 
            +
            <div id="paging">
         | 
| 3 | 
            +
                <%= @count %> items
         | 
| 4 | 
            +
              <% if @page_count > 1 %>
         | 
| 5 | 
            +
                Page <span class="page-number"><%= @p %></span> of <span class="page-count"><%= @page_count %></span>
         | 
| 6 | 
            +
                <% prev_link, next_link = paging_links(@p, @page_count, :q => params[:q]) %>
         | 
| 7 | 
            +
                <%= prev_link %>
         | 
| 8 | 
            +
                <%= next_link %>
         | 
| 9 | 
            +
              <% end %>
         | 
| 10 | 
            +
            </div>
         | 
| 2 11 | 
             
            <form method="get" id="search-form">
         | 
| 3 12 | 
             
              <fieldset>
         | 
| 4 | 
            -
                <input type="text" name="q" value="<%= params[:q]  | 
| 13 | 
            +
                <input type="text" name="q" value="<%= params[:q].blank? ? 'Search...' : params[:q] %>" size="40" />
         | 
| 5 14 | 
             
              </fieldset>
         | 
| 6 15 | 
             
            </form>
         | 
| 7 16 | 
             
            <div class="wrapper">
         | 
| @@ -38,7 +47,7 @@ | |
| 38 47 | 
             
              <a href="/new/<%= @model.name %>" class="action new" id="new-button">New</a>
         | 
| 39 48 | 
             
              <form action="/delete/<%= @model.name %>" method="post" id="delete-form">
         | 
| 40 49 | 
             
                <input type="hidden" id="delete-list" name="id" value="" />
         | 
| 41 | 
            -
                <button type="submit" id="delete-button" disabled="disabled">Delete</button>
         | 
| 50 | 
            +
                <button type="submit" id="delete-button" disabled="disabled" onclick="return confirm('Are you sure you want to delete these items?');">Delete</button>
         | 
| 42 51 | 
             
              </form>
         | 
| 43 52 | 
             
            </div>
         | 
| 44 53 | 
             
            <script type="text/javascript" src="/js/index.js"></script>
         | 
| @@ -43,13 +43,13 @@ module Togo | |
| 43 43 | 
             
                    @response.status = 404
         | 
| 44 44 | 
             
                    @response.write("404 Not Found")
         | 
| 45 45 | 
             
                  else
         | 
| 46 | 
            -
             | 
| 46 | 
            +
                    begin
         | 
| 47 47 | 
             
                      __before if defined? __before
         | 
| 48 48 | 
             
                      @response.write(send(method))
         | 
| 49 | 
            -
             | 
| 50 | 
            -
             | 
| 51 | 
            -
             | 
| 52 | 
            -
             | 
| 49 | 
            +
                    rescue => detail
         | 
| 50 | 
            +
                      @response.status = 500
         | 
| 51 | 
            +
                     @response.write(["Error: #{detail}",$!.backtrace.join("<br />\n")].join("<br />\n"))
         | 
| 52 | 
            +
                    end
         | 
| 53 53 | 
             
                  end
         | 
| 54 54 | 
             
                end
         | 
| 55 55 |  | 
| @@ -112,8 +112,11 @@ module Togo | |
| 112 112 | 
             
                    method_name = "__#{type.downcase}#{clean_path(route)}"
         | 
| 113 113 | 
             
                    k = []
         | 
| 114 114 | 
             
                    p = route.gsub(/(:\w+)/){|m| k << m[1..-1]; "([^?/#&]+)"}
         | 
| 115 | 
            -
                     | 
| 116 | 
            -
                     | 
| 115 | 
            +
                    r = [/^#{p}$/,k,method_name]
         | 
| 116 | 
            +
                    if not routes[type].rindex{|t| t[0] == r[0]}
         | 
| 117 | 
            +
                      routes[type].push(r)
         | 
| 118 | 
            +
                      define_method(method_name, &block)
         | 
| 119 | 
            +
                    end
         | 
| 117 120 | 
             
                  end
         | 
| 118 121 |  | 
| 119 122 | 
             
                  def before(&block)
         | 
| @@ -136,6 +139,13 @@ module Togo | |
| 136 139 | 
             
                      builder.use Rack::ShowExceptions
         | 
| 137 140 | 
             
                      builder.use opts[:reloader] if opts[:reloader]
         | 
| 138 141 | 
             
                    end
         | 
| 142 | 
            +
                    if opts[:routes] and opts[:routes].is_a?(Array)
         | 
| 143 | 
            +
                      opts[:routes].each do |r|
         | 
| 144 | 
            +
                        send(r[:type].to_sym, r[:path]) do
         | 
| 145 | 
            +
                          erb r[:template].to_sym
         | 
| 146 | 
            +
                        end
         | 
| 147 | 
            +
                      end
         | 
| 148 | 
            +
                    end
         | 
| 139 149 | 
             
                    builder.use Rack::Static, :urls => opts[:static_urls], :root => opts[:public_path]
         | 
| 140 150 | 
             
                    builder.run new(opts)
         | 
| 141 151 | 
             
                    if opts[:standalone]
         | 
    
        data/lib/togo/model/model.rb
    CHANGED
    
    | @@ -51,6 +51,11 @@ module Togo | |
| 51 51 |  | 
| 52 52 | 
             
                    def stage_content(content,attrs)
         | 
| 53 53 | 
             
                      content.attributes = properties.inject({}){|m,p| attrs[p.name.to_sym] ? m.merge!(p.name.to_sym => attrs[p.name.to_sym]) : m}
         | 
| 54 | 
            +
                      relationships.each do |r| 
         | 
| 55 | 
            +
                        key = "related_#{r[0]}".to_sym
         | 
| 56 | 
            +
                        next if not attrs[key] or attrs[key] == 'unset'
         | 
| 57 | 
            +
                        content = RelationshipManager.new(content, r, :ids => attrs[key]).relate
         | 
| 58 | 
            +
                      end
         | 
| 54 59 | 
             
                      content
         | 
| 55 60 | 
             
                    end
         | 
| 56 61 |  | 
| @@ -68,12 +73,16 @@ module Togo | |
| 68 73 |  | 
| 69 74 | 
             
                    def search(opts)
         | 
| 70 75 | 
             
                      q = "%#{opts[:q].gsub(/\s+/,'%')}%"
         | 
| 76 | 
            +
                      limit = opts[:limit]
         | 
| 77 | 
            +
                      offset = opts[:offset]
         | 
| 71 78 | 
             
                      conditions, values = [], []
         | 
| 72 79 | 
             
                      search_properties.each{|l|
         | 
| 73 80 | 
             
                        conditions << "#{l.name} like ?"
         | 
| 74 81 | 
             
                        values << q
         | 
| 75 82 | 
             
                      }
         | 
| 76 | 
            -
                       | 
| 83 | 
            +
                      params = {:conditions => [conditions.join(' OR ')] + values}
         | 
| 84 | 
            +
                      params.merge!(:limit => limit.to_i, :offset => offset.to_i) if limit and offset
         | 
| 85 | 
            +
                      all(params)
         | 
| 77 86 | 
             
                    end
         | 
| 78 87 |  | 
| 79 88 | 
             
                    def field_class_for(property)
         | 
| @@ -89,7 +98,7 @@ module Togo | |
| 89 98 | 
             
                    def type_from_property(property)
         | 
| 90 99 | 
             
                      case property
         | 
| 91 100 | 
             
                        when ::DataMapper::Property
         | 
| 92 | 
            -
                          Extlib::Inflection.demodulize(property.type).downcase
         | 
| 101 | 
            +
                          Extlib::Inflection.demodulize(property.type || property.class).downcase # type seems to be deprecated in 1.0
         | 
| 93 102 | 
             
                        when ::DataMapper::Associations::ManyToOne::Relationship
         | 
| 94 103 | 
             
                          'belongs_to'
         | 
| 95 104 | 
             
                        when ::DataMapper::Associations::OneToMany::Relationship
         | 
| @@ -112,12 +121,50 @@ module Togo | |
| 112 121 | 
             
                    end
         | 
| 113 122 |  | 
| 114 123 | 
             
                    def search_properties
         | 
| 124 | 
            +
                      # Support dm 0.10.x and 1.x by checking for deprecated(?) types
         | 
| 115 125 | 
             
                      only_properties = [String, ::DataMapper::Types::Text]
         | 
| 116 | 
            -
                       | 
| 126 | 
            +
                      begin # rescue exception when not using dm-core 1.0, these don't exist in the 0.10.x series
         | 
| 127 | 
            +
                        only_properties.concat([::DataMapper::Property::String, ::DataMapper::Property::Text])
         | 
| 128 | 
            +
                      rescue
         | 
| 129 | 
            +
                      end
         | 
| 130 | 
            +
                      properties.select{|p| only_properties.include?(p.type || p.class)} # type seems to be depracated in 1.0
         | 
| 117 131 | 
             
                    end
         | 
| 118 132 |  | 
| 119 133 | 
             
                  end
         | 
| 120 134 |  | 
| 121 135 | 
             
                end # Model
         | 
| 136 | 
            +
             | 
| 137 | 
            +
                class RelationshipManager
         | 
| 138 | 
            +
                  def initialize(content, relationship, opts = {})
         | 
| 139 | 
            +
                    @content = content
         | 
| 140 | 
            +
                    @relationship = relationship[1]
         | 
| 141 | 
            +
                    @relationship_name = relationship[0]
         | 
| 142 | 
            +
                    @ids = (opts[:ids] || '').split(',').map(&:to_i)
         | 
| 143 | 
            +
                    define_values
         | 
| 144 | 
            +
                  end
         | 
| 145 | 
            +
             | 
| 146 | 
            +
                  def relate
         | 
| 147 | 
            +
                    @content.send("#{@relationship_name}=", find_for_assignment)
         | 
| 148 | 
            +
                    @content
         | 
| 149 | 
            +
                  end
         | 
| 150 | 
            +
                  
         | 
| 151 | 
            +
                  def define_values
         | 
| 152 | 
            +
                    case @relationship
         | 
| 153 | 
            +
                      when ::DataMapper::Associations::ManyToOne::Relationship
         | 
| 154 | 
            +
                        @unset_value = nil
         | 
| 155 | 
            +
                        @related_model = @relationship.parent_model
         | 
| 156 | 
            +
                        @find_op = Proc.new{|p| p.get(@ids.first)}
         | 
| 157 | 
            +
                      when ::DataMapper::Associations::OneToMany::Relationship
         | 
| 158 | 
            +
                        @unset_value = []
         | 
| 159 | 
            +
                        @related_model = @relationship.child_model
         | 
| 160 | 
            +
                        @find_op = Proc.new{|p| p.all(:id => @ids)}
         | 
| 161 | 
            +
                    end
         | 
| 162 | 
            +
                  end
         | 
| 163 | 
            +
             | 
| 164 | 
            +
                  def find_for_assignment
         | 
| 165 | 
            +
                    return @unset_value if @ids.blank?
         | 
| 166 | 
            +
                    @find_op.call(@related_model)
         | 
| 167 | 
            +
                  end
         | 
| 168 | 
            +
                end # RelationshipManager
         | 
| 122 169 | 
             
              end # DataMapper
         | 
| 123 170 | 
             
            end # Togo
         | 
| @@ -1,29 +1,65 @@ | |
| 1 | 
            -
            <%  | 
| 2 | 
            -
            <%  | 
| 3 | 
            -
             | 
| 4 | 
            -
             | 
| 1 | 
            +
            <% content_count = content.send(property.name.to_sym).blank? ? 0 : 1 %>
         | 
| 2 | 
            +
            <% related_content = [content.send(property.name.to_sym)].compact %>
         | 
| 3 | 
            +
            <% related_ids = related_content.map(&:id) %>
         | 
| 4 | 
            +
            <% related_model = property.parent_model %>
         | 
| 5 | 
            +
            <input type="hidden" name="related_<%= property.name %>" class="related_ids" value="unset" />
         | 
| 6 | 
            +
            <label for="<%= property.name %>"><%= related_model.display_name %></label>
         | 
| 7 | 
            +
            <div class="search" id="search-<%= related_model.name %>">
         | 
| 8 | 
            +
              <input type="text" name="search-<%= property.name %>" value="Search..." />
         | 
| 9 | 
            +
              <div class="paging">
         | 
| 10 | 
            +
                Page <span class="page-number">1</span> of <span class="page-count"><%= (content_count/10).ceil %></span>
         | 
| 11 | 
            +
                <a href="#" rel="prev">« Previous</a> <a href="#" rel="next">Next »</a>
         | 
| 12 | 
            +
              </div>
         | 
| 13 | 
            +
            </div>
         | 
| 14 | 
            +
            <table>
         | 
| 5 15 | 
             
              <thead>
         | 
| 6 16 | 
             
                <tr>
         | 
| 7 | 
            -
                   | 
| 8 | 
            -
                  <th></th>
         | 
| 9 | 
            -
                  <% end %>
         | 
| 17 | 
            +
                  <th class="checkbox"></th>
         | 
| 10 18 | 
             
                  <% related_model.list_properties.each do |p| %>
         | 
| 11 19 | 
             
                  <th><%= p.name.to_s.humanize.titleize %></th>
         | 
| 12 20 | 
             
                  <% end %>
         | 
| 21 | 
            +
                  <% if related_model.list_properties.size < 2 %>
         | 
| 22 | 
            +
                  <th></th>
         | 
| 23 | 
            +
                  <% end %>
         | 
| 13 24 | 
             
                </tr>
         | 
| 14 25 | 
             
              </thead>
         | 
| 26 | 
            +
              <tfoot>
         | 
| 27 | 
            +
                <tr>
         | 
| 28 | 
            +
                  <td class="checkbox"></td>
         | 
| 29 | 
            +
                  <td>
         | 
| 30 | 
            +
                    <span class="associated-count-display" style="<%= (content_count == 0 or content_count < 10) ? 'display: none;' : '' %>">
         | 
| 31 | 
            +
                      ...and <span class="associated-count"><%= content_count-related_content.size %></span> more associated items
         | 
| 32 | 
            +
                    </span>
         | 
| 33 | 
            +
                  </td>
         | 
| 34 | 
            +
                  <td colspan="<%= related_model.form_properties.size-1 %>">
         | 
| 35 | 
            +
                    <button class="association-modify">Modify</button>
         | 
| 36 | 
            +
                  </td>
         | 
| 37 | 
            +
                </tr>
         | 
| 38 | 
            +
              </tfoot>
         | 
| 15 39 | 
             
              <tbody>
         | 
| 16 | 
            -
                <% if  | 
| 40 | 
            +
                <% if related_content.empty? %>
         | 
| 17 41 | 
             
                <tr>
         | 
| 18 | 
            -
                  <td colspan="<%= related_model. | 
| 42 | 
            +
                  <td colspan="<%= related_model.form_properties.size %>">No Content Associated.</td>
         | 
| 19 43 | 
             
                </tr>
         | 
| 20 44 | 
             
                <% else %>
         | 
| 45 | 
            +
                <% related_content.each do |c| %>
         | 
| 21 46 | 
             
                <tr>
         | 
| 22 | 
            -
                  <td class="checkbox"><input type=" | 
| 47 | 
            +
                  <td class="checkbox"><input type="radio" name="selection[<%= c.id %>]" value="<%= c.id %>" id="selection_<%= related_model.name %>_<%= c.id %>" checked="checked" /></td>
         | 
| 23 48 | 
             
                  <% related_model.list_properties.each_with_index do |p,i| %>
         | 
| 24 | 
            -
                  <td><% if i == 0 %><a href="/edit/<%= related_model.name %>/<%=  | 
| 49 | 
            +
                  <td><% if i == 0 %><a href="/edit/<%= related_model.name %>/<%= c.id %>"><%= c.send(p.name.to_sym) || '-' %></a><% else %><%= c.send(p.name.to_sym) %><% end %></td>
         | 
| 25 50 | 
             
                  <% end %>
         | 
| 26 | 
            -
                </tr>
         | 
| 51 | 
            +
                </tr>   
         | 
| 52 | 
            +
                <% end %>
         | 
| 27 53 | 
             
                <% end %>
         | 
| 28 54 | 
             
              </tbody>
         | 
| 29 55 | 
             
            </table>
         | 
| 56 | 
            +
            <script type="text/javascript">
         | 
| 57 | 
            +
              new AssociationManager({
         | 
| 58 | 
            +
                relationship: 'belongs_to',
         | 
| 59 | 
            +
                name: '<%= property.name %>',
         | 
| 60 | 
            +
                model: '<%= related_model.name %>',
         | 
| 61 | 
            +
                fields: <%= related_model.list_properties.map(&:name).to_json %>,
         | 
| 62 | 
            +
                count: <%= content_count %>,
         | 
| 63 | 
            +
                related_ids: <%= related_ids.to_json %>
         | 
| 64 | 
            +
              });
         | 
| 65 | 
            +
            </script>
         | 
| @@ -1,2 +1,2 @@ | |
| 1 1 | 
             
            <label for="<%= property.name %>"><%= property.model.property_options[property.name][:label] rescue property.name.to_s.humanize.titleize %></label>
         | 
| 2 | 
            -
            <input type="text" name="<%= property.name %>" id="<%= property.name %>" value="<%= content.send(property.name.to_sym) %>" />
         | 
| 2 | 
            +
            <input type="text" name="<%= property.name %>" id="<%= property.name %>" value="<%= content.send(property.name.to_sym).strftime("%B %d, %Y %I:%M%p") rescue '' %>" />
         | 
| @@ -1,31 +1,62 @@ | |
| 1 | 
            -
            <%  | 
| 2 | 
            -
            <%  | 
| 1 | 
            +
            <% content_count = content.send(property.name.to_sym).size %>
         | 
| 2 | 
            +
            <% related_ids = content.send(property.name.to_sym).map(&:id) %>
         | 
| 3 | 
            +
            <% related_content = content.send(property.name.to_sym)[0...10] %>
         | 
| 4 | 
            +
            <% related_model = property.child_model %>
         | 
| 5 | 
            +
            <input type="hidden" name="related_<%= property.name %>" class="related_ids" value="unset" />
         | 
| 3 6 | 
             
            <label for="<%= property.name %>"><%= related_model.display_name %></label>
         | 
| 4 | 
            -
            < | 
| 7 | 
            +
            <div class="search" id="search-<%= related_model.name %>">
         | 
| 8 | 
            +
              <input type="text" name="search-<%= property.name %>" value="Search..." />
         | 
| 9 | 
            +
              <div class="paging">
         | 
| 10 | 
            +
                Page <span class="page-number">1</span> of <span class="page-count"><%= (content_count/10).ceil %></span>
         | 
| 11 | 
            +
                <a href="#" rel="prev">« Previous</a> <a href="#" rel="next">Next »</a>
         | 
| 12 | 
            +
              </div>
         | 
| 13 | 
            +
            </div>
         | 
| 14 | 
            +
            <table>
         | 
| 5 15 | 
             
              <thead>
         | 
| 6 16 | 
             
                <tr>
         | 
| 7 | 
            -
                   | 
| 8 | 
            -
                  <th></th>
         | 
| 9 | 
            -
                  <% end %>
         | 
| 17 | 
            +
                  <th class="checkbox"></th>
         | 
| 10 18 | 
             
                  <% related_model.list_properties.each do |p| %>
         | 
| 11 19 | 
             
                  <th><%= p.name.to_s.humanize.titleize %></th>
         | 
| 12 20 | 
             
                  <% end %>
         | 
| 13 21 | 
             
                </tr>
         | 
| 14 22 | 
             
              </thead>
         | 
| 23 | 
            +
              <tfoot>
         | 
| 24 | 
            +
                <tr>
         | 
| 25 | 
            +
                  <td class="checkbox"></td>
         | 
| 26 | 
            +
                  <td>
         | 
| 27 | 
            +
                    <span class="associated-count-display" style="<%= (content_count == 0 or content_count < 10) ? 'display: none;' : '' %>">
         | 
| 28 | 
            +
                      ...and <span class="associated-count"><%= content_count-related_content.size %></span> more associated items
         | 
| 29 | 
            +
                    </span>
         | 
| 30 | 
            +
                  </td>
         | 
| 31 | 
            +
                  <td colspan="<%= related_model.form_properties.size-1 %>">
         | 
| 32 | 
            +
                    <button class="association-modify">Modify</button>
         | 
| 33 | 
            +
                  </td>
         | 
| 34 | 
            +
                </tr>
         | 
| 35 | 
            +
              </tfoot>
         | 
| 15 36 | 
             
              <tbody>
         | 
| 16 | 
            -
                <% if  | 
| 37 | 
            +
                <% if related_content.empty? %>
         | 
| 17 38 | 
             
                <tr>
         | 
| 18 | 
            -
                  <td colspan="<%= related_model. | 
| 39 | 
            +
                  <td colspan="<%= related_model.form_properties.size %>">No Content Associated.</td>
         | 
| 19 40 | 
             
                </tr>
         | 
| 20 41 | 
             
                <% else %>
         | 
| 21 42 | 
             
                <% related_content.each do |c| %>
         | 
| 22 43 | 
             
                <tr>
         | 
| 23 | 
            -
                  <td class="checkbox"><input type="checkbox" name="selection[<%= c.id %>]" value=" | 
| 44 | 
            +
                  <td class="checkbox"><input type="checkbox" name="selection[<%= c.id %>]" value="<%= c.id %>" id="selection_<%= related_model.name %>_<%= c.id %>" checked="checked" /></td>
         | 
| 24 45 | 
             
                  <% related_model.list_properties.each_with_index do |p,i| %>
         | 
| 25 46 | 
             
                  <td><% if i == 0 %><a href="/edit/<%= related_model.name %>/<%= c.id %>"><%= c.send(p.name.to_sym) || '-' %></a><% else %><%= c.send(p.name.to_sym) %><% end %></td>
         | 
| 26 47 | 
             
                  <% end %>
         | 
| 27 48 | 
             
                </tr>   
         | 
| 28 | 
            -
                <% end  | 
| 49 | 
            +
                <% end %>
         | 
| 29 50 | 
             
                <% end %>
         | 
| 30 51 | 
             
              </tbody>
         | 
| 31 52 | 
             
            </table>
         | 
| 53 | 
            +
            <script type="text/javascript">
         | 
| 54 | 
            +
              new AssociationManager({
         | 
| 55 | 
            +
                relationship: 'has_n',
         | 
| 56 | 
            +
                name: '<%= property.name %>',
         | 
| 57 | 
            +
                model: '<%= related_model.name %>',
         | 
| 58 | 
            +
                fields: <%= related_model.list_properties.map(&:name).to_json %>,
         | 
| 59 | 
            +
                count: <%= content_count %>,
         | 
| 60 | 
            +
                related_ids: <%= related_ids.to_json %>
         | 
| 61 | 
            +
              });
         | 
| 62 | 
            +
            </script>
         | 
    
        metadata
    CHANGED
    
    | @@ -1,13 +1,13 @@ | |
| 1 1 | 
             
            --- !ruby/object:Gem::Specification 
         | 
| 2 2 | 
             
            name: togo
         | 
| 3 3 | 
             
            version: !ruby/object:Gem::Version 
         | 
| 4 | 
            -
              hash:  | 
| 4 | 
            +
              hash: 15
         | 
| 5 5 | 
             
              prerelease: false
         | 
| 6 6 | 
             
              segments: 
         | 
| 7 7 | 
             
              - 0
         | 
| 8 | 
            -
              -  | 
| 9 | 
            -
              -  | 
| 10 | 
            -
              version: 0. | 
| 8 | 
            +
              - 4
         | 
| 9 | 
            +
              - 0
         | 
| 10 | 
            +
              version: 0.4.0
         | 
| 11 11 | 
             
            platform: ruby
         | 
| 12 12 | 
             
            authors: 
         | 
| 13 13 | 
             
            - Matt King
         | 
| @@ -15,7 +15,7 @@ autorequire: | |
| 15 15 | 
             
            bindir: bin
         | 
| 16 16 | 
             
            cert_chain: []
         | 
| 17 17 |  | 
| 18 | 
            -
            date: 2010- | 
| 18 | 
            +
            date: 2010-08-10 00:00:00 -07:00
         | 
| 19 19 | 
             
            default_executable: 
         | 
| 20 20 | 
             
            dependencies: 
         | 
| 21 21 | 
             
            - !ruby/object:Gem::Dependency 
         | 
| @@ -63,6 +63,7 @@ files: | |
| 63 63 | 
             
            - Changelog
         | 
| 64 64 | 
             
            - LICENSE
         | 
| 65 65 | 
             
            - lib/togo/admin/admin.rb
         | 
| 66 | 
            +
            - lib/togo/admin/helpers.rb
         | 
| 66 67 | 
             
            - lib/togo/admin/public/css/screen.css
         | 
| 67 68 | 
             
            - lib/togo/admin/public/img/bg-header.png
         | 
| 68 69 | 
             
            - lib/togo/admin/public/img/bg-headline.png
         | 
| @@ -70,6 +71,7 @@ files: | |
| 70 71 | 
             
            - lib/togo/admin/public/img/bg-side.png
         | 
| 71 72 | 
             
            - lib/togo/admin/public/img/btn-bg.gif
         | 
| 72 73 | 
             
            - lib/togo/admin/public/img/btn-bg.png
         | 
| 74 | 
            +
            - lib/togo/admin/public/js/edit.js
         | 
| 73 75 | 
             
            - lib/togo/admin/public/js/index.js
         | 
| 74 76 | 
             
            - lib/togo/admin/public/js/togo.js
         | 
| 75 77 | 
             
            - lib/togo/admin/views/custom_title.erb
         |