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 ADDED
@@ -0,0 +1,4 @@
1
+ .DS_Store
2
+ *~
3
+ pkg
4
+ build
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/TODO ADDED
@@ -0,0 +1 @@
1
+
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.3.0
File without changes
@@ -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
+