sofav 0.2.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.
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
+ }));