jipe 1.0.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/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,{}))
|