jipe 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/Gemfile +8 -0
- data/Gemfile.lock +16 -0
- data/README +47 -0
- data/Rakefile +47 -0
- data/VERSION +1 -0
- data/app/controllers/jipe_controller.rb +17 -0
- data/app/views/jipe/jester.js.erb +860 -0
- data/app/views/jipe/jipe.js.erb +454 -0
- data/assets/images/edit-field.png +0 -0
- data/assets/javascripts/jester.js +877 -0
- data/assets/javascripts/jipe.js +393 -0
- data/generators/jipe/USAGE +8 -0
- data/generators/jipe/jipe_generator.rb +8 -0
- data/generators/jipe/templates/edit-field.png +0 -0
- data/init.rb +1 -0
- data/install.rb +1 -0
- data/jipe.gemspec +62 -0
- data/lib/jipe.rb +138 -0
- data/rails/init.rb +2 -0
- data/tasks/jipe_tasks.rake +4 -0
- data/test/jipe_test.rb +8 -0
- data/uninstall.rb +1 -0
- metadata +119 -0
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
data/README
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
JIPE: In-Place Editing Components for Jester
|
2
|
+
============================================
|
3
|
+
|
4
|
+
JIPE stands for Jester In-Place Editing. It started out as a replacement for
|
5
|
+
Rails' built-in in_place_editor helper function using Eric Mill's Jester
|
6
|
+
library, but is beginning to add additional editing widgets as well.
|
7
|
+
|
8
|
+
The upshot of this is that if you have RESTful controllers in your Rails app,
|
9
|
+
then JIPE can very quickly help you set up in-place editing for your RESTful
|
10
|
+
objects.
|
11
|
+
|
12
|
+
Setup
|
13
|
+
=====
|
14
|
+
|
15
|
+
To begin using JIPE in your project, run:
|
16
|
+
|
17
|
+
script/generate jipe
|
18
|
+
|
19
|
+
This will copy the JIPE icons into your public/images directory.
|
20
|
+
|
21
|
+
Usage
|
22
|
+
=====
|
23
|
+
|
24
|
+
To use JIPE, you'll need to include the Javascripts in your layout:
|
25
|
+
|
26
|
+
<%= javascript_include_tag 'jipe/jester' %>
|
27
|
+
<%= javascript_include_tag 'jipe/jipe' %>
|
28
|
+
|
29
|
+
This requires Rails 2.3, since it makes use of the Engines-like features. For
|
30
|
+
older versions of Rails, you will need to either install the Engines plugin,
|
31
|
+
or copy the Javascripts into your public/javascript directory yourself.
|
32
|
+
|
33
|
+
You'll also need to tell Jester how to access your REST resources, like so:
|
34
|
+
|
35
|
+
<script type="text/javascript">
|
36
|
+
Resource.model("Post");
|
37
|
+
</script>
|
38
|
+
|
39
|
+
Now that you've done that, creating in-place editing fields is easy:
|
40
|
+
|
41
|
+
Title: <%= jipe_editor @post, "title" %><br/>
|
42
|
+
<%= jipe_editor @post, "body", :rows => 4 %>
|
43
|
+
|
44
|
+
In addition to textbox-style in-place editors, JIPE lets you do clickable image
|
45
|
+
toggles for boolean fields. For example:
|
46
|
+
|
47
|
+
Mood: <%= jipe_image_toggle @post, "is_happy", "smiley.png", "frowny.png" %>
|
data/Rakefile
ADDED
@@ -0,0 +1,47 @@
|
|
1
|
+
require 'rubygems'
|
2
|
+
require 'bundler'
|
3
|
+
begin
|
4
|
+
Bundler.setup(:default, :development)
|
5
|
+
rescue Bundler::BundlerError => e
|
6
|
+
$stderr.puts e.message
|
7
|
+
$stderr.puts "Run `bundle install` to install missing gems"
|
8
|
+
exit e.status_code
|
9
|
+
end
|
10
|
+
require 'rake'
|
11
|
+
|
12
|
+
require 'jeweler'
|
13
|
+
Jeweler::Tasks.new do |gem|
|
14
|
+
# gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
|
15
|
+
gem.name = "jipe"
|
16
|
+
gem.homepage = "http://github.com/nbudin/jipe"
|
17
|
+
gem.license = "MIT"
|
18
|
+
gem.summary = %Q{RESTful In-place editors for Rails using Jester}
|
19
|
+
gem.email = "natbudin@gmail.com"
|
20
|
+
gem.authors = ["Nat Budin"]
|
21
|
+
# Include your dependencies below. Runtime dependencies are required when using your gem,
|
22
|
+
# and development dependencies are only needed for development (ie running rake tasks, tests, etc)
|
23
|
+
# gem.add_runtime_dependency 'jabber4r', '> 0.1'
|
24
|
+
# gem.add_development_dependency 'rspec', '> 1.2.3'
|
25
|
+
end
|
26
|
+
Jeweler::RubygemsDotOrgTasks.new
|
27
|
+
|
28
|
+
require 'rake/testtask'
|
29
|
+
require 'rake/rdoctask'
|
30
|
+
desc 'Default: run unit tests.'
|
31
|
+
task :default => :test
|
32
|
+
|
33
|
+
desc 'Test the jipe plugin.'
|
34
|
+
Rake::TestTask.new(:test) do |t|
|
35
|
+
t.libs << 'lib'
|
36
|
+
t.pattern = 'test/**/*_test.rb'
|
37
|
+
t.verbose = true
|
38
|
+
end
|
39
|
+
|
40
|
+
desc 'Generate documentation for the jipe plugin.'
|
41
|
+
Rake::RDocTask.new(:rdoc) do |rdoc|
|
42
|
+
rdoc.rdoc_dir = 'rdoc'
|
43
|
+
rdoc.title = 'Jipe'
|
44
|
+
rdoc.options << '--line-numbers' << '--inline-source'
|
45
|
+
rdoc.rdoc_files.include('README')
|
46
|
+
rdoc.rdoc_files.include('lib/**/*.rb')
|
47
|
+
end
|
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
1.0.0
|
@@ -0,0 +1,860 @@
|
|
1
|
+
// Jester version 1.5
|
2
|
+
// Released October 25th, 2007
|
3
|
+
|
4
|
+
// Compatible, tested with Prototype 1.6.0.2
|
5
|
+
|
6
|
+
// Copyright 2007, thoughtbot, inc.
|
7
|
+
// Released under the MIT License.
|
8
|
+
|
9
|
+
Jester = {}
|
10
|
+
Jester.Resource = function(){};
|
11
|
+
|
12
|
+
// Doing it this way forces the validation of the syntax but gives flexibility enough to rename the new class.
|
13
|
+
Jester.Constructor = function(model){
|
14
|
+
return (function CONSTRUCTOR() {
|
15
|
+
this.klass = CONSTRUCTOR;
|
16
|
+
this.initialize.apply(this, arguments);
|
17
|
+
this.after_initialization.apply(this, arguments);
|
18
|
+
}).toString().replace(/CONSTRUCTOR/g, model);
|
19
|
+
}
|
20
|
+
|
21
|
+
// universal Jester callback holder for remote JSON loading
|
22
|
+
var jesterCallback = null;
|
23
|
+
|
24
|
+
Object.extend(Jester.Resource, {
|
25
|
+
model: function(model, options)
|
26
|
+
{
|
27
|
+
var new_model = null;
|
28
|
+
new_model = eval(model + " = " + Jester.Constructor(model));
|
29
|
+
new_model.prototype = new Jester.Resource();
|
30
|
+
Object.extend(new_model, Jester.Resource);
|
31
|
+
|
32
|
+
// We delay instantiating XML.ObjTree() so that it can be listed at the end of this file instead of the beginning
|
33
|
+
if (!Jester.Tree) {
|
34
|
+
Jester.Tree = new XML.ObjTree();
|
35
|
+
Jester.Tree.attr_prefix = "@";
|
36
|
+
}
|
37
|
+
if (!options) options = {};
|
38
|
+
|
39
|
+
var default_options = {
|
40
|
+
format: "xml",
|
41
|
+
singular: model.underscore(),
|
42
|
+
name: model
|
43
|
+
}
|
44
|
+
options = Object.extend(default_options, options);
|
45
|
+
options.format = options.format.toLowerCase();
|
46
|
+
options.plural = options.singular.pluralize(options.plural);
|
47
|
+
options.singular_xml = options.singular.replace(/_/g, "-");
|
48
|
+
options.plural_xml = options.plural.replace(/_/g, "-");
|
49
|
+
options.remote = false;
|
50
|
+
|
51
|
+
// Establish prefix
|
52
|
+
var default_prefix = window.location.protocol + "//" + window.location.hostname + (window.location.port ? ":" + window.location.port : "");
|
53
|
+
if (options.prefix && options.prefix.match(/^https?:/))
|
54
|
+
options.remote = true;
|
55
|
+
|
56
|
+
if (!options.prefix)
|
57
|
+
options.prefix = default_prefix;
|
58
|
+
|
59
|
+
if (!options.prefix.match(/^(https?|file):/))
|
60
|
+
options.prefix = default_prefix + (options.prefix.match(/^\//) ? "" : "/") + options.prefix;
|
61
|
+
|
62
|
+
options.prefix = options.prefix.replace(/\b\/+$/,"");
|
63
|
+
|
64
|
+
// Establish custom URLs
|
65
|
+
options.urls = Object.extend(this._default_urls(options), options.urls);
|
66
|
+
|
67
|
+
// Assign options to model
|
68
|
+
new_model.name = model;
|
69
|
+
new_model.options = options;
|
70
|
+
for(var opt in options)
|
71
|
+
new_model["_" + opt] = options[opt];
|
72
|
+
|
73
|
+
// Establish custom URL helpers
|
74
|
+
for (var url in options.urls)
|
75
|
+
eval('new_model._' + url + '_url = function(params) {return this._url_for("' + url + '", params);}');
|
76
|
+
|
77
|
+
if (options.checkNew)
|
78
|
+
this.buildAttributes(new_model, options);
|
79
|
+
|
80
|
+
if (window)
|
81
|
+
window[model] = new_model;
|
82
|
+
|
83
|
+
return new_model;
|
84
|
+
},
|
85
|
+
|
86
|
+
buildAttributes: function(model, options) {
|
87
|
+
model = model || this;
|
88
|
+
var async = options.asynchronous;
|
89
|
+
|
90
|
+
if (async == null)
|
91
|
+
async = true;
|
92
|
+
|
93
|
+
var buildWork = bind(model, function(doc) {
|
94
|
+
if (this._format == "json")
|
95
|
+
this._attributes = this._attributesFromJSON(doc);
|
96
|
+
else
|
97
|
+
this._attributes = this._attributesFromTree(doc[this._singular_xml]);
|
98
|
+
});
|
99
|
+
model.requestAndParse(options.format, buildWork, model._new_url(), {asynchronous: async});
|
100
|
+
},
|
101
|
+
|
102
|
+
loadRemoteJSON : function(url, callback, user_callback) {
|
103
|
+
// tack on user_callback if there is one, and only if it's really a function
|
104
|
+
if (typeof(user_callback) == "function")
|
105
|
+
jesterCallback = function(doc) {user_callback(callback(doc));}
|
106
|
+
else
|
107
|
+
jesterCallback = callback;
|
108
|
+
|
109
|
+
var script = document.createElement("script");
|
110
|
+
script.type = "text/javascript";
|
111
|
+
|
112
|
+
if (url.indexOf("?") == -1)
|
113
|
+
url += "?";
|
114
|
+
else
|
115
|
+
url += "&";
|
116
|
+
url += "callback=jesterCallback";
|
117
|
+
script.src = url;
|
118
|
+
|
119
|
+
document.firstChild.appendChild(script);
|
120
|
+
},
|
121
|
+
|
122
|
+
requestAndParse : function(format, callback, url, options, user_callback, remote) {
|
123
|
+
if (remote && format == "json" && user_callback)
|
124
|
+
return this.loadRemoteJSON(url, callback, user_callback)
|
125
|
+
|
126
|
+
parse_and_callback = null;
|
127
|
+
if (format.toLowerCase() == "json") {
|
128
|
+
parse_and_callback = function(transport) {
|
129
|
+
if (transport.status == 500) return callback(null);
|
130
|
+
eval("var attributes = " + transport.responseText); // hashes need this kind of eval
|
131
|
+
return callback(attributes);
|
132
|
+
}
|
133
|
+
} else {
|
134
|
+
parse_and_callback = function(transport) {
|
135
|
+
if (transport.status == 500) return callback(null);
|
136
|
+
return callback(Jester.Tree.parseXML(transport.responseText));
|
137
|
+
}
|
138
|
+
}
|
139
|
+
|
140
|
+
// most parse requests are going to be a GET
|
141
|
+
if (!(options.postBody || options.parameters || options.postbody || options.method == "post")) {
|
142
|
+
options.method = "get";
|
143
|
+
}
|
144
|
+
|
145
|
+
return this.request(parse_and_callback, url, options, user_callback);
|
146
|
+
},
|
147
|
+
|
148
|
+
// Helper to aid in handling either async or synchronous requests
|
149
|
+
request : function(callback, url, options, user_callback) {
|
150
|
+
if (user_callback) {
|
151
|
+
options.asynchronous = true;
|
152
|
+
// if an options hash was given instead of a callback
|
153
|
+
if (typeof(user_callback) == "object") {
|
154
|
+
for (var x in user_callback)
|
155
|
+
options[x] = user_callback[x];
|
156
|
+
user_callback = options.onComplete;
|
157
|
+
}
|
158
|
+
}
|
159
|
+
else
|
160
|
+
user_callback = function(arg){return arg;}
|
161
|
+
|
162
|
+
if (options.asynchronous) {
|
163
|
+
options.onComplete = function(transport, json) {user_callback(callback(transport), json);}
|
164
|
+
return new Ajax.Request(url, options).transport;
|
165
|
+
}
|
166
|
+
else
|
167
|
+
{
|
168
|
+
options.asynchronous = false; // Make sure it's set, to avoid being overridden.
|
169
|
+
return callback(new Ajax.Request(url, options).transport);
|
170
|
+
}
|
171
|
+
},
|
172
|
+
|
173
|
+
find : function(id, params, callback) {
|
174
|
+
// allow a params hash to be omitted and a callback function given directly
|
175
|
+
if (!callback && typeof(params) == "function") {
|
176
|
+
callback = params;
|
177
|
+
params = null;
|
178
|
+
}
|
179
|
+
|
180
|
+
var findAllWork = bind(this, function(doc) {
|
181
|
+
if (!doc) return null;
|
182
|
+
|
183
|
+
var collection = this._loadCollection(doc);
|
184
|
+
|
185
|
+
if (!collection) return null;
|
186
|
+
|
187
|
+
// This is better than requiring the controller to support a "limit" parameter
|
188
|
+
if (id == "first")
|
189
|
+
return collection[0];
|
190
|
+
|
191
|
+
return collection;
|
192
|
+
});
|
193
|
+
|
194
|
+
var findOneWork = bind(this, function(doc) {
|
195
|
+
if (!doc) return null;
|
196
|
+
|
197
|
+
var base = this._loadSingle(doc);
|
198
|
+
|
199
|
+
// if there were no properties, it was probably not actually loaded
|
200
|
+
if (!base || base._properties.length == 0) return null;
|
201
|
+
|
202
|
+
// even if the ID didn't come back, we obviously knew the ID to search with, so set it
|
203
|
+
if (!base._properties.include("id")) base._setAttribute("id", parseInt(id))
|
204
|
+
|
205
|
+
return base;
|
206
|
+
});
|
207
|
+
|
208
|
+
if (id == "first" || id == "all") {
|
209
|
+
var url = this._list_url(params);
|
210
|
+
return this.requestAndParse(this._format, findAllWork, url, {}, callback, this._remote);
|
211
|
+
}
|
212
|
+
else {
|
213
|
+
if (isNaN(parseInt(id))) return null;
|
214
|
+
if (!params) params = {};
|
215
|
+
params.id = id;
|
216
|
+
|
217
|
+
var url = this._show_url(params);
|
218
|
+
return this.requestAndParse(this._format, findOneWork, url, {}, callback, this._remote);
|
219
|
+
}
|
220
|
+
},
|
221
|
+
|
222
|
+
build : function(attributes) {
|
223
|
+
return new this(attributes);
|
224
|
+
},
|
225
|
+
|
226
|
+
create : function(attributes, callback) {
|
227
|
+
var base = new this(attributes);
|
228
|
+
|
229
|
+
createWork = bind(this, function(saved) {
|
230
|
+
return callback(base);
|
231
|
+
});
|
232
|
+
|
233
|
+
if (callback) {
|
234
|
+
return base.save(createWork);
|
235
|
+
}
|
236
|
+
else {
|
237
|
+
base.save();
|
238
|
+
return base;
|
239
|
+
}
|
240
|
+
},
|
241
|
+
|
242
|
+
// Destroys a REST object. Can be used as follows:
|
243
|
+
// object.destroy() - when called on an instance of a model, destroys that instance
|
244
|
+
// Model.destroy(1) - destroys the Model object with ID 1
|
245
|
+
// Model.destroy({parent: 3, id: 1}) - destroys the Model object with Parent ID 3 and ID 1
|
246
|
+
//
|
247
|
+
// Any of these forms can also be passed a callback function as an additional parameter and it works as you expect.
|
248
|
+
destroy : function(params, callback) {
|
249
|
+
if (typeof(params) == "function") {
|
250
|
+
callback = params;
|
251
|
+
params = null;
|
252
|
+
}
|
253
|
+
if (typeof(params) == "number") {
|
254
|
+
params = {id: params};
|
255
|
+
}
|
256
|
+
params.id = params.id || this.id;
|
257
|
+
if (!params.id) return false;
|
258
|
+
|
259
|
+
var destroyWork = bind(this, function(transport) {
|
260
|
+
if (transport.status == 200) {
|
261
|
+
if (!params.id || this.id == params.id)
|
262
|
+
this.id = null;
|
263
|
+
return this;
|
264
|
+
}
|
265
|
+
else
|
266
|
+
return false;
|
267
|
+
});
|
268
|
+
|
269
|
+
return this.request(destroyWork, this._destroy_url(params), {method: "delete"}, callback);
|
270
|
+
},
|
271
|
+
|
272
|
+
_interpolate: function(string, params) {
|
273
|
+
if (!params) return string;
|
274
|
+
|
275
|
+
var result = string;
|
276
|
+
params.each(function(pair) {
|
277
|
+
var re = new RegExp(":" + pair.key, "g");
|
278
|
+
if (result.match(re)) {
|
279
|
+
result = result.replace(re, pair.value);
|
280
|
+
params.unset(pair.key);
|
281
|
+
}
|
282
|
+
});
|
283
|
+
return result;
|
284
|
+
},
|
285
|
+
|
286
|
+
_url_for : function(action, params) {
|
287
|
+
if (!this._urls[action]) return "";
|
288
|
+
// if an integer is sent, it's assumed just the ID is a parameter
|
289
|
+
if (typeof(params) == "number") params = {id: params}
|
290
|
+
|
291
|
+
if (params) params = $H(params);
|
292
|
+
|
293
|
+
var url = this._interpolate(this._prefix + this._urls[action], params)
|
294
|
+
return url + (params && params.any() ? "?" + params.toQueryString() : "");
|
295
|
+
},
|
296
|
+
|
297
|
+
_default_urls : function(options) {
|
298
|
+
urls = {
|
299
|
+
'show' : "/" + options.plural + "/:id." + options.format,
|
300
|
+
'list' : "/" + options.plural + "." + options.format,
|
301
|
+
'new' : "/" + options.plural + "/new." + options.format
|
302
|
+
}
|
303
|
+
urls.create = urls.list;
|
304
|
+
urls.destroy = urls.update = urls.show;
|
305
|
+
|
306
|
+
return urls;
|
307
|
+
},
|
308
|
+
|
309
|
+
// Converts a JSON hash returns from ActiveRecord::Base#to_json into a hash of attribute values
|
310
|
+
// Does not handle associations, as AR's #to_json doesn't either
|
311
|
+
// Also, JSON doesn't include room to store types, so little auto-transforming is done here (just on 'id')
|
312
|
+
_attributesFromJSON : function(json) {
|
313
|
+
if (!json || json.constructor != Object) return false;
|
314
|
+
if (json.attributes) json = json.attributes;
|
315
|
+
|
316
|
+
var attributes = {};
|
317
|
+
var i = 0;
|
318
|
+
for (var attr in json) {
|
319
|
+
var value = json[attr];
|
320
|
+
if (attr == "id")
|
321
|
+
value = parseInt(value);
|
322
|
+
else if (attr.match(/(created_at|created_on|updated_at|updated_on)/)) {
|
323
|
+
var date = Date.parse(value);
|
324
|
+
if (date && !isNaN(date)) value = date;
|
325
|
+
}
|
326
|
+
attributes[attr] = value;
|
327
|
+
i += 1;
|
328
|
+
}
|
329
|
+
if (i == 0) return false; // empty hashes should just return false
|
330
|
+
|
331
|
+
return attributes;
|
332
|
+
},
|
333
|
+
|
334
|
+
// Converts the XML tree returned from a single object into a hash of attribute values
|
335
|
+
_attributesFromTree : function(elements) {
|
336
|
+
var attributes = {}
|
337
|
+
for (var attr in elements) {
|
338
|
+
// pull out the value
|
339
|
+
var value = elements[attr];
|
340
|
+
if (elements[attr] && elements[attr]["@type"]) {
|
341
|
+
if (elements[attr]["#text"])
|
342
|
+
value = elements[attr]["#text"];
|
343
|
+
else
|
344
|
+
value = undefined;
|
345
|
+
}
|
346
|
+
|
347
|
+
// handle empty value (pass it through)
|
348
|
+
if (!value) {}
|
349
|
+
|
350
|
+
// handle scalars
|
351
|
+
else if (typeof(value) == "string") {
|
352
|
+
// perform any useful type transformations
|
353
|
+
if (elements[attr]["@type"] == "integer") {
|
354
|
+
var num = parseInt(value);
|
355
|
+
if (!isNaN(num)) value = num;
|
356
|
+
}
|
357
|
+
else if (elements[attr]["@type"] == "boolean")
|
358
|
+
value = (value == "true");
|
359
|
+
else if (elements[attr]["@type"] == "datetime") {
|
360
|
+
var date = Date.parse(value);
|
361
|
+
if (!isNaN(date)) value = date;
|
362
|
+
}
|
363
|
+
}
|
364
|
+
// handle arrays (associations)
|
365
|
+
else {
|
366
|
+
var relation = value; // rename for clarity in the context of an association
|
367
|
+
|
368
|
+
// first, detect if it's has_one/belongs_to, or has_many
|
369
|
+
var i = 0;
|
370
|
+
var singular = null;
|
371
|
+
var has_many = false;
|
372
|
+
for (var val in relation) {
|
373
|
+
if (i == 0)
|
374
|
+
singular = val;
|
375
|
+
i += 1;
|
376
|
+
}
|
377
|
+
|
378
|
+
// has_many
|
379
|
+
if (relation[singular] && typeof(relation[singular]) == "object" && i == 1) {
|
380
|
+
var value = [];
|
381
|
+
var plural = attr;
|
382
|
+
var name = singular.camelize().capitalize();
|
383
|
+
|
384
|
+
// force array
|
385
|
+
if (!(elements[plural][singular].length > 0))
|
386
|
+
elements[plural][singular] = [elements[plural][singular]];
|
387
|
+
|
388
|
+
elements[plural][singular].each( bind(this, function(single) {
|
389
|
+
// if the association hasn't been modeled, do a default modeling here
|
390
|
+
// hosted object's prefix and format are inherited, singular and plural are set
|
391
|
+
// from the XML
|
392
|
+
if (eval("typeof(" + name + ")") == "undefined") {
|
393
|
+
Jester.Resource.model(name, {prefix: this._prefix, singular: singular, plural: plural, format: this._format});
|
394
|
+
}
|
395
|
+
var base = eval(name + ".build(this._attributesFromTree(single))");
|
396
|
+
value.push(base);
|
397
|
+
}));
|
398
|
+
}
|
399
|
+
// has_one or belongs_to
|
400
|
+
else {
|
401
|
+
singular = attr;
|
402
|
+
var name = singular.capitalize();
|
403
|
+
|
404
|
+
// if the association hasn't been modeled, do a default modeling here
|
405
|
+
// hosted object's prefix and format are inherited, singular is set from the XML
|
406
|
+
if (eval("typeof(" + name + ")") == "undefined") {
|
407
|
+
Jester.Resource.model(name, {prefix: this._prefix, singular: singular, format: this._format});
|
408
|
+
}
|
409
|
+
value = eval(name + ".build(this._attributesFromTree(value))");
|
410
|
+
}
|
411
|
+
}
|
412
|
+
|
413
|
+
// transform attribute name if needed
|
414
|
+
attribute = attr.replace(/-/g, "_");
|
415
|
+
attributes[attribute] = value;
|
416
|
+
}
|
417
|
+
|
418
|
+
return attributes;
|
419
|
+
},
|
420
|
+
|
421
|
+
_loadSingle : function(doc) {
|
422
|
+
var attributes;
|
423
|
+
if (this._format == "json")
|
424
|
+
attributes = this._attributesFromJSON(doc);
|
425
|
+
else
|
426
|
+
attributes = this._attributesFromTree(doc[this._singular_xml]);
|
427
|
+
|
428
|
+
return this.build(attributes);
|
429
|
+
},
|
430
|
+
|
431
|
+
_loadCollection : function(doc) {
|
432
|
+
var collection;
|
433
|
+
if (this._format == "json") {
|
434
|
+
collection = doc.map( bind(this, function(item) {
|
435
|
+
return this.build(this._attributesFromJSON(item));
|
436
|
+
}));
|
437
|
+
}
|
438
|
+
else {
|
439
|
+
// if only one result, wrap it in an array
|
440
|
+
if (!Jester.Resource.elementHasMany(doc[this._plural_xml]))
|
441
|
+
doc[this._plural_xml][this._singular_xml] = [doc[this._plural_xml][this._singular_xml]];
|
442
|
+
|
443
|
+
collection = doc[this._plural_xml][this._singular_xml].map( bind(this, function(elem) {
|
444
|
+
return this.build(this._attributesFromTree(elem));
|
445
|
+
}));
|
446
|
+
}
|
447
|
+
return collection;
|
448
|
+
}
|
449
|
+
|
450
|
+
});
|
451
|
+
|
452
|
+
Object.extend(Jester.Resource.prototype, {
|
453
|
+
initialize : function(attributes) {
|
454
|
+
// Initialize no attributes, no associations
|
455
|
+
this._properties = [];
|
456
|
+
this._associations = [];
|
457
|
+
|
458
|
+
this.setAttributes(this.klass._attributes || {});
|
459
|
+
this.setAttributes(attributes);
|
460
|
+
|
461
|
+
// Initialize with no errors
|
462
|
+
this.errors = [];
|
463
|
+
|
464
|
+
// Establish custom URL helpers
|
465
|
+
for (var url in this.klass._urls)
|
466
|
+
eval('this._' + url + '_url = function(params) {return this._url_for("' + url + '", params);}');
|
467
|
+
},
|
468
|
+
after_initialization: function(){},
|
469
|
+
|
470
|
+
new_record : function() {return !(this.id);},
|
471
|
+
valid : function() {return ! this.errors.any();},
|
472
|
+
|
473
|
+
reload : function(callback) {
|
474
|
+
var reloadWork = bind(this, function(copy) {
|
475
|
+
this._resetAttributes(copy.attributes(true));
|
476
|
+
|
477
|
+
if (callback)
|
478
|
+
return callback(this);
|
479
|
+
else
|
480
|
+
return this;
|
481
|
+
});
|
482
|
+
|
483
|
+
if (this.id) {
|
484
|
+
if (callback)
|
485
|
+
return this.klass.find(this.id, {}, reloadWork);
|
486
|
+
else
|
487
|
+
return reloadWork(this.klass.find(this.id));
|
488
|
+
}
|
489
|
+
else
|
490
|
+
return this;
|
491
|
+
},
|
492
|
+
|
493
|
+
// Destroys a REST object. Can be used as follows:
|
494
|
+
// object.destroy() - when called on an instance of a model, destroys that instance
|
495
|
+
// Model.destroy(1) - destroys the Model object with ID 1
|
496
|
+
// Model.destroy({parent: 3, id: 1}) - destroys the Model object with Parent ID 3 and ID 1
|
497
|
+
//
|
498
|
+
// Any of these forms can also be passed a callback function as an additional parameter and it works as you expect.
|
499
|
+
destroy : function(params, callback) {
|
500
|
+
if (params === undefined) {
|
501
|
+
params = {};
|
502
|
+
}
|
503
|
+
if (typeof(params) == "function") {
|
504
|
+
callback = params;
|
505
|
+
params = {};
|
506
|
+
}
|
507
|
+
if (typeof(params) == "number") {
|
508
|
+
params = {id: params};
|
509
|
+
}
|
510
|
+
if (!params.id) {
|
511
|
+
params.id = this.id;
|
512
|
+
}
|
513
|
+
if (!params.id) return false;
|
514
|
+
|
515
|
+
// collect params from instance if we're being called as an instance method
|
516
|
+
if (this._properties !== undefined) {
|
517
|
+
(this._properties).each( bind(this, function(value, i) {
|
518
|
+
if (params[value] === undefined) {
|
519
|
+
params[value] = this[value];
|
520
|
+
}
|
521
|
+
}));
|
522
|
+
}
|
523
|
+
|
524
|
+
var destroyWork = bind(this, function(transport) {
|
525
|
+
if (transport.status == 200) {
|
526
|
+
if (!params.id || this.id == params.id)
|
527
|
+
this.id = null;
|
528
|
+
return this;
|
529
|
+
}
|
530
|
+
else
|
531
|
+
return false;
|
532
|
+
});
|
533
|
+
|
534
|
+
return this.klass.request(destroyWork, this._destroy_url(params), {method: "delete"}, callback);
|
535
|
+
},
|
536
|
+
|
537
|
+
save : function(callback) {
|
538
|
+
var saveWork = bind(this, function(transport) {
|
539
|
+
var saved = false;
|
540
|
+
|
541
|
+
if (transport.responseText && (transport.responseText.strip() != "")) {
|
542
|
+
var errors = this._errorsFrom(transport.responseText);
|
543
|
+
if (errors)
|
544
|
+
this._setErrors(errors);
|
545
|
+
else {
|
546
|
+
var attributes;
|
547
|
+
if (this.klass._format == "json") {
|
548
|
+
attributes = this._attributesFromJSON(transport.responseText);
|
549
|
+
}
|
550
|
+
else {
|
551
|
+
var doc = Jester.Tree.parseXML(transport.responseText);
|
552
|
+
if (doc[this.klass._singular_xml])
|
553
|
+
attributes = this._attributesFromTree(doc[this.klass._singular_xml]);
|
554
|
+
}
|
555
|
+
if (attributes)
|
556
|
+
this._resetAttributes(attributes);
|
557
|
+
}
|
558
|
+
}
|
559
|
+
|
560
|
+
// Get ID from the location header if it's there
|
561
|
+
if (this.new_record() && transport.status == 201) {
|
562
|
+
loc = transport.getResponseHeader("location");
|
563
|
+
if (loc) {
|
564
|
+
id = parseInt(loc.match(/\/([^\/]*?)(\.\w+)?$/)[1]);
|
565
|
+
if (!isNaN(id))
|
566
|
+
this._setProperty("id", id)
|
567
|
+
}
|
568
|
+
}
|
569
|
+
|
570
|
+
return (transport.status >= 200 && transport.status < 300 && this.errors.length == 0);
|
571
|
+
});
|
572
|
+
|
573
|
+
// reset errors
|
574
|
+
this._setErrors([]);
|
575
|
+
|
576
|
+
var url = null;
|
577
|
+
var method = null;
|
578
|
+
|
579
|
+
// collect params
|
580
|
+
var params = {};
|
581
|
+
var urlParams = {};
|
582
|
+
(this._properties).each( bind(this, function(value, i) {
|
583
|
+
params[this.klass._singular + "[" + value + "]"] = this[value];
|
584
|
+
urlParams[value] = this[value];
|
585
|
+
}));
|
586
|
+
|
587
|
+
// distinguish between create and update
|
588
|
+
if (this.new_record()) {
|
589
|
+
url = this._create_url(urlParams);
|
590
|
+
method = "post";
|
591
|
+
}
|
592
|
+
else {
|
593
|
+
url = this._update_url(urlParams);
|
594
|
+
method = "put";
|
595
|
+
}
|
596
|
+
|
597
|
+
|
598
|
+
// send the request
|
599
|
+
return this.klass.request(saveWork, url, {parameters: params, method: method}, callback);
|
600
|
+
},
|
601
|
+
|
602
|
+
setAttributes : function(attributes)
|
603
|
+
{
|
604
|
+
$H(attributes).each(bind(this, function(attr){ this._setAttribute(attr.key, attr.value) }));
|
605
|
+
return attributes;
|
606
|
+
},
|
607
|
+
|
608
|
+
updateAttributes : function(attributes, callback)
|
609
|
+
{
|
610
|
+
this.setAttributes(attributes);
|
611
|
+
return this.save(callback);
|
612
|
+
},
|
613
|
+
|
614
|
+
// mimics ActiveRecord's behavior of omitting associations, but keeping foreign keys
|
615
|
+
attributes : function(include_associations) {
|
616
|
+
var attributes = {}
|
617
|
+
for (var i=0; i<this._properties.length; i++)
|
618
|
+
attributes[this._properties[i]] = this[this._properties[i]];
|
619
|
+
if (include_associations) {
|
620
|
+
for (var i=0; i<this._associations.length; i++)
|
621
|
+
attributes[this._associations[i]] = this[this._associations[i]];
|
622
|
+
}
|
623
|
+
return attributes;
|
624
|
+
},
|
625
|
+
|
626
|
+
/*
|
627
|
+
Internal methods.
|
628
|
+
*/
|
629
|
+
|
630
|
+
_attributesFromJSON: function()
|
631
|
+
{
|
632
|
+
return this.klass._attributesFromJSON.apply(this.klass, arguments);
|
633
|
+
},
|
634
|
+
|
635
|
+
_attributesFromTree: function()
|
636
|
+
{
|
637
|
+
return this.klass._attributesFromTree.apply(this.klass, arguments);
|
638
|
+
},
|
639
|
+
|
640
|
+
_errorsFrom : function(raw) {
|
641
|
+
if (this.klass._format == "json")
|
642
|
+
return this._errorsFromJSON(raw);
|
643
|
+
else
|
644
|
+
return this._errorsFromXML(raw);
|
645
|
+
},
|
646
|
+
|
647
|
+
// Pulls errors from JSON
|
648
|
+
_errorsFromJSON : function(json) {
|
649
|
+
try {
|
650
|
+
json = eval(json); // okay for arrays
|
651
|
+
} catch(e) {
|
652
|
+
return false;
|
653
|
+
}
|
654
|
+
|
655
|
+
if (!(json && json.constructor == Array && json[0] && json[0].constructor == Array)) return false;
|
656
|
+
|
657
|
+
return json.map(function(pair) {
|
658
|
+
return pair[0].capitalize() + " " + pair[1];
|
659
|
+
});
|
660
|
+
},
|
661
|
+
|
662
|
+
// Pulls errors from XML
|
663
|
+
_errorsFromXML : function(xml) {
|
664
|
+
if (!xml) return false;
|
665
|
+
var doc = Jester.Tree.parseXML(xml);
|
666
|
+
|
667
|
+
if (doc && doc.errors) {
|
668
|
+
var errors = [];
|
669
|
+
if (typeof(doc.errors.error) == "string")
|
670
|
+
doc.errors.error = [doc.errors.error];
|
671
|
+
|
672
|
+
doc.errors.error.each(function(value, index) {
|
673
|
+
errors.push(value);
|
674
|
+
});
|
675
|
+
|
676
|
+
return errors;
|
677
|
+
}
|
678
|
+
else return false;
|
679
|
+
},
|
680
|
+
|
681
|
+
// Sets errors with an array. Could be extended at some point to include breaking error messages into pairs (attribute, msg).
|
682
|
+
_setErrors : function(errors) {
|
683
|
+
this.errors = errors;
|
684
|
+
},
|
685
|
+
|
686
|
+
|
687
|
+
// Sets all attributes and associations at once
|
688
|
+
// Deciding between the two on whether the attribute is a complex object or a scalar
|
689
|
+
_resetAttributes : function(attributes) {
|
690
|
+
this._clear();
|
691
|
+
for (var attr in attributes)
|
692
|
+
this._setAttribute(attr, attributes[attr]);
|
693
|
+
},
|
694
|
+
|
695
|
+
_setAttribute : function(attribute, value) {
|
696
|
+
if (value && typeof(value) == "object" && value.constructor != Date)
|
697
|
+
this._setAssociation(attribute, value);
|
698
|
+
else
|
699
|
+
this._setProperty(attribute, value);
|
700
|
+
},
|
701
|
+
|
702
|
+
_setProperties : function(properties) {
|
703
|
+
this._clearProperties();
|
704
|
+
for (var prop in properties)
|
705
|
+
this._setProperty(prop, properties[prop])
|
706
|
+
},
|
707
|
+
|
708
|
+
_setAssociations : function(associations) {
|
709
|
+
this._clearAssociations();
|
710
|
+
for (var assoc in associations)
|
711
|
+
this._setAssociation(assoc, associations[assoc])
|
712
|
+
},
|
713
|
+
|
714
|
+
_setProperty : function(property, value) {
|
715
|
+
this[property] = value;
|
716
|
+
if (!(this._properties.include(property)))
|
717
|
+
this._properties.push(property);
|
718
|
+
},
|
719
|
+
|
720
|
+
_setAssociation : function(association, value) {
|
721
|
+
this[association] = value;
|
722
|
+
if (!(this._associations.include(association)))
|
723
|
+
this._associations.push(association);
|
724
|
+
},
|
725
|
+
|
726
|
+
_clear : function() {
|
727
|
+
this._clearProperties();
|
728
|
+
this._clearAssociations();
|
729
|
+
},
|
730
|
+
|
731
|
+
_clearProperties : function() {
|
732
|
+
for (var i=0; i<this._properties.length; i++)
|
733
|
+
this[this._properties[i]] = null;
|
734
|
+
this._properties = [];
|
735
|
+
},
|
736
|
+
|
737
|
+
_clearAssociations : function() {
|
738
|
+
for (var i=0; i<this._associations.length; i++)
|
739
|
+
this[this._associations[i]] = null;
|
740
|
+
this._associations = [];
|
741
|
+
},
|
742
|
+
|
743
|
+
// helper URLs
|
744
|
+
_url_for : function(action, params) {
|
745
|
+
if (!params) params = this.id;
|
746
|
+
if (typeof(params) == "object" && !params.id)
|
747
|
+
params.id = this.id;
|
748
|
+
|
749
|
+
return this.klass._url_for(action, params);
|
750
|
+
}
|
751
|
+
|
752
|
+
});
|
753
|
+
|
754
|
+
// Returns true if the element has more objects beneath it, or just 1 or more attributes.
|
755
|
+
// It's not perfect, this would mess up if an object had only one attribute, and it was an array.
|
756
|
+
// For now, this is just one of the difficulties of dealing with ObjTree.
|
757
|
+
Jester.Resource.elementHasMany = function(element) {
|
758
|
+
var i = 0;
|
759
|
+
var singular = null;
|
760
|
+
var has_many = false;
|
761
|
+
for (var val in element) {
|
762
|
+
if (i == 0)
|
763
|
+
singular = val;
|
764
|
+
i += 1;
|
765
|
+
}
|
766
|
+
|
767
|
+
return (element[singular] && typeof(element[singular]) == "object" && element[singular].length != null && i == 1);
|
768
|
+
}
|
769
|
+
|
770
|
+
// This bind function is a modification of the standard Prototype bind function.
|
771
|
+
// Use this instead of Prototype's when running in XULRunner due to a longstanding
|
772
|
+
// bug in the javascript interpreter.
|
773
|
+
|
774
|
+
function bind(context, func) {
|
775
|
+
var __method = func, args = $A(func.arguments), object = context;
|
776
|
+
|
777
|
+
return function() {
|
778
|
+
return __method.apply(object, args.concat($A(arguments)));
|
779
|
+
}
|
780
|
+
}
|
781
|
+
|
782
|
+
// If there is no object already called Resource, we define one to make things a little cleaner for us.
|
783
|
+
if(typeof(Resource) == "undefined")
|
784
|
+
Resource = Jester.Resource;
|
785
|
+
|
786
|
+
|
787
|
+
|
788
|
+
|
789
|
+
/*
|
790
|
+
Inflector library, contributed graciously to Jester by Ryan Schuft.
|
791
|
+
The library in full is a complete port of Rails' Inflector, though Jester only uses its pluralization.
|
792
|
+
Its home page can be found at: http://code.google.com/p/inflection-js/
|
793
|
+
*/
|
794
|
+
|
795
|
+
if (!String.prototype.pluralize) String.prototype.pluralize = function(plural) {
|
796
|
+
var str=this;
|
797
|
+
if(plural)str=plural;
|
798
|
+
else {
|
799
|
+
var uncountable_words=['equipment','information','rice','money','species','series','fish','sheep','moose'];
|
800
|
+
var uncountable=false;
|
801
|
+
for(var x=0;!uncountable&&x<uncountable_words.length;x++)uncountable=(uncountable_words[x].toLowerCase()==str.toLowerCase());
|
802
|
+
if(!uncountable) {
|
803
|
+
var rules=[
|
804
|
+
[new RegExp('(m)an$','gi'),'$1en'],
|
805
|
+
[new RegExp('(pe)rson$','gi'),'$1ople'],
|
806
|
+
[new RegExp('(child)$','gi'),'$1ren'],
|
807
|
+
[new RegExp('(ax|test)is$','gi'),'$1es'],
|
808
|
+
[new RegExp('(octop|vir)us$','gi'),'$1i'],
|
809
|
+
[new RegExp('(alias|status)$','gi'),'$1es'],
|
810
|
+
[new RegExp('(bu)s$','gi'),'$1ses'],
|
811
|
+
[new RegExp('(buffal|tomat)o$','gi'),'$1oes'],
|
812
|
+
[new RegExp('([ti])um$','gi'),'$1a'],
|
813
|
+
[new RegExp('sis$','gi'),'ses'],
|
814
|
+
[new RegExp('(?:([^f])fe|([lr])f)$','gi'),'$1$2ves'],
|
815
|
+
[new RegExp('(hive)$','gi'),'$1s'],
|
816
|
+
[new RegExp('([^aeiouy]|qu)y$','gi'),'$1ies'],
|
817
|
+
[new RegExp('(x|ch|ss|sh)$','gi'),'$1es'],
|
818
|
+
[new RegExp('(matr|vert|ind)ix|ex$','gi'),'$1ices'],
|
819
|
+
[new RegExp('([m|l])ouse$','gi'),'$1ice'],
|
820
|
+
[new RegExp('^(ox)$','gi'),'$1en'],
|
821
|
+
[new RegExp('(quiz)$','gi'),'$1zes'],
|
822
|
+
[new RegExp('s$','gi'),'s'],
|
823
|
+
[new RegExp('$','gi'),'s']
|
824
|
+
];
|
825
|
+
var matched=false;
|
826
|
+
for(var x=0;!matched&&x<=rules.length;x++) {
|
827
|
+
matched=str.match(rules[x][0]);
|
828
|
+
if(matched)str=str.replace(rules[x][0],rules[x][1]);
|
829
|
+
}
|
830
|
+
}
|
831
|
+
}
|
832
|
+
return str;
|
833
|
+
};
|
834
|
+
|
835
|
+
/*
|
836
|
+
|
837
|
+
This is a lighter form of ObjTree, with parts Jester doesn't use removed.
|
838
|
+
Compressed using http://dean.edwards.name/packer/.
|
839
|
+
Homepage: http://www.kawa.net/works/js/xml/objtree-e.html
|
840
|
+
|
841
|
+
XML.ObjTree -- XML source code from/to JavaScript object like E4X
|
842
|
+
|
843
|
+
Copyright (c) 2005-2006 Yusuke Kawasaki. All rights reserved.
|
844
|
+
This program is free software; you can redistribute it and/or
|
845
|
+
modify it under the Artistic license. Or whatever license I choose,
|
846
|
+
which I will do instead of keeping this documentation like it is.
|
847
|
+
|
848
|
+
*/
|
849
|
+
|
850
|
+
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('5(p(o)==\'w\')o=v(){};o.r=v(){m 9};o.r.1i="0.1b";o.r.u.14=\'<?L 1s="1.0" 1o="1n-8" ?>\\n\';o.r.u.Y=\'-\';o.r.u.1c=\'1a/L\';o.r.u.N=v(a){6 b;5(W.U){6 c=K U();6 d=c.1r(a,"1p/L");5(!d)m;b=d.A}q 5(W.10){c=K 10(\'1k.1h\');c.1g=z;c.1e(a);b=c.A}5(!b)m;m 9.E(b)};o.r.u.1d=v(c,d,e){6 f={};y(6 g 19 d){f[g]=d[g]}5(!f.M){5(p(f.18)=="w"&&p(f.17)=="w"&&p(f.16)=="w"){f.M="15"}q{f.M="13"}}5(e){f.X=V;6 h=9;6 i=e;6 j=f.T;f.T=v(a){6 b;5(a&&a.x&&a.x.A){b=h.E(a.x.A)}q 5(a&&a.J){b=h.N(a.J)}i(b,a);5(j)j(a)}}q{f.X=z}6 k;5(p(S)!="w"&&S.I){f.1q=c;6 l=K S.I(f);5(l)k=l.12}q 5(p(Q)!="w"&&Q.I){6 l=K Q.I(c,f);5(l)k=l.12}5(e)m k;5(k&&k.x&&k.x.A){m 9.E(k.x.A)}q 5(k&&k.J){m 9.N(k.J)}};o.r.u.E=v(a){5(!a)m;9.H={};5(9.P){y(6 i=0;i<9.P.t;i++){9.H[9.P[i]]=1}}6 b=9.O(a);5(9.H[a.F]){b=[b]}5(a.B!=11){6 c={};c[a.F]=b;b=c}m b};o.r.u.O=v(a){5(a.B==7){m}5(a.B==3||a.B==4){6 b=a.G.1j(/[^\\1f-\\1l]/);5(b==1m)m z;m a.G}6 c;6 d={};5(a.D&&a.D.t){c={};y(6 i=0;i<a.D.t;i++){6 e=a.D[i].F;5(p(e)!="Z")C;6 f=a.D[i].G;5(!f)C;e=9.Y+e;5(p(d[e])=="w")d[e]=0;d[e]++;9.R(c,e,d[e],f)}}5(a.s&&a.s.t){6 g=V;5(c)g=z;y(6 i=0;i<a.s.t&&g;i++){6 h=a.s[i].B;5(h==3||h==4)C;g=z}5(g){5(!c)c="";y(6 i=0;i<a.s.t;i++){c+=a.s[i].G}}q{5(!c)c={};y(6 i=0;i<a.s.t;i++){6 e=a.s[i].F;5(p(e)!="Z")C;6 f=9.O(a.s[i]);5(f==z)C;5(p(d[e])=="w")d[e]=0;d[e]++;9.R(c,e,d[e],f)}}}m c};o.r.u.R=v(a,b,c,d){5(9.H[b]){5(c==1)a[b]=[];a[b][a[b].t]=d}q 5(c==1){a[b]=d}q 5(c==2){a[b]=[a[b],d]}q{a[b][a[b].t]=d}};',62,91,'|||||if|var|||this|||||||||||||return||XML|typeof|else|ObjTree|childNodes|length|prototype|function|undefined|responseXML|for|false|documentElement|nodeType|continue|attributes|parseDOM|nodeName|nodeValue|__force_array|Request|responseText|new|xml|method|parseXML|parseElement|force_array|Ajax|addNode|HTTP|onComplete|DOMParser|true|window|asynchronous|attr_prefix|string|ActiveXObject||transport|post|xmlDecl|get|parameters|postbody|postBody|in|text|24|overrideMimeType|parseHTTP|loadXML|x00|async|XMLDOM|VERSION|match|Microsoft|x20|null|UTF|encoding|application|uri|parseFromString|version'.split('|'),0,{}))
|
851
|
+
|
852
|
+
/*
|
853
|
+
|
854
|
+
This is a Date parsing library by Nicholas Barthelemy, packed to keep jester.js light.
|
855
|
+
Homepage: https://svn.nbarthelemy.com/date-js/
|
856
|
+
Compressed using http://dean.edwards.name/packer/.
|
857
|
+
|
858
|
+
*/
|
859
|
+
|
860
|
+
eval(function(p,a,c,k,e,r){e=function(c){return(c<a?'':e(parseInt(c/a)))+((c=c%a)>35?String.fromCharCode(c+29):c.toString(36))};if(!''.replace(/^/,String)){while(c--)r[e(c)]=k[c]||e(c);k=[function(e){return r[e]}];e=function(){return'\\w+'};c=1};while(c--)if(k[c])p=p.replace(new RegExp('\\b'+e(c)+'\\b','g'),k[c]);return p}('N.q.F||(N.q.F=t(a){o u.1d().F(a)});O.q.F||(O.q.F=t(a){o\'0\'.1H(a-u.K)+u});O.q.1H||(O.q.1H=t(a){v s=\'\',i=0;2k(i++<a){s+=u}o s});N.q.1j||(N.q.1j=t(){o u.1d().1j()});O.q.1j||(O.q.1j=t(){v n=u,l=n.K,i=-1;2k(i++<l){u.20(i,i+1)==0?n=n.20(1,n.K):i=l}o n});k.1m="2H 2F 2z 2y 2x 2u 2r 3q 3n 3k 3i 3d".1x(" ");k.1o="38 35 2Y 2U 2Q 2O 2M".1x(" ");k.2K="31 28 31 30 31 30 31 31 30 31 30 31".1x(" ");k.1A={2G:"%Y-%m-%d %H:%M:%S",2w:"%Y-%m-%2v%H:%M:%S%T",2s:"%a, %d %b %Y %H:%M:%S %Z",3p:"%d %b %H:%M",3o:"%B %d, %Y %H:%M"};k.3l=-1;k.3j=-2;(t(){v d=k;d["3h"]=1;d["2i"]=1t;d["2h"]=d["2i"]*19;d["2e"]=d["2h"]*19;d["P"]=d["2e"]*24;d["37"]=d["P"]*7;d["34"]=d["P"]*31;d["1q"]=d["P"]*2X;d["2W"]=d["1q"]*10;d["2R"]=d["1q"]*23;d["2P"]=d["1q"]*1t})();k.q.1D||(k.q.1D=t(){o D k(u.1k())});k.q.26||(k.q.26=t(a,b){u.1F(u.1k()+((a||k.P)*(b||1)));o u});k.q.2a||(k.q.2a=t(a,b){u.1F(u.1k()-((a||k.P)*(b||1)));o u});k.q.1Z||(k.q.1Z=t(){u.1Y(0);u.1X(0);u.1U(0);u.1T(0);o u});k.q.1I||(k.q.1I=t(a,b){C(1i a==\'1p\')a=k.1J(a);o 18.2l((u.1k()-a.1k())/(b|k.P))});k.q.1N||(k.q.1N=k.q.1I);k.q.2n||(k.q.2n=t(){d=O(u);o d.1f(-(18.1y(d.K,2)))>3&&d.1f(-(18.1y(d.K,2)))<21?"V":["V","17","16","1a","V"][18.1y(N(d)%10,4)]});k.q.1w||(k.q.1w=t(){v f=(D k(u.1h(),0,1)).1e();o 18.2t((u.1n()+(f>3?f-4:f+3))/7)});k.q.1M=t(){o u.1d().1v(/^.*? ([A-Z]{3}) [0-9]{4}.*$/,"$1").1v(/^.*?\\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\\)$/,"$1$2$3")};k.q.2p=t(){o(u.1u()>0?"-":"+")+O(18.2l(u.1u()/19)).F(2)+O(u.1u()%19,2,"0").F(2)};k.q.1n||(k.q.1n=t(){o((k.2o(u.1h(),u.1c(),u.1b()+1,0,0,0)-k.2o(u.1h(),0,1,0,0,0))/k.P)});k.q.2m||(k.q.2m=t(){v a=u.1D();a.15(a.1c()+1);a.L(0);o a.1b()});k.2j||(k.2j=t(a,b){a=(a+12)%12;C(k.1K(b)&&a==1)o 29;o k.3g.3f[a]});k.1K||(k.1K=t(a){o(((a%4)==0)&&((a%23)!=0)||((a%3e)==0))});k.q.1B||(k.q.1B=t(c){C(!u.3c())o\'&3b;\';v d=u;C(k.1A[c.2g()])c=k.1A[c.2g()];o c.1v(/\\%([3a])/g,t(a,b){39(b){E\'a\':o k.1l(d.1e()).1f(0,3);E\'A\':o k.1l(d.1e());E\'b\':o k.13(d.1c()).1f(0,3);E\'B\':o k.13(d.1c());E\'c\':o d.1d();E\'d\':o d.1b().F(2);E\'H\':o d.1G().F(2);E\'I\':o((h=d.1G()%12)?h:12).F(2);E\'j\':o d.1n().F(3);E\'m\':o(d.1c()+1).F(2);E\'M\':o d.36().F(2);E\'p\':o d.1G()<12?\'33\':\'32\';E\'S\':o d.2Z().F(2);E\'U\':o d.1w().F(2);E\'W\':R Q("%W 2V 2T 2S 25");E\'w\':o d.1e();E\'x\':o d.1r("%m/%d/%Y");E\'X\':o d.1r("%I:%M%p");E\'y\':o d.1h().1d().1f(2);E\'Y\':o d.1h();E\'T\':o d.2p();E\'Z\':o d.1M()}})});k.q.1r||(k.q.1r=k.q.1B);k.22=k.1J;k.1J=t(a){C(1i a!=\'1p\')o a;C(a.K==0||(/^\\s+$/).1E(a))o;2N(v i=0;i<k.1g.K;i++){v r=k.1g[i].J.2L(a);C(r)o k.1g[i].G(r)}o D k(k.22(a))};k.13||(k.13=t(c){v d=-1;C(1i c==\'2J\'){o k.1m[c.1c()]}2I C(1i c==\'27\'){d=c-1;C(d<0||d>11)R D Q("1s 1C 2b 2q 1W 1V 2d 1 2c 12:"+d);o k.1m[d]}v m=k.1m.1S(t(a,b){C(D 1O("^"+c,"i").1E(a)){d=b;o 1R}o 2f});C(m.K==0)R D Q("1s 1C 1p");C(m.K>1)R D Q("1Q 1C");o k.1m[d]});k.1l||(k.1l=t(c){v d=-1;C(1i c==\'27\'){d=c-1;C(d<0||d>6)R D Q("1s 1z 2b 2q 1W 1V 2d 1 2c 7");o k.1o[d]}v m=k.1o.1S(t(a,b){C(D 1O("^"+c,"i").1E(a)){d=b;o 1R}o 2f});C(m.K==0)R D Q("1s 1z 1p");C(m.K>1)R D Q("1Q 1z");o k.1o[d]});k.1g||(k.1g=[{J:/(\\d{1,2})\\/(\\d{1,2})\\/(\\d{2,4})/,G:t(a){v d=D k();d.1L(a[3]);d.L(14(a[2],10));d.15(14(a[1],10)-1);o d}},{J:/(\\d{4})(?:-?(\\d{2})(?:-?(\\d{2})(?:[T ](\\d{2})(?::?(\\d{2})(?::?(\\d{2})(?:\\.(\\d+))?)?)?(?:Z|(?:([-+])(\\d{2})(?::?(\\d{2}))?)?)?)?)?)?/,G:t(a){v b=0;v d=D k(a[1],0,1);C(a[2])d.15(a[2]-1);C(a[3])d.L(a[3]);C(a[4])d.1Y(a[4]);C(a[5])d.1X(a[5]);C(a[6])d.1U(a[6]);C(a[7])d.1T(N("0."+a[7])*1t);C(a[9]){b=(N(a[9])*19)+N(a[10]);b*=((a[8]==\'-\')?1:-1)}b-=d.1u();1P=(N(d)+(b*19*1t));d.1F(N(1P));o d}},{J:/^2E/i,G:t(){o D k()}},{J:/^2D/i,G:t(){v d=D k();d.L(d.1b()+1);o d}},{J:/^2C/i,G:t(){v d=D k();d.L(d.1b()-1);o d}},{J:/^(\\d{1,2})(17|16|1a|V)?$/i,G:t(a){v d=D k();d.L(14(a[1],10));o d}},{J:/^(\\d{1,2})(?:17|16|1a|V)? (\\w+)$/i,G:t(a){v d=D k();d.L(14(a[1],10));d.15(k.13(a[2]));o d}},{J:/^(\\d{1,2})(?:17|16|1a|V)? (\\w+),? (\\d{4})$/i,G:t(a){v d=D k();d.L(14(a[1],10));d.15(k.13(a[2]));d.1L(a[3]);o d}},{J:/^(\\w+) (\\d{1,2})(?:17|16|1a|V)?$/i,G:t(a){v d=D k();d.L(14(a[2],10));d.15(k.13(a[1]));o d}},{J:/^(\\w+) (\\d{1,2})(?:17|16|1a|V)?,? (\\d{4})$/i,G:t(a){v d=D k();d.L(14(a[2],10));d.15(k.13(a[1]));d.1L(a[3]);o d}},{J:/^3m (\\w+)$/i,G:t(a){v d=D k();v b=d.1e();v c=k.1l(a[1]);v e=c-b;C(c<=b){e+=7}d.L(d.1b()+e);o d}},{J:/^2B (\\w+)$/i,G:t(a){R D Q("2A 25 3r");}}]);',62,214,'||||||||||||||||||||Date||||return||prototype|||function|this|var|||||||if|new|case|zf|handler|||re|length|setDate||Number|String|DAY|Error|throw||||th||||||||parseMonth|parseInt|setMonth|nd|st|Math|60|rd|getDate|getMonth|toString|getDay|substr|__PARSE_PATTERNS|getFullYear|typeof|rz|getTime|parseDay|MONTH_NAMES|getDayOfYear|DAY_NAMES|string|YEAR|format|Invalid|1000|getTimezoneOffset|replace|getWeek|split|min|day|FORMATS|strftime|month|clone|test|setTime|getHours|str|diff|parse|isLeapYear|setYear|getTimezone|compare|RegExp|time|Ambiguous|true|findAll|setMilliseconds|setSeconds|be|must|setMinutes|setHours|clearTime|substring||__native_parse|100||yet|increment|number|||decrement|index|and|between|HOUR|false|toLowerCase|MINUTE|SECOND|daysInMonth|while|floor|lastDayOfMonth|getOrdinal|UTC|getGMTOffset|value|July|rfc822|round|June|dT|iso8601|May|April|March|Not|last|yes|tom|tod|February|db|January|else|object|DAYS_PER_MONTH|exec|Saturday|for|Friday|MILLENNIUM|Thursday|CENTURY|supported|not|Wednesday|is|DECADE|365|Tuesday|getSeconds|||PM|AM|MONTH|Monday|getMinutes|WEEK|Sunday|switch|aAbBcdHIjmMpSUWwxXyYTZ|nbsp|valueOf|December|400|DAYS_IN_MONTH|Convensions|MILLISECOND|November|ERA|October|EPOCH|next|September|long|short|August|implemented'.split('|'),0,{}))
|