ratnikov-ajax_resource 0.02 → 0.03
Sign up to get free protection for your applications and to get access to all the features.
- data/jake.yml +25 -0
- data/src/attribute_mod.js +48 -0
- data/src/base.js +149 -0
- data/src/errors.js +53 -0
- data/src/form.js +111 -0
- data/src/init.js +3 -0
- data/src/routing.js +41 -0
- data/src/semaphore.js +49 -0
- data/src/view.js +24 -0
- metadata +11 -4
data/jake.yml
ADDED
@@ -0,0 +1,25 @@
|
|
1
|
+
---
|
2
|
+
source_directory: src
|
3
|
+
build_directory: build
|
4
|
+
packer:
|
5
|
+
shrink_vars: true
|
6
|
+
private: true
|
7
|
+
base62: true
|
8
|
+
|
9
|
+
packages:
|
10
|
+
base:
|
11
|
+
- init
|
12
|
+
- attribute_mod
|
13
|
+
- routing
|
14
|
+
- view
|
15
|
+
- base
|
16
|
+
|
17
|
+
utils:
|
18
|
+
- semaphore
|
19
|
+
- form
|
20
|
+
- errors
|
21
|
+
|
22
|
+
bundles:
|
23
|
+
ajax_resource:
|
24
|
+
- base
|
25
|
+
- utils
|
@@ -0,0 +1,48 @@
|
|
1
|
+
AjaxResource.AttributeMod = function() {
|
2
|
+
this._attributes = {};
|
3
|
+
this._errors = [];
|
4
|
+
};
|
5
|
+
|
6
|
+
AjaxResource.AttributeMod.prototype.attributes = function() {
|
7
|
+
return this._attributes;
|
8
|
+
};
|
9
|
+
|
10
|
+
AjaxResource.AttributeMod.prototype.errors = function() {
|
11
|
+
return this._errors;
|
12
|
+
};
|
13
|
+
|
14
|
+
AjaxResource.AttributeMod.prototype.has_errors = function() {
|
15
|
+
return this.errors().length !== 0;
|
16
|
+
};
|
17
|
+
|
18
|
+
AjaxResource.AttributeMod.prototype.valid = function() {
|
19
|
+
return !this.has_errors();
|
20
|
+
};
|
21
|
+
|
22
|
+
AjaxResource.AttributeMod.prototype.id = function() {
|
23
|
+
if (typeof this.attributes().id !== "undefined") {
|
24
|
+
return this.attributes().id;
|
25
|
+
} else {
|
26
|
+
|
27
|
+
// if there is no id attribute, return null as id
|
28
|
+
return null;
|
29
|
+
}
|
30
|
+
};
|
31
|
+
|
32
|
+
AjaxResource.AttributeMod.prototype.is_new = function() {
|
33
|
+
return this.id() === null;
|
34
|
+
};
|
35
|
+
|
36
|
+
AjaxResource.AttributeMod.prototype.update_attributes = function(json) {
|
37
|
+
if (typeof(json.errors) !== "undefined") {
|
38
|
+
this._errors = json.errors;
|
39
|
+
delete json.errors;
|
40
|
+
} else {
|
41
|
+
|
42
|
+
// if there are no errors reported by json, emptying out errors
|
43
|
+
this._errors = [];
|
44
|
+
}
|
45
|
+
|
46
|
+
// use all other properties as attributes
|
47
|
+
jQuery.extend(this._attributes, json);
|
48
|
+
};
|
data/src/base.js
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
|
2
|
+
AjaxResource.Base = function(spec) {
|
3
|
+
AjaxResource.AttributeMod.apply(this);
|
4
|
+
|
5
|
+
if (typeof spec.resource !== "undefined") {
|
6
|
+
this._resource_name = spec.resource;
|
7
|
+
} else {
|
8
|
+
throw("Must specify resource");
|
9
|
+
}
|
10
|
+
|
11
|
+
// include the routes
|
12
|
+
this._assign_routes(spec);
|
13
|
+
};
|
14
|
+
|
15
|
+
AjaxResource.Base.prototype._assign_routes = function(spec) {
|
16
|
+
var route_spec = {};
|
17
|
+
|
18
|
+
if (typeof spec.controller !== "undefined") {
|
19
|
+
route_spec.controller = spec.controller;
|
20
|
+
} else {
|
21
|
+
route_spec.controller = this.resource_name() + 's';
|
22
|
+
}
|
23
|
+
|
24
|
+
AjaxResource.Routing.apply(this, [ route_spec ]);
|
25
|
+
};
|
26
|
+
|
27
|
+
jQuery.extend(AjaxResource.Base.prototype, AjaxResource.AttributeMod.prototype);
|
28
|
+
jQuery.extend(AjaxResource.Base.prototype, AjaxResource.Routing.prototype);
|
29
|
+
jQuery.extend(AjaxResource.Base.prototype, AjaxResource.View.prototype);
|
30
|
+
|
31
|
+
AjaxResource.Base.prototype.resource_name = function() {
|
32
|
+
return this._resource_name;
|
33
|
+
};
|
34
|
+
|
35
|
+
AjaxResource.Base.prototype.serialized_attributes = function() {
|
36
|
+
var base = this;
|
37
|
+
var serialized = {};
|
38
|
+
jQuery.each(this.attributes(), function(key, value) {
|
39
|
+
serialized[base.resource_name() + '[' + key + ']'] = value;
|
40
|
+
});
|
41
|
+
return serialized;
|
42
|
+
};
|
43
|
+
|
44
|
+
AjaxResource.Base.prototype.parse_json = function(json) {
|
45
|
+
var resource_json = json[this.resource_name()];
|
46
|
+
|
47
|
+
if (typeof resource_json !== "undefined") {
|
48
|
+
|
49
|
+
if (typeof resource_json.html !== "undefined") {
|
50
|
+
// set to use custom html if specified
|
51
|
+
this.set_custom(resource_json.html);
|
52
|
+
delete resource_json.html;
|
53
|
+
} else {
|
54
|
+
// otherwise set to use default html
|
55
|
+
this.set_custom(null);
|
56
|
+
}
|
57
|
+
|
58
|
+
// update the attributes with the resource_json
|
59
|
+
this.update_attributes(resource_json);
|
60
|
+
|
61
|
+
return true;
|
62
|
+
} else {
|
63
|
+
// if no resource json was parsed, return false
|
64
|
+
return false;
|
65
|
+
}
|
66
|
+
};
|
67
|
+
|
68
|
+
AjaxResource.Base.prototype.create = function(success_callback) {
|
69
|
+
|
70
|
+
// sanity check
|
71
|
+
if (!this.is_new()) {
|
72
|
+
throw("Cannot create an existing record");
|
73
|
+
}
|
74
|
+
|
75
|
+
var post_data = jQuery.extend({ _method : 'post' }, this.serialized_attributes());
|
76
|
+
var resource = this;
|
77
|
+
|
78
|
+
return jQuery.ajax({
|
79
|
+
type: 'POST',
|
80
|
+
url: this.collection_path(),
|
81
|
+
data: post_data,
|
82
|
+
success: function(json) {
|
83
|
+
if (resource.parse_json(json)) {
|
84
|
+
success_callback(resource);
|
85
|
+
} else {
|
86
|
+
// do nothing on error
|
87
|
+
}
|
88
|
+
},
|
89
|
+
dataType: 'json'
|
90
|
+
});
|
91
|
+
};
|
92
|
+
|
93
|
+
AjaxResource.Base.prototype.update = function(success_callback) {
|
94
|
+
|
95
|
+
// sanity check
|
96
|
+
if (this.is_new()) {
|
97
|
+
throw("Cannot update a new record");
|
98
|
+
}
|
99
|
+
|
100
|
+
var post_data = jQuery.extend({ _method : 'put' }, this.serialized_attributes());
|
101
|
+
var resource = this;
|
102
|
+
|
103
|
+
return jQuery.ajax({
|
104
|
+
type: 'POST',
|
105
|
+
url : this.member_path(this.id()),
|
106
|
+
data: post_data,
|
107
|
+
success: function(json) {
|
108
|
+
if (resource.parse_json(json)) {
|
109
|
+
success_callback(resource);
|
110
|
+
} else {
|
111
|
+
// do nothing on error
|
112
|
+
}
|
113
|
+
},
|
114
|
+
dataType: 'json'
|
115
|
+
});
|
116
|
+
};
|
117
|
+
|
118
|
+
AjaxResource.Base.prototype.save = function(callback) {
|
119
|
+
if (this.is_new()) {
|
120
|
+
return this.create(callback);
|
121
|
+
} else {
|
122
|
+
return this.update(callback);
|
123
|
+
}
|
124
|
+
};
|
125
|
+
|
126
|
+
AjaxResource.Base.prototype.destroy = function(success_callback) {
|
127
|
+
|
128
|
+
// sanity check
|
129
|
+
if (this.is_new()) {
|
130
|
+
throw("Cannot destroy a new record");
|
131
|
+
}
|
132
|
+
|
133
|
+
var post_data = { _method : 'delete' };
|
134
|
+
var resource = this;
|
135
|
+
|
136
|
+
return jQuery.ajax({
|
137
|
+
type : 'POST',
|
138
|
+
url : this.member_path(this.id()),
|
139
|
+
data : post_data,
|
140
|
+
success: function(json) {
|
141
|
+
if (resource.parse_json(json)) {
|
142
|
+
success_callback(resource);
|
143
|
+
} else {
|
144
|
+
// do nothing on error
|
145
|
+
}
|
146
|
+
},
|
147
|
+
dataType: 'json'
|
148
|
+
});
|
149
|
+
};
|
data/src/errors.js
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
|
2
|
+
AjaxResource.Errors = function(selector) {
|
3
|
+
this._error_div = jQuery(selector).find('div.error');
|
4
|
+
};
|
5
|
+
|
6
|
+
AjaxResource.Errors.prototype.error_div = function() {
|
7
|
+
return this._error_div;
|
8
|
+
};
|
9
|
+
|
10
|
+
AjaxResource.Errors.prototype.ul = function() {
|
11
|
+
return this.error_div().find("ul");
|
12
|
+
};
|
13
|
+
|
14
|
+
AjaxResource.Errors.prototype.append = function(errors) {
|
15
|
+
var self = this;
|
16
|
+
jQuery.each(errors, function() {
|
17
|
+
if (this.length == 2) {
|
18
|
+
var desc = this[0];
|
19
|
+
var msg = this[1];
|
20
|
+
} else {
|
21
|
+
throw "Unsupported error format for: "+this;
|
22
|
+
}
|
23
|
+
|
24
|
+
var full_message;
|
25
|
+
if (desc !== 'base') {
|
26
|
+
full_message = desc + ' ' + msg;
|
27
|
+
} else {
|
28
|
+
// Use only the error message for base errors
|
29
|
+
full_message = msg;
|
30
|
+
}
|
31
|
+
|
32
|
+
self.ul().append("<li>" + full_message + "</li>");
|
33
|
+
});
|
34
|
+
};
|
35
|
+
|
36
|
+
AjaxResource.Errors.prototype.clear = function() {
|
37
|
+
this.ul().html("");
|
38
|
+
this.hide();
|
39
|
+
};
|
40
|
+
|
41
|
+
AjaxResource.Errors.prototype.set = function(errors) {
|
42
|
+
this.clear();
|
43
|
+
this.append(errors);
|
44
|
+
this.show();
|
45
|
+
};
|
46
|
+
|
47
|
+
AjaxResource.Errors.prototype.hide = function() {
|
48
|
+
this.error_div().hide();
|
49
|
+
};
|
50
|
+
|
51
|
+
AjaxResource.Errors.prototype.show = function() {
|
52
|
+
this.error_div().show();
|
53
|
+
};
|
data/src/form.js
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
|
2
|
+
AjaxResource.Form = function(form, options) {
|
3
|
+
var self = this;
|
4
|
+
|
5
|
+
this._form = jQuery(form);
|
6
|
+
this._error_panel = new AjaxResource.Errors(form);
|
7
|
+
|
8
|
+
if (typeof options === "undefined") {
|
9
|
+
options = {};
|
10
|
+
}
|
11
|
+
|
12
|
+
if (typeof options.model !== "undefined") {
|
13
|
+
this._model = options.model;
|
14
|
+
} else {
|
15
|
+
throw("Must specify model");
|
16
|
+
}
|
17
|
+
|
18
|
+
this._on_save = options.on_save;
|
19
|
+
|
20
|
+
this._semaphore = new AjaxResource.Semaphore();
|
21
|
+
|
22
|
+
this.submit_button().bind("click", function(event) {
|
23
|
+
event.preventDefault();
|
24
|
+
self.submit();
|
25
|
+
});
|
26
|
+
};
|
27
|
+
|
28
|
+
AjaxResource.Form.prototype.form = function() {
|
29
|
+
return this._form;
|
30
|
+
};
|
31
|
+
|
32
|
+
AjaxResource.Form.prototype.submit_button = function() {
|
33
|
+
return this.form().find(":submit");
|
34
|
+
};
|
35
|
+
|
36
|
+
AjaxResource.Form.prototype.error_panel = function(){
|
37
|
+
return this._error_panel;
|
38
|
+
}
|
39
|
+
|
40
|
+
AjaxResource.Form.prototype.fetch_model = function() {
|
41
|
+
if (jQuery.isFunction(this._model)) {
|
42
|
+
return this._model();
|
43
|
+
} else {
|
44
|
+
return this._model;
|
45
|
+
}
|
46
|
+
};
|
47
|
+
|
48
|
+
AjaxResource.Form.prototype.semaphore = function() {
|
49
|
+
return this._semaphore;
|
50
|
+
};
|
51
|
+
|
52
|
+
AjaxResource.Form.prototype.on_save = function(model) {
|
53
|
+
if (typeof this._on_save !== "undefined") {
|
54
|
+
this._on_save(model);
|
55
|
+
} else {
|
56
|
+
// do nothing since no on_save callback was specified
|
57
|
+
}
|
58
|
+
};
|
59
|
+
|
60
|
+
AjaxResource.Form.prototype.parse_fields = function() {
|
61
|
+
var parsed_attributes = {};
|
62
|
+
jQuery(this.form()).find(":input").each(function() {
|
63
|
+
var regex = '(.*)\\[(.*)\\]';
|
64
|
+
var match = new RegExp(regex).exec(jQuery(this).attr("name"));
|
65
|
+
if (match !== null) {
|
66
|
+
var resource_name = match[1];
|
67
|
+
var attr_name = match[2];
|
68
|
+
|
69
|
+
// initialize the hash for the resource if not present yet
|
70
|
+
if (typeof parsed_attributes[resource_name] === "undefined") {
|
71
|
+
parsed_attributes[resource_name] = {};
|
72
|
+
}
|
73
|
+
|
74
|
+
parsed_attributes[resource_name][attr_name] = jQuery(this).attr("value");
|
75
|
+
} else {
|
76
|
+
// input field name does not match the foo[bar] format so ignoring
|
77
|
+
}
|
78
|
+
});
|
79
|
+
|
80
|
+
return parsed_attributes;
|
81
|
+
};
|
82
|
+
|
83
|
+
AjaxResource.Form.prototype.submit = function() {
|
84
|
+
var self = this;
|
85
|
+
if (this.semaphore().available()) {
|
86
|
+
var model = this.fetch_model();
|
87
|
+
|
88
|
+
// update the model from the attributes within the form
|
89
|
+
model.parse_json(this.parse_fields());
|
90
|
+
|
91
|
+
model.save(function(saved_model) {
|
92
|
+
if (saved_model.valid()) {
|
93
|
+
// if the model is valid, clear error_panel and execute the on_saved callback
|
94
|
+
|
95
|
+
self.error_panel().clear();
|
96
|
+
self.on_save(saved_model);
|
97
|
+
} else {
|
98
|
+
// if returned model contained error_panel, report them
|
99
|
+
self.error_panel().set(saved_model.errors());
|
100
|
+
}
|
101
|
+
|
102
|
+
// the request was handled, decrease the semaphore
|
103
|
+
self.semaphore().dec();
|
104
|
+
});
|
105
|
+
|
106
|
+
// just initiated request, increment semaphore to prevent further submissions
|
107
|
+
this.semaphore().inc();
|
108
|
+
} else {
|
109
|
+
// do nothing if semaphore is not available
|
110
|
+
}
|
111
|
+
};
|
data/src/init.js
ADDED
data/src/routing.js
ADDED
@@ -0,0 +1,41 @@
|
|
1
|
+
|
2
|
+
/*
|
3
|
+
* Creates a new routing object.
|
4
|
+
*
|
5
|
+
* Following options are required:
|
6
|
+
*
|
7
|
+
* - controller: the name of the controller to use for routing.
|
8
|
+
*
|
9
|
+
* Following options are available:
|
10
|
+
*
|
11
|
+
* - prefix: prefix to use by the route. By default is ''
|
12
|
+
*/
|
13
|
+
AjaxResource.Routing = function(spec) {
|
14
|
+
if (typeof spec.controller !== "undefined") {
|
15
|
+
this._controller_name = spec.controller;
|
16
|
+
} else {
|
17
|
+
throw "Must specify controller to use";
|
18
|
+
}
|
19
|
+
|
20
|
+
if (typeof spec.prefix !== "undefined") {
|
21
|
+
this._prefix = spec.prefix;
|
22
|
+
} else {
|
23
|
+
this._prefix = '';
|
24
|
+
}
|
25
|
+
};
|
26
|
+
|
27
|
+
AjaxResource.Routing.prototype.controller_name = function() {
|
28
|
+
return this._controller_name;
|
29
|
+
};
|
30
|
+
|
31
|
+
AjaxResource.Routing.prototype.prefix = function() {
|
32
|
+
return this._prefix;
|
33
|
+
};
|
34
|
+
|
35
|
+
AjaxResource.Routing.prototype.collection_path = function() {
|
36
|
+
return this.prefix() + '/' + this.controller_name();
|
37
|
+
};
|
38
|
+
|
39
|
+
AjaxResource.Routing.prototype.member_path = function(identifier) {
|
40
|
+
return this.prefix() + '/' + this.controller_name() + '/' + identifier;
|
41
|
+
};
|
data/src/semaphore.js
ADDED
@@ -0,0 +1,49 @@
|
|
1
|
+
|
2
|
+
AjaxResource.Semaphore = function(options) {
|
3
|
+
if (typeof options === "undefined") {
|
4
|
+
options = {};
|
5
|
+
}
|
6
|
+
|
7
|
+
this._value = 0;
|
8
|
+
|
9
|
+
this._on_available = options.on_available;
|
10
|
+
this._on_unavailable = options.on_unavailable;
|
11
|
+
};
|
12
|
+
|
13
|
+
AjaxResource.Semaphore.prototype.dec = function() {
|
14
|
+
if (this._value > 0) {
|
15
|
+
this._value -= 1;
|
16
|
+
|
17
|
+
if (this.available()) {
|
18
|
+
this.on_available();
|
19
|
+
}
|
20
|
+
|
21
|
+
} else {
|
22
|
+
// do nothing since value should not go below zero
|
23
|
+
}
|
24
|
+
};
|
25
|
+
|
26
|
+
AjaxResource.Semaphore.prototype.inc = function() {
|
27
|
+
var was_available = this.available();
|
28
|
+
this._value += 1;
|
29
|
+
|
30
|
+
if (was_available && !this.available()) {
|
31
|
+
this.on_unavailable();
|
32
|
+
}
|
33
|
+
};
|
34
|
+
|
35
|
+
AjaxResource.Semaphore.prototype.on_available = function() {
|
36
|
+
if (typeof this._on_available !== "undefined") {
|
37
|
+
this._on_available();
|
38
|
+
}
|
39
|
+
};
|
40
|
+
|
41
|
+
AjaxResource.Semaphore.prototype.on_unavailable = function() {
|
42
|
+
if (typeof this._on_unavailable !== "undefined") {
|
43
|
+
this._on_unavailable();
|
44
|
+
}
|
45
|
+
};
|
46
|
+
|
47
|
+
AjaxResource.Semaphore.prototype.available = function() {
|
48
|
+
return this._value === 0;
|
49
|
+
};
|
data/src/view.js
ADDED
@@ -0,0 +1,24 @@
|
|
1
|
+
|
2
|
+
AjaxResource.View = function() { };
|
3
|
+
|
4
|
+
AjaxResource.View.prototype.default_view = function() {
|
5
|
+
return this.resource_name() + ' (id: '+this.id()+')';
|
6
|
+
};
|
7
|
+
|
8
|
+
AjaxResource.View.prototype.set_custom = function(html) {
|
9
|
+
if (html !== null) {
|
10
|
+
this._html = html;
|
11
|
+
} else {
|
12
|
+
// if null, delete the entry, dropping back to default view
|
13
|
+
delete this._html;
|
14
|
+
}
|
15
|
+
};
|
16
|
+
|
17
|
+
AjaxResource.View.prototype.html = function() {
|
18
|
+
if (typeof this._html !== "undefined") {
|
19
|
+
return this._html;
|
20
|
+
} else {
|
21
|
+
// fall back to default view
|
22
|
+
return this.default_view();
|
23
|
+
}
|
24
|
+
};
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: ratnikov-ajax_resource
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: "0.
|
4
|
+
version: "0.03"
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Dmitry Ratnikov
|
@@ -9,7 +9,7 @@ autorequire:
|
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
11
|
|
12
|
-
date: 2009-02-
|
12
|
+
date: 2009-02-27 00:00:00 -08:00
|
13
13
|
default_executable:
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
@@ -31,9 +31,16 @@ extensions: []
|
|
31
31
|
extra_rdoc_files: []
|
32
32
|
|
33
33
|
files:
|
34
|
-
- build/ajax_resource-min.js
|
35
|
-
- build/ajax_resource-src.js
|
36
34
|
- tasks/ajax_resource.rb
|
35
|
+
- jake.yml
|
36
|
+
- src/init.js
|
37
|
+
- src/view.js
|
38
|
+
- src/attribute_mod.js
|
39
|
+
- src/routing.js
|
40
|
+
- src/base.js
|
41
|
+
- src/semaphore.js
|
42
|
+
- src/errors.js
|
43
|
+
- src/form.js
|
37
44
|
has_rdoc: false
|
38
45
|
homepage: http://github.com/ratnikov/ajax_resource/tree/master
|
39
46
|
post_install_message:
|