rep.ajax.toolkit 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|