upmin 0.0.34

Sign up to get free protection for your applications and to get access to all the features.
Files changed (91) hide show
  1. checksums.yaml +7 -0
  2. data/Rakefile +34 -0
  3. data/app/assets/images/upmin/logo_large.png +0 -0
  4. data/app/assets/images/upmin/logo_small.png +0 -0
  5. data/app/assets/javascripts/upmin/application.js +38 -0
  6. data/app/assets/javascripts/upmin/jquery-clockpicker.js +729 -0
  7. data/app/assets/javascripts/upmin/models.js +23 -0
  8. data/app/assets/javascripts/upmin/pikaday.js +997 -0
  9. data/app/assets/stylesheets/upmin/application.css +16 -0
  10. data/app/assets/stylesheets/upmin/base.css.scss +24 -0
  11. data/app/assets/stylesheets/upmin/button_mixins.scss +37 -0
  12. data/app/assets/stylesheets/upmin/colors.scss +20 -0
  13. data/app/assets/stylesheets/upmin/instances.css.scss +91 -0
  14. data/app/assets/stylesheets/upmin/jquery-clockpicker.css +370 -0
  15. data/app/assets/stylesheets/upmin/models.css.scss +207 -0
  16. data/app/assets/stylesheets/upmin/pikaday.css +196 -0
  17. data/app/controllers/upmin/application_controller.rb +4 -0
  18. data/app/controllers/upmin/models_controller.rb +94 -0
  19. data/app/helpers/upmin/application_helper.rb +17 -0
  20. data/app/helpers/upmin/instances_helper.rb +13 -0
  21. data/app/helpers/upmin/models_helper.rb +4 -0
  22. data/app/views/layouts/upmin/_navbar.html.haml +15 -0
  23. data/app/views/layouts/upmin/application.html.haml +24 -0
  24. data/app/views/upmin/models/_search_result.html.haml +25 -0
  25. data/app/views/upmin/models/dashboard.html.haml +9 -0
  26. data/app/views/upmin/models/search.html.haml +20 -0
  27. data/app/views/upmin/models/show.html.haml +21 -0
  28. data/app/views/upmin/search_boxes/_unknown.html.haml +31 -0
  29. data/app/views/upmin/search_fields/_string.html.haml +3 -0
  30. data/app/views/upmin/types/_datetime.html.haml +95 -0
  31. data/app/views/upmin/types/_integer.html.haml +6 -0
  32. data/app/views/upmin/types/_string.html.haml +7 -0
  33. data/app/views/upmin/types/_unknown.html.haml +2 -0
  34. data/app/views/upmin/types/_unknown_collection.html.haml +18 -0
  35. data/app/views/upmin/types/_unknown_model.html.haml +57 -0
  36. data/app/views/upmin/types/_unknown_model_badge.html.haml +3 -0
  37. data/app/views/upmin/types/_unknown_model_nested.html.haml +24 -0
  38. data/app/views/upmin/types/_unknown_model_search_result.html.haml +31 -0
  39. data/config/routes.rb +19 -0
  40. data/lib/tasks/accordive_rails_tasks.rake +4 -0
  41. data/lib/tasks/upmin_tasks.rake +4 -0
  42. data/lib/upmin.rb +44 -0
  43. data/lib/upmin/engine.rb +7 -0
  44. data/lib/upmin/graph/collection_node.rb +72 -0
  45. data/lib/upmin/graph/data_node.rb +69 -0
  46. data/lib/upmin/graph/model_node.rb +151 -0
  47. data/lib/upmin/graph/node.rb +98 -0
  48. data/lib/upmin/model.rb +131 -0
  49. data/lib/upmin/railtie.rb +19 -0
  50. data/lib/upmin/railties/active_record.rb +156 -0
  51. data/lib/upmin/railties/render.rb +56 -0
  52. data/lib/upmin/railties/render_helpers.rb +57 -0
  53. data/lib/upmin/version.rb +3 -0
  54. data/test/controllers/upmin/model_controller_test.rb +11 -0
  55. data/test/dummy/README.rdoc +28 -0
  56. data/test/dummy/Rakefile +6 -0
  57. data/test/dummy/app/assets/javascripts/application.js +13 -0
  58. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  59. data/test/dummy/app/controllers/application_controller.rb +5 -0
  60. data/test/dummy/app/helpers/application_helper.rb +2 -0
  61. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  62. data/test/dummy/bin/bundle +3 -0
  63. data/test/dummy/bin/rails +4 -0
  64. data/test/dummy/bin/rake +4 -0
  65. data/test/dummy/config.ru +4 -0
  66. data/test/dummy/config/application.rb +23 -0
  67. data/test/dummy/config/boot.rb +5 -0
  68. data/test/dummy/config/database.yml +25 -0
  69. data/test/dummy/config/environment.rb +5 -0
  70. data/test/dummy/config/environments/development.rb +37 -0
  71. data/test/dummy/config/environments/production.rb +83 -0
  72. data/test/dummy/config/environments/test.rb +39 -0
  73. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  74. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  75. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  76. data/test/dummy/config/initializers/inflections.rb +16 -0
  77. data/test/dummy/config/initializers/mime_types.rb +4 -0
  78. data/test/dummy/config/initializers/session_store.rb +3 -0
  79. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  80. data/test/dummy/config/locales/en.yml +23 -0
  81. data/test/dummy/config/routes.rb +4 -0
  82. data/test/dummy/config/secrets.yml +22 -0
  83. data/test/dummy/public/404.html +67 -0
  84. data/test/dummy/public/422.html +67 -0
  85. data/test/dummy/public/500.html +66 -0
  86. data/test/dummy/public/favicon.ico +0 -0
  87. data/test/helpers/upmin/model_helper_test.rb +6 -0
  88. data/test/integration/navigation_test.rb +10 -0
  89. data/test/test_helper.rb +15 -0
  90. data/test/upmin_test.rb +7 -0
  91. metadata +253 -0
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 42151768753dc50012890574362fad83e1f88f50
4
+ data.tar.gz: 52a43f1825583fec02869d6fbf5b9e5c9eb8dde0
5
+ SHA512:
6
+ metadata.gz: d1df60ed7f4062152d1f33d73d4f3f6c74b2011c11b40cde33b3876d2dda9b9d0f6a982854b563d0f4cb6ece40dcebbe4c9c7866ef6ef18abce39ce6d742f8bd
7
+ data.tar.gz: 95c1399670622c6a1e7e24fabe608f5edadda633d37642de93d381188c61e917d098c33f66e6ea52b8cc67620264557cfce0a414bd05fc053e651ecb34c71987
@@ -0,0 +1,34 @@
1
+ begin
2
+ require 'bundler/setup'
3
+ rescue LoadError
4
+ puts 'You must `gem install bundler` and `bundle install` to run rake tasks'
5
+ end
6
+
7
+ require 'rdoc/task'
8
+
9
+ RDoc::Task.new(:rdoc) do |rdoc|
10
+ rdoc.rdoc_dir = 'rdoc'
11
+ rdoc.title = 'Upmin'
12
+ rdoc.options << '--line-numbers'
13
+ rdoc.rdoc_files.include('README.rdoc')
14
+ rdoc.rdoc_files.include('lib/**/*.rb')
15
+ end
16
+
17
+ APP_RAKEFILE = File.expand_path("../test/dummy/Rakefile", __FILE__)
18
+ load 'rails/tasks/engine.rake'
19
+
20
+
21
+
22
+ Bundler::GemHelper.install_tasks
23
+
24
+ require 'rake/testtask'
25
+
26
+ Rake::TestTask.new(:test) do |t|
27
+ t.libs << 'lib'
28
+ t.libs << 'test'
29
+ t.pattern = 'test/**/*_test.rb'
30
+ t.verbose = false
31
+ end
32
+
33
+
34
+ task default: :test
@@ -0,0 +1,38 @@
1
+ // This is a manifest file that'll be compiled into application.js, which will include all the files
2
+ // listed below.
3
+ //
4
+ // Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
5
+ // or vendor/assets/javascripts of plugins, if any, can be referenced here using a relative path.
6
+ //
7
+ // It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
8
+ // compiled file.
9
+ //
10
+ // Read Sprockets README (https://github.com/sstephenson/sprockets#sprockets-directives) for details
11
+ // about supported directives.
12
+ //
13
+ //= require jquery
14
+ //= require jquery_ujs
15
+ //= require ./jquery-clockpicker
16
+ //= require_tree .
17
+
18
+ function runUpmin() {
19
+ var Upmin = window.Upmin;
20
+ var controller = $("body").data("controller");
21
+ var action = $("body").data("action");
22
+
23
+ if ("object" === typeof Upmin) {
24
+
25
+ // Check if a controller specific initializer exists.
26
+ var Controller = Upmin[controller];
27
+ if ("object" === typeof Controller) {
28
+ Controller.init();
29
+
30
+ // Check if an action specific initializer exists.
31
+ var Action = Controller[action];
32
+ if ("object" === typeof Action) {
33
+ Action.init();
34
+ }
35
+ }
36
+ }
37
+ };
38
+ $(document).ready(runUpmin);
@@ -0,0 +1,729 @@
1
+ /*!
2
+ * ClockPicker v0.0.7 (http://weareoutman.github.io/clockpicker/)
3
+ * Copyright 2014 Wang Shenwei.
4
+ * Licensed under MIT (https://github.com/weareoutman/clockpicker/blob/gh-pages/LICENSE)
5
+ */
6
+
7
+ ;(function(){
8
+ var $ = window.jQuery,
9
+ $win = $(window),
10
+ $doc = $(document),
11
+ $body;
12
+
13
+ // Can I use inline svg ?
14
+ var svgNS = 'http://www.w3.org/2000/svg',
15
+ svgSupported = 'SVGAngle' in window && (function(){
16
+ var supported,
17
+ el = document.createElement('div');
18
+ el.innerHTML = '<svg/>';
19
+ supported = (el.firstChild && el.firstChild.namespaceURI) == svgNS;
20
+ el.innerHTML = '';
21
+ return supported;
22
+ })();
23
+
24
+ // Can I use transition ?
25
+ var transitionSupported = (function(){
26
+ var style = document.createElement('div').style;
27
+ return 'transition' in style ||
28
+ 'WebkitTransition' in style ||
29
+ 'MozTransition' in style ||
30
+ 'msTransition' in style ||
31
+ 'OTransition' in style;
32
+ })();
33
+
34
+ // Listen touch events in touch screen device, instead of mouse events in desktop.
35
+ var touchSupported = 'ontouchstart' in window,
36
+ mousedownEvent = 'mousedown' + ( touchSupported ? ' touchstart' : ''),
37
+ mousemoveEvent = 'mousemove.clockpicker' + ( touchSupported ? ' touchmove.clockpicker' : ''),
38
+ mouseupEvent = 'mouseup.clockpicker' + ( touchSupported ? ' touchend.clockpicker' : '');
39
+
40
+ // Vibrate the device if supported
41
+ var vibrate = navigator.vibrate ? 'vibrate' : navigator.webkitVibrate ? 'webkitVibrate' : null;
42
+
43
+ function createSvgElement(name) {
44
+ return document.createElementNS(svgNS, name);
45
+ }
46
+
47
+ function leadingZero(num) {
48
+ return (num < 10 ? '0' : '') + num;
49
+ }
50
+
51
+ // Get a unique id
52
+ var idCounter = 0;
53
+ function uniqueId(prefix) {
54
+ var id = ++idCounter + '';
55
+ return prefix ? prefix + id : id;
56
+ }
57
+
58
+ // Clock size
59
+ var dialRadius = 100,
60
+ outerRadius = 80,
61
+ // innerRadius = 80 on 12 hour clock
62
+ innerRadius = 54,
63
+ tickRadius = 13,
64
+ diameter = dialRadius * 2,
65
+ duration = transitionSupported ? 350 : 1;
66
+
67
+ // Popover template
68
+ var tpl = [
69
+ '<div class="popover clockpicker-popover">',
70
+ '<div class="arrow"></div>',
71
+ '<div class="popover-title">',
72
+ '<span class="clockpicker-span-hours text-primary"></span>',
73
+ ' : ',
74
+ '<span class="clockpicker-span-minutes"></span>',
75
+ '<span class="clockpicker-span-am-pm"></span>',
76
+ '</div>',
77
+ '<div class="popover-content">',
78
+ '<div class="clockpicker-plate">',
79
+ '<div class="clockpicker-canvas"></div>',
80
+ '<div class="clockpicker-dial clockpicker-hours"></div>',
81
+ '<div class="clockpicker-dial clockpicker-minutes clockpicker-dial-out"></div>',
82
+ '</div>',
83
+ '<span class="clockpicker-am-pm-block">',
84
+ '</span>',
85
+ '</div>',
86
+ '</div>'
87
+ ].join('');
88
+
89
+ // ClockPicker
90
+ function ClockPicker(element, options) {
91
+ var popover = $(tpl),
92
+ plate = popover.find('.clockpicker-plate'),
93
+ hoursView = popover.find('.clockpicker-hours'),
94
+ minutesView = popover.find('.clockpicker-minutes'),
95
+ amPmBlock = popover.find('.clockpicker-am-pm-block'),
96
+ isInput = element.prop('tagName') === 'INPUT',
97
+ input = isInput ? element : element.find('input'),
98
+ addon = element.find('.input-group-addon'),
99
+ self = this,
100
+ timer;
101
+
102
+ this.id = uniqueId('cp');
103
+ this.element = element;
104
+ this.options = options;
105
+ this.isAppended = false;
106
+ this.isShown = false;
107
+ this.currentView = 'hours';
108
+ this.isInput = isInput;
109
+ this.input = input;
110
+ this.addon = addon;
111
+ this.popover = popover;
112
+ this.plate = plate;
113
+ this.hoursView = hoursView;
114
+ this.minutesView = minutesView;
115
+ this.amPmBlock = amPmBlock;
116
+ this.spanHours = popover.find('.clockpicker-span-hours');
117
+ this.spanMinutes = popover.find('.clockpicker-span-minutes');
118
+ this.spanAmPm = popover.find('.clockpicker-span-am-pm');
119
+ this.amOrPm = "PM";
120
+
121
+ // Setup for for 12 hour clock if option is selected
122
+ if (options.twelvehour) {
123
+
124
+ var amPmButtonsTemplate = ['<div class="clockpicker-am-pm-block">',
125
+ '<button type="button" class="btn btn-sm btn-default clockpicker-button clockpicker-am-button">',
126
+ 'AM</button>',
127
+ '<button type="button" class="btn btn-sm btn-default clockpicker-button clockpicker-pm-button">',
128
+ 'PM</button>',
129
+ '</div>'].join('');
130
+
131
+ var amPmButtons = $(amPmButtonsTemplate);
132
+ //amPmButtons.appendTo(plate);
133
+
134
+ ////Not working b/c they are not shown when this runs
135
+ //$('clockpicker-am-button')
136
+ // .on("click", function() {
137
+ // self.amOrPm = "AM";
138
+ // $('.clockpicker-span-am-pm').empty().append('AM');
139
+ // });
140
+ //
141
+ //$('clockpicker-pm-button')
142
+ // .on("click", function() {
143
+ // self.amOrPm = "PM";
144
+ // $('.clockpicker-span-am-pm').empty().append('PM');
145
+ // });
146
+
147
+ $('<button type="button" class="btn btn-sm btn-default clockpicker-button am-button">' + "AM" + '</button>')
148
+ .on("click", function() {
149
+ self.amOrPm = "AM";
150
+ $('.clockpicker-span-am-pm').empty().append('AM');
151
+ }).appendTo(this.amPmBlock);
152
+
153
+
154
+ $('<button type="button" class="btn btn-sm btn-default clockpicker-button pm-button">' + "PM" + '</button>')
155
+ .on("click", function() {
156
+ self.amOrPm = 'PM';
157
+ $('.clockpicker-span-am-pm').empty().append('PM');
158
+ }).appendTo(this.amPmBlock);
159
+
160
+ }
161
+
162
+ if (! options.autoclose) {
163
+ // If autoclose is not setted, append a button
164
+ $('<button type="button" class="btn btn-sm btn-default btn-block clockpicker-button">' + options.donetext + '</button>')
165
+ .click($.proxy(this.done, this))
166
+ .appendTo(popover);
167
+ }
168
+
169
+ // Placement and arrow align - make sure they make sense.
170
+ if ((options.placement === 'top' || options.placement === 'bottom') && (options.align === 'top' || options.align === 'bottom')) options.align = 'left';
171
+ if ((options.placement === 'left' || options.placement === 'right') && (options.align === 'left' || options.align === 'right')) options.align = 'top';
172
+
173
+ popover.addClass(options.placement);
174
+ popover.addClass('clockpicker-align-' + options.align);
175
+
176
+ this.spanHours.click($.proxy(this.toggleView, this, 'hours'));
177
+ this.spanMinutes.click($.proxy(this.toggleView, this, 'minutes'));
178
+
179
+ // Show or toggle
180
+ input.on('focus.clockpicker click.clockpicker', $.proxy(this.show, this));
181
+ addon.on('click.clockpicker', $.proxy(this.toggle, this));
182
+
183
+ // Build ticks
184
+ var tickTpl = $('<div class="clockpicker-tick"></div>'),
185
+ i, tick, radian, radius;
186
+
187
+ // Hours view
188
+ if (options.twelvehour) {
189
+ for (i = 1; i < 13; i += 1) {
190
+ tick = tickTpl.clone();
191
+ radian = i / 6 * Math.PI;
192
+ radius = outerRadius;
193
+ tick.css('font-size', '120%');
194
+ tick.css({
195
+ left: dialRadius + Math.sin(radian) * radius - tickRadius,
196
+ top: dialRadius - Math.cos(radian) * radius - tickRadius
197
+ });
198
+ tick.html(i === 0 ? '00' : i);
199
+ hoursView.append(tick);
200
+ tick.on(mousedownEvent, mousedown);
201
+ }
202
+ } else {
203
+ for (i = 0; i < 24; i += 1) {
204
+ tick = tickTpl.clone();
205
+ radian = i / 6 * Math.PI;
206
+ var inner = i > 0 && i < 13;
207
+ radius = inner ? innerRadius : outerRadius;
208
+ tick.css({
209
+ left: dialRadius + Math.sin(radian) * radius - tickRadius,
210
+ top: dialRadius - Math.cos(radian) * radius - tickRadius
211
+ });
212
+ if (inner) {
213
+ tick.css('font-size', '120%');
214
+ }
215
+ tick.html(i === 0 ? '00' : i);
216
+ hoursView.append(tick);
217
+ tick.on(mousedownEvent, mousedown);
218
+ }
219
+ }
220
+
221
+ // Minutes view
222
+ for (i = 0; i < 60; i += 5) {
223
+ tick = tickTpl.clone();
224
+ radian = i / 30 * Math.PI;
225
+ tick.css({
226
+ left: dialRadius + Math.sin(radian) * outerRadius - tickRadius,
227
+ top: dialRadius - Math.cos(radian) * outerRadius - tickRadius
228
+ });
229
+ tick.css('font-size', '120%');
230
+ tick.html(leadingZero(i));
231
+ minutesView.append(tick);
232
+ tick.on(mousedownEvent, mousedown);
233
+ }
234
+
235
+ // Clicking on minutes view space
236
+ plate.on(mousedownEvent, function(e){
237
+ if ($(e.target).closest('.clockpicker-tick').length === 0) {
238
+ mousedown(e, true);
239
+ }
240
+ });
241
+
242
+ // Mousedown or touchstart
243
+ function mousedown(e, space) {
244
+ var offset = plate.offset(),
245
+ isTouch = /^touch/.test(e.type),
246
+ x0 = offset.left + dialRadius,
247
+ y0 = offset.top + dialRadius,
248
+ dx = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
249
+ dy = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0,
250
+ z = Math.sqrt(dx * dx + dy * dy),
251
+ moved = false;
252
+
253
+ // When clicking on minutes view space, check the mouse position
254
+ if (space && (z < outerRadius - tickRadius || z > outerRadius + tickRadius)) {
255
+ return;
256
+ }
257
+ e.preventDefault();
258
+
259
+ // Set cursor style of body after 200ms
260
+ var movingTimer = setTimeout(function(){
261
+ $body.addClass('clockpicker-moving');
262
+ }, 200);
263
+
264
+ // Place the canvas to top
265
+ if (svgSupported) {
266
+ plate.append(self.canvas);
267
+ }
268
+
269
+ // Clock
270
+ self.setHand(dx, dy, ! space, true);
271
+
272
+ // Mousemove on document
273
+ $doc.off(mousemoveEvent).on(mousemoveEvent, function(e){
274
+ e.preventDefault();
275
+ var isTouch = /^touch/.test(e.type),
276
+ x = (isTouch ? e.originalEvent.touches[0] : e).pageX - x0,
277
+ y = (isTouch ? e.originalEvent.touches[0] : e).pageY - y0;
278
+ if (! moved && x === dx && y === dy) {
279
+ // Clicking in chrome on windows will trigger a mousemove event
280
+ return;
281
+ }
282
+ moved = true;
283
+ self.setHand(x, y, false, true);
284
+ });
285
+
286
+ // Mouseup on document
287
+ $doc.off(mouseupEvent).on(mouseupEvent, function(e){
288
+ $doc.off(mouseupEvent);
289
+ e.preventDefault();
290
+ var isTouch = /^touch/.test(e.type),
291
+ x = (isTouch ? e.originalEvent.changedTouches[0] : e).pageX - x0,
292
+ y = (isTouch ? e.originalEvent.changedTouches[0] : e).pageY - y0;
293
+ if ((space || moved) && x === dx && y === dy) {
294
+ self.setHand(x, y);
295
+ }
296
+ if (self.currentView === 'hours') {
297
+ self.toggleView('minutes', duration / 2);
298
+ } else {
299
+ if (options.autoclose) {
300
+ self.minutesView.addClass('clockpicker-dial-out');
301
+ setTimeout(function(){
302
+ self.done();
303
+ }, duration / 2);
304
+ }
305
+ }
306
+ plate.prepend(canvas);
307
+
308
+ // Reset cursor style of body
309
+ clearTimeout(movingTimer);
310
+ $body.removeClass('clockpicker-moving');
311
+
312
+ // Unbind mousemove event
313
+ $doc.off(mousemoveEvent);
314
+ });
315
+ }
316
+
317
+ if (svgSupported) {
318
+ // Draw clock hands and others
319
+ var canvas = popover.find('.clockpicker-canvas'),
320
+ svg = createSvgElement('svg');
321
+ svg.setAttribute('class', 'clockpicker-svg');
322
+ svg.setAttribute('width', diameter);
323
+ svg.setAttribute('height', diameter);
324
+ var g = createSvgElement('g');
325
+ g.setAttribute('transform', 'translate(' + dialRadius + ',' + dialRadius + ')');
326
+ var bearing = createSvgElement('circle');
327
+ bearing.setAttribute('class', 'clockpicker-canvas-bearing');
328
+ bearing.setAttribute('cx', 0);
329
+ bearing.setAttribute('cy', 0);
330
+ bearing.setAttribute('r', 2);
331
+ var hand = createSvgElement('line');
332
+ hand.setAttribute('x1', 0);
333
+ hand.setAttribute('y1', 0);
334
+ var bg = createSvgElement('circle');
335
+ bg.setAttribute('class', 'clockpicker-canvas-bg');
336
+ bg.setAttribute('r', tickRadius);
337
+ var fg = createSvgElement('circle');
338
+ fg.setAttribute('class', 'clockpicker-canvas-fg');
339
+ fg.setAttribute('r', 3.5);
340
+ g.appendChild(hand);
341
+ g.appendChild(bg);
342
+ g.appendChild(fg);
343
+ g.appendChild(bearing);
344
+ svg.appendChild(g);
345
+ canvas.append(svg);
346
+
347
+ this.hand = hand;
348
+ this.bg = bg;
349
+ this.fg = fg;
350
+ this.bearing = bearing;
351
+ this.g = g;
352
+ this.canvas = canvas;
353
+ }
354
+
355
+ raiseCallback(this.options.init);
356
+ }
357
+
358
+ function raiseCallback(callbackFunction) {
359
+ if (callbackFunction && typeof callbackFunction === "function") {
360
+ callbackFunction();
361
+ }
362
+ }
363
+
364
+ // Default options
365
+ ClockPicker.DEFAULTS = {
366
+ 'default': '', // default time, 'now' or '13:14' e.g.
367
+ fromnow: 0, // set default time to * milliseconds from now (using with default = 'now')
368
+ placement: 'bottom', // clock popover placement
369
+ align: 'left', // popover arrow align
370
+ donetext: '完成', // done button text
371
+ autoclose: false, // auto close when minute is selected
372
+ twelvehour: false, // change to 12 hour AM/PM clock from 24 hour
373
+ vibrate: true // vibrate the device when dragging clock hand
374
+ };
375
+
376
+ // Show or hide popover
377
+ ClockPicker.prototype.toggle = function(){
378
+ this[this.isShown ? 'hide' : 'show']();
379
+ };
380
+
381
+ // Set popover position
382
+ ClockPicker.prototype.locate = function(){
383
+ var element = this.element,
384
+ popover = this.popover,
385
+ offset = element.offset(),
386
+ width = element.outerWidth(),
387
+ height = element.outerHeight(),
388
+ placement = this.options.placement,
389
+ align = this.options.align,
390
+ styles = {},
391
+ self = this;
392
+
393
+ popover.show();
394
+
395
+ // Place the popover
396
+ switch (placement) {
397
+ case 'bottom':
398
+ styles.top = offset.top + height;
399
+ break;
400
+ case 'right':
401
+ styles.left = offset.left + width;
402
+ break;
403
+ case 'top':
404
+ styles.top = offset.top - popover.outerHeight();
405
+ break;
406
+ case 'left':
407
+ styles.left = offset.left - popover.outerWidth();
408
+ break;
409
+ }
410
+
411
+ // Align the popover arrow
412
+ switch (align) {
413
+ case 'left':
414
+ styles.left = offset.left;
415
+ break;
416
+ case 'right':
417
+ styles.left = offset.left + width - popover.outerWidth();
418
+ break;
419
+ case 'top':
420
+ styles.top = offset.top;
421
+ break;
422
+ case 'bottom':
423
+ styles.top = offset.top + height - popover.outerHeight();
424
+ break;
425
+ }
426
+
427
+ popover.css(styles);
428
+ };
429
+
430
+ // Show popover
431
+ ClockPicker.prototype.show = function(e){
432
+ // Not show again
433
+ if (this.isShown) {
434
+ return;
435
+ }
436
+
437
+ raiseCallback(this.options.beforeShow);
438
+
439
+ var self = this;
440
+
441
+ // Initialize
442
+ if (! this.isAppended) {
443
+ // Append popover to body
444
+ $body = $(document.body).append(this.popover);
445
+
446
+ // Reset position when resize
447
+ $win.on('resize.clockpicker' + this.id, function(){
448
+ if (self.isShown) {
449
+ self.locate();
450
+ }
451
+ });
452
+
453
+ this.isAppended = true;
454
+ }
455
+
456
+ // Get the time
457
+ var value = ((this.input.prop('value') || this.options['default'] || '') + '').split(':');
458
+ if (value[0] === 'now') {
459
+ var now = new Date(+ new Date() + this.options.fromnow);
460
+ value = [
461
+ now.getHours(),
462
+ now.getMinutes()
463
+ ];
464
+ }
465
+ this.hours = + value[0] || 0;
466
+ this.minutes = + value[1] || 0;
467
+ this.spanHours.html(leadingZero(this.hours));
468
+ this.spanMinutes.html(leadingZero(this.minutes));
469
+
470
+ // Toggle to hours view
471
+ this.toggleView('hours');
472
+
473
+ // Set position
474
+ this.locate();
475
+
476
+ this.isShown = true;
477
+
478
+ // Hide when clicking or tabbing on any element except the clock, input and addon
479
+ $doc.on('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id, function(e){
480
+ var target = $(e.target);
481
+ if (target.closest(self.popover).length === 0 &&
482
+ target.closest(self.addon).length === 0 &&
483
+ target.closest(self.input).length === 0) {
484
+ self.hide();
485
+ }
486
+ });
487
+
488
+ // Hide when ESC is pressed
489
+ $doc.on('keyup.clockpicker.' + this.id, function(e){
490
+ if (e.keyCode === 27) {
491
+ self.hide();
492
+ }
493
+ });
494
+
495
+ raiseCallback(this.options.afterShow);
496
+ };
497
+
498
+ // Hide popover
499
+ ClockPicker.prototype.hide = function(){
500
+ raiseCallback(this.options.beforeHide);
501
+
502
+ this.isShown = false;
503
+
504
+ // Unbinding events on document
505
+ $doc.off('click.clockpicker.' + this.id + ' focusin.clockpicker.' + this.id);
506
+ $doc.off('keyup.clockpicker.' + this.id);
507
+
508
+ this.popover.hide();
509
+
510
+ raiseCallback(this.options.afterHide);
511
+ };
512
+
513
+ // Toggle to hours or minutes view
514
+ ClockPicker.prototype.toggleView = function(view, delay){
515
+ var raiseAfterHourSelect = false;
516
+ if (view === 'minutes' && $(this.hoursView).css("visibility") === "visible") {
517
+ raiseCallback(this.options.beforeHourSelect);
518
+ raiseAfterHourSelect = true;
519
+ }
520
+ var isHours = view === 'hours',
521
+ nextView = isHours ? this.hoursView : this.minutesView,
522
+ hideView = isHours ? this.minutesView : this.hoursView;
523
+
524
+ this.currentView = view;
525
+
526
+ this.spanHours.toggleClass('text-primary', isHours);
527
+ this.spanMinutes.toggleClass('text-primary', ! isHours);
528
+
529
+ // Let's make transitions
530
+ hideView.addClass('clockpicker-dial-out');
531
+ nextView.css('visibility', 'visible').removeClass('clockpicker-dial-out');
532
+
533
+ // Reset clock hand
534
+ this.resetClock(delay);
535
+
536
+ // After transitions ended
537
+ clearTimeout(this.toggleViewTimer);
538
+ this.toggleViewTimer = setTimeout(function(){
539
+ hideView.css('visibility', 'hidden');
540
+ }, duration);
541
+
542
+ if (raiseAfterHourSelect) {
543
+ raiseCallback(this.options.afterHourSelect);
544
+ }
545
+ };
546
+
547
+ // Reset clock hand
548
+ ClockPicker.prototype.resetClock = function(delay){
549
+ var view = this.currentView,
550
+ value = this[view],
551
+ isHours = view === 'hours',
552
+ unit = Math.PI / (isHours ? 6 : 30),
553
+ radian = value * unit,
554
+ radius = isHours && value > 0 && value < 13 ? innerRadius : outerRadius,
555
+ x = Math.sin(radian) * radius,
556
+ y = - Math.cos(radian) * radius,
557
+ self = this;
558
+ if (svgSupported && delay) {
559
+ self.canvas.addClass('clockpicker-canvas-out');
560
+ setTimeout(function(){
561
+ self.canvas.removeClass('clockpicker-canvas-out');
562
+ self.setHand(x, y);
563
+ }, delay);
564
+ } else {
565
+ this.setHand(x, y);
566
+ }
567
+ };
568
+
569
+ // Set clock hand to (x, y)
570
+ ClockPicker.prototype.setHand = function(x, y, roundBy5, dragging){
571
+ var radian = Math.atan2(x, - y),
572
+ isHours = this.currentView === 'hours',
573
+ unit = Math.PI / (isHours || roundBy5 ? 6 : 30),
574
+ z = Math.sqrt(x * x + y * y),
575
+ options = this.options,
576
+ inner = isHours && z < (outerRadius + innerRadius) / 2,
577
+ radius = inner ? innerRadius : outerRadius,
578
+ value;
579
+
580
+ if (options.twelvehour) {
581
+ radius = outerRadius;
582
+ }
583
+
584
+ // Radian should in range [0, 2PI]
585
+ if (radian < 0) {
586
+ radian = Math.PI * 2 + radian;
587
+ }
588
+
589
+ // Get the round value
590
+ value = Math.round(radian / unit);
591
+
592
+ // Get the round radian
593
+ radian = value * unit;
594
+
595
+ // Correct the hours or minutes
596
+ if (options.twelvehour) {
597
+ if (isHours) {
598
+ if (value === 0) {
599
+ value = 12;
600
+ }
601
+ } else {
602
+ if (roundBy5) {
603
+ value *= 5;
604
+ }
605
+ if (value === 60) {
606
+ value = 0;
607
+ }
608
+ }
609
+ } else {
610
+ if (isHours) {
611
+ if (value === 12) {
612
+ value = 0;
613
+ }
614
+ value = inner ? (value === 0 ? 12 : value) : value === 0 ? 0 : value + 12;
615
+ } else {
616
+ if (roundBy5) {
617
+ value *= 5;
618
+ }
619
+ if (value === 60) {
620
+ value = 0;
621
+ }
622
+ }
623
+ }
624
+
625
+ // Once hours or minutes changed, vibrate the device
626
+ if (this[this.currentView] !== value) {
627
+ if (vibrate && this.options.vibrate) {
628
+ // Do not vibrate too frequently
629
+ if (! this.vibrateTimer) {
630
+ navigator[vibrate](10);
631
+ this.vibrateTimer = setTimeout($.proxy(function(){
632
+ this.vibrateTimer = null;
633
+ }, this), 100);
634
+ }
635
+ }
636
+ }
637
+
638
+ this[this.currentView] = value;
639
+ this[isHours ? 'spanHours' : 'spanMinutes'].html(leadingZero(value));
640
+
641
+ // If svg is not supported, just add an active class to the tick
642
+ if (! svgSupported) {
643
+ this[isHours ? 'hoursView' : 'minutesView'].find('.clockpicker-tick').each(function(){
644
+ var tick = $(this);
645
+ tick.toggleClass('active', value === + tick.html());
646
+ });
647
+ return;
648
+ }
649
+
650
+ // Place clock hand at the top when dragging
651
+ if (dragging || (! isHours && value % 5)) {
652
+ this.g.insertBefore(this.hand, this.bearing);
653
+ this.g.insertBefore(this.bg, this.fg);
654
+ this.bg.setAttribute('class', 'clockpicker-canvas-bg clockpicker-canvas-bg-trans');
655
+ } else {
656
+ // Or place it at the bottom
657
+ this.g.insertBefore(this.hand, this.bg);
658
+ this.g.insertBefore(this.fg, this.bg);
659
+ this.bg.setAttribute('class', 'clockpicker-canvas-bg');
660
+ }
661
+
662
+ // Set clock hand and others' position
663
+ var cx = Math.sin(radian) * radius,
664
+ cy = - Math.cos(radian) * radius;
665
+ this.hand.setAttribute('x2', cx);
666
+ this.hand.setAttribute('y2', cy);
667
+ this.bg.setAttribute('cx', cx);
668
+ this.bg.setAttribute('cy', cy);
669
+ this.fg.setAttribute('cx', cx);
670
+ this.fg.setAttribute('cy', cy);
671
+ };
672
+
673
+ // Hours and minutes are selected
674
+ ClockPicker.prototype.done = function() {
675
+ raiseCallback(this.options.beforeDone);
676
+ this.hide();
677
+ var last = this.input.prop('value'),
678
+ value = leadingZero(this.hours) + ':' + leadingZero(this.minutes);
679
+ if (this.options.twelvehour) {
680
+ value = value + this.amOrPm;
681
+ }
682
+
683
+ this.input.prop('value', value);
684
+ if (value !== last) {
685
+ this.input.triggerHandler('change');
686
+ if (! this.isInput) {
687
+ this.element.trigger('change');
688
+ }
689
+ }
690
+
691
+ if (this.options.autoclose) {
692
+ this.input.trigger('blur');
693
+ }
694
+
695
+ raiseCallback(this.options.afterDone);
696
+ };
697
+
698
+ // Remove clockpicker from input
699
+ ClockPicker.prototype.remove = function() {
700
+ this.element.removeData('clockpicker');
701
+ this.input.off('focus.clockpicker click.clockpicker');
702
+ this.addon.off('click.clockpicker');
703
+ if (this.isShown) {
704
+ this.hide();
705
+ }
706
+ if (this.isAppended) {
707
+ $win.off('resize.clockpicker' + this.id);
708
+ this.popover.remove();
709
+ }
710
+ };
711
+
712
+ // Extends $.fn.clockpicker
713
+ $.fn.clockpicker = function(option){
714
+ var args = Array.prototype.slice.call(arguments, 1);
715
+ return this.each(function(){
716
+ var $this = $(this),
717
+ data = $this.data('clockpicker');
718
+ if (! data) {
719
+ var options = $.extend({}, ClockPicker.DEFAULTS, $this.data(), typeof option == 'object' && option);
720
+ $this.data('clockpicker', new ClockPicker($this, options));
721
+ } else {
722
+ // Manual operatsions. show, hide, remove, e.g.
723
+ if (typeof data[option] === 'function') {
724
+ data[option].apply(data, args);
725
+ }
726
+ }
727
+ });
728
+ };
729
+ }());