scaffolding_extensions 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. data/LICENSE +19 -0
  2. data/README +144 -0
  3. data/contrib/scaffold_associations_tree/README +9 -0
  4. data/contrib/scaffold_associations_tree/bullet.gif +0 -0
  5. data/contrib/scaffold_associations_tree/minus.gif +0 -0
  6. data/contrib/scaffold_associations_tree/plus.gif +0 -0
  7. data/contrib/scaffold_associations_tree/scaffold_associations_tree.css +20 -0
  8. data/contrib/scaffold_associations_tree/scaffold_associations_tree.js +57 -0
  9. data/contrib/scaffold_auto_complete_style/README +8 -0
  10. data/contrib/scaffold_auto_complete_style/auto_complete.css +23 -0
  11. data/contrib/scaffold_form_focus/README +12 -0
  12. data/contrib/scaffold_form_focus/scaffold_form_focus.js +21 -0
  13. data/contrib/scaffold_jquery_autocomplete/README +8 -0
  14. data/contrib/scaffold_jquery_autocomplete/jquery.ui.se_autocomplete.css +22 -0
  15. data/contrib/scaffold_jquery_autocomplete/jquery.ui.se_autocomplete.js +121 -0
  16. data/doc/advanced.txt +154 -0
  17. data/doc/camping.txt +25 -0
  18. data/doc/controller_spec.txt +20 -0
  19. data/doc/conversion.txt +102 -0
  20. data/doc/model_spec.txt +54 -0
  21. data/doc/ramaze.txt +20 -0
  22. data/doc/sinatra.txt +20 -0
  23. data/doc/testing.txt +12 -0
  24. data/lib/scaffolding_extensions.rb +89 -0
  25. data/lib/scaffolding_extensions/controller.rb +79 -0
  26. data/lib/scaffolding_extensions/controller/action_controller.rb +116 -0
  27. data/lib/scaffolding_extensions/controller/camping.rb +150 -0
  28. data/lib/scaffolding_extensions/controller/ramaze.rb +116 -0
  29. data/lib/scaffolding_extensions/controller/sinatra.rb +183 -0
  30. data/lib/scaffolding_extensions/helper.rb +304 -0
  31. data/lib/scaffolding_extensions/jquery_helper.rb +58 -0
  32. data/lib/scaffolding_extensions/meta_controller.rb +337 -0
  33. data/lib/scaffolding_extensions/meta_model.rb +571 -0
  34. data/lib/scaffolding_extensions/model.rb +30 -0
  35. data/lib/scaffolding_extensions/model/active_record.rb +184 -0
  36. data/lib/scaffolding_extensions/model/ardm.rb +47 -0
  37. data/lib/scaffolding_extensions/model/data_mapper.rb +190 -0
  38. data/lib/scaffolding_extensions/overridable.rb +67 -0
  39. data/lib/scaffolding_extensions/prototype_helper.rb +59 -0
  40. data/scaffolds/edit.rhtml +12 -0
  41. data/scaffolds/habtm.rhtml +24 -0
  42. data/scaffolds/index.rhtml +6 -0
  43. data/scaffolds/layout.rhtml +82 -0
  44. data/scaffolds/list.rhtml +13 -0
  45. data/scaffolds/listtable.rhtml +46 -0
  46. data/scaffolds/manage.rhtml +15 -0
  47. data/scaffolds/merge.rhtml +23 -0
  48. data/scaffolds/new.rhtml +5 -0
  49. data/scaffolds/search.rhtml +11 -0
  50. data/scaffolds/show.rhtml +19 -0
  51. data/test/scaffolding_extensions_test.rb +44 -0
  52. metadata +106 -0
data/LICENSE ADDED
@@ -0,0 +1,19 @@
1
+ Copyright (c) 2004-2008 Jeremy Evans
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ of this software and associated documentation files (the "Software"), to deal
5
+ in the Software without restriction, including without limitation the rights
6
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ copies of the Software, and to permit persons to whom the Software is
8
+ furnished to do so, subject to the following conditions:
9
+
10
+ The above copyright notice and this permission notice shall be included in
11
+ all copies or substantial portions of the Software.
12
+
13
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19
+ SOFTWARE.
data/README ADDED
@@ -0,0 +1,144 @@
1
+ = ScaffoldingExtensions
2
+
3
+ Scaffolding Extensions provides a powerful and simple to use administrative
4
+ interface to perform common actions on models. It has the following features:
5
+
6
+ * Creates pages for browsing, creating, deleting, displaying, updating,
7
+ searching, and merging records
8
+ * Choose which fields are displayed in the scaffolds and in which order
9
+ * Handles associated records for common one-to-many, many-to-one, and
10
+ many-to-many associations
11
+ * Extensive support for modifying the display and working of the plugin
12
+ * Advanced features such as access control, autocompleting, and eager loading
13
+
14
+ Scaffolding Extensions is not a code generator, and isn't really scaffolding at
15
+ all, as you are not expected to modify the output. Instead, you use
16
+ configuration options inside the model to control the display of the pages.
17
+ The scaffolding analogy is misleading, Scaffolding Extensions is not really
18
+ scaffolding--it is a completely functional structure that you can easily modify
19
+ to better suit your needs.
20
+
21
+ Scaffolding Extensions currently supports:
22
+
23
+ * Web Frameworks
24
+ * Rails 2.0
25
+ * Ramaze 0.3.5
26
+ * Camping 1.5
27
+ * Sinatra 0.1.7
28
+ * Object/Relational Mappers
29
+ * ActiveRecord 2.0
30
+ * DataMapper 0.2.5
31
+ * Javascript Libaries (used for Ajax/Autocompleting)
32
+ * Prototype 1.6.0.1
33
+ * JQuery 1.2.3
34
+
35
+ Support for other web frameworks and ORMs can be added, see the
36
+ controller_spec.txt and model_spec.txt files for the methods that need to be
37
+ defined.
38
+
39
+ The current version of Scaffolding Extensions is quite different from older
40
+ versions (svn revision 89 and previous). Older versions of Rails should be
41
+ able to use an older version of the plugin, which won't be discussed further as
42
+ it is substantially different from the current version (see the conversion.txt
43
+ file for details).
44
+
45
+ You can get Scaffolding Extensions via subversion or as a gem:
46
+
47
+ * svn: svn://code.jeremyevans.net/rails-plugins/scaffolding_extensions
48
+ * gem: sudo gem install scaffolding_extensions
49
+ * RDoc: http://code.jeremyevans.net/doc/scaffolding_extensions
50
+ * Bug Tracker: http://rubyforge.org/tracker/?atid=22169&group_id=5726&func=browse
51
+ * Forum: http://rubyforge.org/forum/forum.php?forum_id=22403
52
+
53
+ == Quick Start
54
+
55
+ The recommended use of the plugin is to execute:
56
+
57
+ scaffold_all_models
58
+
59
+ inside of a controller. We'll assume the path to the controller is /admin.
60
+
61
+ Then go to the index page for the controller (e.g. http://website/admin).
62
+ You'll see a link to a management page for each of your models. Each
63
+ management page has links to browse, create, delete, edit, show, search, and
64
+ merge pages for the model. The pages are usable right away, but you'll want to
65
+ add some configuration code to your models to specify the default names to
66
+ display in select boxes, attributes to show on the forms, associations to show,
67
+ whether to use select boxes or autocompleting text boxes, etc..
68
+
69
+ == Customization
70
+
71
+ The main reason to use this plugin are the features and extent of customization
72
+ it provides. Customization is done by adding methods and instance variables
73
+ to the model class itself. Here are some common customizations:
74
+
75
+ class Album < ActiveRecord::Base
76
+ has_and_belongs_to_many :artists
77
+ belongs_to :genre
78
+ @scaffold_fields = [:name, :rating, :genre, :numtracks]
79
+ @scaffold_select_order = 'name'
80
+ @scaffold_column_names = {:numtracks=>'Number of Tracks'}
81
+ @scaffold_use_auto_complete = true
82
+ def scaffold_name
83
+ name[0...50]
84
+ end
85
+ end
86
+
87
+ @scaffold_fields determines which fields are shown in the new, edit, and search
88
+ forms (which use the order specified). It should be a list of symbols. If you
89
+ want some pages to show more fields than others, you can define variables such
90
+ as @scaffold_edit_fields or @scaffold_search_fields to override the defaults
91
+ for certain pages.
92
+
93
+ @scaffold_select_order determines which order is used in the SQL ORDER BY
94
+ clause when displaying a list of objects (for example, when choosing an object
95
+ to edit).
96
+
97
+ @scaffold_column_names specifies the visible names for each attribute.
98
+
99
+ @scaffold_use_auto_complete turns on autocompleting for the model, instead
100
+ of using select boxes (necessary for a decent response time if you have a large
101
+ number of records). See the advanced.txt for more autocompleting options.
102
+
103
+ scaffold_name is an instance method that determines the name to use for each
104
+ album inside those select boxes.
105
+
106
+ Notice in this case that genre was specified. In this case, our schema has
107
+ genre_id as a foreign key to the genres table. If you specified genre_id,
108
+ you'd get a usual text input box for the foreign key integer. If you specify
109
+ genre (the name of the belongs_to association), instead of a text input box,
110
+ you will get a select box with all genres, allowing you to pick one. It will
111
+ use the @scaffold_select_order variable and scaffold_name method in the Genre
112
+ model to format the select box (though this can be overridden with the
113
+ @scaffold_genre_select_order_association variable in the Album model).
114
+
115
+ If you have @scaffold_auto_complete_options set in the Genre model (or
116
+ @scaffold_genre_association_use_auto_complete set in the Album model), there
117
+ will be an autocompleting text box instead of a select box (though since there
118
+ aren't that many genres, you would probably be better off with a select box in
119
+ this case).
120
+
121
+ There are a ton of other customization options:
122
+
123
+ * Override the input widget type and widget options per attribute
124
+ * Choose which associations to display on the edit screen of a model
125
+ * Choose which associations to eagerly load when displaying select boxes for
126
+ the model
127
+ * Set the number of records returned in searching or browsing
128
+ * Specify different fields used in each type of scaffold (e.g. certain fields
129
+ can only be viewed, not edited)
130
+ * Control access to the model via a session variable (e.g. so a user can only
131
+ see objects with a matching user_id)
132
+
133
+ Consult advanced.txt and/or the RDoc if you would like more information on
134
+ these (and many other options).
135
+
136
+ == Testing
137
+
138
+ See the testing.txt file for details on the plugin's automated test suite and
139
+ Rails functional testing support.
140
+
141
+ == Questions?
142
+
143
+ Please post on the RubyForge forum if you have any questions about Scaffolding
144
+ Extensions.
@@ -0,0 +1,9 @@
1
+ This allows you to change the scaffolded display of associated items in the
2
+ edit view from a simple list to a explorer-style clickable tree.
3
+
4
+ To use this, include the .gifs in the /images subdirectory (or modify the .css
5
+ file), and add the .css and .js files to the layout.
6
+
7
+ Then set this in your application's environment:
8
+
9
+ ActiveRecord::Base::SCAFFOLD_OPTIONS[:association_list_class] = 'scaffold_associations_tree'
@@ -0,0 +1,20 @@
1
+ /* Turn off list bullets */
2
+ ul.scaffold_associations_tree li { list-style: none; }
3
+
4
+ ul.scaffold_associations_tree, ul.scaffold_associations_tree ul, ul.scaffold_associations_tree li {
5
+ margin: 0;
6
+ padding: 0;
7
+ }
8
+
9
+ /* This controls the indent for each sublist */
10
+ ul.scaffold_associations_tree ul, ul.scaffold_associations_tree li a.treestatus { padding-left: 20px; }
11
+
12
+ /* Show "bullets" in the links, depending on the class of the
13
+ LI that the link's in */
14
+ ul.scaffold_associations_tree li.sat_open a.treestatus { background: url("/images/minus.gif") center left no-repeat; }
15
+ ul.scaffold_associations_tree li.sat_closed a.treestatus { background: url("/images/plus.gif") center left no-repeat; }
16
+ ul.scaffold_associations_tree li.sat_bullet a.treestatus { background: url("/images/bullet.gif") center left no-repeat; }
17
+
18
+ /* Actually show and hide sublists */
19
+ ul.scaffold_associations_tree li.sat_open ul { display: block; }
20
+ ul.scaffold_associations_tree li.sat_closed ul { display: none; }
@@ -0,0 +1,57 @@
1
+ /* Based on aqtree3clickable from http://www.kryogenix.org/code/browser/aqlists/
2
+ Modifications by Jeremy Evans (code@jeremyevans.net) */
3
+
4
+ addEvent(window, "load", makeTreesC);
5
+
6
+ function makeTreesC() {
7
+ if (!document.createElement)
8
+ return;
9
+ uls = document.getElementsByTagName("ul");
10
+ for (uli=0;uli<uls.length;uli++) {
11
+ ul = uls[uli];
12
+ if (ul.nodeName == "UL" && ul.className == "scaffold_associations_tree") {
13
+ processULELC(ul);
14
+ }
15
+ }
16
+ }
17
+
18
+ function processULELC(ul) {
19
+ if (!ul.childNodes || ul.childNodes.length == 0)
20
+ return;
21
+ for (var itemi=0;itemi<ul.childNodes.length;itemi++) {
22
+ var item = ul.childNodes[itemi];
23
+ if (item.nodeName == "LI") {
24
+ var subul;
25
+ subul = "";
26
+ for (var sitemi=0;sitemi<item.childNodes.length;sitemi++) {
27
+ if (item.childNodes[sitemi].nodeName == "UL") {
28
+ subul = item.childNodes[sitemi];
29
+ processULELC(subul);
30
+ }
31
+ }
32
+ if (subul) {
33
+ item.className = 'sat_closed'
34
+ item.innerHTML = '<a href="#" class="treestatus" onclick=\'this.parentNode.className = (this.parentNode.className=="sat_open") ? "sat_closed" : "sat_open"; return false;\'></a>' + item.innerHTML
35
+ } else {
36
+ item.className = "sat_bullet";
37
+ item.innerHTML = '<a href="#" class="treestatus" onclick=\'return false;\'></a>' + item.innerHTML
38
+ }
39
+ }
40
+ }
41
+ }
42
+
43
+ /* Utility functions */
44
+
45
+ function addEvent(obj, evType, fn){
46
+ /* adds an eventListener for browsers which support it
47
+ Written by Scott Andrew: nice one, Scott */
48
+ if (obj.addEventListener){
49
+ obj.addEventListener(evType, fn, false);
50
+ return true;
51
+ } else if (obj.attachEvent){
52
+ var r = obj.attachEvent("on"+evType, fn);
53
+ return r;
54
+ } else {
55
+ return false;
56
+ }
57
+ }
@@ -0,0 +1,8 @@
1
+ This allows you to skip the output of the style when using scaffold auto
2
+ completing. This is necessary if you want to be XHTML compliant, as style tags
3
+ aren't allowed inside form elements.
4
+
5
+ To use this, add the .css file to your layout. Then set this in your
6
+ application's environment:
7
+
8
+ ScaffoldingExtensions.auto_complete_skip_style = true
@@ -0,0 +1,23 @@
1
+ div.auto_complete {
2
+ width: 350px;
3
+ background: #fff;
4
+ }
5
+ div.auto_complete ul {
6
+ border:1px solid #888;
7
+ margin:0;
8
+ padding:0;
9
+ width:100%;
10
+ list-style-type:none;
11
+ }
12
+ div.auto_complete ul li {
13
+ margin:0;
14
+ padding:3px;
15
+ }
16
+ div.auto_complete ul li.selected {
17
+ background-color: #ffb;
18
+ }
19
+ div.auto_complete ul strong.highlight {
20
+ color: #800;
21
+ margin:0;
22
+ padding:0;
23
+ }
@@ -0,0 +1,12 @@
1
+ This allows you to auto focus the page to the first form field.
2
+
3
+ To use this, conditionally include the .js file in your layout via something
4
+ like:
5
+
6
+ <%= javascript_include_tag('scaffold_form_focus') if @scaffold_options %>
7
+
8
+ The if statement makes it take effect for the scaffolded pages (which always
9
+ define @scaffold_options). Note that the code is fairly simplistic, it just
10
+ focuses on the first text input, password input, textarea, or
11
+ select box on the page, so if your layout has a form before the scaffolded
12
+ form, you'll probably want to modify the code.
@@ -0,0 +1,21 @@
1
+ function scaffold_form_focus() {
2
+ num_forms = document.forms.length
3
+ for(i=0;i<num_forms;i++) {
4
+ elements = document.forms[i].elements
5
+ num_elements = elements.length
6
+ for(j=0;j<num_elements;j++) {
7
+ element = elements[j]
8
+ tagName = element.tagName
9
+ if(tagName == 'SELECT' || tagName == 'TEXTAREA' || (tagName == 'INPUT' && (element.type == 'text' || element.type == 'password'))) {
10
+ if(tagName != 'SELECT') {
11
+ element.selectionStart = 0
12
+ element.selectionEnd = 0
13
+ }
14
+ element.focus()
15
+ return
16
+ }
17
+ }
18
+ }
19
+ }
20
+
21
+ scaffold_form_focus()
@@ -0,0 +1,8 @@
1
+ If you are using JQuery javascript library with the plugin, you need to use this
2
+ plugin for autocomplete functionality. It's a modification of jq-autocomplete
3
+ [1]. It also requires the Dimensions JQuery plugin [2]. To use this plugin,
4
+ add the include the css file as a stylesheet and the js file as a script (in
5
+ addition to JQuery and the Dimensions plugin).
6
+
7
+ [1] http://plugins.jquery.com/project/jq-autocomplete
8
+ [2] http://plugins.jquery.com/project/dimensions
@@ -0,0 +1,22 @@
1
+ ul.se_autocomplete {
2
+ position: absolute;
3
+ overflow: hidden;
4
+ background-color: #fff;
5
+ border: 1px solid #aaa;
6
+ margin: 0px;
7
+ padding: 0;
8
+ list-style: none;
9
+ font: normal .75em/.75em Verdana, Arial, sans-serif;
10
+ color: #333;
11
+ }
12
+ ul.se_autocomplete li {
13
+ display: block;
14
+ padding: .3em .5em .3em .3em;
15
+ overflow: hidden;
16
+ width: 100%;
17
+ }
18
+
19
+ ul.se_autocomplete li.active {
20
+ background-color: #3875d7;
21
+ color: #fff;
22
+ }
@@ -0,0 +1,121 @@
1
+ /* Copyright 2007 Yehuda Katz, Rein Henrichs
2
+ * Copyright 2008 Jeremy Evans
3
+ */
4
+
5
+ (function($) {
6
+
7
+ $.ui = $.ui || {}; $.ui.autocomplete = $.ui.autocomplete || {}; var active;
8
+
9
+ $.fn.autocompleteMode = function(container, input, size, opt) {
10
+ var original = input.val(); var selected = -1; var self = this;
11
+
12
+ $.data(document.body, "autocompleteMode", true);
13
+
14
+ $("body").one("cancel.autocomplete", function() {
15
+ input.trigger("cancel.autocomplete"); $("body").trigger("off.autocomplete"); input.val(original);
16
+ });
17
+
18
+ $("body").one("activate.autocomplete", function() {
19
+ input.trigger("activate.autocomplete", [$(active[0]).html()]); $("body").trigger("off.autocomplete");
20
+ });
21
+
22
+ $("body").one("off.autocomplete", function(e, reset) {
23
+ container.remove();
24
+ $.data(document.body, "autocompleteMode", false);
25
+ input.unbind("keydown.autocomplete");
26
+ $("body").add(window).unbind("click.autocomplete").unbind("cancel.autocomplete").unbind("activate.autocomplete");
27
+ });
28
+
29
+ // If a click bubbles all the way up to the window, close the autocomplete
30
+ $(window).bind("click.autocomplete", function() { $("body").trigger("cancel.autocomplete"); });
31
+
32
+ var select = function() {
33
+ active = $("> *", container).removeClass("active").slice(selected, selected + 1).addClass("active");
34
+ input.trigger("itemSelected.autocomplete", [$(active[0]).html()]);
35
+ input.val($(active[0]).html());
36
+ };
37
+
38
+ container.mouseover(function(e) {
39
+ // If you hover over the container, but not its children, return
40
+ if(e.target == container[0]) return;
41
+ // Set the selected item to the item hovered over and make it active
42
+ selected = $("> *", container).index($(e.target).is('li') ? $(e.target)[0] : $(e.target).parents('li')[0]); select();
43
+ }).bind("click.autocomplete", function(e) {
44
+ $("body").trigger("activate.autocomplete"); $.data(document.body, "suppressKey", false);
45
+ });
46
+
47
+ input
48
+ .bind("keydown.autocomplete", function(e) {
49
+ if(e.which == 27) { $("body").trigger("cancel.autocomplete"); }
50
+ else if(e.which == 13) { $("body").trigger("activate.autocomplete"); }
51
+ else if(e.which == 40 || e.which == 9 || e.which == 38) {
52
+ switch(e.which) {
53
+ case 40:
54
+ case 9:
55
+ selected = selected >= size - 1 ? 0 : selected + 1; break;
56
+ case 38:
57
+ selected = selected <= 0 ? size - 1 : selected - 1; break;
58
+ default: break;
59
+ }
60
+ select();
61
+ } else { return true; }
62
+ $.data(document.body, "suppressKey", true);
63
+ });
64
+ };
65
+
66
+ $.fn.autocomplete = function(opt) {
67
+ var ajax = opt.ajax;
68
+ var association = opt.association
69
+ opt = $.extend({}, {
70
+ timeout: 1000,
71
+ getList: function(input) {
72
+ params = "id=" + input.val()
73
+ if(association) {
74
+ params += '&association=' + association
75
+ }
76
+ $.get(ajax, params, function(html) { input.trigger("updateList", [html]); });
77
+ }
78
+ }, opt);
79
+
80
+ return this.each(function() {
81
+
82
+ $(this)
83
+ .keypress(function(e) {
84
+ var typingTimeout = $.data(this, "typingTimeout");
85
+ if(typingTimeout) window.clearInterval(typingTimeout);
86
+
87
+ if($.data(document.body, "suppressKey"))
88
+ return $.data(document.body, "suppressKey", false);
89
+ else if($.data(document.body, "autocompleteMode") && e.charCode < 32 && e.keyCode != 8 && e.keyCode != 46) return false;
90
+ else {
91
+ $.data(this, "typingTimeout", window.setTimeout(function() {
92
+ $(e.target).trigger("autocomplete");
93
+ }, opt.timeout));
94
+ }
95
+ })
96
+ .bind("autocomplete", function() {
97
+ var self = $(this);
98
+
99
+ self.one("updateList", function(e, list) {
100
+ $("body").trigger("off.autocomplete");
101
+
102
+ list = $(list);
103
+ var length = list.children().length;
104
+ if(!length) return false;
105
+
106
+ list.addClass('se_autocomplete');
107
+
108
+ var offset = self.offset();
109
+
110
+ list.css({top: offset.top + self.outerHeight(), left: offset.left, width: self.width()}).appendTo("body");
111
+
112
+ $("body").autocompleteMode(list, self, length, opt);
113
+ });
114
+
115
+ opt.getList(self);
116
+ });
117
+
118
+ });
119
+ };
120
+
121
+ })(jQuery);