hobo-jquery 1.3.0pre2

Sign up to get free protection for your applications and to get access to all the features.
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 );