ratnikov-ajax_resource 0.02 → 0.03
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/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:
|