sofav 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (71) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +35 -0
  4. data/Rakefile +34 -0
  5. data/app/builders/admin/form_builder.rb +250 -0
  6. data/app/controllers/application_controller.rb +23 -0
  7. data/app/decorators/base_decorator.rb +172 -0
  8. data/app/helpers/admin/core_helper.rb +237 -0
  9. data/app/helpers/admin/show_view_helper.rb +69 -0
  10. data/app/helpers/admin/tags_helper.rb +104 -0
  11. data/app/services/active_record/search_service.rb +121 -0
  12. data/app/views/admin/application/_breadcrumb_nav.html.erb +10 -0
  13. data/app/views/admin/application/_collection.html.erb +79 -0
  14. data/app/views/admin/application/_save_js.js.erb +20 -0
  15. data/app/views/admin/application/_sidebar.html.erb +62 -0
  16. data/app/views/admin/application/_topbar.html.erb +32 -0
  17. data/app/views/admin/application/actions/charge.html.erb +3 -0
  18. data/app/views/admin/application/actions/destroy.html.erb +5 -0
  19. data/app/views/admin/application/actions/edit.html.erb +1 -0
  20. data/app/views/admin/application/actions/edit_sights.html.erb +1 -0
  21. data/app/views/admin/application/actions/feedback.html.erb +4 -0
  22. data/app/views/admin/application/actions/index_charge.html.erb +1 -0
  23. data/app/views/admin/application/actions/index_destroy.html.erb +1 -0
  24. data/app/views/admin/application/actions/index_edit.html.erb +1 -0
  25. data/app/views/admin/application/actions/index_edit_sights.html.erb +1 -0
  26. data/app/views/admin/application/actions/index_show.html.erb +1 -0
  27. data/app/views/admin/application/actions/new.html.erb +1 -0
  28. data/app/views/admin/application/create.js.erb +1 -0
  29. data/app/views/admin/application/destroy.js.erb +6 -0
  30. data/app/views/admin/application/edit.html.erb +18 -0
  31. data/app/views/admin/application/index.html.erb +23 -0
  32. data/app/views/admin/application/new.html.erb +18 -0
  33. data/app/views/admin/application/partials/_map_pos_picker.html.erb +101 -0
  34. data/app/views/admin/application/partials/_sort_script.html.erb +32 -0
  35. data/app/views/admin/application/show.html.erb +16 -0
  36. data/app/views/admin/application/update.js.erb +1 -0
  37. data/app/views/admin/dashboard/index.html.erb +8 -0
  38. data/app/views/layouts/admin.html.erb +30 -0
  39. data/config/locales/devise.en.yml +64 -0
  40. data/config/locales/devise.zh-CN.yml +120 -0
  41. data/config/locales/doorkeeper.en.yml +124 -0
  42. data/config/locales/doorkeeper.zh-CN.yml +132 -0
  43. data/config/locales/en.yml +23 -0
  44. data/config/locales/enumerize/defaults.zh-CN.yml +7 -0
  45. data/config/locales/kaminari.zh-CN.yml +17 -0
  46. data/config/locales/rails.zh-CN.yml +204 -0
  47. data/config/locales/views/actions.zh-CN.yml +21 -0
  48. data/config/locales/views/attributes.zh-CN.yml +15 -0
  49. data/config/locales/views/breadcrumb.zh-CN.yml +5 -0
  50. data/config/locales/views/common.zh-CN.yml +7 -0
  51. data/config/locales/views/enums.zh-CN.yml +13 -0
  52. data/config/locales/views/profiles.zh-CN.yml +3 -0
  53. data/lib/generators/sofav/USAGE +0 -0
  54. data/lib/generators/sofav/sofav_generator.rb +50 -0
  55. data/lib/generators/sofav/templates/activerecord.zh-CN.yml +4 -0
  56. data/lib/generators/sofav/templates/attribute_types.zh-CN.yml +2 -0
  57. data/lib/sofav.rb +6 -0
  58. data/lib/sofav/decorator.rb +47 -0
  59. data/lib/sofav/local.rb +52 -0
  60. data/lib/sofav/version.rb +3 -0
  61. data/lib/tasks/sofa_tasks.rake +4 -0
  62. data/vendor/assets/javascripts/admin/here.js +0 -0
  63. data/vendor/assets/javascripts/bootbox.js +1020 -0
  64. data/vendor/assets/javascripts/bootstrap.js +2377 -0
  65. data/vendor/assets/javascripts/jquery-ui.js +5169 -0
  66. data/vendor/assets/javascripts/select2.js +5725 -0
  67. data/vendor/assets/stylesheets/bootstrap.css +6800 -0
  68. data/vendor/assets/stylesheets/bootstrap.css.map +1 -0
  69. data/vendor/assets/stylesheets/jquery-ui.css +453 -0
  70. data/vendor/assets/stylesheets/select2.css +484 -0
  71. metadata +211 -0
@@ -0,0 +1,52 @@
1
+ require "yaml"
2
+
3
+ module Sofav
4
+ module Local
5
+ def create_config_record(file_name, record)
6
+ record_config = YAML.load_file(File.join(__dir__, '../generators/sofav/templates/activerecord.zh-CN.yml'))
7
+
8
+ record_config["zh-CN"]["activerecord"]["models"] = {"#{file_name}" => nil}
9
+ record_config["zh-CN"]["activerecord"]["attributes"] = {"#{file_name}" => {"#{record.first}" => nil}}
10
+ record.shift
11
+
12
+ record.each do |a|
13
+ record_config["zh-CN"]["activerecord"]["attributes"]["#{file_name}"][a] = nil
14
+ end
15
+
16
+ create_file "config/locales/activerecord/#{file_name}.zh-CN.yml", <<-FILE
17
+ #{record_config.to_yaml}
18
+ FILE
19
+ end
20
+
21
+ def create_config_attribute(file_name, types)
22
+ attributes_config = YAML.load_file(File.join(__dir__, '../generators/sofav/templates/attribute_types.zh-CN.yml'))
23
+ attributes_config["zh-CN"]["attribute_types"] = {"#{file_name}" => {"#{types.first.name}" => nil}}
24
+
25
+ types.each do |t|
26
+ attributes_config["zh-CN"]["attribute_types"]["#{file_name}"]["#{t.name}"] = {"type" => nil}
27
+ attributes_config["zh-CN"]["attribute_types"]["#{file_name}"]["#{t.name}"]["required"] = true
28
+ attributes_config["zh-CN"]["attribute_types"]["#{file_name}"]["#{t.name}"]["type"] = type_field(t.type)
29
+ end
30
+
31
+ create_file "config/locales/attribute_types/#{file_name}.zh-CN.yml", <<-FILE
32
+ #{attributes_config.to_yaml}
33
+ FILE
34
+ end
35
+
36
+ private
37
+ def type_field(type)
38
+ case type
39
+ when 'integer', 'float'
40
+ "number_field"
41
+ when 'datetime'
42
+ "datetime_select"
43
+ when 'boolean'
44
+ "collection_check_box"
45
+ when 'date'
46
+ "date_select"
47
+ else
48
+ "text_field"
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,3 @@
1
+ module Sofav
2
+ VERSION = '0.2.0'
3
+ end
@@ -0,0 +1,4 @@
1
+ # desc "Explaining what the task does"
2
+ # task :sofa do
3
+ # # Task goes here
4
+ # end
File without changes
@@ -0,0 +1,1020 @@
1
+ /**
2
+ * bootbox.js [master branch]
3
+ *
4
+ * http://bootboxjs.com/license.txt
5
+ */
6
+
7
+ // @see https://github.com/makeusabrew/bootbox/issues/180
8
+ // @see https://github.com/makeusabrew/bootbox/issues/186
9
+ (function (root, factory) {
10
+
11
+ "use strict";
12
+ if (typeof define === "function" && define.amd) {
13
+ // AMD. Register as an anonymous module.
14
+ define(["jquery"], factory);
15
+ } else if (typeof exports === "object") {
16
+ // Node. Does not work with strict CommonJS, but
17
+ // only CommonJS-like environments that support module.exports,
18
+ // like Node.
19
+
20
+ if (typeof $ === "undefined") {
21
+ module.exports = factory(require("jquery"));
22
+ } else {
23
+ module.exports = factory($); // jshint ignore:line
24
+ }
25
+
26
+ } else {
27
+ // Browser globals (root is window)
28
+ root.bootbox = factory(root.jQuery);
29
+ }
30
+
31
+ }(this, function init($, undefined) {
32
+
33
+ "use strict";
34
+
35
+ // the base DOM structure needed to create a modal
36
+ var templates = {
37
+ dialog:
38
+ "<div class='bootbox modal' tabindex='-1' role='dialog' aria-hidden='true'>" +
39
+ "<div class='modal-dialog'>" +
40
+ "<div class='modal-content'>" +
41
+ "<div class='modal-body'><div class='bootbox-body'></div></div>" +
42
+ "</div>" +
43
+ "</div>" +
44
+ "</div>",
45
+ header:
46
+ "<div class='modal-header'>" +
47
+ "<h4 class='modal-title'></h4>" +
48
+ "</div>",
49
+ footer:
50
+ "<div class='modal-footer'></div>",
51
+ closeButton:
52
+ "<button type='button' class='bootbox-close-button close' aria-hidden='true'>&times;</button>",
53
+ form:
54
+ "<form class='bootbox-form'></form>",
55
+ inputs: {
56
+ text:
57
+ "<input class='bootbox-input bootbox-input-text form-control' autocomplete=off type=text />",
58
+ textarea:
59
+ "<textarea class='bootbox-input bootbox-input-textarea form-control'></textarea>",
60
+ email:
61
+ "<input class='bootbox-input bootbox-input-email form-control' autocomplete='off' type='email' />",
62
+ select:
63
+ "<select class='bootbox-input bootbox-input-select form-control'></select>",
64
+ checkbox:
65
+ "<div class='checkbox'><label><input class='bootbox-input bootbox-input-checkbox' type='checkbox' /></label></div>",
66
+ date:
67
+ "<input class='bootbox-input bootbox-input-date form-control' autocomplete=off type='date' />",
68
+ time:
69
+ "<input class='bootbox-input bootbox-input-time form-control' autocomplete=off type='time' />",
70
+ number:
71
+ "<input class='bootbox-input bootbox-input-number form-control' autocomplete=off type='number' />",
72
+ password:
73
+ "<input class='bootbox-input bootbox-input-password form-control' autocomplete='off' type='password' />"
74
+ }
75
+ };
76
+
77
+ var defaults = {
78
+ // default language
79
+ locale: "en",
80
+ // show backdrop or not. Default to static so user has to interact with dialog
81
+ backdrop: "static",
82
+ // animate the modal in/out
83
+ animate: true,
84
+ // additional class string applied to the top level dialog
85
+ className: null,
86
+ // whether or not to include a close button
87
+ closeButton: true,
88
+ // show the dialog immediately by default
89
+ show: true,
90
+ // dialog container
91
+ container: "body"
92
+ };
93
+
94
+ // our public object; augmented after our private API
95
+ var exports = {};
96
+
97
+ /**
98
+ * @private
99
+ */
100
+ function _t(key) {
101
+ var locale = locales[defaults.locale];
102
+ return locale ? locale[key] : locales.en[key];
103
+ }
104
+
105
+ function processCallback(e, dialog, callback) {
106
+ e.stopPropagation();
107
+ e.preventDefault();
108
+
109
+ // by default we assume a callback will get rid of the dialog,
110
+ // although it is given the opportunity to override this
111
+
112
+ // so, if the callback can be invoked and it *explicitly returns false*
113
+ // then we'll set a flag to keep the dialog active...
114
+ var preserveDialog = $.isFunction(callback) && callback.call(dialog, e) === false;
115
+
116
+ // ... otherwise we'll bin it
117
+ if (!preserveDialog) {
118
+ dialog.modal("hide");
119
+ }
120
+ }
121
+
122
+ // Bootstrap 3.x supports back to IE8 on Windows (http://getbootstrap.com/getting-started/#support)
123
+ // so unfortunately we can't just get away with assuming Object.keys exists
124
+ function getKeyLength(obj) {
125
+ if (Object.keys) {
126
+ return Object.keys(obj).length;
127
+ }
128
+
129
+ var k, t = 0;
130
+ for (k in obj) {
131
+ t ++;
132
+ }
133
+ return t;
134
+ }
135
+
136
+ // tiny wrapper function around jQuery.each; just adds index as the third parameter
137
+ function each(collection, iterator) {
138
+ var index = 0;
139
+ $.each(collection, function(key, value) {
140
+ iterator(key, value, index++);
141
+ });
142
+ }
143
+
144
+ /**
145
+ * Filter and tidy up any user supplied parameters to this dialog.
146
+ * Also looks for any shorthands used and ensures that the options
147
+ * which are returned are all normalized properly
148
+ */
149
+ function sanitize(options) {
150
+ var buttons;
151
+ var total;
152
+
153
+ if (typeof options !== "object") {
154
+ throw new Error("Please supply an object of options");
155
+ }
156
+
157
+ if (!options.message) {
158
+ throw new Error("Please specify a message");
159
+ }
160
+
161
+ // make sure any supplied options take precedence over defaults
162
+ options = $.extend({}, defaults, options);
163
+
164
+ // no buttons is still a valid dialog but it's cleaner toalways have
165
+ // a buttons object to iterate over, even if it's empty
166
+ if (!options.buttons) {
167
+ options.buttons = {};
168
+ }
169
+
170
+ buttons = options.buttons;
171
+
172
+ total = getKeyLength(buttons);
173
+
174
+ each(buttons, function(key, button, index) {
175
+ var isLast = index === total-1;
176
+
177
+ if ($.isFunction(button)) {
178
+ // short form, assume value is our callback. Since button
179
+ // isn't an object it isn't a reference either so re-assign it
180
+ button = buttons[key] = {
181
+ callback: button
182
+ };
183
+ }
184
+
185
+ // before any further checks make sure by now button is the correct type
186
+ if ($.type(button) !== "object") {
187
+ throw new Error("button with key " + key + " must be an object");
188
+ }
189
+
190
+ if (!button.label) {
191
+ // the lack of an explicit label means we'll assume the key is good enough
192
+ button.label = key;
193
+ }
194
+
195
+ if (!button.className) {
196
+ if (total <= 2 && isLast) {
197
+ // always add a primary to the main option in a one or two-button dialog
198
+ button.className = "btn-primary";
199
+ } else {
200
+ button.className = "btn-default";
201
+ }
202
+ }
203
+ });
204
+
205
+ return options;
206
+ }
207
+
208
+ /**
209
+ * map a flexible set of arguments into a single returned object
210
+ * if args.length is already one just return it, otherwise
211
+ * use the properties argument to map the unnamed args to
212
+ * object properties
213
+ * so in the latter case:
214
+ * mapArguments(["foo", $.noop], ["message", "callback"])
215
+ * -> { message: "foo", callback: $.noop }
216
+ */
217
+ function mapArguments(args, properties) {
218
+ var argn = args.length;
219
+ var options = {};
220
+
221
+ if (argn < 1 || argn > 2) {
222
+ throw new Error("Invalid argument length");
223
+ }
224
+
225
+ if (argn === 2 || typeof args[0] === "string") {
226
+ options[properties[0]] = args[0];
227
+ options[properties[1]] = args[1];
228
+ } else {
229
+ options = args[0];
230
+ }
231
+
232
+ return options;
233
+ }
234
+
235
+ /**
236
+ * merge a set of default dialog options with user supplied arguments
237
+ */
238
+ function mergeArguments(defaults, args, properties) {
239
+ return $.extend(
240
+ // deep merge
241
+ true,
242
+ // ensure the target is an empty, unreferenced object
243
+ {},
244
+ // the base options object for this type of dialog (often just buttons)
245
+ defaults,
246
+ // args could be an object or array; if it's an array properties will
247
+ // map it to a proper options object
248
+ mapArguments(
249
+ args,
250
+ properties
251
+ )
252
+ );
253
+ }
254
+
255
+ /**
256
+ * this entry-level method makes heavy use of composition to take a simple
257
+ * range of inputs and return valid options suitable for passing to bootbox.dialog
258
+ */
259
+ function mergeDialogOptions(className, labels, properties, args) {
260
+ // build up a base set of dialog properties
261
+ var baseOptions = {
262
+ className: "bootbox-" + className,
263
+ buttons: createLabels.apply(null, labels)
264
+ };
265
+
266
+ // ensure the buttons properties generated, *after* merging
267
+ // with user args are still valid against the supplied labels
268
+ return validateButtons(
269
+ // merge the generated base properties with user supplied arguments
270
+ mergeArguments(
271
+ baseOptions,
272
+ args,
273
+ // if args.length > 1, properties specify how each arg maps to an object key
274
+ properties
275
+ ),
276
+ labels
277
+ );
278
+ }
279
+
280
+ /**
281
+ * from a given list of arguments return a suitable object of button labels
282
+ * all this does is normalise the given labels and translate them where possible
283
+ * e.g. "ok", "confirm" -> { ok: "OK", cancel: "Annuleren" }
284
+ */
285
+ function createLabels() {
286
+ var buttons = {};
287
+
288
+ for (var i = 0, j = arguments.length; i < j; i++) {
289
+ var argument = arguments[i];
290
+ var key = argument.toLowerCase();
291
+ var value = argument.toUpperCase();
292
+
293
+ buttons[key] = {
294
+ label: _t(value)
295
+ };
296
+ }
297
+
298
+ return buttons;
299
+ }
300
+
301
+ function validateButtons(options, buttons) {
302
+ var allowedButtons = {};
303
+ each(buttons, function(key, value) {
304
+ allowedButtons[value] = true;
305
+ });
306
+
307
+ each(options.buttons, function(key) {
308
+ if (allowedButtons[key] === undefined) {
309
+ throw new Error("button key " + key + " is not allowed (options are " + buttons.join("\n") + ")");
310
+ }
311
+ });
312
+
313
+ return options;
314
+ }
315
+
316
+ exports.alert = function() {
317
+ var options;
318
+
319
+ options = mergeDialogOptions("alert", ["ok"], ["message", "callback"], arguments);
320
+
321
+ // @TODO: can this move inside exports.dialog when we're iterating over each
322
+ // button and checking its button.callback value instead?
323
+ if (options.callback && !$.isFunction(options.callback)) {
324
+ throw new Error("alert requires callback property to be a function when provided");
325
+ }
326
+
327
+ /**
328
+ * override the ok and escape callback to make sure they just invoke
329
+ * the single user-supplied one (if provided)
330
+ */
331
+ options.buttons.ok.callback = options.onEscape = function() {
332
+ if ($.isFunction(options.callback)) {
333
+ return options.callback.call(this);
334
+ }
335
+ return true;
336
+ };
337
+
338
+ return exports.dialog(options);
339
+ };
340
+
341
+ exports.confirm = function() {
342
+ var options;
343
+
344
+ options = mergeDialogOptions("confirm", ["cancel", "confirm"], ["message", "callback"], arguments);
345
+
346
+ // confirm specific validation; they don't make sense without a callback so make
347
+ // sure it's present
348
+ if (!$.isFunction(options.callback)) {
349
+ throw new Error("confirm requires a callback");
350
+ }
351
+
352
+ /**
353
+ * overrides; undo anything the user tried to set they shouldn't have
354
+ */
355
+ options.buttons.cancel.callback = options.onEscape = function() {
356
+ return options.callback.call(this, false);
357
+ };
358
+
359
+ options.buttons.confirm.callback = function() {
360
+ return options.callback.call(this, true);
361
+ };
362
+
363
+ return exports.dialog(options);
364
+ };
365
+
366
+ exports.prompt = function() {
367
+ var options;
368
+ var defaults;
369
+ var dialog;
370
+ var form;
371
+ var input;
372
+ var shouldShow;
373
+ var inputOptions;
374
+
375
+ // we have to create our form first otherwise
376
+ // its value is undefined when gearing up our options
377
+ // @TODO this could be solved by allowing message to
378
+ // be a function instead...
379
+ form = $(templates.form);
380
+
381
+ // prompt defaults are more complex than others in that
382
+ // users can override more defaults
383
+ // @TODO I don't like that prompt has to do a lot of heavy
384
+ // lifting which mergeDialogOptions can *almost* support already
385
+ // just because of 'value' and 'inputType' - can we refactor?
386
+ defaults = {
387
+ className: "bootbox-prompt",
388
+ buttons: createLabels("cancel", "confirm"),
389
+ value: "",
390
+ inputType: "text"
391
+ };
392
+
393
+ options = validateButtons(
394
+ mergeArguments(defaults, arguments, ["title", "callback"]),
395
+ ["cancel", "confirm"]
396
+ );
397
+
398
+ // capture the user's show value; we always set this to false before
399
+ // spawning the dialog to give us a chance to attach some handlers to
400
+ // it, but we need to make sure we respect a preference not to show it
401
+ shouldShow = (options.show === undefined) ? true : options.show;
402
+
403
+ /**
404
+ * overrides; undo anything the user tried to set they shouldn't have
405
+ */
406
+ options.message = form;
407
+
408
+ options.buttons.cancel.callback = options.onEscape = function() {
409
+ return options.callback.call(this, null);
410
+ };
411
+
412
+ options.buttons.confirm.callback = function() {
413
+ var value;
414
+
415
+ if (options.inputType === "checkbox") {
416
+ value = input.find("input:checked").map(function() {
417
+ return $(this).val();
418
+ }).get();
419
+ } else {
420
+ value = input.val();
421
+ }
422
+
423
+ return options.callback.call(this, value);
424
+ };
425
+
426
+ options.show = false;
427
+
428
+ // prompt specific validation
429
+ if (!options.title) {
430
+ throw new Error("prompt requires a title");
431
+ }
432
+
433
+ if (!$.isFunction(options.callback)) {
434
+ throw new Error("prompt requires a callback");
435
+ }
436
+
437
+ if (!templates.inputs[options.inputType]) {
438
+ throw new Error("invalid prompt type");
439
+ }
440
+
441
+ // create the input based on the supplied type
442
+ input = $(templates.inputs[options.inputType]);
443
+
444
+ switch (options.inputType) {
445
+ case "text":
446
+ case "textarea":
447
+ case "email":
448
+ case "date":
449
+ case "time":
450
+ case "number":
451
+ case "password":
452
+ input.val(options.value);
453
+ break;
454
+
455
+ case "select":
456
+ var groups = {};
457
+ inputOptions = options.inputOptions || [];
458
+
459
+ if (!$.isArray(inputOptions)) {
460
+ throw new Error("Please pass an array of input options");
461
+ }
462
+
463
+ if (!inputOptions.length) {
464
+ throw new Error("prompt with select requires options");
465
+ }
466
+
467
+ each(inputOptions, function(_, option) {
468
+
469
+ // assume the element to attach to is the input...
470
+ var elem = input;
471
+
472
+ if (option.value === undefined || option.text === undefined) {
473
+ throw new Error("each option needs a `value` and a `text` property");
474
+ }
475
+
476
+ // ... but override that element if this option sits in a group
477
+
478
+ if (option.group) {
479
+ // initialise group if necessary
480
+ if (!groups[option.group]) {
481
+ groups[option.group] = $("<optgroup/>").attr("label", option.group);
482
+ }
483
+
484
+ elem = groups[option.group];
485
+ }
486
+
487
+ elem.append("<option value='" + option.value + "'>" + option.text + "</option>");
488
+ });
489
+
490
+ each(groups, function(_, group) {
491
+ input.append(group);
492
+ });
493
+
494
+ // safe to set a select's value as per a normal input
495
+ input.val(options.value);
496
+ break;
497
+
498
+ case "checkbox":
499
+ var values = $.isArray(options.value) ? options.value : [options.value];
500
+ inputOptions = options.inputOptions || [];
501
+
502
+ if (!inputOptions.length) {
503
+ throw new Error("prompt with checkbox requires options");
504
+ }
505
+
506
+ if (!inputOptions[0].value || !inputOptions[0].text) {
507
+ throw new Error("each option needs a `value` and a `text` property");
508
+ }
509
+
510
+ // checkboxes have to nest within a containing element, so
511
+ // they break the rules a bit and we end up re-assigning
512
+ // our 'input' element to this container instead
513
+ input = $("<div/>");
514
+
515
+ each(inputOptions, function(_, option) {
516
+ var checkbox = $(templates.inputs[options.inputType]);
517
+
518
+ checkbox.find("input").attr("value", option.value);
519
+ checkbox.find("label").append(option.text);
520
+
521
+ // we've ensured values is an array so we can always iterate over it
522
+ each(values, function(_, value) {
523
+ if (value === option.value) {
524
+ checkbox.find("input").prop("checked", true);
525
+ }
526
+ });
527
+
528
+ input.append(checkbox);
529
+ });
530
+ break;
531
+ }
532
+
533
+ // @TODO provide an attributes option instead
534
+ // and simply map that as keys: vals
535
+ if (options.placeholder) {
536
+ input.attr("placeholder", options.placeholder);
537
+ }
538
+
539
+ if (options.pattern) {
540
+ input.attr("pattern", options.pattern);
541
+ }
542
+
543
+ if (options.maxlength) {
544
+ input.attr("maxlength", options.maxlength);
545
+ }
546
+
547
+ // now place it in our form
548
+ form.append(input);
549
+
550
+ form.on("submit", function(e) {
551
+ e.preventDefault();
552
+ // Fix for SammyJS (or similar JS routing library) hijacking the form post.
553
+ e.stopPropagation();
554
+ // @TODO can we actually click *the* button object instead?
555
+ // e.g. buttons.confirm.click() or similar
556
+ dialog.find(".btn-primary").click();
557
+ });
558
+
559
+ dialog = exports.dialog(options);
560
+
561
+ // clear the existing handler focusing the submit button...
562
+ dialog.off("shown.bs.modal");
563
+
564
+ // ...and replace it with one focusing our input, if possible
565
+ dialog.on("shown.bs.modal", function() {
566
+ // need the closure here since input isn't
567
+ // an object otherwise
568
+ input.focus();
569
+ });
570
+
571
+ if (shouldShow === true) {
572
+ dialog.modal("show");
573
+ }
574
+
575
+ return dialog;
576
+ };
577
+
578
+ exports.dialog = function(options) {
579
+ options = sanitize(options);
580
+
581
+ var dialog = $(templates.dialog);
582
+ var innerDialog = dialog.find(".modal-dialog");
583
+ var body = dialog.find(".modal-body");
584
+ var buttons = options.buttons;
585
+ var buttonStr = "";
586
+ var callbacks = {
587
+ onEscape: options.onEscape
588
+ };
589
+
590
+ if ($.fn.modal === undefined) {
591
+ throw new Error(
592
+ "$.fn.modal is not defined; please double check you have included " +
593
+ "the Bootstrap JavaScript library. See http://getbootstrap.com/javascript/ " +
594
+ "for more details."
595
+ );
596
+ }
597
+
598
+ each(buttons, function(key, button) {
599
+
600
+ // @TODO I don't like this string appending to itself; bit dirty. Needs reworking
601
+ // can we just build up button elements instead? slower but neater. Then button
602
+ // can just become a template too
603
+ buttonStr += "<button data-bb-handler='" + key + "' type='button' class='btn " + button.className + "'>" + button.label + "</button>";
604
+ callbacks[key] = button.callback;
605
+ });
606
+
607
+ body.find(".bootbox-body").html(options.message);
608
+
609
+ if (options.animate === true) {
610
+ dialog.addClass("fade");
611
+ }
612
+
613
+ if (options.className) {
614
+ dialog.addClass(options.className);
615
+ }
616
+
617
+ if (options.size === "large") {
618
+ innerDialog.addClass("modal-lg");
619
+ } else if (options.size === "small") {
620
+ innerDialog.addClass("modal-sm");
621
+ }
622
+
623
+ if (options.title) {
624
+ body.before(templates.header);
625
+ }
626
+
627
+ if (options.closeButton) {
628
+ var closeButton = $(templates.closeButton);
629
+
630
+ if (options.title) {
631
+ dialog.find(".modal-header").prepend(closeButton);
632
+ } else {
633
+ closeButton.css("margin-top", "-2px").prependTo(body);
634
+ }
635
+ }
636
+
637
+ if (options.title) {
638
+ dialog.find(".modal-title").html(options.title);
639
+ }
640
+
641
+ if (buttonStr.length) {
642
+ body.after(templates.footer);
643
+ dialog.find(".modal-footer").html(buttonStr);
644
+ }
645
+
646
+
647
+ /**
648
+ * Bootstrap event listeners; these handle extra
649
+ * setup & teardown required after the underlying
650
+ * modal has performed certain actions
651
+ */
652
+
653
+ // make sure we unbind any listeners once the dialog has definitively been dismissed
654
+ dialog.one("hide.bs.modal", function() {
655
+ dialog.off("escape.close.bb");
656
+ dialog.off("click");
657
+ });
658
+
659
+ dialog.one("hidden.bs.modal", function(e) {
660
+ // ensure we don't accidentally intercept hidden events triggered
661
+ // by children of the current dialog. We shouldn't anymore now BS
662
+ // namespaces its events; but still worth doing
663
+ if (e.target === this) {
664
+ dialog.remove();
665
+ }
666
+ });
667
+
668
+ /*
669
+ dialog.on("show.bs.modal", function() {
670
+ // sadly this doesn't work; show is called *just* before
671
+ // the backdrop is added so we'd need a setTimeout hack or
672
+ // otherwise... leaving in as would be nice
673
+ if (options.backdrop) {
674
+ dialog.next(".modal-backdrop").addClass("bootbox-backdrop");
675
+ }
676
+ });
677
+ */
678
+
679
+ dialog.one("shown.bs.modal", function() {
680
+ dialog.find(".btn-primary:first").focus();
681
+ });
682
+
683
+ /**
684
+ * Bootbox event listeners; used to decouple some
685
+ * behaviours from their respective triggers
686
+ */
687
+
688
+ if (options.backdrop !== "static") {
689
+ // A boolean true/false according to the Bootstrap docs
690
+ // should show a dialog the user can dismiss by clicking on
691
+ // the background.
692
+ // We always only ever pass static/false to the actual
693
+ // $.modal function because with `true` we can't trap
694
+ // this event (the .modal-backdrop swallows it)
695
+ // However, we still want to sort of respect true
696
+ // and invoke the escape mechanism instead
697
+ dialog.on("click.dismiss.bs.modal", function(e) {
698
+ // @NOTE: the target varies in >= 3.3.x releases since the modal backdrop
699
+ // moved *inside* the outer dialog rather than *alongside* it
700
+ if (dialog.children(".modal-backdrop").length) {
701
+ e.currentTarget = dialog.children(".modal-backdrop").get(0);
702
+ }
703
+
704
+ if (e.target !== e.currentTarget) {
705
+ return;
706
+ }
707
+
708
+ dialog.trigger("escape.close.bb");
709
+ });
710
+ }
711
+
712
+ dialog.on("escape.close.bb", function(e) {
713
+ // the if statement looks redundant but it isn't; without it
714
+ // if we *didn't* have an onEscape handler then processCallback
715
+ // would automatically dismiss the dialog
716
+ if (callbacks.onEscape) {
717
+ processCallback(e, dialog, callbacks.onEscape);
718
+ }
719
+ });
720
+
721
+ /**
722
+ * Standard jQuery event listeners; used to handle user
723
+ * interaction with our dialog
724
+ */
725
+
726
+ dialog.on("click", ".modal-footer button", function(e) {
727
+ var callbackKey = $(this).data("bb-handler");
728
+
729
+ processCallback(e, dialog, callbacks[callbackKey]);
730
+ });
731
+
732
+ dialog.on("click", ".bootbox-close-button", function(e) {
733
+ // onEscape might be falsy but that's fine; the fact is
734
+ // if the user has managed to click the close button we
735
+ // have to close the dialog, callback or not
736
+ processCallback(e, dialog, callbacks.onEscape);
737
+ });
738
+
739
+ dialog.on("keyup", function(e) {
740
+ if (e.which === 27) {
741
+ dialog.trigger("escape.close.bb");
742
+ }
743
+ });
744
+
745
+ // the remainder of this method simply deals with adding our
746
+ // dialogent to the DOM, augmenting it with Bootstrap's modal
747
+ // functionality and then giving the resulting object back
748
+ // to our caller
749
+
750
+ $(options.container).append(dialog);
751
+
752
+ dialog.modal({
753
+ backdrop: options.backdrop ? "static": false,
754
+ keyboard: false,
755
+ show: false
756
+ });
757
+
758
+ if (options.show) {
759
+ dialog.modal("show");
760
+ }
761
+
762
+ // @TODO should we return the raw element here or should
763
+ // we wrap it in an object on which we can expose some neater
764
+ // methods, e.g. var d = bootbox.alert(); d.hide(); instead
765
+ // of d.modal("hide");
766
+
767
+ /*
768
+ function BBDialog(elem) {
769
+ this.elem = elem;
770
+ }
771
+
772
+ BBDialog.prototype = {
773
+ hide: function() {
774
+ return this.elem.modal("hide");
775
+ },
776
+ show: function() {
777
+ return this.elem.modal("show");
778
+ }
779
+ };
780
+ */
781
+
782
+ return dialog;
783
+
784
+ };
785
+
786
+ exports.setDefaults = function() {
787
+ var values = {};
788
+
789
+ if (arguments.length === 2) {
790
+ // allow passing of single key/value...
791
+ values[arguments[0]] = arguments[1];
792
+ } else {
793
+ // ... and as an object too
794
+ values = arguments[0];
795
+ }
796
+
797
+ $.extend(defaults, values);
798
+ };
799
+
800
+ exports.hideAll = function() {
801
+ $(".bootbox").modal("hide");
802
+
803
+ return exports;
804
+ };
805
+
806
+
807
+ /**
808
+ * standard locales. Please add more according to ISO 639-1 standard. Multiple language variants are
809
+ * unlikely to be required. If this gets too large it can be split out into separate JS files.
810
+ */
811
+ var locales = {
812
+ ar : {
813
+ OK : "موافق",
814
+ CANCEL : "الغاء",
815
+ CONFIRM : "تأكيد"
816
+ },
817
+ bg_BG : {
818
+ OK : "Ок",
819
+ CANCEL : "Отказ",
820
+ CONFIRM : "Потвърждавам"
821
+ },
822
+ br : {
823
+ OK : "OK",
824
+ CANCEL : "Cancelar",
825
+ CONFIRM : "Sim"
826
+ },
827
+ cs : {
828
+ OK : "OK",
829
+ CANCEL : "Zrušit",
830
+ CONFIRM : "Potvrdit"
831
+ },
832
+ da : {
833
+ OK : "OK",
834
+ CANCEL : "Annuller",
835
+ CONFIRM : "Accepter"
836
+ },
837
+ de : {
838
+ OK : "OK",
839
+ CANCEL : "Abbrechen",
840
+ CONFIRM : "Akzeptieren"
841
+ },
842
+ el : {
843
+ OK : "Εντάξει",
844
+ CANCEL : "Ακύρωση",
845
+ CONFIRM : "Επιβεβαίωση"
846
+ },
847
+ en : {
848
+ OK : "OK",
849
+ CANCEL : "Cancel",
850
+ CONFIRM : "OK"
851
+ },
852
+ es : {
853
+ OK : "OK",
854
+ CANCEL : "Cancelar",
855
+ CONFIRM : "Aceptar"
856
+ },
857
+ eu : {
858
+ OK : "OK",
859
+ CANCEL : "Ezeztatu",
860
+ CONFIRM : "Onartu"
861
+ },
862
+ et : {
863
+ OK : "OK",
864
+ CANCEL : "Katkesta",
865
+ CONFIRM : "OK"
866
+ },
867
+ fa : {
868
+ OK : "قبول",
869
+ CANCEL : "لغو",
870
+ CONFIRM : "تایید"
871
+ },
872
+ fi : {
873
+ OK : "OK",
874
+ CANCEL : "Peruuta",
875
+ CONFIRM : "OK"
876
+ },
877
+ fr : {
878
+ OK : "OK",
879
+ CANCEL : "Annuler",
880
+ CONFIRM : "Confirmer"
881
+ },
882
+ he : {
883
+ OK : "אישור",
884
+ CANCEL : "ביטול",
885
+ CONFIRM : "אישור"
886
+ },
887
+ hu : {
888
+ OK : "OK",
889
+ CANCEL : "Mégsem",
890
+ CONFIRM : "Megerősít"
891
+ },
892
+ hr : {
893
+ OK : "OK",
894
+ CANCEL : "Odustani",
895
+ CONFIRM : "Potvrdi"
896
+ },
897
+ id : {
898
+ OK : "OK",
899
+ CANCEL : "Batal",
900
+ CONFIRM : "OK"
901
+ },
902
+ it : {
903
+ OK : "OK",
904
+ CANCEL : "Annulla",
905
+ CONFIRM : "Conferma"
906
+ },
907
+ ja : {
908
+ OK : "OK",
909
+ CANCEL : "キャンセル",
910
+ CONFIRM : "確認"
911
+ },
912
+ lt : {
913
+ OK : "Gerai",
914
+ CANCEL : "Atšaukti",
915
+ CONFIRM : "Patvirtinti"
916
+ },
917
+ lv : {
918
+ OK : "Labi",
919
+ CANCEL : "Atcelt",
920
+ CONFIRM : "Apstiprināt"
921
+ },
922
+ nl : {
923
+ OK : "OK",
924
+ CANCEL : "Annuleren",
925
+ CONFIRM : "Accepteren"
926
+ },
927
+ no : {
928
+ OK : "OK",
929
+ CANCEL : "Avbryt",
930
+ CONFIRM : "OK"
931
+ },
932
+ pl : {
933
+ OK : "OK",
934
+ CANCEL : "Anuluj",
935
+ CONFIRM : "Potwierdź"
936
+ },
937
+ pt : {
938
+ OK : "OK",
939
+ CANCEL : "Cancelar",
940
+ CONFIRM : "Confirmar"
941
+ },
942
+ ru : {
943
+ OK : "OK",
944
+ CANCEL : "Отмена",
945
+ CONFIRM : "Применить"
946
+ },
947
+ sk : {
948
+ OK : "OK",
949
+ CANCEL : "Zrušiť",
950
+ CONFIRM : "Potvrdiť"
951
+ },
952
+ sl : {
953
+ OK : "OK",
954
+ CANCEL : "Prekliči",
955
+ CONFIRM : "Potrdi"
956
+ },
957
+ sq : {
958
+ OK : "OK",
959
+ CANCEL : "Anulo",
960
+ CONFIRM : "Prano"
961
+ },
962
+ sv : {
963
+ OK : "OK",
964
+ CANCEL : "Avbryt",
965
+ CONFIRM : "OK"
966
+ },
967
+ th : {
968
+ OK : "ตกลง",
969
+ CANCEL : "ยกเลิก",
970
+ CONFIRM : "ยืนยัน"
971
+ },
972
+ tr : {
973
+ OK : "Tamam",
974
+ CANCEL : "İptal",
975
+ CONFIRM : "Onayla"
976
+ },
977
+ zh_CN : {
978
+ OK : "OK",
979
+ CANCEL : "取消",
980
+ CONFIRM : "确认"
981
+ },
982
+ zh_TW : {
983
+ OK : "OK",
984
+ CANCEL : "取消",
985
+ CONFIRM : "確認"
986
+ }
987
+ };
988
+
989
+ exports.addLocale = function(name, values) {
990
+ $.each(["OK", "CANCEL", "CONFIRM"], function(_, v) {
991
+ if (!values[v]) {
992
+ throw new Error("Please supply a translation for '" + v + "'");
993
+ }
994
+ });
995
+
996
+ locales[name] = {
997
+ OK: values.OK,
998
+ CANCEL: values.CANCEL,
999
+ CONFIRM: values.CONFIRM
1000
+ };
1001
+
1002
+ return exports;
1003
+ };
1004
+
1005
+ exports.removeLocale = function(name) {
1006
+ delete locales[name];
1007
+
1008
+ return exports;
1009
+ };
1010
+
1011
+ exports.setLocale = function(name) {
1012
+ return exports.setDefaults("locale", name);
1013
+ };
1014
+
1015
+ exports.init = function(_$) {
1016
+ return init(_$ || $);
1017
+ };
1018
+
1019
+ return exports;
1020
+ }));