hobo-jquery 1.3.0pre2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitmodules +3 -0
- data/LICENSE.txt +22 -0
- data/README.markdown +54 -0
- data/TODO.markdown +0 -0
- data/hobo-jquery.gemspec +9 -0
- data/jquery/javascripts/jquery-1.5.2.min.js +16 -0
- data/jquery/javascripts/jquery-ui-1.8.11.custom.min.js +783 -0
- data/jquery/stylesheets/smoothness/images/ui-bg_flat_0_aaaaaa_40x100.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-bg_flat_75_ffffff_40x100.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-bg_glass_55_fbf9ee_1x400.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-bg_glass_65_ffffff_1x400.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-bg_glass_75_dadada_1x400.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-bg_glass_75_e6e6e6_1x400.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-bg_glass_95_fef1ec_1x400.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-bg_highlight-soft_75_cccccc_1x100.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-icons_222222_256x240.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-icons_2e83ff_256x240.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-icons_454545_256x240.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-icons_888888_256x240.png +0 -0
- data/jquery/stylesheets/smoothness/images/ui-icons_cd0a0a_256x240.png +0 -0
- data/jquery/stylesheets/smoothness/jquery-ui-1.8.11.custom.css +573 -0
- data/lib/doc.dryml +75 -0
- data/lib/doc.rb +15 -0
- data/lib/doc_generator.dryml +79 -0
- data/lib/generators/hobo_jquery/install_generator.rb +23 -0
- data/lib/hobo-jquery.rb +6 -0
- data/lib/hobo-jquery/railtie.rb +9 -0
- data/lib/html2markdown.pl +7 -0
- data/lib/render.rake +39 -0
- data/lib/tasks/hobo-contrib.rake +2 -0
- data/public/javascripts/event.simulate.js +64 -0
- data/public/javascripts/hobo-jquery.js +663 -0
- data/public/stylesheets/hobo-jquery.css +16 -0
- data/rails/init.rb +0 -0
- data/taglibs/hobo-jquery.dryml +613 -0
- data/update-docs.sh +16 -0
- metadata +89 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
|
2
|
+
// stolen from clean.css
|
3
|
+
|
4
|
+
ul.hjq-input-many {list-style-type: none;}
|
5
|
+
|
6
|
+
ul.hjq-input-many > li { overflow:hidden; zoom:1;}
|
7
|
+
ul.hjq-input-many .item {float:left;}
|
8
|
+
ul.hjq-input-many div.buttons {float:left; margin-left:10px;}
|
9
|
+
|
10
|
+
li.input-many-template { display:none; }
|
11
|
+
|
12
|
+
.hjq-combobox .ui-button { margin-left: -1px; }
|
13
|
+
.hjq-combobox .ui-button-icon-only .ui-button-text { padding: 0; }
|
14
|
+
.hjq-combobox button.ui-button-icon-only { width: 20px; }
|
15
|
+
.hjq-combobox .ui-autocomplete-input { margin-right: 0; }
|
16
|
+
.hjq-combobox {white-space: nowrap;}
|
data/rails/init.rb
ADDED
File without changes
|
@@ -0,0 +1,613 @@
|
|
1
|
+
<!--
|
2
|
+
|
3
|
+
This is the jQuery plugin for Hobo. This plugin provides the jQuery UI widgets in a Hobo friendly manner, provides useful tags and functions for jQuery javascript coding, and provides additional composite tags.
|
4
|
+
|
5
|
+
To use, see the [installation instructions](http://cookbook.hobocentral.net/plugins/hobo-jquery) [(alternate link)](README.html)
|
6
|
+
|
7
|
+
The jQuery UI tags support all of the [options that the corresponding jQuery UI widgets provide](http://docs.jquery.com/UI). For example:
|
8
|
+
|
9
|
+
<hjq-datepicker dateFormat="yy-mm-dd" />
|
10
|
+
|
11
|
+
Options that expect a type other than string can be provided by passing a ruby object:
|
12
|
+
|
13
|
+
<hjq-datepicker dayNamesMin="&['Di', 'Lu', 'Ma', 'Me', 'Je', 'Ve', 'Sa']" />
|
14
|
+
|
15
|
+
Events are also supported. Pass in a global Javascript function name:
|
16
|
+
|
17
|
+
<hjq-datepicker onSelect="hjq.util.log" />
|
18
|
+
|
19
|
+
-->
|
20
|
+
|
21
|
+
<!--
|
22
|
+
This is Hobo jQuery's standard tag annotation method. This annotation is automatically added to Hobo jQuery tags, but you may wish to use it in your own tag definitions.
|
23
|
+
|
24
|
+
The current implementation uses HTML comments for annotation. This may change in the future, if somebody can suggest a better mechanism. [Here is a good discussion of the alternatives.](http://www.1729.com/blog/HtmlAnnotations.html). Two more possibilities are http://www.w3.org/TR/xhtml-rdfa-primer and http://ejohn.org/blog/html-5-data-attributes/.
|
25
|
+
|
26
|
+
To use it, invoke this tag directly before the tag to annotate. To get the annotations, call hjq.getAnnotations with _this_ set to the element that has been annotated. For example:
|
27
|
+
|
28
|
+
<def tag="mytag">
|
29
|
+
<annotate-tag option_a="42"/>
|
30
|
+
<div class="hjq-annotated">
|
31
|
+
...
|
32
|
+
</div>
|
33
|
+
</def>
|
34
|
+
|
35
|
+
jQuery(this).find('.hjq-annotated').each(function() {
|
36
|
+
var annotations = hjq.getAnnotations.call(this);
|
37
|
+
...
|
38
|
+
});
|
39
|
+
-->
|
40
|
+
<def tag="annotate-tag">
|
41
|
+
<%= "<!-- json_annotation (#{attributes.to_json}); -->".html_safe %>
|
42
|
+
</def>
|
43
|
+
|
44
|
+
<!--
|
45
|
+
These are the Hobo jQuery and jQuery assets required by Hobo jQuery. You will probably wish to add this to your page definition:
|
46
|
+
|
47
|
+
<extend tag="page">
|
48
|
+
<old-page merge>
|
49
|
+
<custom-scripts:>
|
50
|
+
<hjq-assets/>
|
51
|
+
</custom-scripts>
|
52
|
+
</old-page>
|
53
|
+
</extend>
|
54
|
+
-->
|
55
|
+
<def tag="hjq-assets">
|
56
|
+
<jquery-assets/>
|
57
|
+
<hobo-jquery-assets/>
|
58
|
+
</def>
|
59
|
+
|
60
|
+
<def tag="jquery-assets">
|
61
|
+
<stylesheet name='smoothness/jquery-ui-1.8.11.custom.css' />
|
62
|
+
<javascript name="jquery-1.5.2.min.js" />
|
63
|
+
<javascript name="jquery-ui-1.8.11.custom.min.js" />
|
64
|
+
</def>
|
65
|
+
|
66
|
+
<def tag="hobo-jquery-assets">
|
67
|
+
<stylesheet name='hobo-jquery.css' />
|
68
|
+
<javascript name="hobo-jquery.js" />
|
69
|
+
<javascript name="event.simulate.js" />
|
70
|
+
<script type="text/javascript">
|
71
|
+
jQuery.noConflict();
|
72
|
+
jQuery(document).ready(function() {
|
73
|
+
hjq.initialize.call(document);
|
74
|
+
});
|
75
|
+
</script>
|
76
|
+
</def>
|
77
|
+
|
78
|
+
|
79
|
+
<!--
|
80
|
+
This is the [jQuery-UI datepicker](http://docs.jquery.com/UI/Datepicker). All options and events are supported.
|
81
|
+
|
82
|
+
It's probably easiest to add this to your application.dryml:
|
83
|
+
|
84
|
+
<def tag="input" for="Date">
|
85
|
+
<hjq-datepicker dateFormat="yy-mm-dd" merge />
|
86
|
+
</def>
|
87
|
+
|
88
|
+
It's probably useful to set global options such as dateFormat in application.dryml and set local options such as yearRange in the tag invocation:
|
89
|
+
|
90
|
+
<input:birthdate yearRange="1900:#{Date.today.year}" />
|
91
|
+
|
92
|
+
-->
|
93
|
+
<def attrs="name" tag="hjq-datepicker">
|
94
|
+
<%
|
95
|
+
options, attrs = attributes.partition_hash(['altField', 'altFormat', 'appendText', 'buttonImage', 'buttonImageOnly', 'buttonText', 'changeMonth', 'changeYear', 'closeText', 'constrainInput', 'currentText', 'dateFormat', 'dayNames', 'dayNamesMin', 'dayNamesShort', 'defaultDate', 'duration', 'firstDay', 'gotoCurrent', 'hideIfNoPrevNext', 'isRTL', 'maxDate', 'minDate', 'monthNames', 'monthNamesShort', 'navigationAsDateFormat', 'nextText', 'numberOfMonths', 'prevText', 'selectOtherMonths','shortYearCutoff', 'showAnim', 'showButtonPanel', 'showCurrentAtPos', 'showMonthAfterYear', 'showOn', 'showOptions', 'showOtherMonths', 'stepMonths', 'yearRange'])
|
96
|
+
events, html_attrs = attrs.partition_hash(['beforeShow', 'beforeShowDay', 'onChangeMonthYear', 'onClose', 'onSelect'])
|
97
|
+
%>
|
98
|
+
<annotate-tag tag="hjq-datepicker" init="hjq.datepicker.init" options="&options" events="&events" />
|
99
|
+
<% html_attrs = add_classes(html_attrs, "hjq-annotated"); %>
|
100
|
+
<%= text_field_tag(name, this, html_attrs) %>
|
101
|
+
</def>
|
102
|
+
|
103
|
+
|
104
|
+
<!--
|
105
|
+
This is the [jQuery-UI autocompleter](http://jqueryui.com/demos/autocomplete/). All options and events provided by the jQuery autocompleter are supported in this Hobo tag. Refer to the jquery autocomplete documentation for more details.
|
106
|
+
|
107
|
+
This tag works very similarly to the `name-one` tag used in Hobo. The main reason I use `hjq-autocomplete` is that the scriptaculous widget used by Hobo for `name-one` does not work well in Internet Explorer: https://prototype.lighthouseapp.com/projects/8887/tickets/33
|
108
|
+
|
109
|
+
If you do not provide a `source` option, this tag will essentially provide a default similar to this:
|
110
|
+
|
111
|
+
<hjq-autocomplete:project source="&Project.find(:all, :limit => 5000).map {|p| p.name}" />
|
112
|
+
|
113
|
+
This will provide a local autocomplete, putting all possible options into your HTML. This is useful when you have too many options to make a `select-one` infeasible, but will overload your server and crash your browser if you have millions of options.
|
114
|
+
|
115
|
+
To do a server side autocomplete, use a path for your source option:
|
116
|
+
|
117
|
+
<hjq-autocomplete:project source="&query_projects_path" delay="500" minLength="3" />
|
118
|
+
|
119
|
+
And add something like this to your controller:
|
120
|
+
|
121
|
+
index_action :query do
|
122
|
+
render :json => Project.name_contains(params[:term]).limit(100).*.name
|
123
|
+
end
|
124
|
+
-->
|
125
|
+
<def tag="hjq-autocomplete">
|
126
|
+
<%
|
127
|
+
options, attrs = attributes.partition_hash(['disabled', 'appendTo', 'autoFocus', 'delay', 'minLength', 'position', 'source'])
|
128
|
+
events, html_attrs = attrs.partition_hash(['create', 'search', 'open', 'focus', 'select', 'close', 'change'])
|
129
|
+
options["source"] ||= begin
|
130
|
+
complete_target = this_field_reflection.klass
|
131
|
+
complete_target.find(:all, :limit => 5000).*.send(complete_target.name_attribute)
|
132
|
+
end
|
133
|
+
|
134
|
+
%>
|
135
|
+
<annotate-tag tag="hjq-autocomplete" init="hjq.autocomplete.init" options="&options" events="&events" />
|
136
|
+
<% html_attrs = add_classes(html_attrs, "hjq-annotated")
|
137
|
+
html_attrs["name"] ||= param_name_for_this
|
138
|
+
html_attrs["value"] ||= name(:no_wrapper => true, :if_present => true)
|
139
|
+
%>
|
140
|
+
<%= text_field_tag(name, this, html_attrs) %>
|
141
|
+
|
142
|
+
</def>
|
143
|
+
|
144
|
+
|
145
|
+
<!-- Creates an enhanced version of the [jQuery-UI demo combobox](http://jqueryui.com/demos/autocomplete/#combobox). This will turn any select (even a select-many) into a combobox.
|
146
|
+
|
147
|
+
NOTE: backwards compatibility with the Hobo&Rails prototype/lowpro handlers requires event.simulate.js. You may need to rerun your hobo_query:install generator to copy this file into your application.
|
148
|
+
|
149
|
+
Standard usage:
|
150
|
+
|
151
|
+
<hjq-combobox>
|
152
|
+
<select .../>
|
153
|
+
</hjq-combobox>
|
154
|
+
|
155
|
+
If you don't supply the select, it will use Hobo's `<input>` tag. This is usually the right default.
|
156
|
+
|
157
|
+
You can tell Hobo to always use a combobox for select-one, select-many, select-input and/or select-menu. Notice that we also changed the limit - 100 is the default, which makes sense for a select, but not for a combobox.
|
158
|
+
|
159
|
+
<extend tag="select-one">
|
160
|
+
<hjq-combobox tag="select-one">
|
161
|
+
<old-select-one limit="5000" merge/>
|
162
|
+
</hjq-combobox>
|
163
|
+
</extend>
|
164
|
+
|
165
|
+
To make the behavior more like the demo's, turn off autoFill and clearButton:
|
166
|
+
|
167
|
+
<hjq-combobox autoFill="&false" clearButton="&false"/>
|
168
|
+
|
169
|
+
Combobox is a new control that is built on autocomplete. The autocomplete options may not be used directly. If you need any of them let me know, they should not be too hard to add if you volunteer to test them.
|
170
|
+
|
171
|
+
### Attributes
|
172
|
+
|
173
|
+
- `autoFill` (default: true): select first value rather than clearing if there's a match
|
174
|
+
|
175
|
+
- `clearButton` (default: true): add a "clear" button
|
176
|
+
|
177
|
+
- `adjustWidth` (default: true): if true, will set the autocomplete width the same as the old select. (requires jQuery 1.4.4 to work on IE8)
|
178
|
+
|
179
|
+
- uiStyle (default: false): if true, will add classes so that the autocomplete input takes a jQuery-UI style. If false, the standard Hobo style is used.
|
180
|
+
|
181
|
+
### Events
|
182
|
+
|
183
|
+
- `selected` (default: `$(this).simulate('change');`): this is a jQuery-UI event handler. The default value for this option fires the `onchange` handler on the underlying `select` in a manner compatible with both prototype.js and jQuery. For this to work, event.simulate.js must be loaded, and jQuery must be put into noConflict mode. hobo-jquery should be doing both of these for you if you are using it as recommended in the README.
|
184
|
+
|
185
|
+
-->
|
186
|
+
|
187
|
+
<def tag="hjq-combobox" options="tag">
|
188
|
+
<%
|
189
|
+
options, attrs = attributes.partition_hash(['autoFill', 'clearButton', 'adjustWidth', 'uiStyle'])
|
190
|
+
events, html_attrs = attrs.partition_hash(['selected'])
|
191
|
+
%>
|
192
|
+
<annotate-tag tag="hjq-combobox" init="hjq.combobox.init" options="&options" events="&events"/>
|
193
|
+
<% tag ||= 'input' %>
|
194
|
+
<span class="hjq-combobox hjq-annotated" param="default">
|
195
|
+
<input merge param />
|
196
|
+
</span>
|
197
|
+
</def>
|
198
|
+
|
199
|
+
|
200
|
+
<!-- Creates a sub-section of the form which the user can repeat using (+) and (-) buttons, in order to allow an entire `has_many` collection to be created/edited in a single form.
|
201
|
+
|
202
|
+
Most of the enhancements from hjq-input-many have been folded back into hjq-input-many, but there are a few remaining differences:
|
203
|
+
|
204
|
+
- it supports delayed initialization, required for some Javascript elements like the jQuery UI datepicker
|
205
|
+
- the name of the main parameter is `item` rather than `default`
|
206
|
+
- hjq-input-many allows you to provide javascript callbacks. input-many fires
|
207
|
+
rapid:add, rapid:change and rapid:remove events that can be hooked.
|
208
|
+
|
209
|
+
### Example
|
210
|
+
|
211
|
+
Say you are creating a new `Category` in your online shop, and you want to create some initial products *in the same form*, you can add the following to your form:
|
212
|
+
|
213
|
+
<hjq-input-many:products fields="name, price" />
|
214
|
+
|
215
|
+
You'll often want to provide the `item` parameter:
|
216
|
+
|
217
|
+
<hjq-input-many:products><item:><field-list fields="name, price" /></item:></hjq-input-many>
|
218
|
+
|
219
|
+
A fully worked up example of nested hjq-input-many's may be found in [agility/jquery-test](http://github.com/tablatom/agility/blob/jquery-test/app/views/projects/nested_has_many_test.dryml)
|
220
|
+
|
221
|
+
### Attributes
|
222
|
+
|
223
|
+
- `minimum`: the minimum number of items in the collection. Currently only '0' and '1' are supported values. The default is '0'.
|
224
|
+
|
225
|
+
- `fields`: passed down to the `field-list` tag in the default `item`.
|
226
|
+
|
227
|
+
- `template`: the default values for new items. Normally this functionality is better provided by Model.new, but it's here if you need it.
|
228
|
+
|
229
|
+
- `add-hook`: a javascript function to call after an item has been added. The context will be set to the new item.
|
230
|
+
|
231
|
+
- `remove-hook`: a javascript function to call before an item is removed. The context will be set to the item. If the hook returns false, the remove is cancelled.
|
232
|
+
|
233
|
+
|
234
|
+
-->
|
235
|
+
<def tag="hjq-input-many" attrs="minimum, fields, template, add-hook, remove-hook" polymorphic >
|
236
|
+
<%
|
237
|
+
# helper function to create id's on buttons to facilitate testing
|
238
|
+
def underize(s)
|
239
|
+
s.gsub(/\[/,"_").gsub(/\]/,"")
|
240
|
+
end
|
241
|
+
%>
|
242
|
+
<set empty="&this.empty?"/>
|
243
|
+
<annotate-tag tag="hjq-input-many" init="hjq.input_many.init" merge-attrs="minimum, add-hook, remove-hook" />
|
244
|
+
<ul class="hjq-input-many #{this_field.dasherize} #{css_data :input_many_prefix, param_name_for_this} hjq-annotated" merge-attrs="&attributes - attrs_for(:hjq_input_many_item)">
|
245
|
+
<% template ||= this.try.new_candidate || this.member_class.new %>
|
246
|
+
<% minimum ||= 0 ; minimum = minimum.to_i %>
|
247
|
+
<fake-field-context fake-field="-1" context="&template">
|
248
|
+
<li class="input-many-template" id="#{param_name_for_this}">
|
249
|
+
<hjq-input-many-item param="item" merge-attrs="fields" />
|
250
|
+
<div class="buttons">
|
251
|
+
<button param="remove-item" id="#{underize param_name_for_this}_remove">-</button>
|
252
|
+
<button param="add-item" id="#{underize param_name_for_this}_add">+</button>
|
253
|
+
</div>
|
254
|
+
</li>
|
255
|
+
</fake-field-context>
|
256
|
+
<li class="empty #{'hidden' unless this.empty? and minimum==0}" id="#{param_name_for_this}[-1]_empty">
|
257
|
+
<!-- HACK way to signal an empty collection to the controller -->
|
258
|
+
<input type="hidden" class="empty-input" id="#{param_name_for_this}" name="#{param_name_for_this}" value="" disabled="&(!this.empty? || minimum>0)" />
|
259
|
+
<fake-field-context fake-field="-1" context="&template">
|
260
|
+
<div param="empty-message">
|
261
|
+
<ht key="#{this.class.to_s.underscore}.collection.empty_message">
|
262
|
+
No <%= this.class.name.titleize.downcase.pluralize %>.
|
263
|
+
</ht>
|
264
|
+
</div>
|
265
|
+
<div class="buttons">
|
266
|
+
<button param="remove-item" class="hidden" id="#{underize param_name_for_this}_remove">-</button>
|
267
|
+
<button param="add-item" id="#{underize param_name_for_this}_add">+</button>
|
268
|
+
</div>
|
269
|
+
</fake-field-context>
|
270
|
+
</li>
|
271
|
+
<fake-field-context fake-field="0" context="&template">
|
272
|
+
<li if="&(this_parent.empty? && minimum>0)" id="#{param_name_for_this}">
|
273
|
+
<hjq-input-many-item param="item" merge-attrs="fields" />
|
274
|
+
<div class="buttons">
|
275
|
+
<button param="remove-item" class="hidden" id="#{underize param_name_for_this}_remove">-</button>
|
276
|
+
<button param="add-item" id="#{underize param_name_for_this}_add">+</button>
|
277
|
+
</div>
|
278
|
+
</li>
|
279
|
+
</fake-field-context>
|
280
|
+
<li repeat class="#{'record-with-errors' unless this.errors.empty?}" id="#{param_name_for_this}">
|
281
|
+
<error-messages without-heading class="sub-record"/>
|
282
|
+
<hidden-id-field/>
|
283
|
+
<hjq-input-many-item param="item" merge-attrs="fields" />
|
284
|
+
<div class="buttons">
|
285
|
+
<button param="remove-item" class="#{'hidden' if this_parent.length<=minimum}" id="#{underize param_name_for_this}_remove">-</button>
|
286
|
+
<button param="add-item" class="#{'hidden' if not last_item?}" id="#{underize param_name_for_this}_add">+</button>
|
287
|
+
</div>
|
288
|
+
</li>
|
289
|
+
</ul>
|
290
|
+
</def>
|
291
|
+
|
292
|
+
<!--
|
293
|
+
This is the default item used by `<hjq-input-many>`. Redefine or extend if desired.
|
294
|
+
-->
|
295
|
+
<def tag="hjq-input-many-item" attrs="fields">
|
296
|
+
<card param="default">
|
297
|
+
<header: replace />
|
298
|
+
<body:><field-list merge-attrs="fields" /></body:>
|
299
|
+
</card>
|
300
|
+
</def>
|
301
|
+
|
302
|
+
<!--
|
303
|
+
`<formlet>` works like an AJAX `<form>` without actually being an AJAX `<form>`. This is useful in scenarios where forms aren't allowed or are problematic, like inside of another form.
|
304
|
+
|
305
|
+
### Attributes
|
306
|
+
|
307
|
+
- standard Hobo HTML attributes (id, part, et cetera). All supported.
|
308
|
+
|
309
|
+
- standard Hobo AJAX attributes: (see [the cookbook](http://cookbook.hobocentral.net/api_taglibs/rapid_forms) )
|
310
|
+
|
311
|
+
- `update`: (optional). The DOM ID's of parts to update. Note that you get an AJAX formlet whether this is provided or not.
|
312
|
+
|
313
|
+
- `message`: the spinner message
|
314
|
+
|
315
|
+
- `spinner-next-to`: DOM id of an element to position the ajax progress spinner next to.
|
316
|
+
|
317
|
+
- `before`, `success`, `error`, `complete`: These callbacks are slightly different from the standard form callbacks. They are called with "this" set to the formlet. Also, you may pass in a function name rather than a javascript fragment. If the before callback returns false, it will cancel the submission.
|
318
|
+
|
319
|
+
- `confirm`: a message to be displayed in a JavaScript confirm dialog. By default there is no confirm dialog
|
320
|
+
|
321
|
+
- not supported: `type, script, params, reset-form, refocus-form, result-update`. None of these are particularly hard to support. Email or post a message to the list if you need them.
|
322
|
+
|
323
|
+
- standard Hobo Form attributes (all supported):
|
324
|
+
|
325
|
+
- `hidden-fields`
|
326
|
+
|
327
|
+
- `action`
|
328
|
+
|
329
|
+
- `method`
|
330
|
+
|
331
|
+
- `web-method`
|
332
|
+
|
333
|
+
- `lifecycle`
|
334
|
+
|
335
|
+
- `owner`
|
336
|
+
-->
|
337
|
+
<def tag="formlet" polymorphic attrs="update, hidden-fields, action, method, web-method, lifecycle, owner">
|
338
|
+
<%
|
339
|
+
#FIXME: code very similar to <form>. refactor out common code.
|
340
|
+
|
341
|
+
ajax_attrs, html_attrs = attributes.partition_hash(Hobo::Rapid::Helper::AJAX_ATTRS)
|
342
|
+
|
343
|
+
ajax_options = {}
|
344
|
+
|
345
|
+
new_record = this.try.new_record?
|
346
|
+
|
347
|
+
method = if method.nil?
|
348
|
+
(action || web_method || new_record) ? "post" : "put"
|
349
|
+
else
|
350
|
+
method.downcase
|
351
|
+
end
|
352
|
+
|
353
|
+
ajax_options[:url] = action || begin
|
354
|
+
target = if owner
|
355
|
+
collection_name = this.class.reverse_reflection(owner).name
|
356
|
+
this.send(owner).send(collection_name)
|
357
|
+
else
|
358
|
+
this
|
359
|
+
end
|
360
|
+
action = web_method || lifecycle
|
361
|
+
object_url(target, action, :method => "post")
|
362
|
+
end
|
363
|
+
|
364
|
+
if action.nil? && (ajax_options[:url].nil? ||
|
365
|
+
(lifecycle.nil? && new_record && !this.creatable_by?(current_user)) ||
|
366
|
+
(lifecycle.nil? && !new_record && !can_edit?))
|
367
|
+
Dryml.last_if = false
|
368
|
+
logger.info("blank!!!")
|
369
|
+
""
|
370
|
+
else
|
371
|
+
if method == "put"
|
372
|
+
# browsers don't support put -- use post and add the Rails _method hack
|
373
|
+
http_method_hidden = hidden_field_tag("_method", "PUT")
|
374
|
+
ajax_options[:type] = "post"
|
375
|
+
else
|
376
|
+
ajax_options[:type] = method
|
377
|
+
end
|
378
|
+
|
379
|
+
ajax_attrs[:update] = if update
|
380
|
+
update.split(/,\s*/)
|
381
|
+
else
|
382
|
+
[]
|
383
|
+
end
|
384
|
+
|
385
|
+
hiddens = nil
|
386
|
+
body = with_form_context do
|
387
|
+
# It is important to evaluate parameters.default first, in order to populate scope.form_field_names
|
388
|
+
b = parameters.default
|
389
|
+
hiddens = self.hidden_fields(:fields => hidden_fields) if new_record
|
390
|
+
b
|
391
|
+
end
|
392
|
+
|
393
|
+
auth_token = if method.nil? || method == 'get' || !protect_against_forgery?
|
394
|
+
''
|
395
|
+
else
|
396
|
+
element(:input, {:type => "hidden",
|
397
|
+
:name => request_forgery_protection_token.to_s,
|
398
|
+
:value => form_authenticity_token}, nil, true, true)
|
399
|
+
end
|
400
|
+
|
401
|
+
unless method == "get"
|
402
|
+
page_path = if (request.post? || request.put?) && params[:page_path]
|
403
|
+
params[:page_path]
|
404
|
+
else
|
405
|
+
request.fullpath
|
406
|
+
end
|
407
|
+
page_path_hidden = hidden_field_tag("page_path", page_path)
|
408
|
+
end
|
409
|
+
|
410
|
+
hiddens_div = element(:div, {:class => "hidden-fields"}, [http_method_hidden, page_path_hidden, auth_token, hiddens].safe_join)
|
411
|
+
|
412
|
+
body = [hiddens_div, body].safe_join
|
413
|
+
|
414
|
+
if action.nil? # don't add automatic css classes if the action was specified
|
415
|
+
if web_method
|
416
|
+
add_classes!(html_attrs, "#{type_id.dasherize}-#{web_method}-form")
|
417
|
+
else
|
418
|
+
add_classes!(html_attrs, "#{'new ' if new_record}#{type_id.dasherize}")
|
419
|
+
end
|
420
|
+
end
|
421
|
+
|
422
|
+
Dryml.last_if = true
|
423
|
+
end
|
424
|
+
|
425
|
+
add_classes!(html_attrs, "formlet")
|
426
|
+
%>
|
427
|
+
<annotate-tag tag="formlet" ajax-attrs="&ajax_attrs" ajax-options="&ajax_options" />
|
428
|
+
<%= element("div", html_attrs, body) %>
|
429
|
+
</def>
|
430
|
+
|
431
|
+
<!--
|
432
|
+
Submit button for use with `<formlet>`. Submit won't work, because `<formlet>` isn't a form.
|
433
|
+
-->
|
434
|
+
<def tag="formlet-submit" attrs="label, image, method">
|
435
|
+
<% label ||= 'submit' %>
|
436
|
+
<if test='&image'>
|
437
|
+
<input class='image-button formlet-submit-button' type='image' src='&image' onClick='hjq.formlet.submit.call(this)' merge-attrs />
|
438
|
+
</if>
|
439
|
+
<else>
|
440
|
+
<input class='button formlet-submit-button' type='button' value='#{label}' onClick='hjq.formlet.submit.call(this)' merge-attrs />
|
441
|
+
</else>
|
442
|
+
</def>
|
443
|
+
|
444
|
+
<!--
|
445
|
+
Submit button for use with the `<form>`. This uses hjq.form.submit to
|
446
|
+
submit the form via jquery form plugin ajax, rather than using the
|
447
|
+
standard HTTP or Hobo form submission mechanisms. The main advantage
|
448
|
+
this has is that the jquery form plugin supports ajax submission of
|
449
|
+
attachments.
|
450
|
+
|
451
|
+
You must have the jquery form plugin installed. It is not installed
|
452
|
+
automatically by hobo-jquery.
|
453
|
+
|
454
|
+
FIXME: this function HARD CODES it's ajax options, in particular
|
455
|
+
update="attachments-div". It does not (yet) get the parameters from
|
456
|
+
the form, nor does it get them through the standard hobo-jquery
|
457
|
+
mechanism. update in hobo-jquery.js.
|
458
|
+
-->
|
459
|
+
<def tag="hjq-submit" attrs="label, image, method">
|
460
|
+
<% label ||= 'submit' %>
|
461
|
+
<if test='&image'>
|
462
|
+
<input class='image-button hjq-form-submit-button' type='image' src='&image' onClick='hjq.form.submit.call(this)' merge-attrs />
|
463
|
+
</if>
|
464
|
+
<else>
|
465
|
+
<input class='button hjq-form-submit-button' type='button' value='#{label}' onClick='hjq.form.submit.call(this)' merge-attrs />
|
466
|
+
</else>
|
467
|
+
</def>
|
468
|
+
|
469
|
+
<!--
|
470
|
+
The JQuery UI [dialog](http://jqueryui.com/demos/dialog/). All options and methods are supported.
|
471
|
+
|
472
|
+
The default for [autoOpen](http://jqueryui.com/demos/dialog/#option-autoOpen) has been changed to `"&false"`. To display the dialog set autoOpen to `"&true"`, use a `<hjq-dialog-open-button>`, use the [jQuery dialog open method](http://jqueryui.com/demos/dialog/#method-open), or use an `<hjq-dialog-and-button>` instead.
|
473
|
+
|
474
|
+
The default for [position](http://jqueryui.com/demos/dialog/#option-position) is also set so that the dialog appears where it is defined.
|
475
|
+
|
476
|
+
The [buttons](http://jqueryui.com/demos/dialog/#option-buttons) option has been changed slightly. Instead of an Hash, it expects a list of pairs. (Unlike Javascript Objects, Ruby Hash's do not preserve order). Because of quoting issues, it's usually easier to assign the list to a variable and then include the list:
|
477
|
+
|
478
|
+
<% buttons = [ ["cancel", "jQuery(this).dialog('close')"] ] %>
|
479
|
+
<hjq-dialog buttons="&buttons" />
|
480
|
+
|
481
|
+
or
|
482
|
+
|
483
|
+
<hjq-dialog id="story-dialog" buttons='&{"cancel" => "jQuery(this).dialog(\"close\")"}' />
|
484
|
+
|
485
|
+
or
|
486
|
+
|
487
|
+
<hjq-dialog id="story-dialog" buttons='&{"cancel" => "hjq.dialog.close"}' />
|
488
|
+
|
489
|
+
Because Ruby Hash's do not preserve order, you may use a list of pairs instead of a Hash.
|
490
|
+
|
491
|
+
JQuery UI is sometimes picky about types. For instance, the width option must be a number, not a string. By default Hobo options are strings, but you can easily send a number by using the ampersand to switch to ruby mode:
|
492
|
+
|
493
|
+
<hjq-dialog width="&640"/>
|
494
|
+
|
495
|
+
The following functions are predefined for use in dialog buttons:
|
496
|
+
|
497
|
+
* `hjq.dialog.close`: close the dialog box
|
498
|
+
* `hjq.dialog.submit_formlet`: submits all formlets inside the dialog
|
499
|
+
* `hjq.dialog.submit_formlet_and_close`: submits any enclosed formlets, then closes the dialog.
|
500
|
+
-->
|
501
|
+
<def tag="hjq-dialog" attrs="buttons">
|
502
|
+
<% #FIXME: dry these names
|
503
|
+
options, attrs = attributes.partition_hash(["autoOpen", "bgiframe", "closeOnEscape", "dialogClass", "draggable", "height", "hide", "maxHeight", "maxWidth", "minWidth", "modal", "position", "resizable", "show", "stack", "title", "width", "zIndex"])
|
504
|
+
events, html_attrs = attrs.partition_hash(["beforeclose", "open", "focus", "dragStart", "drag", "dragStop", "resizeStart", "resize", "resizeStop", "close"])
|
505
|
+
options["autoOpen"]=false unless options.has_key?("autoOpen")
|
506
|
+
%>
|
507
|
+
<span class="hjq-dialog-position"></span>
|
508
|
+
<annotate-tag tag="hjq-dialog" init="hjq.dialog.init" options="&options" events="&events" buttons="&buttons" />
|
509
|
+
<% html_attrs = add_classes(html_attrs, "hjq-annotated hidden hjq-dialog"); %>
|
510
|
+
<div merge-attrs="&html_attrs" param="default" />
|
511
|
+
</def>
|
512
|
+
|
513
|
+
<!--
|
514
|
+
When pressed, will open a `<hjq-dialog>`.
|
515
|
+
|
516
|
+
### Attributes
|
517
|
+
* `dialog` (required):a CSS selector (AKA jQuery selector) to locate the dialog in the DOM. Remember, if you're passing an ID, place a '#' in front of the ID.
|
518
|
+
* supports all `<button>` attributes except for `onclick`
|
519
|
+
|
520
|
+
### Example
|
521
|
+
<hjq-dialog id="hello-dialog">Hello!</hjq-dialog>
|
522
|
+
<hjq-dialog-open-button dialog="#hello-dialog">Press Me!</hjq-dialog-open-button>
|
523
|
+
-->
|
524
|
+
<def tag="hjq-dialog-open-button" attrs="dialog">
|
525
|
+
<button onclick="hjq.dialog_opener.click(this, jQuery('#{dialog}')); return false;" param="default" class="hjq-dialog-open-button" merge />
|
526
|
+
</def>
|
527
|
+
|
528
|
+
<!--
|
529
|
+
Defines an `<hjq-dialog>` and a `<hjq-dialog-open-button>` and links the two together.
|
530
|
+
|
531
|
+
### Attributes
|
532
|
+
* `id` (optional): the DOM id to use for the dialog
|
533
|
+
-->
|
534
|
+
<def tag="hjq-dialog-and-button">
|
535
|
+
<% # we can't use jQuery traversal functions since the dialog will move around in the DOM %>
|
536
|
+
<% attributes["id"]="hjq-dialog-#{(rand*1000000000).to_i}" unless attributes.has_key?("id") %>
|
537
|
+
<hjq-dialog param merge />
|
538
|
+
<hjq-dialog-open-button dialog="##{attributes['id']}" param />
|
539
|
+
</def>
|
540
|
+
|
541
|
+
<!--
|
542
|
+
Defines a `<select-one>` and a button that brings up an `<hjq-dialog>` containing a form allowing you to enter a new item. After the form is filled in and closed, the `<select-one>` is changed to select the new item.
|
543
|
+
|
544
|
+
### Caveats
|
545
|
+
|
546
|
+
`select-one-or-new-dialog` is a very simple control that depends on a large number of Hobo and hobo-jquery components to be working correctly in the standard manner. These include ajax, a standard form context, and formlets.
|
547
|
+
|
548
|
+
#### sort order
|
549
|
+
|
550
|
+
The javascript for this control is a quick hack that assumes the new item created will be the last item in the select. If you've defined a sort order, this assumption will be broken. It will also be broken if there are more items than the select-one limit.
|
551
|
+
|
552
|
+
#### parameterizing parts
|
553
|
+
|
554
|
+
Because the select-one is a part, it cannot be parameterized. However, you can adjust its definition by defining the polymorphic input for the type:
|
555
|
+
|
556
|
+
<def tag="input" for="StoryStatus">
|
557
|
+
<select-one include-none="&false" />
|
558
|
+
</def>
|
559
|
+
|
560
|
+
#### controller actions
|
561
|
+
|
562
|
+
For Hobo ajax to work, standard controller actions are required. If you have customized the sub-item's create action, you may break select-one-or-new-dialog.
|
563
|
+
|
564
|
+
There is one case where you have to customize the create action. For Ajax to work, Hobo has to resurrect the context. This is straightforward if the parent context exists in the database. If it does not, then Hobo uses whatever 'this' is created by the controller action. In our case, this is usually the wrong thing.
|
565
|
+
|
566
|
+
Example:
|
567
|
+
|
568
|
+
class Story
|
569
|
+
hobo_model
|
570
|
+
fields do
|
571
|
+
name :string
|
572
|
+
end
|
573
|
+
belongs_to :story_status
|
574
|
+
# plus permissions, etc...
|
575
|
+
end
|
576
|
+
|
577
|
+
app/views/stories/new.dryml:
|
578
|
+
|
579
|
+
<new-page>
|
580
|
+
<field-list:>
|
581
|
+
<story-status-view:>
|
582
|
+
<select-one-or-new-dialog/>
|
583
|
+
</story-status-view:>
|
584
|
+
</field-list:>
|
585
|
+
</new-page>
|
586
|
+
|
587
|
+
app/controllers/story_statuses_controller.rb:
|
588
|
+
|
589
|
+
class StoryStatusesController < ApplicationController
|
590
|
+
def create
|
591
|
+
hobo_create do
|
592
|
+
if request.xhr?
|
593
|
+
self.this = Story.new
|
594
|
+
end
|
595
|
+
end
|
596
|
+
end
|
597
|
+
end
|
598
|
+
|
599
|
+
|
600
|
+
-->
|
601
|
+
<def tag="select-one-or-new-dialog">
|
602
|
+
<% dialog_id="hjq-dialog-#{typed_id.gsub(':', '-')}" %>
|
603
|
+
<% part_id = "select-one-#{typed_id.gsub(':', '-')}" %>
|
604
|
+
<input part="select-one-or-new-dialog-select" id="#{part_id}"/>
|
605
|
+
<hjq-dialog-open-button dialog="##{dialog_id}" param>
|
606
|
+
New <% this_field.humanize %>
|
607
|
+
</hjq-dialog-open-button>
|
608
|
+
<hjq-dialog param buttons='&[["cancel", "hjq.dialog.close"], ["ok", "hjq.dialog.submit_formlet_and_close"]]' title="New #{this_field.humanize}" id="&dialog_id" >
|
609
|
+
<formlet with="&this_type.new" update="&part_id" success="jQuery('##{part_id} select').val(jQuery('##{part_id} option:last').val())">
|
610
|
+
<field-list />
|
611
|
+
</formlet>
|
612
|
+
</hjq-dialog>
|
613
|
+
</def>
|