hobo-jquery 1.3.0pre2

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.
Files changed (37) hide show
  1. data/.gitmodules +3 -0
  2. data/LICENSE.txt +22 -0
  3. data/README.markdown +54 -0
  4. data/TODO.markdown +0 -0
  5. data/hobo-jquery.gemspec +9 -0
  6. data/jquery/javascripts/jquery-1.5.2.min.js +16 -0
  7. data/jquery/javascripts/jquery-ui-1.8.11.custom.min.js +783 -0
  8. data/jquery/stylesheets/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
  9. data/jquery/stylesheets/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
  10. data/jquery/stylesheets/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
  11. data/jquery/stylesheets/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
  12. data/jquery/stylesheets/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
  13. data/jquery/stylesheets/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
  14. data/jquery/stylesheets/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
  15. data/jquery/stylesheets/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
  16. data/jquery/stylesheets/smoothness/images/ui-icons_222222_256x240.png +0 -0
  17. data/jquery/stylesheets/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
  18. data/jquery/stylesheets/smoothness/images/ui-icons_454545_256x240.png +0 -0
  19. data/jquery/stylesheets/smoothness/images/ui-icons_888888_256x240.png +0 -0
  20. data/jquery/stylesheets/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
  21. data/jquery/stylesheets/smoothness/jquery-ui-1.8.11.custom.css +573 -0
  22. data/lib/doc.dryml +75 -0
  23. data/lib/doc.rb +15 -0
  24. data/lib/doc_generator.dryml +79 -0
  25. data/lib/generators/hobo_jquery/install_generator.rb +23 -0
  26. data/lib/hobo-jquery.rb +6 -0
  27. data/lib/hobo-jquery/railtie.rb +9 -0
  28. data/lib/html2markdown.pl +7 -0
  29. data/lib/render.rake +39 -0
  30. data/lib/tasks/hobo-contrib.rake +2 -0
  31. data/public/javascripts/event.simulate.js +64 -0
  32. data/public/javascripts/hobo-jquery.js +663 -0
  33. data/public/stylesheets/hobo-jquery.css +16 -0
  34. data/rails/init.rb +0 -0
  35. data/taglibs/hobo-jquery.dryml +613 -0
  36. data/update-docs.sh +16 -0
  37. metadata +89 -0
@@ -0,0 +1,75 @@
1
+ <def tag="param-list" >
2
+ <ul if="&this.any?">
3
+ <% this.each {|param| %>
4
+ <li><%= param.first %>
5
+ <param-list with="&param.last"/>
6
+ </li>
7
+ <% } %>
8
+ </ul>
9
+ </def>
10
+
11
+ <def tag="tag-link" attrs="link, content">
12
+ <li>
13
+ <a href="##{link}">
14
+ <span>
15
+ <%= content %>
16
+ </span>
17
+ </a>
18
+ </li>
19
+ </def>
20
+
21
+
22
+ <html>
23
+ <head>
24
+ <link type="text/css" href="stylesheets/themes/smoothness/ui.all.css" rel="stylesheet" />
25
+ <script type="text/javascript" src="javascripts/jquery-1.5.2.min.js"></script>
26
+ <script type="text/javascript" src="javascripts/jquery-ui-1.8.11.custom.min.js"></script>
27
+ <script type="text/javascript">
28
+ jQuery(document).ready(function(){
29
+ jQuery("#tabs-top").tabs();
30
+ jQuery(".taglib").tabs();
31
+ jQuery(".source").accordion({collapsible: true, active: false, autoHeight: false });
32
+ });
33
+ </script>
34
+ </head>
35
+ <body>
36
+ <div id="tabs-top">
37
+ <ul>
38
+ <repeat with="&this">
39
+ <tag-link content="&this.name" link="#{this.name}-taglib"/>
40
+ </repeat>
41
+ </ul>
42
+ <repeat>
43
+ <div class="taglib" id="#{this.name}-taglib">
44
+ <%= this.comment_html %>
45
+ <ul>
46
+ <repeat:tag_defs>
47
+ <tag-link content="&this.name" link="#{this.name}-tag"/>
48
+ </repeat>
49
+ </ul>
50
+ <repeat:tag_defs>
51
+ <div id="#{this.name}-tag">
52
+
53
+ <h2><%= this.name %></h2>
54
+
55
+ <%= this.comment_intro_html %>
56
+
57
+ <unless test="&this.parameters.empty?">
58
+ <h3>Parameters</h3>
59
+ <param-list with="&this.parameters"/>
60
+ </unless>
61
+
62
+ <%= this.comment_rest_html %>
63
+
64
+ <div class="source">
65
+ <h3><a href="#">Source</a></h3>
66
+ <pre><code><%= h this.source %></code></pre>
67
+ </div>
68
+
69
+ </div>
70
+ </repeat>
71
+ </div>
72
+ </repeat>
73
+ </div>
74
+ </body>
75
+ </html>
@@ -0,0 +1,15 @@
1
+ require 'rubygems'
2
+ require 'maruku'
3
+ require 'dryml'
4
+ require 'action_view'
5
+ require 'action_controller'
6
+
7
+ taglibs = Dir["../taglibs/**/*.dryml"].reject {|filename|
8
+ File.basename(filename).match(/^hobo-jquery-.*/)
9
+ }.map {|filename|
10
+ Dryml::DrymlDoc::Taglib.new("../taglibs", filename)
11
+ }
12
+
13
+ out=Dryml.render(open("doc.dryml").read, {:this => taglibs}, "doc.dryml")
14
+
15
+ open("../documentation/doc.html", "w").write(out)
@@ -0,0 +1,79 @@
1
+ <include src="core" plugin="hobo/hobo"/>
2
+ <include src="rapid" plugin="hobo/hobo"/>
3
+
4
+ <def tag="param-list" >
5
+ <ul if="&this.any?">
6
+ <% this.each {|param| %>
7
+ <li><%= param.first %>
8
+ <param-list with="&param.last"/>
9
+ </li>
10
+ <% } %>
11
+ </ul>
12
+ </def>
13
+
14
+ <def tag="tag-link" attrs="link, content">
15
+ <li>
16
+ <a href="##{link}">
17
+ <span>
18
+ <%= content %>
19
+ </span>
20
+ </a>
21
+ </li>
22
+ </def>
23
+
24
+
25
+ <html>
26
+ <head>
27
+ <link type="text/css" href="stylesheets/themes/smoothness/ui.all.css" rel="stylesheet" />
28
+ <script type="text/javascript" src="javascripts/jquery-1.5.2.min.js"></script>
29
+ <script type="text/javascript" src="javascripts/jquery-ui-1.8.11.custom.min.js"></script>
30
+ <script type="text/javascript">
31
+ jQuery(document).ready(function(){
32
+ jQuery("#tabs-top").tabs();
33
+ jQuery(".taglib").tabs();
34
+ jQuery(".source").accordion({collapsible: true, active: false, autoHeight: false });
35
+ });
36
+ </script>
37
+ </head>
38
+ <body>
39
+ <div id="tabs-top">
40
+ <ul>
41
+ <% this.each {|taglib| %>
42
+ <tag-link content="&taglib.name" link="#{taglib.name}-taglib"/>
43
+ <% } %>
44
+ </ul>
45
+ <!-- unfortunately, <repeat> is problematic here -->
46
+ <% this.each {|taglib| %>
47
+ <div class="taglib" id="#{taglib.name}-taglib">
48
+ <%= taglib.comment_html %>
49
+ <ul>
50
+ <% taglib.tag_defs.each {|tag| %>
51
+ <tag-link content="&tag.name" link="#{tag.name}-tag"/>
52
+ <% } %>
53
+ </ul>
54
+ <% taglib.tag_defs.each {|tag| %>
55
+ <div id="#{tag.name}-tag">
56
+
57
+ <h2><%= tag.name %></h2>
58
+
59
+ <%= tag.comment_intro_html %>
60
+
61
+ <% if !tag.parameters.empty? %>
62
+ <h3>Parameters</h3>
63
+ <param-list with="&tag.parameters"/>
64
+ <% end %>
65
+
66
+ <%= tag.comment_rest_html %>
67
+
68
+ <div class="source">
69
+ <h3><a href="#">Source</a></h3>
70
+ <pre><code><%= h tag.source %></code></pre>
71
+ </div>
72
+
73
+ </div>
74
+ <% } %>
75
+ </div>
76
+ <% } %>
77
+ </div>
78
+ </body>
79
+ </html>
@@ -0,0 +1,23 @@
1
+ module HoboJquery
2
+ module Generators
3
+ class InstallGenerator < ::Rails::Generators::Base
4
+ source_root File.expand_path('../../../..', __FILE__)
5
+
6
+ desc "Installs javascript and css files for hobo-jquery, jquery and jquery-ui into public/"
7
+ def install
8
+ base_pathname = Pathname.new(File.expand_path('../../../../public', __FILE__))
9
+ Dir[File.expand_path('../../../../public/**/*.*', __FILE__)].each do |fn|
10
+ rfn=Pathname.new(fn).relative_path_from(base_pathname)
11
+ copy_file "public/#{rfn}", "public/#{rfn}"
12
+ end
13
+
14
+
15
+ base_pathname = Pathname.new(File.expand_path('../../../../jquery', __FILE__))
16
+ Dir[File.expand_path('../../../../jquery/**/*.*', __FILE__)].each do |fn|
17
+ rfn=Pathname.new(fn).relative_path_from(base_pathname)
18
+ copy_file "jquery/#{rfn}", "public/#{rfn}"
19
+ end
20
+ end
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,6 @@
1
+ module HoboJquery
2
+ @@root = Pathname.new File.expand_path('../..', __FILE__)
3
+ def self.root; @@root; end
4
+
5
+ require 'hobo-jquery/railtie' if defined?(Rails)
6
+ end
@@ -0,0 +1,9 @@
1
+ require 'hobo-jquery'
2
+ require 'rails'
3
+ module HoboJquery
4
+ class Railtie < Rails::Railtie
5
+ rake_tasks do
6
+ load "tasks/hobo-contrib.rake"
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,7 @@
1
+ #!/usr/bin/perl -w
2
+
3
+ # cpan install HTML::WikiConverter::Markdown
4
+
5
+ use HTML::WikiConverter;
6
+ my $wc = new HTML::WikiConverter( dialect => 'Markdown' );
7
+ print $wc->html2wiki( uri => "file://$ARGV[0]" ), "\n\n";
@@ -0,0 +1,39 @@
1
+ # This requires a working hobo skeleton. Rather than create a
2
+ # symbolic dependency, I haven't included the skeleton.
3
+ #
4
+ # Link this file from lib/tasks/render.rake and link
5
+ # doc_generator.dryml to app/views/taglibs in your skeleton
6
+
7
+ desc "generate all"
8
+ task :generate_all => ["../hobo-jquery-gh-pages/README.html", "../hobo-jquery-gh-pages/documentation.html"]
9
+
10
+ desc "generate README.html"
11
+ file "../hobo-jquery-gh-pages/README.html" => [:environment, "#{RAILS_ROOT}/../hobo-jquery/README.markdown"] do |t|
12
+ sh "maruku --html #{RAILS_ROOT}/../hobo-jquery/README.markdown -o #{RAILS_ROOT}/../hobo-jquery-gh-pages/README.html"
13
+ end
14
+
15
+ desc "generate documentation"
16
+ file "../hobo-jquery-gh-pages/documentation.html" => ["#{RAILS_ROOT}/app/views/taglibs/doc_generator.dryml", :environment]+Dir["#{RAILS_ROOT}/../hobo-jquery/taglibs/**/*.dryml"] do |t|
17
+
18
+ require 'maruku'
19
+
20
+ taglibs = Dir["#{RAILS_ROOT}/../hobo-jquery/taglibs/**/*.dryml"].reject {|filename|
21
+ File.basename(filename).match(/^hobo-jquery-.*/)
22
+ }.map {|filename|
23
+ Hobo::Dryml::DrymlDoc::Taglib.new("#{RAILS_ROOT}/../hobo-jquery/taglibs", filename)
24
+ }
25
+
26
+ src = open(t.prerequisites.first).read
27
+ locals = []
28
+ imports = []
29
+ renderer_class = Hobo::Dryml.make_renderer_class(src, File.dirname(t.prerequisites.first), locals, imports)
30
+ assigns = {}
31
+ view = ActionView::Base.new(ActionController::Base.view_paths, assigns)
32
+ view.extend(ActionView::Helpers::TagHelper)
33
+ view.extend(Hobo::HoboHelper)
34
+ view.extend(Hobo::RapidHelper)
35
+ renderer = renderer_class.new(File.basename(t.prerequisites.first, ".dryml"), view)
36
+ page_locals = ""
37
+ open(t.name, "w").write(renderer.render_page(taglibs, page_locals).strip)
38
+ end
39
+
@@ -0,0 +1,2 @@
1
+ namespace :hobo_jquery do
2
+ end
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Event.simulate(@element, eventName[, options]) -> Element
3
+ *
4
+ * - @element: element to fire event on
5
+ * - eventName: name of event to fire (only MouseEvents and HTMLEvents interfaces are supported)
6
+ * - options: optional object to fine-tune event properties - pointerX, pointerY, ctrlKey, etc.
7
+ *
8
+ * $('foo').simulate('click'); // => fires "click" event on an element with id=foo
9
+ *
10
+ **/
11
+ (function(){
12
+
13
+ var eventMatchers = {
14
+ 'HTMLEvents': /^(?:load|unload|abort|error|select|change|submit|reset|focus|blur|resize|scroll)$/,
15
+ 'MouseEvents': /^(?:click|mouse(?:down|up|over|move|out))$/
16
+ }
17
+ var defaultOptions = {
18
+ pointerX: 0,
19
+ pointerY: 0,
20
+ button: 0,
21
+ ctrlKey: false,
22
+ altKey: false,
23
+ shiftKey: false,
24
+ metaKey: false,
25
+ bubbles: true,
26
+ cancelable: true
27
+ }
28
+
29
+ Event.simulate = function(element, eventName) {
30
+ var options = Object.extend(defaultOptions, arguments[2] || { });
31
+ var oEvent, eventType = null;
32
+
33
+ element = $(element);
34
+
35
+ for (var name in eventMatchers) {
36
+ if (eventMatchers[name].test(eventName)) { eventType = name; break; }
37
+ }
38
+
39
+ if (!eventType)
40
+ throw new SyntaxError('Only HTMLEvents and MouseEvents interfaces are supported');
41
+
42
+ if (document.createEvent) {
43
+ oEvent = document.createEvent(eventType);
44
+ if (eventType == 'HTMLEvents') {
45
+ oEvent.initEvent(eventName, options.bubbles, options.cancelable);
46
+ }
47
+ else {
48
+ oEvent.initMouseEvent(eventName, options.bubbles, options.cancelable, document.defaultView,
49
+ options.button, options.pointerX, options.pointerY, options.pointerX, options.pointerY,
50
+ options.ctrlKey, options.altKey, options.shiftKey, options.metaKey, options.button, element);
51
+ }
52
+ element.dispatchEvent(oEvent);
53
+ }
54
+ else {
55
+ options.clientX = options.pointerX;
56
+ options.clientY = options.pointerY;
57
+ oEvent = Object.extend(document.createEventObject(), options);
58
+ element.fireEvent('on' + eventName, oEvent);
59
+ }
60
+ return element;
61
+ }
62
+
63
+ Element.addMethods({ simulate: Event.simulate });
64
+ })()
@@ -0,0 +1,663 @@
1
+ // our monkey patches to Function, properly namespaced.
2
+
3
+ /* bind the context and return a lambda */
4
+ Function.prototype.hjq_bind = function(context) {
5
+ var that=this;
6
+ return function() {
7
+ return that.apply(context, arguments);
8
+ }
9
+ };
10
+
11
+ /* return a lambda that calls "this" and then calls "f". Depending on the return value of the lambda is probably a bad idea. */
12
+ Function.prototype.hjq_chain = function(f) {
13
+ var that=this;
14
+ return function() {
15
+ var r=that.apply(this, arguments);
16
+ if(f) {
17
+ r=f.apply(this, arguments);
18
+ }
19
+ return r;
20
+ }
21
+ };
22
+
23
+ // we add our own hide and show to jQuery so that we get consistent behaviour and so we can plug our tests in.
24
+ jQuery.fn.hjq_hide = function(options, callback) {
25
+ var settings = jQuery.extend({
26
+ effect: 'blind', speed: 500
27
+ }, options);
28
+ var cb = callback;
29
+ if(callback && hjq.hideComplete) {
30
+ cb = (function() {
31
+ callback.apply(this, arguments);
32
+ hjq.hideComplete.apply(this, arguments);
33
+ });
34
+ } else if (hjq.hideComplete) {
35
+ cb = hjq.hideComplete;
36
+ }
37
+ return this.hide(settings.effect, settings, settings.speed, cb);
38
+ };
39
+
40
+ jQuery.fn.hjq_show = function(options, callback) {
41
+ var cb = callback;
42
+ var settings = jQuery.extend({
43
+ effect: 'blind', speed: 500, callback: undefined
44
+ }, options);
45
+ if(callback && hjq.showComplete) {
46
+ cb = (function() {
47
+ settings.callback.apply(this, arguments);
48
+ hjq.showComplete.apply(this, arguments);
49
+ });
50
+ } else if (hjq.showComplete) {
51
+ cb = hjq.showComplete;
52
+ }
53
+ return this.show(settings.effect, settings, settings.speed, cb);
54
+ };
55
+
56
+ // Our monkey patches to Prototype
57
+
58
+ // the Hobo part mechanism uses Element.update to do it's work
59
+ Element.addMethods({update: Element.Methods.update.hjq_chain(function(id, content) {
60
+ if(!id.nodeType) id="#"+id; // assume it's a string
61
+ hjq.initialize.apply(jQuery(id));
62
+ })});
63
+
64
+ var hjq = (function() {
65
+ return {
66
+
67
+ /* calls all "init" functions with their this and their annotations */
68
+ initialize: function() {
69
+ jQuery(this).find('.hjq-annotated').each(function() {
70
+ var annotations = hjq.getAnnotations.call(this);
71
+ if(annotations.init) {
72
+ hjq.util.createFunction(annotations.init).call(this, annotations);
73
+ };
74
+ });
75
+ },
76
+
77
+ /* returns JSON annotations for *this* */
78
+ getAnnotations: function() {
79
+ // Unforunately, jQuery does not traverse comment nodes, so we're using the DOM methods directly
80
+
81
+ // previous is probably a textNode containing whitespace
82
+ var comment = this.previousSibling;
83
+ if(comment.nodeType!=Node.COMMENT_NODE) { comment = comment.previousSibling; }
84
+ if(comment.nodeType!=Node.COMMENT_NODE) { return ({}); }
85
+
86
+ var json = RegExp(/^\s*json_annotation\s*(\(\{.*\}\)\;)\s*$/).exec(comment.nodeValue)[1];
87
+ return eval(json);
88
+ },
89
+
90
+ /* given annotations, turns the values in the _events_ object into functions, merges them into _options_ and returns _options_ */
91
+ getOptions: function(annotations) {
92
+ for(var key in annotations.events) {
93
+ annotations.options[key] = hjq.util.createFunction(annotations.events[key]);
94
+ }
95
+ return annotations.options;
96
+ },
97
+
98
+
99
+ /* hooks for debugging & testing */
100
+
101
+ hideComplete: undefined,
102
+
103
+ bindHideCallback: function(f) {
104
+ /* FIXME: I suppose we should properly chain here....*/
105
+ hjq.hideComplete = f;
106
+ },
107
+
108
+ showComplete: undefined,
109
+
110
+ bindShowCallback: function(f) {
111
+ /* FIXME:chain */
112
+ hjq.showComplete = f;
113
+ },
114
+
115
+ /* these are functions I shouldn't be writing myself -- should be in a library somewhere! */
116
+ util: {
117
+ /* given a global function name, find the function */
118
+ functionByName: function(name) {
119
+ var descend = window; // find function by name on the root object
120
+ jQuery.each(name.split("."), function() {
121
+ if(descend) descend = descend[this];
122
+ });
123
+ return descend;
124
+ },
125
+
126
+ /* Given a function name or javascript fragment, return a function */
127
+ createFunction: function(script) {
128
+ if(!script) return function() {};
129
+ var f=hjq.util.functionByName(script);
130
+ if(f) return f;
131
+ return function() { return eval(script); };
132
+ },
133
+
134
+ /* Iterates through this and arguments until either a jQuery or an element is found, and returns it, jQuerified */
135
+ jQuerifyFirstElement: function() {
136
+ if(this.nodeType==1) return jQuery(this);
137
+ if(this.jquery) return this;
138
+ for(var i=0; i<arguments.length; i++) {
139
+ if(arguments[i].nodeType==1) return jQuery(arguments[i]);
140
+ if(arguments[i].jquery) return arguments[i];
141
+ }
142
+ return [];
143
+ },
144
+
145
+ /* log to console, if available */
146
+ log: function(s) {
147
+ if(console && console.log) console.log(s);
148
+ }
149
+ },
150
+
151
+ input_many: {
152
+ init: function (annotations) {
153
+ var me = jQuery(this);
154
+
155
+ // disable all elements inside our template, and mark them so we can find them later.
156
+ me.find(".input-many-template :input[disabled=false]").each(function() {
157
+ this.disabled = true;
158
+ jQuery(this).addClass("input_many_template_input");
159
+ });
160
+
161
+ // bind event handlers
162
+ me.find(".remove-item").click(hjq.input_many.removeOne);
163
+ me.find(".add-item").click(hjq.input_many.addOne);
164
+ },
165
+
166
+ addOne: function () {
167
+ var me = jQuery(this).parent().parent();
168
+ var top = me.parent();
169
+ var template = top.children("li.input-many-template");
170
+ var clone = template.clone(true).removeClass("input-many-template");
171
+ // length-2 because ignore the template li and the empty li
172
+ var name_updater = hjq.input_many.getNameUpdater.call(top, top.children().length-2);
173
+ var params = hjq.getAnnotations.call(top.get(0));
174
+
175
+ // enable previously marked elements
176
+ clone.find(".input_many_template_input").each(function() {
177
+ this.disabled = false;
178
+ jQuery(this).removeClass("input_many_template_input");
179
+ });
180
+
181
+ // update id & name
182
+ clone.find("*").each(function() {
183
+ name_updater.call(this);
184
+ });
185
+ name_updater.call(clone.get(0));
186
+
187
+ // do the add with anim
188
+ clone.css("display", "none").insertAfter(me).hjq_show();
189
+
190
+ // initialize subelements
191
+ hjq.initialize.call(me.next().get(0));
192
+
193
+ // and reinitialize low-pro too
194
+ Event.addBehavior.reload();
195
+
196
+ // visibility
197
+ if(me.hasClass("empty")) {
198
+ me.addClass("hidden");
199
+ me.find("input.empty-input").attr("disabled", true);
200
+ } else {
201
+ // now that we've added an element after us, we should only have a '-' button
202
+ me.children("div.buttons").children("button.remove-item").removeClass("hidden");
203
+ me.children("div.buttons").children("button.add-item").addClass("hidden");
204
+ }
205
+
206
+ hjq.util.createFunction(params.add_hook).call(me.get(0));
207
+
208
+ return false; // prevent bubbling
209
+ },
210
+
211
+ removeOne: function() {
212
+ var me = jQuery(this).parent().parent();
213
+ var top = me.parent();
214
+ var params = hjq.getAnnotations.call(top.get(0));
215
+
216
+ if(params.remove_hook) {
217
+ if(!hjq.util.createFunction(params.remove_hook).call(me.get(0))) {
218
+ return false;
219
+ }
220
+ }
221
+
222
+ // rename everybody from me onwards
223
+ var i=hjq.input_many.getIndex.call(me.get(0))
224
+ var n=me.next();
225
+ for(; n.length>0; i+=1, n=n.next()) {
226
+ var name_updater = hjq.input_many.getNameUpdater.call(top, i);
227
+ n.find("*").each(function() {
228
+ name_updater.call(this);
229
+ });
230
+ name_updater.call(n.get(0));
231
+ }
232
+
233
+ // adjust +/- buttons on the button element as appropriate
234
+ var last=top.children("li:last");
235
+ if(last.get(0)==me.get(0)) {
236
+ last = last.prev();
237
+ }
238
+
239
+ if(last.hasClass("empty")) {
240
+ last.removeClass("hidden");
241
+ last.find("input.empty-input").removeAttr("disabled");
242
+ } else {
243
+ // if we've reached the minimum, we don't want to add the '-' button
244
+ if(top.children().length-3 <= (params['minimum']||0)) {
245
+ last.children("div.buttons").children("button.remove-item").addClass("hidden");
246
+ } else {
247
+ last.children("div.buttons").children("button.remove-item").removeClass("hidden");
248
+ }
249
+ last.children("div.buttons").children("button.add-item").removeClass("hidden");
250
+ }
251
+
252
+ // remove with animation
253
+ me.hjq_hide({}, function() { jQuery(this).remove(); });
254
+
255
+ return false; //prevent bubbling
256
+ },
257
+
258
+ // given this==the input-many, returns a lambda that updates the name & id for an element
259
+ getNameUpdater: function(new_index) {
260
+ var name_prefix = Hobo.getClassData(this.get(0), 'input-many-prefix');
261
+ var id_prefix = name_prefix.replace(/\[/g, "_").replace(/\]/g, "");
262
+ var name_re = RegExp("^" + RegExp.escape(name_prefix)+ "\[\-?[0-9]+\]");
263
+ var name_sub = name_prefix + '[' + new_index.toString() + ']';
264
+ var id_re = RegExp("^" + RegExp.escape(id_prefix)+ "_\-?[0-9]+");
265
+ var id_sub = id_prefix + '_' + new_index.toString();
266
+ var class_re = RegExp(RegExp.escape(name_prefix)+ "\[\-?[0-9]+\]");
267
+ var class_sub = name_sub;
268
+
269
+ return function() {
270
+ if(this.name) {
271
+ this.name = this.name.replace(name_re, name_sub);
272
+ }
273
+ if (id_prefix==this.id.slice(0, id_prefix.length)) {
274
+ this.id = this.id.replace(id_re, id_sub);
275
+ } else {
276
+ // silly rails. text_area_tag and text_field_tag use different conventions for the id.
277
+ if(name_prefix==this.id.slice(0, name_prefix.length)) {
278
+ this.id = this.id.replace(name_re, name_sub);
279
+ } /* else {
280
+ hjq.util.log("hjq.input_many.update_id: id_prefix "+id_prefix+" didn't match input "+this.id);
281
+ } */
282
+ }
283
+ if (class_re.test(this.className)) {
284
+ this.className = this.className.replace(class_re, class_sub);
285
+ }
286
+ return this;
287
+ };
288
+ },
289
+
290
+ // given this==an input-many item, get the submit index
291
+ getIndex: function() {
292
+ return Number(this.id.match(/\[([0-9])+\]$/)[1]);
293
+ }
294
+
295
+ },
296
+
297
+ form: {
298
+ /* this function uses the jquery form plugin to submit the
299
+ form via jquery form plugin ajax, rather than using the
300
+ standard HTTP or Hobo form submission mechanisms. The
301
+ main advantage this has is that the jquery form plugin
302
+ supports ajax submission of attachments.
303
+
304
+ You must have the jquery form plugin installed. It is
305
+ not installed automatically by hobo-jquery.
306
+
307
+ FIXME: this function HARD CODES it's ajax options, in
308
+ particular update="attachments-div". It does not (yet)
309
+ get the parameters from the form, nor does it get them
310
+ through the standard hobo-jquery mechanism.
311
+ */
312
+ submit: function() {
313
+ var attrs = {
314
+ update: ['attachments-div']
315
+ };
316
+ var options = {
317
+ complete: Hobo.hideSpinner,
318
+ data: {},
319
+ dataType: 'script',
320
+ beforeSend: function(xhr) { xhr.setRequestHeader("Accept", "text/javascript"); }
321
+ };
322
+ var form = jQuery(this).closest("form");
323
+
324
+ for(i=0; i<attrs.update.length; i++) {
325
+ var id = attrs.update[i];
326
+ if(id=="self") {
327
+ for(var el=jQuery(this); el.length && !hoboParts[el.attr("id")]; el=el.parent());
328
+ id = ( el.length ? el.attr("id") : undefined) ;
329
+ }
330
+ if(id) {
331
+ options.data["render["+i+"][part_context]"] = hoboParts[id];
332
+ options.data["render["+i+"][id]"] = id;
333
+ }
334
+ }
335
+
336
+ Hobo.showSpinner(attrs.message || "Saving...", attrs.spinner_next_to);
337
+ form.ajaxSubmit(options);
338
+
339
+ //prevent bubbling
340
+ return false;
341
+ }
342
+ },
343
+
344
+ formlet: {
345
+ // call with this==the formlet or a child of the formlet to submit the formlet
346
+ submit: function(extra_callbacks, extra_options) {
347
+ var formlet = jQuery(jQuery(this).closest(".formlet").get(0));
348
+ var annotations = hjq.getAnnotations.call(formlet.get(0));
349
+
350
+ var options = annotations.ajax_options;
351
+ var attrs = annotations.ajax_attrs;
352
+ jQuery.extend(options, extra_options);
353
+
354
+ if(!extra_callbacks) extra_callbacks = {};
355
+
356
+ if(attrs.before) {
357
+ if(!hjq.util.createFunction(attrs.before).call(formlet.get(0))) {
358
+ return false;
359
+ }
360
+ }
361
+
362
+ if(attrs.confirm) {
363
+ if(!confirm(attrs.confirm)) {
364
+ return false;
365
+ }
366
+ }
367
+
368
+ // make sure we don't serialize any nested forms
369
+ options.data = formlet.find(":input").
370
+ not(formlet.find("form :input")).
371
+ not(formlet.find(".formlet :input")).
372
+ serialize();
373
+ options.dataType = 'script';
374
+
375
+ // we tell our controller which parts to return by sending it a "render" array.
376
+ for(i=0; i<attrs.update.length; i++) {
377
+ var id = attrs.update[i];
378
+ if(id=="self") {
379
+ for(var el=jQuery(this); el.length && !hoboParts[el.attr("id")]; el=el.parent());
380
+ id = ( el.length ? el.attr("id") : undefined) ;
381
+ }
382
+ if(id) {
383
+ options.data += "&" + encodeURIComponent("render["+i+"][part_context]") + "=" + encodeURIComponent(hoboParts[id]);
384
+ options.data += "&" + encodeURIComponent("render["+i+"][id]") + "=" + id;
385
+ }
386
+ }
387
+
388
+ Hobo.showSpinner(attrs.message || "Saving...", attrs.spinner_next_to);
389
+
390
+ options.success = hjq.util.createFunction(attrs.success).hjq_chain(extra_callbacks.success).hjq_bind(formlet.get(0));
391
+ options.error = hjq.util.createFunction(attrs.error).hjq_chain(extra_callbacks.error).hjq_bind(formlet.get(0));
392
+ options.complete = Hobo.hideSpinner.hjq_chain(hjq.util.createFunction(attrs.complete)).hjq_chain(extra_callbacks.complete).hjq_bind(formlet.get(0));
393
+
394
+ jQuery.ajax(options);
395
+
396
+ //prevent bubbling
397
+ return false;
398
+ }
399
+ },
400
+
401
+ datepicker: {
402
+ init: function(annotations) {
403
+ if(!this.disabled) {
404
+ jQuery(this).datepicker(hjq.getOptions(annotations));
405
+ }
406
+ }
407
+ },
408
+
409
+ autocomplete: {
410
+ init: function(annotations) {
411
+ if(!this.disabled) {
412
+ jQuery(this).autocomplete(hjq.getOptions(annotations));
413
+ }
414
+ }
415
+ },
416
+
417
+ combobox: {
418
+ init: function(annotations) {
419
+ var select = jQuery(this).find('select');
420
+ if(!select.attr('disabled')) {
421
+ var options = hjq.getOptions(annotations);
422
+ options.selected = options.selected || function(event, ui) {
423
+ // fire the prototype.js event on the <select/> for backwards compatibility
424
+ $(this).simulate('change');
425
+ }
426
+ select.combobox(options);
427
+ }
428
+ }
429
+ },
430
+
431
+ dialog: {
432
+ init: function(annotations) {
433
+ var options=hjq.getOptions(annotations);
434
+ if(!options.position) {
435
+ var pos = jQuery(this).prev().position();
436
+ options.position = [pos.left, pos.top];
437
+ }
438
+ if(annotations.buttons) {
439
+ options.buttons = {};
440
+ for(var i=0; i<annotations.buttons.length; i++) {
441
+ options.buttons[annotations.buttons[i][0]] = hjq.util.createFunction(annotations.buttons[i][1])
442
+ }
443
+ }
444
+ jQuery(this).dialog(options);
445
+ },
446
+
447
+ /* useful in the "buttons" option. Dialog is an optional parameter -- if not set, 'this' is closed instead. */
448
+ close: function() {
449
+ var jq=hjq.util.jQuerifyFirstElement.apply(this, arguments);
450
+ if(!jq.hasClass("hjq-dialog")) jq=jq.parents(".hjq-dialog");
451
+ jq.dialog('close');
452
+ },
453
+
454
+ /* useful in the "buttons" option. Will submit any enclosed formlets. */
455
+ submit_formlet: function(extra_options, extra_attrs) {
456
+ jQuery(this).find(".formlet").each(function() {
457
+ hjq.formlet.submit.call(this, extra_options, extra_attrs);
458
+ });
459
+ },
460
+
461
+ /* useful in the "buttons" option. Will submit all enclosed forms, whether AJAX or not */
462
+ submit_form: function() {
463
+ jQuery(this).find("form").each(function() {
464
+ if(jQuery(this).attr("onsubmit")) {
465
+ eval("onsubmit_func = function() {\n"+jQuery(this).attr("onsubmit")+"\n}");
466
+ onsubmit_func.apply(this);
467
+ } else {
468
+ this.submit();
469
+ }
470
+ });
471
+ },
472
+
473
+ /* calls submit_form, and then closes the dialog box. */
474
+
475
+ /* useful in the "buttons" option. Submits any enclosed formlets, and closes them */
476
+ submit_formlet_and_close: function() {
477
+ var dialog = jQuery(this);
478
+ hjq.dialog.submit_formlet.call(this, {success: function() {hjq.dialog.close.call(dialog);}});
479
+ }
480
+ },
481
+
482
+ dialog_opener: {
483
+ click: function(button, selector) {
484
+ var dialog = jQuery(selector);
485
+ if(dialog.dialog('isOpen')) {
486
+ dialog.dialog('close');
487
+ } else {
488
+ dialog.dialog('open');
489
+ }
490
+ }
491
+ }
492
+ };
493
+ })();
494
+
495
+
496
+ /* stolen from http://jqueryui.com/demos/autocomplete/#combobox
497
+ *
498
+ * and these options added.
499
+ *
500
+ * - autoFill (default: true): select first value rather than clearing if there's a match
501
+ *
502
+ * - clearButton (default: true): add a "clear" button
503
+ *
504
+ * - adjustWidth (default: true): if true, will set the autocomplete width the same as
505
+ * the old select. (requires jQuery 1.4.4 to work on IE8)
506
+ *
507
+ * - uiStyle (default: false): if true, will add classes so that the autocomplete input
508
+ * takes a jQuery-UI style
509
+ */
510
+ (function( $ ) {
511
+ $.widget( "ui.combobox", {
512
+ options: {
513
+ autoFill: true,
514
+ clearButton: true,
515
+ adjustWidth: true,
516
+ uiStyle: false,
517
+ selected: null,
518
+ },
519
+ _create: function() {
520
+ var self = this,
521
+ select = this.element.hide(),
522
+ selected = select.children( ":selected" ),
523
+ value = selected.val() ? selected.text() : "",
524
+ found = false;
525
+ var input = this.input = $( "<input>" )
526
+ .attr('title', '' + select.attr("title") + '')
527
+ .insertAfter( select )
528
+ .val( value )
529
+ .autocomplete({
530
+ delay: 0,
531
+ minLength: 0,
532
+ source: function( request, response ) {
533
+ var matcher = new RegExp( $.ui.autocomplete.escapeRegex(request.term), "i" );
534
+ var resp = select.children( "option" ).map(function() {
535
+ var text = $( this ).text();
536
+ if ( this.value && ( !request.term || matcher.test(text) ) )
537
+ return {
538
+ label: text.replace(
539
+ new RegExp(
540
+ "(?![^&;]+;)(?!<[^<>]*)(" +
541
+ $.ui.autocomplete.escapeRegex(request.term) +
542
+ ")(?![^<>]*>)(?![^&;]+;)", "gi"
543
+ ), "<strong>$1</strong>" ),
544
+ value: text,
545
+ option: this
546
+ };
547
+ });
548
+ found = resp.length > 0;
549
+ response( resp );
550
+ },
551
+ select: function( event, ui ) {
552
+ ui.item.option.selected = true;
553
+ self._trigger( "selected", event, {
554
+ item: ui.item.option
555
+ });
556
+ },
557
+ change: function( event, ui ) {
558
+ if ( !ui.item ) {
559
+ var matcher = new RegExp( "^" + $.ui.autocomplete.escapeRegex( $(this).val() ) + "$", "i" ),
560
+ valid = false;
561
+ select.children( "option" ).each(function() {
562
+ if ( $( this ).text().match( matcher ) ) {
563
+ this.selected = valid = true;
564
+ return false;
565
+ }
566
+ });
567
+ if ( !valid || input.data("autocomplete").term=="" ) {
568
+ // set to first suggestion, unless blank or autoFill is turned off
569
+ var suggestion;
570
+ if(!self.options.autoFill || input.data("autocomplete").term=="") found=false;
571
+ if(found) {
572
+ suggestion = jQuery(input.data("autocomplete").widget()).find("li:first");
573
+ var option = select.find("option:contains('"+suggestion.text()+"')").attr('selected', true);
574
+ $(this).val(suggestion.text());
575
+ input.data("autocomplete").term = suggestion.text();
576
+ self._trigger( "selected", event, { item: option[0] });
577
+ } else {
578
+ select.find("option:selected").removeAttr("selected");
579
+ $(this).val('');
580
+ input.data( "autocomplete" ).term = '';
581
+ self._trigger( "selected", event, { item: null });
582
+ }
583
+ return found;
584
+ }
585
+ }
586
+ }
587
+ });
588
+
589
+ if( self.options.adjustWidth ) { input.width(select.width()); }
590
+
591
+ if( self.options.uiStyle ) {
592
+ input.addClass( "ui-widget ui-widget-content ui-corner-left" );
593
+ }
594
+
595
+
596
+ input.data( "autocomplete" )._renderItem = function( ul, item ) {
597
+ return $( "<li></li>" )
598
+ .data( "item.autocomplete", item )
599
+ .append( "<a>" + item.label + "</a>" )
600
+ .appendTo( ul );
601
+ };
602
+
603
+ this.button = $( "<button type='button'>&nbsp;</button>" )
604
+ .attr( "tabIndex", -1 )
605
+ .attr( "title", "Show All Items" )
606
+ .insertAfter( input )
607
+ .button({
608
+ icons: {
609
+ primary: "ui-icon-triangle-1-s"
610
+ },
611
+ text: false
612
+ })
613
+ .removeClass( "ui-corner-all" )
614
+ .addClass( "ui-corner-right ui-button-icon" )
615
+ .click(function() {
616
+ // close if already visible
617
+ if ( input.autocomplete( "widget" ).is( ":visible" ) ) {
618
+ input.autocomplete( "close" );
619
+ return;
620
+ }
621
+
622
+ // work around a bug (likely same cause as #5265)
623
+ $( this ).blur();
624
+
625
+ // pass empty string as value to search for, displaying all results
626
+ input.autocomplete( "search", "" );
627
+ input.focus();
628
+ });
629
+
630
+ if( self.options.clearButton ) {
631
+ this.clear_button = $( "<button type='button'>&nbsp;</button>" )
632
+ .attr( "tabIndex", -1 )
633
+ .attr( "title", "Clear Entry" )
634
+ .insertAfter( input )
635
+ .button({
636
+ icons: {
637
+ primary: "ui-icon-close"
638
+ },
639
+ text: false
640
+ })
641
+ .removeClass( "ui-corner-all" )
642
+ .click(function(event, ui) {
643
+
644
+ select.find("option:selected").removeAttr("selected");
645
+ input.val( "" );
646
+ input.data( "autocomplete" ).term = "";
647
+ self._trigger( "selected", event, { item: null });
648
+
649
+ // work around a bug (likely same cause as #5265)
650
+ $( this ).blur();
651
+ });
652
+ }
653
+
654
+ },
655
+
656
+ destroy: function() {
657
+ this.input.remove();
658
+ this.button.remove();
659
+ this.element.show();
660
+ $.Widget.prototype.destroy.call( this );
661
+ }
662
+ });
663
+ })( jQuery );