rep.ajax.toolkit 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/.gitignore +4 -0
- data/LICENSE +22 -0
- data/README +1 -0
- data/Rakefile +27 -0
- data/TODO +1 -0
- data/VERSION +1 -0
- data/lib/rep.ajax.toolkit.rb +0 -0
- data/public/images/rep.ajax-validate/green_check.png +0 -0
- data/public/images/rep.ajax-validate/red_cross.png +0 -0
- data/public/images/rep.ajax-validate/spinner_sm.gif +0 -0
- data/public/javascripts/rep.ajax-validate.js +192 -0
- data/public/javascripts/rep.hint.js +37 -0
- data/public/javascripts/rep.widgets/events.js +55 -0
- data/public/javascripts/rep.widgets/global.js +52 -0
- data/public/javascripts/rep.widgets/model.js +165 -0
- data/public/javascripts/rep.widgets/widget.js +217 -0
- data/public/javascripts/rep.widgets.js +13 -0
- data/public/stylesheets/rep.ajax-validate.css +7 -0
- data/rep.ajax.toolkit.gemspec +63 -0
- metadata +113 -0
data/.gitignore
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2009 MIT Hyperstudio
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person
|
4
|
+
obtaining a copy of this software and associated documentation
|
5
|
+
files (the "Software"), to deal in the Software without
|
6
|
+
restriction, including without limitation the rights to use,
|
7
|
+
copy, modify, merge, publish, distribute, sublicense, and/or sell
|
8
|
+
copies of the Software, and to permit persons to whom the
|
9
|
+
Software is furnished to do so, subject to the following
|
10
|
+
conditions:
|
11
|
+
|
12
|
+
The above copyright notice and this permission notice shall be
|
13
|
+
included in all copies or substantial portions of the Software.
|
14
|
+
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
16
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
|
17
|
+
OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
18
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
|
19
|
+
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
20
|
+
WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
21
|
+
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
22
|
+
OTHER DEALINGS IN THE SOFTWARE.
|
data/README
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
|
data/Rakefile
ADDED
@@ -0,0 +1,27 @@
|
|
1
|
+
begin
|
2
|
+
require 'jeweler'
|
3
|
+
Jeweler::Tasks.new do |s|
|
4
|
+
s.name = "rep.ajax.toolkit"
|
5
|
+
s.summary = "Hyperstudio ajax tools"
|
6
|
+
s.description = "Hyperstudio ajax tools"
|
7
|
+
s.email = "yorkc@mit.edu"
|
8
|
+
s.homepage = "http://github.com/repertoire/rep.ajax.toolkit"
|
9
|
+
s.authors = ["Christopher York"]
|
10
|
+
s.add_dependency('repertoire-assets', '>=0.1.0')
|
11
|
+
s.add_dependency('rep.jquery', '>=1.3.2')
|
12
|
+
end
|
13
|
+
Jeweler::RubyforgeTasks.new do |rubyforge|
|
14
|
+
rubyforge.doc_task = "yardoc"
|
15
|
+
end
|
16
|
+
|
17
|
+
Jeweler::GemcutterTasks.new
|
18
|
+
rescue LoadError
|
19
|
+
puts "Jeweler not available. Install it with: sudo gem install jeweler"
|
20
|
+
end
|
21
|
+
|
22
|
+
begin
|
23
|
+
require 'repertoire-assets'
|
24
|
+
# Repertoire::Assets::Tasks.new(:gem_excludes => ["jquery"])
|
25
|
+
rescue LoadError
|
26
|
+
puts "Repertoire assets not available. Install it with: sudo gem install repertoire-assets"
|
27
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.3.0
|
File without changes
|
Binary file
|
Binary file
|
Binary file
|
@@ -0,0 +1,192 @@
|
|
1
|
+
//= require <jquery>
|
2
|
+
//= require <jquery/form>
|
3
|
+
|
4
|
+
//= require "../stylesheets/rep.ajax-validate.css"
|
5
|
+
//= provide "../images/rep.ajax-validate/*"
|
6
|
+
|
7
|
+
/*
|
8
|
+
* Repertoire incremental form validation via ajax
|
9
|
+
*
|
10
|
+
* Requires the JQuery Form plugin (http://www.malsup.com/jquery/form/)
|
11
|
+
*
|
12
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
13
|
+
* Christopher York, 03/2009
|
14
|
+
* Revised 06/2009
|
15
|
+
*/
|
16
|
+
|
17
|
+
(function($) {
|
18
|
+
//
|
19
|
+
// Usage:
|
20
|
+
//
|
21
|
+
// Invoke ajaxValidate on the form to be incrementally validated. On the server side,
|
22
|
+
// your validation method should accept the same parameters as the form's actual submit method,
|
23
|
+
// and return a JSON hash of errors in the form { field_name: [error1, error2, ...]}; or 'true' if the
|
24
|
+
// entire form validates. By default, the plugin only enables the submit button when every
|
25
|
+
// field is valid.
|
26
|
+
//
|
27
|
+
// Basic Example:
|
28
|
+
//
|
29
|
+
// Only the url for the validation webservice is required.
|
30
|
+
//
|
31
|
+
// $('#my form').ajaxValidate({ url: '/validate_user' })
|
32
|
+
//
|
33
|
+
// Example with options:
|
34
|
+
//
|
35
|
+
// $('#my form').ajaxValidate({
|
36
|
+
// url: '/validate_user', /* validation webservice url */
|
37
|
+
// disable: false, /* allow submit for invalid forms */
|
38
|
+
// feedback: '#validation_feedback', /* put feedback in a special div rather than per field */
|
39
|
+
// delegate: { 'user[password_confirmation]': 'user[password]' } /* validate password whenever confirmation changes */
|
40
|
+
// initialize: true /* validate all fields on form load */
|
41
|
+
// })
|
42
|
+
//
|
43
|
+
// Also, you can configure the format of the error html by redefining ajaxValidate.format. To use the default styling,
|
44
|
+
// link to the rep.ajax-validate.css file and associated assets.
|
45
|
+
//
|
46
|
+
// Note that input fields are identified by the 'name' attribute rather than id or class, for consistency
|
47
|
+
// with form-processing code on the server side. So if your form looks like:
|
48
|
+
//
|
49
|
+
// <form method="post" action="/users">
|
50
|
+
// <div>Email:<input type="text" name="user[email]"/><div class='validate'/></div>
|
51
|
+
// <div>Password:<input type="password" name="user[password]"/><div class='validate'/></div>
|
52
|
+
// <div>Confirm:<input type="password" name="user[confirm_password]"/><div class='validate'/></div>
|
53
|
+
// <div style="clear:both"><input type="submit"/></div>
|
54
|
+
// </form>
|
55
|
+
//
|
56
|
+
// Your validation web service should return JSON keyed to the text fields' name attributes:
|
57
|
+
//
|
58
|
+
// { 'user[email]': [ 'Email has already been taken by another user', 'Email must be in mit.edu domain' ], ... }
|
59
|
+
//
|
60
|
+
// One special case to ease usability: When the user hovers over the submit button in preparation to submit, the
|
61
|
+
// validator runs if the input is disabled. This handles a common case where the user has filled the final field
|
62
|
+
// but not moved cursor focus. Disabling is done via javascript rather than html, since some browsers swallow
|
63
|
+
// all events for disabled inputs.
|
64
|
+
//
|
65
|
+
$.fn.ajaxValidate = function($$options) {
|
66
|
+
// plugin defaults + options
|
67
|
+
var $settings = $.extend({}, $.fn.ajaxValidate.defaults, $$options);
|
68
|
+
return this.each(function() {
|
69
|
+
var $form = $(this);
|
70
|
+
// element specific options
|
71
|
+
var o = $.meta ? $.extend({}, $settings, $form.data()) : $settings;
|
72
|
+
// initialize and setup submit button
|
73
|
+
initialize($form, o, false);
|
74
|
+
// install event handlers to validate single fields when they lose focus
|
75
|
+
$form.find(':input').blur(function() {
|
76
|
+
// determine if field delegates to another and locate the feedback element
|
77
|
+
var field = canonical_field($form, $(this), o);
|
78
|
+
// remove existing feedback during server activity
|
79
|
+
field.$feedback.empty();
|
80
|
+
// core operation: submit form via ajax and update field feedback with formatted errors
|
81
|
+
validate_form($form, field.$feedback, o, function(data) {
|
82
|
+
disabled = data !== true
|
83
|
+
set_disabled($form, data !== true, o);
|
84
|
+
set_feedback(field.$feedback, data[field.name], o);
|
85
|
+
});
|
86
|
+
});
|
87
|
+
|
88
|
+
// handle disabling form and submit button
|
89
|
+
if (o.disable) {
|
90
|
+
var $submit = $form.find(o.disable);
|
91
|
+
|
92
|
+
// only allow submit if button not disabled
|
93
|
+
$form.submit(function() {
|
94
|
+
var disabled = $submit.hasClass('disabled');
|
95
|
+
var $validate = $form.find('.validate');
|
96
|
+
if (disabled) {
|
97
|
+
$validate.fadeOut(function() { $validate.fadeIn() });
|
98
|
+
}
|
99
|
+
return !disabled;
|
100
|
+
});
|
101
|
+
|
102
|
+
// validate entire form when user hovers on disabled button
|
103
|
+
$submit.mouseover(function() {
|
104
|
+
initialize($form, o, true);
|
105
|
+
});
|
106
|
+
};
|
107
|
+
});
|
108
|
+
};
|
109
|
+
|
110
|
+
//
|
111
|
+
// format errors. pass in an array of strings, or true/undefined if there are no errors
|
112
|
+
//
|
113
|
+
$.fn.ajaxValidate.format = function(errors, opts) {
|
114
|
+
if (undefined != errors || true == errors) {
|
115
|
+
return '<ul class="error">' + $.map(errors, function(e) { return ('<li>' + e + '</li>'); }).join('') + '</ul>';
|
116
|
+
} else {
|
117
|
+
return '<ul class="pass"></ul>'
|
118
|
+
}
|
119
|
+
};
|
120
|
+
|
121
|
+
//
|
122
|
+
// option defaults
|
123
|
+
//
|
124
|
+
$.fn.ajaxValidate.defaults = {
|
125
|
+
initialize: false, /* validate all fields' initial values when form loads */
|
126
|
+
feedback: '~ .validate', /* jquery selector for error feedback element, relative to the field it monitors */
|
127
|
+
disable: ':input[type=submit]', /* Disable submit button until form validates: supply selector or false */
|
128
|
+
type: 'POST', /* HTTP verb to use when calling web service */
|
129
|
+
delegate: {}, /* delegate validation of one field to another */
|
130
|
+
spinner: 'spinner', /* css class to add to feedback during ajax processing */
|
131
|
+
};
|
132
|
+
|
133
|
+
// internal helper functions
|
134
|
+
|
135
|
+
// pre-flight validation for all fields
|
136
|
+
function initialize($form, opts, force) {
|
137
|
+
validate_form($form, $form, opts, function(data) {
|
138
|
+
set_disabled($form, data != true, opts);
|
139
|
+
if (opts.initialize || force) {
|
140
|
+
$form.find(':input').each(function() {
|
141
|
+
var field = canonical_field($form, $(this), opts);
|
142
|
+
set_feedback(field.$feedback, data[field.name], opts);
|
143
|
+
});
|
144
|
+
}
|
145
|
+
});
|
146
|
+
}
|
147
|
+
|
148
|
+
// submit validation info to web service while displaying spinner
|
149
|
+
function validate_form($form, $feedback_elem, opts, callback) {
|
150
|
+
$feedback_elem.addClass(opts.spinner);
|
151
|
+
// submit the form contents to the validation web service
|
152
|
+
$form.ajaxSubmit({
|
153
|
+
url: opts.url,
|
154
|
+
type: opts.type,
|
155
|
+
dataType: 'json',
|
156
|
+
beforeSend: function(xhr) { xhr.setRequestHeader("Accept", "application/json") }, /* JQuery uses wrong content type header */
|
157
|
+
success: function(data) {
|
158
|
+
// remove spinner and yield result to callback
|
159
|
+
$feedback_elem.removeClass(opts.spinner);
|
160
|
+
callback(data);
|
161
|
+
}
|
162
|
+
});
|
163
|
+
}
|
164
|
+
|
165
|
+
function canonical_field($form, $field, opts) {
|
166
|
+
// determine name of field to validate (this is the datamapper model attribute)
|
167
|
+
var field_name = $field.attr('name');
|
168
|
+
// handle fields whose validation delegates to another
|
169
|
+
if (opts.delegate[field_name] != undefined) {
|
170
|
+
field_name = opts.delegate[field_name];
|
171
|
+
$field = $form.find(':input[name="' + field_name +'"]');
|
172
|
+
}
|
173
|
+
// determine element to display error feedback
|
174
|
+
var $feedback_elem = $field.find(opts.feedback);
|
175
|
+
// collect info on field that is being validated
|
176
|
+
return { name: field_name, $input: $field, $feedback: $feedback_elem }
|
177
|
+
}
|
178
|
+
|
179
|
+
function set_disabled($form, status, opts) {
|
180
|
+
// enable/disable submit button, if option selected
|
181
|
+
if (opts.disable) {
|
182
|
+
if (status) $form.find(opts.disable).addClass('disabled');
|
183
|
+
else $form.find(opts.disable).removeClass('disabled');
|
184
|
+
}
|
185
|
+
}
|
186
|
+
|
187
|
+
function set_feedback($elem, errors, opts) {
|
188
|
+
// render error markup and update feedback element
|
189
|
+
markup = $.fn.ajaxValidate.format(errors, opts);
|
190
|
+
$elem.html(markup);
|
191
|
+
}
|
192
|
+
})(jQuery);
|
@@ -0,0 +1,37 @@
|
|
1
|
+
//= require <jquery>
|
2
|
+
|
3
|
+
jQuery.fn.hint = function (blurClass) {
|
4
|
+
if (!blurClass) {
|
5
|
+
blurClass = 'blur';
|
6
|
+
}
|
7
|
+
|
8
|
+
return this.each(function () {
|
9
|
+
// get jQuery version of 'this'
|
10
|
+
var $input = jQuery(this),
|
11
|
+
|
12
|
+
// capture the rest of the variable to allow for reuse
|
13
|
+
title = $input.attr('title'),
|
14
|
+
$form = jQuery(this.form),
|
15
|
+
$win = jQuery(window);
|
16
|
+
|
17
|
+
function remove() {
|
18
|
+
if ($input.val() === title && $input.hasClass(blurClass)) {
|
19
|
+
$input.val('').removeClass(blurClass);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
// only apply logic if the element has the attribute
|
24
|
+
if (title) {
|
25
|
+
// on blur, set value to title attr if text is blank
|
26
|
+
$input.blur(function () {
|
27
|
+
if (this.value === '') {
|
28
|
+
$input.val(title).addClass(blurClass);
|
29
|
+
}
|
30
|
+
}).focus(remove).blur(); // now change all inputs to title
|
31
|
+
|
32
|
+
// clear the pre-defined text when form is submitted
|
33
|
+
$form.submit(remove);
|
34
|
+
$win.unload(remove); // handles Firefox's autocomplete
|
35
|
+
}
|
36
|
+
});
|
37
|
+
};
|
@@ -0,0 +1,55 @@
|
|
1
|
+
/*
|
2
|
+
* Repertoire abstract event model, for use with widget model
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
5
|
+
* Christopher York, 11/2009
|
6
|
+
*
|
7
|
+
* Requires jquery 1.3.2+
|
8
|
+
* Support: Firefox 3+ & Safari 4+. IE emphatically not supported.
|
9
|
+
*/
|
10
|
+
|
11
|
+
//= require "global"
|
12
|
+
|
13
|
+
//= require <jquery>
|
14
|
+
|
15
|
+
//
|
16
|
+
// Mixin that adds functionality for event listening to an arbitrary javascript object.
|
17
|
+
//
|
18
|
+
// API is the similar to jquery's bind, unbind, and trigger - except that events cannot be
|
19
|
+
// namespaced.
|
20
|
+
//
|
21
|
+
// N.B. This is not a true event dispatch system: there is no event object, just callbacks.
|
22
|
+
// Implementation may change.
|
23
|
+
//
|
24
|
+
repertoire.events = function(self, $proxy) {
|
25
|
+
|
26
|
+
var handlers = {};
|
27
|
+
|
28
|
+
// mimic jquery's event bind
|
29
|
+
self.bind = function(type, fn) {
|
30
|
+
if (!handlers[type])
|
31
|
+
handlers[type] = [];
|
32
|
+
|
33
|
+
handlers[type].push(fn);
|
34
|
+
};
|
35
|
+
|
36
|
+
// mimic jquery's event unbind
|
37
|
+
self.unbind = function(type, fn) {
|
38
|
+
if (handlers[type]) {
|
39
|
+
handlers[type] = jQuery.grep(handlers[type], function(h) {
|
40
|
+
return h !== fn;
|
41
|
+
});
|
42
|
+
}
|
43
|
+
};
|
44
|
+
|
45
|
+
// wrap jquery's event trigger
|
46
|
+
self.trigger = function(type, data) {
|
47
|
+
if (handlers[type]) {
|
48
|
+
jQuery.each(handlers[type], function() {
|
49
|
+
this.call(self);
|
50
|
+
})
|
51
|
+
}
|
52
|
+
};
|
53
|
+
|
54
|
+
return self;
|
55
|
+
};
|
@@ -0,0 +1,52 @@
|
|
1
|
+
/*
|
2
|
+
* Repertoire abstract ajax widget
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
5
|
+
* Christopher York, 09/2009
|
6
|
+
*
|
7
|
+
* Requires jquery 1.3.2+
|
8
|
+
* Support: Firefox 3+ & Safari 4+. IE emphatically not supported.
|
9
|
+
*/
|
10
|
+
|
11
|
+
//= require <jquery>
|
12
|
+
|
13
|
+
// claim a single global namespace, 'repertoire'
|
14
|
+
repertoire = {};
|
15
|
+
|
16
|
+
// Global defaults inherited by all widgets
|
17
|
+
//
|
18
|
+
// Options:
|
19
|
+
// path_prefix - prefix to add before all generated urls
|
20
|
+
//
|
21
|
+
repertoire.defaults = {
|
22
|
+
path_prefix: ''
|
23
|
+
};
|
24
|
+
|
25
|
+
//
|
26
|
+
// Generates a jquery plugin that attaches a widget instance to each matched element
|
27
|
+
// and exposes plugin defaults.
|
28
|
+
//
|
29
|
+
// N.B. This method is currently only in use in the faceting module, and may be deprecated.
|
30
|
+
//
|
31
|
+
// Usage:
|
32
|
+
// $.fn.my_widget = repertoire.plugin(my_widget);
|
33
|
+
// $.fn.my_widget.defaults = { /* widget option defaults */ };
|
34
|
+
//
|
35
|
+
repertoire.plugin = function(self) {
|
36
|
+
var fn = function(options) {
|
37
|
+
return this.each(function() {
|
38
|
+
var settings = $.extend({}, repertoire.defaults, fn.defaults, html_options($(this)), options);
|
39
|
+
self($(this), settings).initialize();
|
40
|
+
});
|
41
|
+
};
|
42
|
+
fn.defaults = { };
|
43
|
+
return fn;
|
44
|
+
|
45
|
+
// helper: default widget name and title options from dom
|
46
|
+
function html_options($elem) {
|
47
|
+
return {
|
48
|
+
name: $elem.attr('id'),
|
49
|
+
title: $elem.attr('title')
|
50
|
+
};
|
51
|
+
}
|
52
|
+
};
|
@@ -0,0 +1,165 @@
|
|
1
|
+
/*
|
2
|
+
* Repertoire abstract ajax model, for use with widget framework
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
5
|
+
* Christopher York, 11/2009
|
6
|
+
*
|
7
|
+
* Requires jquery 1.3.2+
|
8
|
+
* Support: Firefox 3+ & Safari 4+. IE emphatically not supported.
|
9
|
+
*/
|
10
|
+
|
11
|
+
//= require "global"
|
12
|
+
//= require "widget"
|
13
|
+
//= require "events"
|
14
|
+
|
15
|
+
//= require <jquery>
|
16
|
+
|
17
|
+
//
|
18
|
+
// Abstract model for ajax widgets. This class includes convenience methods for listening to
|
19
|
+
// events on models, for jquery ajax calls, and for param encoding in Merb's style.
|
20
|
+
//
|
21
|
+
// Handles:
|
22
|
+
// - change publication and observing
|
23
|
+
// - url/query-string construction
|
24
|
+
//
|
25
|
+
// Usage. The model provides a facade to your ajax webservice, and may hold state for a widget.
|
26
|
+
// In some cases (e.g. working with large server-side datasets), it may be appropriate to
|
27
|
+
// cache data in the model and fetch it as needed from the webservice.
|
28
|
+
//
|
29
|
+
// Using the ajax param encoders. These convenience methods help you construct the url and
|
30
|
+
// assemble params for an ajax call. It's not required to use them, although they make life
|
31
|
+
// easier.
|
32
|
+
//
|
33
|
+
// self.items = function(callback) {
|
34
|
+
// var url = self.default_url(['projects', 'results']);
|
35
|
+
// self.fetch(params, url, 'html', callback);
|
36
|
+
// }
|
37
|
+
//
|
38
|
+
repertoire.model = function(options) {
|
39
|
+
// this object is an abstract class
|
40
|
+
var self = {};
|
41
|
+
|
42
|
+
// default: no options specified
|
43
|
+
options = options || {};
|
44
|
+
|
45
|
+
// mixin event handler functionality
|
46
|
+
repertoire.events(self);
|
47
|
+
|
48
|
+
//
|
49
|
+
// Update the data model given the current state
|
50
|
+
//
|
51
|
+
// You may either pre-process the state and write your own webservice access methods
|
52
|
+
// or use self.fetch for a generic webservice, e.g.
|
53
|
+
//
|
54
|
+
// self.update = function(state, callback) {
|
55
|
+
// var url = self.default_url(['projects', 'results']);
|
56
|
+
// self.fetch(state, url, 'html', callback);
|
57
|
+
// }
|
58
|
+
//
|
59
|
+
self.update = function(state, callback) {
|
60
|
+
throw "Abstract function: redefine self.update() in your data model."
|
61
|
+
};
|
62
|
+
|
63
|
+
//
|
64
|
+
// Utility method to issue an ajax HTTP GET to fetch data from a webservice
|
65
|
+
//
|
66
|
+
// params: params to send as http query line
|
67
|
+
// url: url of webservice to access
|
68
|
+
// type: type of data returned (e.g. 'json', 'html')
|
69
|
+
// callback: function to call with returned data
|
70
|
+
//
|
71
|
+
self.fetch = function(params, url, type, callback, $elem, async) {
|
72
|
+
if (async == null)
|
73
|
+
async = true;
|
74
|
+
|
75
|
+
var spinnerClass = options.spinner || 'loading';
|
76
|
+
if ($elem)
|
77
|
+
$elem.addClass(spinnerClass);
|
78
|
+
$.ajax({
|
79
|
+
async: async,
|
80
|
+
url: url,
|
81
|
+
data: self.to_query_string(params),
|
82
|
+
type: 'GET',
|
83
|
+
dataType: type,
|
84
|
+
// content negotiation problems may require:
|
85
|
+
/* beforeSend: function(xhr) { xhr.setRequestHeader("Accept", "application/json") } */
|
86
|
+
success: callback,
|
87
|
+
error: function(request, textStatus, errorThrown) {
|
88
|
+
if ($elem)
|
89
|
+
$elem.html(options.error || 'Could not load');
|
90
|
+
},
|
91
|
+
complete: function () {
|
92
|
+
if ($elem)
|
93
|
+
$elem.removeClass(spinnerClass);
|
94
|
+
}
|
95
|
+
});
|
96
|
+
};
|
97
|
+
|
98
|
+
//
|
99
|
+
// Format a webservice url, preferring options.url if possible
|
100
|
+
//
|
101
|
+
self.default_url = function(default_parts, ext) {
|
102
|
+
var path_prefix = options.path_prefix || '';
|
103
|
+
var parts = default_parts.slice();
|
104
|
+
|
105
|
+
parts.unshift(path_prefix);
|
106
|
+
url = options.url || parts.join('/')
|
107
|
+
|
108
|
+
if (ext)
|
109
|
+
url += '.' + ext;
|
110
|
+
|
111
|
+
return url;
|
112
|
+
}
|
113
|
+
|
114
|
+
//
|
115
|
+
// Convert a structure of params to a URL query string suitable for use in an HTTP GET request, compliant with Merb's format.
|
116
|
+
//
|
117
|
+
// An example:
|
118
|
+
//
|
119
|
+
// Merb::Parse.params_to_query_string(:filter => {:year => [1593, 1597], :genre => ['Tragedy', 'Comedy'] }, :search => 'William')
|
120
|
+
// ==> "filter[genre][]=Tragedy&filter[genre][]=Comedy&filter[year][]=1593&filter[year][]=1597&search=William"
|
121
|
+
//
|
122
|
+
self.to_query_string = function(value, prefix) {
|
123
|
+
var vs = [];
|
124
|
+
prefix = prefix || '';
|
125
|
+
if (value instanceof Array) {
|
126
|
+
jQuery.each(value, function(i, v) {
|
127
|
+
vs.push(self.to_query_string(v, prefix + '[]'));
|
128
|
+
});
|
129
|
+
return vs.join('&');
|
130
|
+
} else if (typeof(value) == "object") {
|
131
|
+
jQuery.each(value, function(k, v) {
|
132
|
+
vs.push(self.to_query_string(v, (prefix.length > 0) ? (prefix + '[' + escape(k) + ']') : escape(k)));
|
133
|
+
});
|
134
|
+
// minor addition to merb: discard empty value lists { e.g. discipline: [] }
|
135
|
+
vs = array_filter(vs, function(x) { return x !== ""; });
|
136
|
+
return vs.join('&');
|
137
|
+
} else {
|
138
|
+
return prefix + '=' + escape(value);
|
139
|
+
}
|
140
|
+
};
|
141
|
+
|
142
|
+
// Apparently IE doesn't support the filter function? -DD via Brett
|
143
|
+
var array_filter = function (thisArray, fun) {
|
144
|
+
var len = thisArray.length;
|
145
|
+
if (typeof fun != "function")
|
146
|
+
throw new TypeError();
|
147
|
+
|
148
|
+
var res = new Array();
|
149
|
+
var thisp = arguments[1];
|
150
|
+
|
151
|
+
for (var i = 0; i < len; i++) {
|
152
|
+
if (i in thisArray) {
|
153
|
+
var val = thisArray[i]; // in case fun mutates this
|
154
|
+
if (fun.call(thisp, val, i, thisArray))
|
155
|
+
res.push(val);
|
156
|
+
}
|
157
|
+
}
|
158
|
+
|
159
|
+
return res;
|
160
|
+
};
|
161
|
+
|
162
|
+
|
163
|
+
// end of model factory function
|
164
|
+
return self;
|
165
|
+
}
|
@@ -0,0 +1,217 @@
|
|
1
|
+
/*
|
2
|
+
* Repertoire abstract ajax widget
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
5
|
+
* Christopher York, 09/2009
|
6
|
+
*
|
7
|
+
* Requires jquery 1.3.2+
|
8
|
+
* Support: Firefox 3+ & Safari 4+. IE emphatically not supported.
|
9
|
+
*/
|
10
|
+
|
11
|
+
//= require <jquery>
|
12
|
+
|
13
|
+
//= require "global"
|
14
|
+
|
15
|
+
//
|
16
|
+
// Abstract class for ajax widgets
|
17
|
+
//
|
18
|
+
// Handles:
|
19
|
+
// - url/query-string construction
|
20
|
+
// - data assembly for sending to webservice
|
21
|
+
// - ui event delegation hooks
|
22
|
+
// - hooks for injecting custom behaviour
|
23
|
+
//
|
24
|
+
// Options on all subclassed widgets:
|
25
|
+
//
|
26
|
+
// url - provide a url to over-ride the widget's default
|
27
|
+
// spinner - css class to add to widget during ajax loads
|
28
|
+
// error - text to display if ajax load fails
|
29
|
+
// injectors - additional jquery markup to inject into widget (see FAQ)
|
30
|
+
// handlers - additional jquery event handlers to add to widget (see FAQ)
|
31
|
+
// state - additional pre-processing for params sent to webservice (see FAQ)
|
32
|
+
//
|
33
|
+
// Sub-classes are required to over-ride one method: self.render(). If you wish to
|
34
|
+
// use a data model, store a subclass of rep.wigets/model in your widget.
|
35
|
+
//
|
36
|
+
repertoire.widget = function(selector, options) {
|
37
|
+
// this object is an abstract class
|
38
|
+
var self = {};
|
39
|
+
|
40
|
+
// private variables
|
41
|
+
var $widget = $(selector);
|
42
|
+
options = options || {};
|
43
|
+
|
44
|
+
// mix in event handling functionality
|
45
|
+
repertoire.events(self, $widget);
|
46
|
+
|
47
|
+
// to run by hand after sub-classes have evaluated
|
48
|
+
self.initialize = function() {
|
49
|
+
// register any custom handlers
|
50
|
+
if (options.handlers !== undefined)
|
51
|
+
register_handlers(options.handlers);
|
52
|
+
|
53
|
+
// load once at beginning
|
54
|
+
self.refresh();
|
55
|
+
}
|
56
|
+
|
57
|
+
//
|
58
|
+
// Refresh model and render into widget
|
59
|
+
//
|
60
|
+
// Integrates state and markup injectors
|
61
|
+
//
|
62
|
+
self.refresh = function() {
|
63
|
+
var callback;
|
64
|
+
|
65
|
+
// pass to custom state processor
|
66
|
+
if (options.state !== undefined)
|
67
|
+
options.state(self);
|
68
|
+
|
69
|
+
callback = function() {
|
70
|
+
// render the widget
|
71
|
+
var markup = self.render.apply(self, arguments);
|
72
|
+
|
73
|
+
// inject any custom markup
|
74
|
+
if (options.injectors !== undefined)
|
75
|
+
// TODO. figure out how to send all args to injectors
|
76
|
+
process_injectors(markup, options.injectors, arguments[0]);
|
77
|
+
|
78
|
+
// paint markup into the dom
|
79
|
+
if (markup)
|
80
|
+
$widget.html(markup);
|
81
|
+
};
|
82
|
+
|
83
|
+
// start rendering
|
84
|
+
self.reload(callback);
|
85
|
+
};
|
86
|
+
|
87
|
+
//
|
88
|
+
// Render and return markup for this widget.
|
89
|
+
//
|
90
|
+
// Three forms are possible:
|
91
|
+
//
|
92
|
+
// (1) Basic... just return a string or jquery object
|
93
|
+
//
|
94
|
+
// self.render = function() {
|
95
|
+
// return 'Hello world!';
|
96
|
+
// };
|
97
|
+
//
|
98
|
+
// (2) If you just want to tweak the superclass' view:
|
99
|
+
//
|
100
|
+
// var template_fn = self.render; // idiom to access super.render()
|
101
|
+
// self.render = function() {
|
102
|
+
// var markup = template_fn();
|
103
|
+
// return $(markup).find('.title').html('New Title');
|
104
|
+
// };
|
105
|
+
//
|
106
|
+
// (3) If you want to modify the DOM in place, do so
|
107
|
+
// and return nothing.
|
108
|
+
//
|
109
|
+
self.render = function() {
|
110
|
+
return $('<div class="rep"/>'); // namespace for all other widget css is 'rep'
|
111
|
+
};
|
112
|
+
|
113
|
+
|
114
|
+
//
|
115
|
+
// A hook for use when your widget must render the results of an ajax callback. Put
|
116
|
+
// the ajax call in self.reload(). Its results will be passed to self.render().
|
117
|
+
//
|
118
|
+
// self.reload = function(callback) {
|
119
|
+
// $.get('http://www.nytimes.com', callback);
|
120
|
+
// };
|
121
|
+
//
|
122
|
+
// self.render = function(daily_news) {
|
123
|
+
// $(daily_news).find('title').text(); // widget's view is the title of the new york times
|
124
|
+
// }
|
125
|
+
//
|
126
|
+
// N.B. In real-world cases, the call in self.reload should be to your
|
127
|
+
// data model class, which serves as an ajax api facade.
|
128
|
+
//
|
129
|
+
self.reload = function(callback) {
|
130
|
+
callback();
|
131
|
+
}
|
132
|
+
|
133
|
+
//
|
134
|
+
// Register a handler for dom events on this widget. Call with an event selector and a standard jquery event
|
135
|
+
// handler function, e.g.
|
136
|
+
//
|
137
|
+
// self.handler('click.toggle_value!.rep .facet .value', function() { ... });
|
138
|
+
//
|
139
|
+
// Note the syntax used to identify a handler's event, namespace, and the jquery selector: '<event.namespace>!<target>'.
|
140
|
+
// Both event and namespace are optional - leave them out to register a click handler with a unique namespace.
|
141
|
+
//
|
142
|
+
// N.B. This method is intended only for protected use within a widget and its subclasses, since it depends
|
143
|
+
// on the view implementation.
|
144
|
+
//
|
145
|
+
self.handler = function(event_selector, fn) {
|
146
|
+
event_selector = parse_event_selector(event_selector);
|
147
|
+
var event = event_selector[0],
|
148
|
+
selector = event_selector[1]; // why doesn't JS support array decomposition?!?
|
149
|
+
|
150
|
+
// bind new handler
|
151
|
+
$widget.bind(event, function (e) {
|
152
|
+
var $el = $(e.target);
|
153
|
+
var result = false;
|
154
|
+
// walk up dom tree for selector
|
155
|
+
while ($el.length > 0) {
|
156
|
+
if ($el.is(selector)) {
|
157
|
+
result = fn.apply($el[0], [e]);
|
158
|
+
if (result === false)
|
159
|
+
e.preventDefault();
|
160
|
+
return;
|
161
|
+
}
|
162
|
+
$el = $el.parent();
|
163
|
+
}
|
164
|
+
});
|
165
|
+
}
|
166
|
+
|
167
|
+
// PRIVATE
|
168
|
+
|
169
|
+
// register a collection of event handlers
|
170
|
+
function register_handlers(handlers) {
|
171
|
+
$.each(handlers, function(selector, handler) {
|
172
|
+
// register each handler
|
173
|
+
self.handler(selector, function() {
|
174
|
+
// bind self as an argument for the custom handler
|
175
|
+
return handler.apply(this, [self]);
|
176
|
+
});
|
177
|
+
});
|
178
|
+
}
|
179
|
+
|
180
|
+
// inject custom markup into widget
|
181
|
+
function process_injectors($markup, injectors, data) {
|
182
|
+
// workaround for jquery find not matching top element
|
183
|
+
$wrapped = $("<div/>").append($markup);
|
184
|
+
|
185
|
+
$.each(injectors, function(selector, injector) {
|
186
|
+
var $elems = $wrapped.find(selector);
|
187
|
+
if ($elems.length > 0) {
|
188
|
+
injector.apply($elems, [ self, data ]);
|
189
|
+
}
|
190
|
+
});
|
191
|
+
}
|
192
|
+
|
193
|
+
// parse an event name and selector spec
|
194
|
+
function parse_event_selector(event_selector) {
|
195
|
+
var s = event_selector.split('!');
|
196
|
+
var event, selector;
|
197
|
+
|
198
|
+
if (s.length === 2) {
|
199
|
+
event = s[0], selector = s[1];
|
200
|
+
} else if (s.length === 1) {
|
201
|
+
event = 'click', selector = s[0];
|
202
|
+
} else {
|
203
|
+
throw "Could not parse event selector: " + event_selector;
|
204
|
+
}
|
205
|
+
|
206
|
+
if (event.indexOf('.')<0) {
|
207
|
+
// create a default namespace from selector or random number
|
208
|
+
namespace = selector.replace(/[^a-zA-z0-9]/g, '') || new Date().getTime();
|
209
|
+
event = event + '.' + namespace;
|
210
|
+
}
|
211
|
+
|
212
|
+
return [event, selector];
|
213
|
+
}
|
214
|
+
|
215
|
+
// end of widget factory function
|
216
|
+
return self;
|
217
|
+
};
|
@@ -0,0 +1,13 @@
|
|
1
|
+
/*
|
2
|
+
* Repertoire abstract ajax widget
|
3
|
+
*
|
4
|
+
* Copyright (c) 2009 MIT Hyperstudio
|
5
|
+
* Christopher York, 09/2009
|
6
|
+
*
|
7
|
+
* Requires jquery 1.3.2+
|
8
|
+
* Support: Firefox 3+ & Safari 4+. IE emphatically not supported.
|
9
|
+
*/
|
10
|
+
|
11
|
+
//= require "rep.widgets/global"
|
12
|
+
//= require "rep.widgets/widget"
|
13
|
+
//= require "rep.widgets/model"
|
@@ -0,0 +1,7 @@
|
|
1
|
+
/* default css for the Repertoire ajax form validation widget */
|
2
|
+
|
3
|
+
.validate { float: left; padding-left: 5px; }
|
4
|
+
.validate ul { list-style-type: none; border: 0; margin: 0; padding: 0;}
|
5
|
+
.validate .error li { background: url(../images/rep.ajax-validate/red_cross.png) no-repeat left center; color: red; padding-left:15px;}
|
6
|
+
.validate .pass { background: url(../images/rep.ajax-validate/green_check.png) no-repeat left center; color: green; min-height:15px; min-width:15px;}
|
7
|
+
.validate.spinner { background: url(../images/rep.ajax-validate/spinner_sm.gif) no-repeat left center; min-height:15px; min-width:15px;}
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# Generated by jeweler
|
2
|
+
# DO NOT EDIT THIS FILE DIRECTLY
|
3
|
+
# Instead, edit Jeweler::Tasks in Rakefile, and run the gemspec command
|
4
|
+
# -*- encoding: utf-8 -*-
|
5
|
+
|
6
|
+
Gem::Specification.new do |s|
|
7
|
+
s.name = %q{rep.ajax.toolkit}
|
8
|
+
s.version = "0.3.0"
|
9
|
+
|
10
|
+
s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
|
11
|
+
s.authors = ["Christopher York"]
|
12
|
+
s.date = %q{2010-08-01}
|
13
|
+
s.description = %q{Hyperstudio ajax tools}
|
14
|
+
s.email = %q{yorkc@mit.edu}
|
15
|
+
s.extra_rdoc_files = [
|
16
|
+
"LICENSE",
|
17
|
+
"README",
|
18
|
+
"TODO"
|
19
|
+
]
|
20
|
+
s.files = [
|
21
|
+
".gitignore",
|
22
|
+
"LICENSE",
|
23
|
+
"README",
|
24
|
+
"Rakefile",
|
25
|
+
"TODO",
|
26
|
+
"VERSION",
|
27
|
+
"lib/rep.ajax.toolkit.rb",
|
28
|
+
"public/images/rep.ajax-validate/green_check.png",
|
29
|
+
"public/images/rep.ajax-validate/red_cross.png",
|
30
|
+
"public/images/rep.ajax-validate/spinner_sm.gif",
|
31
|
+
"public/javascripts/rep.ajax-validate.js",
|
32
|
+
"public/javascripts/rep.hint.js",
|
33
|
+
"public/javascripts/rep.widgets.js",
|
34
|
+
"public/javascripts/rep.widgets/events.js",
|
35
|
+
"public/javascripts/rep.widgets/global.js",
|
36
|
+
"public/javascripts/rep.widgets/model.js",
|
37
|
+
"public/javascripts/rep.widgets/widget.js",
|
38
|
+
"public/stylesheets/rep.ajax-validate.css",
|
39
|
+
"rep.ajax.toolkit.gemspec"
|
40
|
+
]
|
41
|
+
s.homepage = %q{http://github.com/repertoire/rep.ajax.toolkit}
|
42
|
+
s.rdoc_options = ["--charset=UTF-8"]
|
43
|
+
s.require_paths = ["lib"]
|
44
|
+
s.rubygems_version = %q{1.3.7}
|
45
|
+
s.summary = %q{Hyperstudio ajax tools}
|
46
|
+
|
47
|
+
if s.respond_to? :specification_version then
|
48
|
+
current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
|
49
|
+
s.specification_version = 3
|
50
|
+
|
51
|
+
if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
|
52
|
+
s.add_runtime_dependency(%q<repertoire-assets>, [">= 0.1.0"])
|
53
|
+
s.add_runtime_dependency(%q<rep.jquery>, [">= 1.3.2"])
|
54
|
+
else
|
55
|
+
s.add_dependency(%q<repertoire-assets>, [">= 0.1.0"])
|
56
|
+
s.add_dependency(%q<rep.jquery>, [">= 1.3.2"])
|
57
|
+
end
|
58
|
+
else
|
59
|
+
s.add_dependency(%q<repertoire-assets>, [">= 0.1.0"])
|
60
|
+
s.add_dependency(%q<rep.jquery>, [">= 1.3.2"])
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
metadata
ADDED
@@ -0,0 +1,113 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: rep.ajax.toolkit
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
prerelease: false
|
5
|
+
segments:
|
6
|
+
- 0
|
7
|
+
- 3
|
8
|
+
- 0
|
9
|
+
version: 0.3.0
|
10
|
+
platform: ruby
|
11
|
+
authors:
|
12
|
+
- Christopher York
|
13
|
+
autorequire:
|
14
|
+
bindir: bin
|
15
|
+
cert_chain: []
|
16
|
+
|
17
|
+
date: 2010-08-01 00:00:00 +01:00
|
18
|
+
default_executable:
|
19
|
+
dependencies:
|
20
|
+
- !ruby/object:Gem::Dependency
|
21
|
+
name: repertoire-assets
|
22
|
+
prerelease: false
|
23
|
+
requirement: &id001 !ruby/object:Gem::Requirement
|
24
|
+
none: false
|
25
|
+
requirements:
|
26
|
+
- - ">="
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
segments:
|
29
|
+
- 0
|
30
|
+
- 1
|
31
|
+
- 0
|
32
|
+
version: 0.1.0
|
33
|
+
type: :runtime
|
34
|
+
version_requirements: *id001
|
35
|
+
- !ruby/object:Gem::Dependency
|
36
|
+
name: rep.jquery
|
37
|
+
prerelease: false
|
38
|
+
requirement: &id002 !ruby/object:Gem::Requirement
|
39
|
+
none: false
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
segments:
|
44
|
+
- 1
|
45
|
+
- 3
|
46
|
+
- 2
|
47
|
+
version: 1.3.2
|
48
|
+
type: :runtime
|
49
|
+
version_requirements: *id002
|
50
|
+
description: Hyperstudio ajax tools
|
51
|
+
email: yorkc@mit.edu
|
52
|
+
executables: []
|
53
|
+
|
54
|
+
extensions: []
|
55
|
+
|
56
|
+
extra_rdoc_files:
|
57
|
+
- LICENSE
|
58
|
+
- README
|
59
|
+
- TODO
|
60
|
+
files:
|
61
|
+
- .gitignore
|
62
|
+
- LICENSE
|
63
|
+
- README
|
64
|
+
- Rakefile
|
65
|
+
- TODO
|
66
|
+
- VERSION
|
67
|
+
- lib/rep.ajax.toolkit.rb
|
68
|
+
- public/images/rep.ajax-validate/green_check.png
|
69
|
+
- public/images/rep.ajax-validate/red_cross.png
|
70
|
+
- public/images/rep.ajax-validate/spinner_sm.gif
|
71
|
+
- public/javascripts/rep.ajax-validate.js
|
72
|
+
- public/javascripts/rep.hint.js
|
73
|
+
- public/javascripts/rep.widgets.js
|
74
|
+
- public/javascripts/rep.widgets/events.js
|
75
|
+
- public/javascripts/rep.widgets/global.js
|
76
|
+
- public/javascripts/rep.widgets/model.js
|
77
|
+
- public/javascripts/rep.widgets/widget.js
|
78
|
+
- public/stylesheets/rep.ajax-validate.css
|
79
|
+
- rep.ajax.toolkit.gemspec
|
80
|
+
has_rdoc: true
|
81
|
+
homepage: http://github.com/repertoire/rep.ajax.toolkit
|
82
|
+
licenses: []
|
83
|
+
|
84
|
+
post_install_message:
|
85
|
+
rdoc_options:
|
86
|
+
- --charset=UTF-8
|
87
|
+
require_paths:
|
88
|
+
- lib
|
89
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
90
|
+
none: false
|
91
|
+
requirements:
|
92
|
+
- - ">="
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
segments:
|
95
|
+
- 0
|
96
|
+
version: "0"
|
97
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
98
|
+
none: false
|
99
|
+
requirements:
|
100
|
+
- - ">="
|
101
|
+
- !ruby/object:Gem::Version
|
102
|
+
segments:
|
103
|
+
- 0
|
104
|
+
version: "0"
|
105
|
+
requirements: []
|
106
|
+
|
107
|
+
rubyforge_project:
|
108
|
+
rubygems_version: 1.3.7
|
109
|
+
signing_key:
|
110
|
+
specification_version: 3
|
111
|
+
summary: Hyperstudio ajax tools
|
112
|
+
test_files: []
|
113
|
+
|