smartkiosk-server 0.12.1 → 0.13

Sign up to get free protection for your applications and to get access to all the features.
Files changed (78) hide show
  1. data/.ruby-version +1 -1
  2. data/Gemfile +9 -6
  3. data/Gemfile.lock +36 -9
  4. data/app/assets/images/sort-asc.png +0 -0
  5. data/app/assets/images/sort-desc.png +0 -0
  6. data/app/assets/javascripts/application.js +1 -1
  7. data/app/assets/javascripts/monitoring.js.coffee +25 -0
  8. data/app/assets/javascripts/monitoring/helpers/application.js.coffee +12 -0
  9. data/app/assets/javascripts/monitoring/layouts/application.js.coffee +2 -0
  10. data/app/assets/javascripts/monitoring/pages/application.js.coffee +1 -0
  11. data/app/assets/javascripts/monitoring/pages/welcome/index.js.coffee +229 -0
  12. data/app/assets/javascripts/monitoring/resources/.gitkeep +0 -0
  13. data/app/assets/javascripts/monitoring/routes.js.coffee +8 -0
  14. data/app/assets/javascripts/monitoring/templates/layouts/.gitkeep +0 -0
  15. data/app/assets/javascripts/monitoring/templates/layouts/application.jst.hamlc +1 -0
  16. data/app/assets/javascripts/monitoring/templates/pages/welcome/_row.jst.hamlc +28 -0
  17. data/app/assets/javascripts/monitoring/templates/pages/welcome/_tbody.jst.hamlc +3 -0
  18. data/app/assets/javascripts/monitoring/templates/pages/welcome/index.jst.hamlc +37 -0
  19. data/app/assets/javascripts/monitoring/templates/widgets/.gitkeep +0 -0
  20. data/app/assets/javascripts/monitoring/widgets/.gitkeep +0 -0
  21. data/app/assets/javascripts/monitoring_preloader.js.coffee.erb +17 -0
  22. data/app/assets/stylesheets/active_admin.css.scss +4 -4
  23. data/app/assets/stylesheets/{fonts.css.scss → active_admin/fonts.css.scss} +0 -0
  24. data/app/assets/stylesheets/active_admin/provider_groups.css.scss +12 -0
  25. data/app/assets/stylesheets/{provider_receipt_templates.css.scss → active_admin/provider_receipt_templates.css.scss} +0 -0
  26. data/app/assets/stylesheets/{terminals.css.scss → active_admin/terminals.css.scss} +0 -0
  27. data/app/assets/stylesheets/monitoring.css.scss +4 -0
  28. data/app/assets/stylesheets/monitoring/bootstrap.css +9 -0
  29. data/app/assets/stylesheets/monitoring/theme.css.scss +35 -0
  30. data/app/controllers/monitoring_controller.rb +31 -0
  31. data/app/controllers/payments_controller.rb +1 -1
  32. data/app/controllers/terminal_builds_controller.rb +2 -0
  33. data/app/controllers/welcome_controller.rb +5 -2
  34. data/app/models/ability.rb +6 -4
  35. data/app/models/agent.rb +6 -0
  36. data/app/models/collection.rb +2 -2
  37. data/app/models/payment.rb +3 -3
  38. data/app/models/provider_gateway.rb +1 -1
  39. data/app/models/report_result.rb +1 -1
  40. data/app/models/report_template.rb +3 -3
  41. data/app/models/role.rb +51 -10
  42. data/app/models/terminal.rb +25 -1
  43. data/app/models/terminal_build.rb +1 -1
  44. data/app/models/terminal_order.rb +1 -1
  45. data/app/models/terminal_ping.rb +3 -0
  46. data/app/models/terminal_profile.rb +6 -0
  47. data/app/models/user_role.rb +1 -1
  48. data/app/views/monitoring/index.html.erb +28 -0
  49. data/config.ru +7 -0
  50. data/config/deploy.rb +1 -1
  51. data/config/environments/development.rb +2 -0
  52. data/config/environments/production.rb +1 -1
  53. data/config/initializers/monitoring.rb +3 -0
  54. data/config/initializers/redis.rb +1 -1
  55. data/config/locales/activerecord.ru.yml +14 -0
  56. data/config/locales/smartkiosk.ru.yml +38 -0
  57. data/config/routes.rb +1 -1
  58. data/db/migrate/20130419125334_add_ping_to_terminals.rb +22 -0
  59. data/db/schema.rb +25 -7
  60. data/lib/blueprints.rb +2 -0
  61. data/lib/monitorer.rb +41 -0
  62. data/lib/seeder.rb +1 -1
  63. data/lib/smartkiosk/server/version.rb +1 -1
  64. data/vendor/assets/javascripts/chosen.jquery.js +1 -1
  65. data/vendor/assets/javascripts/cookie.jquery.js +95 -0
  66. data/vendor/assets/javascripts/copypaste.js +148 -0
  67. data/vendor/assets/javascripts/event-drag.jquery.js +402 -0
  68. data/vendor/assets/javascripts/sheetclip.js +87 -0
  69. data/vendor/assets/javascripts/slick.cellrangedecorator.js +64 -0
  70. data/vendor/assets/javascripts/slick.cellrangeselector.js +111 -0
  71. data/vendor/assets/javascripts/slick.cellselectionmodel.js +152 -0
  72. data/vendor/assets/javascripts/slick.core.js +458 -0
  73. data/vendor/assets/javascripts/slick.grid.js +3287 -0
  74. data/vendor/assets/stylesheets/slick.grid.css +167 -0
  75. metadata +42 -9
  76. data/app/assets/stylesheets/provider_groups.css.scss +0 -12
  77. data/app/views/layouts/application.html.erb +0 -1
  78. data/app/views/welcome/index.html.erb +0 -25
@@ -142,7 +142,7 @@ Copyright (c) 2011 by Harvest
142
142
  } else if (this.is_multiple) {
143
143
  this.default_text = this.options.placeholder_text_multiple || this.options.placeholder_text || "Select Some Options";
144
144
  } else {
145
- this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || "Выберите";
145
+ this.default_text = this.options.placeholder_text_single || this.options.placeholder_text || "Select";
146
146
  }
147
147
  return this.results_none_found = this.form_field.getAttribute("data-no_results_text") || this.options.no_results_text || "No results match";
148
148
  };
@@ -0,0 +1,95 @@
1
+ /*!
2
+ * jQuery Cookie Plugin v1.3.1
3
+ * https://github.com/carhartl/jquery-cookie
4
+ *
5
+ * Copyright 2013 Klaus Hartl
6
+ * Released under the MIT license
7
+ */
8
+ (function (factory) {
9
+ if (typeof define === 'function' && define.amd) {
10
+ // AMD. Register as anonymous module.
11
+ define(['jquery'], factory);
12
+ } else {
13
+ // Browser globals.
14
+ factory(jQuery);
15
+ }
16
+ }(function ($) {
17
+
18
+ var pluses = /\+/g;
19
+
20
+ function raw(s) {
21
+ return s;
22
+ }
23
+
24
+ function decoded(s) {
25
+ return decodeURIComponent(s.replace(pluses, ' '));
26
+ }
27
+
28
+ function converted(s) {
29
+ if (s.indexOf('"') === 0) {
30
+ // This is a quoted cookie as according to RFC2068, unescape
31
+ s = s.slice(1, -1).replace(/\\"/g, '"').replace(/\\\\/g, '\\');
32
+ }
33
+ try {
34
+ return config.json ? JSON.parse(s) : s;
35
+ } catch(er) {}
36
+ }
37
+
38
+ var config = $.cookie = function (key, value, options) {
39
+
40
+ // write
41
+ if (value !== undefined) {
42
+ options = $.extend({}, config.defaults, options);
43
+
44
+ if (typeof options.expires === 'number') {
45
+ var days = options.expires, t = options.expires = new Date();
46
+ t.setDate(t.getDate() + days);
47
+ }
48
+
49
+ value = config.json ? JSON.stringify(value) : String(value);
50
+
51
+ return (document.cookie = [
52
+ config.raw ? key : encodeURIComponent(key),
53
+ '=',
54
+ config.raw ? value : encodeURIComponent(value),
55
+ options.expires ? '; expires=' + options.expires.toUTCString() : '', // use expires attribute, max-age is not supported by IE
56
+ options.path ? '; path=' + options.path : '',
57
+ options.domain ? '; domain=' + options.domain : '',
58
+ options.secure ? '; secure' : ''
59
+ ].join(''));
60
+ }
61
+
62
+ // read
63
+ var decode = config.raw ? raw : decoded;
64
+ var cookies = document.cookie.split('; ');
65
+ var result = key ? undefined : {};
66
+ for (var i = 0, l = cookies.length; i < l; i++) {
67
+ var parts = cookies[i].split('=');
68
+ var name = decode(parts.shift());
69
+ var cookie = decode(parts.join('='));
70
+
71
+ if (key && key === name) {
72
+ result = converted(cookie);
73
+ break;
74
+ }
75
+
76
+ if (!key) {
77
+ result[name] = converted(cookie);
78
+ }
79
+ }
80
+
81
+ return result;
82
+ };
83
+
84
+ config.defaults = {};
85
+
86
+ $.removeCookie = function (key, options) {
87
+ if ($.cookie(key) !== undefined) {
88
+ // Must not alter options, thus extending a fresh object...
89
+ $.cookie(key, '', $.extend({}, options, { expires: -1 }));
90
+ return true;
91
+ }
92
+ return false;
93
+ };
94
+
95
+ }));
@@ -0,0 +1,148 @@
1
+ /**
2
+ * CopyPaste.js
3
+ * Creates a textarea that stays hidden on the page and gets focused when user presses CTRL while not having a form input focused
4
+ * In future we may implement a better driver when better APIs are available
5
+ * @constructor
6
+ */
7
+ function CopyPaste(listenerElement) {
8
+ var that = this
9
+ , style;
10
+ listenerElement = listenerElement || document.body;
11
+
12
+ this.disabled = false;
13
+ this.elDiv = document.createElement('DIV');
14
+ style = this.elDiv.style;
15
+ style.position = 'fixed';
16
+ style.top = 0;
17
+ style.left = 0;
18
+ listenerElement.appendChild(this.elDiv);
19
+
20
+ this.elTextarea = document.createElement('TEXTAREA');
21
+ this.elTextarea.className = 'copyPaste';
22
+ style = this.elTextarea.style;
23
+ style.width = '1px';
24
+ style.height = '1px';
25
+ this.elDiv.appendChild(this.elTextarea);
26
+
27
+ if (typeof style.opacity !== 'undefined') {
28
+ style.opacity = 0;
29
+ }
30
+ else {
31
+ /*@cc_on @if (@_jscript)
32
+ if(typeof style.filter === 'string') {
33
+ style.filter = 'alpha(opacity=0)';
34
+ }
35
+ @end @*/
36
+ }
37
+
38
+ this._bindEvent(listenerElement, 'keydown', function (event) {
39
+ if (that.disabled) {
40
+ return true;
41
+ }
42
+ var isCtrlDown = false;
43
+ if (event.metaKey) { //mac
44
+ isCtrlDown = true;
45
+ }
46
+ else if (event.ctrlKey && navigator.userAgent.indexOf('Mac') === -1) { //pc
47
+ isCtrlDown = true;
48
+ }
49
+
50
+ if (isCtrlDown && !(event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88)) {
51
+ if (that.prepareCallback) {
52
+ that.copyable(that.prepareCallback(event));
53
+ }
54
+
55
+ that.selectNodeText(that.elTextarea);
56
+ setTimeout(function () {
57
+ that.selectNodeText(that.elTextarea);
58
+ }, 0);
59
+ }
60
+
61
+ /* 67 = c
62
+ * 86 = v
63
+ * 88 = x
64
+ */
65
+ if (isCtrlDown && (event.keyCode === 67 || event.keyCode === 86 || event.keyCode === 88)) {
66
+ // that.selectNodeText(that.elTextarea);
67
+
68
+ if (event.keyCode === 88) { //works in all browsers, incl. Opera < 12.12
69
+ setTimeout(function () {
70
+ that.triggerCut(event);
71
+ }, 0);
72
+ }
73
+ else if (event.keyCode === 86) {
74
+ setTimeout(function () {
75
+ that.triggerPaste(event);
76
+ }, 0);
77
+ }
78
+ }
79
+ });
80
+ }
81
+
82
+ //http://jsperf.com/textara-selection
83
+ //http://stackoverflow.com/questions/1502385/how-can-i-make-this-code-work-in-ie
84
+ CopyPaste.prototype.selectNodeText = function (el) {
85
+ el.select();
86
+ };
87
+
88
+ CopyPaste.prototype.copyable = function (str) {
89
+ if (typeof str !== 'string' && str.toString === void 0) {
90
+ throw new Error('copyable requires string parameter');
91
+ }
92
+ this.elTextarea.value = str;
93
+ };
94
+
95
+ CopyPaste.prototype.prepare = function (fn) {
96
+ this.prepareCallback = fn;
97
+ }
98
+
99
+ CopyPaste.prototype.onCopy = function (fn) {
100
+ this.copyCallback = fn;
101
+ };
102
+
103
+ CopyPaste.prototype.onCut = function (fn) {
104
+ this.cutCallback = fn;
105
+ };
106
+
107
+ CopyPaste.prototype.onPaste = function (fn) {
108
+ this.pasteCallback = fn;
109
+ };
110
+
111
+ CopyPaste.prototype.triggerCut = function (event) {
112
+ var that = this;
113
+ if (that.cutCallback) {
114
+ setTimeout(function () {
115
+ that.cutCallback(event);
116
+ }, 0);
117
+ }
118
+ };
119
+
120
+ CopyPaste.prototype.triggerPaste = function (event, str) {
121
+ var that = this;
122
+ if (that.pasteCallback) {
123
+ setTimeout(function () {
124
+ that.pasteCallback((str || that.elTextarea.value).replace(/\n$/, ''), event); //remove trailing newline
125
+ }, 0);
126
+ }
127
+ };
128
+
129
+ //http://net.tutsplus.com/tutorials/javascript-ajax/javascript-from-null-cross-browser-event-binding/
130
+ //http://stackoverflow.com/questions/4643249/cross-browser-event-object-normalization
131
+ CopyPaste.prototype._bindEvent = (function () {
132
+ if (document.addEventListener) {
133
+ return function (elem, type, cb) {
134
+ elem.addEventListener(type, cb, false);
135
+ };
136
+ }
137
+ else {
138
+ return function (elem, type, cb) {
139
+ elem.attachEvent('on' + type, function () {
140
+ var e = window['event'];
141
+ e.target = e.srcElement;
142
+ e.relatedTarget = e.relatedTarget || e.type == 'mouseover' ? e.fromElement : e.toElement;
143
+ if (e.target.nodeType === 3) e.target = e.target.parentNode; //Safari bug
144
+ return cb.call(elem, e)
145
+ });
146
+ };
147
+ }
148
+ })();
@@ -0,0 +1,402 @@
1
+ /*!
2
+ * jquery.event.drag - v 2.2
3
+ * Copyright (c) 2010 Three Dub Media - http://threedubmedia.com
4
+ * Open Source MIT License - http://threedubmedia.com/code/license
5
+ */
6
+ // Created: 2008-06-04
7
+ // Updated: 2012-05-21
8
+ // REQUIRES: jquery 1.7.x
9
+
10
+ ;(function( $ ){
11
+
12
+ // add the jquery instance method
13
+ $.fn.drag = function( str, arg, opts ){
14
+ // figure out the event type
15
+ var type = typeof str == "string" ? str : "",
16
+ // figure out the event handler...
17
+ fn = $.isFunction( str ) ? str : $.isFunction( arg ) ? arg : null;
18
+ // fix the event type
19
+ if ( type.indexOf("drag") !== 0 )
20
+ type = "drag"+ type;
21
+ // were options passed
22
+ opts = ( str == fn ? arg : opts ) || {};
23
+ // trigger or bind event handler
24
+ return fn ? this.bind( type, opts, fn ) : this.trigger( type );
25
+ };
26
+
27
+ // local refs (increase compression)
28
+ var $event = $.event,
29
+ $special = $event.special,
30
+ // configure the drag special event
31
+ drag = $special.drag = {
32
+
33
+ // these are the default settings
34
+ defaults: {
35
+ which: 1, // mouse button pressed to start drag sequence
36
+ distance: 0, // distance dragged before dragstart
37
+ not: ':input', // selector to suppress dragging on target elements
38
+ handle: null, // selector to match handle target elements
39
+ relative: false, // true to use "position", false to use "offset"
40
+ drop: true, // false to suppress drop events, true or selector to allow
41
+ click: false // false to suppress click events after dragend (no proxy)
42
+ },
43
+
44
+ // the key name for stored drag data
45
+ datakey: "dragdata",
46
+
47
+ // prevent bubbling for better performance
48
+ noBubble: true,
49
+
50
+ // count bound related events
51
+ add: function( obj ){
52
+ // read the interaction data
53
+ var data = $.data( this, drag.datakey ),
54
+ // read any passed options
55
+ opts = obj.data || {};
56
+ // count another realted event
57
+ data.related += 1;
58
+ // extend data options bound with this event
59
+ // don't iterate "opts" in case it is a node
60
+ $.each( drag.defaults, function( key, def ){
61
+ if ( opts[ key ] !== undefined )
62
+ data[ key ] = opts[ key ];
63
+ });
64
+ },
65
+
66
+ // forget unbound related events
67
+ remove: function(){
68
+ $.data( this, drag.datakey ).related -= 1;
69
+ },
70
+
71
+ // configure interaction, capture settings
72
+ setup: function(){
73
+ // check for related events
74
+ if ( $.data( this, drag.datakey ) )
75
+ return;
76
+ // initialize the drag data with copied defaults
77
+ var data = $.extend({ related:0 }, drag.defaults );
78
+ // store the interaction data
79
+ $.data( this, drag.datakey, data );
80
+ // bind the mousedown event, which starts drag interactions
81
+ $event.add( this, "touchstart mousedown", drag.init, data );
82
+ // prevent image dragging in IE...
83
+ if ( this.attachEvent )
84
+ this.attachEvent("ondragstart", drag.dontstart );
85
+ },
86
+
87
+ // destroy configured interaction
88
+ teardown: function(){
89
+ var data = $.data( this, drag.datakey ) || {};
90
+ // check for related events
91
+ if ( data.related )
92
+ return;
93
+ // remove the stored data
94
+ $.removeData( this, drag.datakey );
95
+ // remove the mousedown event
96
+ $event.remove( this, "touchstart mousedown", drag.init );
97
+ // enable text selection
98
+ drag.textselect( true );
99
+ // un-prevent image dragging in IE...
100
+ if ( this.detachEvent )
101
+ this.detachEvent("ondragstart", drag.dontstart );
102
+ },
103
+
104
+ // initialize the interaction
105
+ init: function( event ){
106
+ // sorry, only one touch at a time
107
+ if ( drag.touched )
108
+ return;
109
+ // the drag/drop interaction data
110
+ var dd = event.data, results;
111
+ // check the which directive
112
+ if ( event.which != 0 && dd.which > 0 && event.which != dd.which )
113
+ return;
114
+ // check for suppressed selector
115
+ if ( $( event.target ).is( dd.not ) )
116
+ return;
117
+ // check for handle selector
118
+ if ( dd.handle && !$( event.target ).closest( dd.handle, event.currentTarget ).length )
119
+ return;
120
+
121
+ drag.touched = event.type == 'touchstart' ? this : null;
122
+ dd.propagates = 1;
123
+ dd.mousedown = this;
124
+ dd.interactions = [ drag.interaction( this, dd ) ];
125
+ dd.target = event.target;
126
+ dd.pageX = event.pageX;
127
+ dd.pageY = event.pageY;
128
+ dd.dragging = null;
129
+ // handle draginit event...
130
+ results = drag.hijack( event, "draginit", dd );
131
+ // early cancel
132
+ if ( !dd.propagates )
133
+ return;
134
+ // flatten the result set
135
+ results = drag.flatten( results );
136
+ // insert new interaction elements
137
+ if ( results && results.length ){
138
+ dd.interactions = [];
139
+ $.each( results, function(){
140
+ dd.interactions.push( drag.interaction( this, dd ) );
141
+ });
142
+ }
143
+ // remember how many interactions are propagating
144
+ dd.propagates = dd.interactions.length;
145
+ // locate and init the drop targets
146
+ if ( dd.drop !== false && $special.drop )
147
+ $special.drop.handler( event, dd );
148
+ // disable text selection
149
+ drag.textselect( false );
150
+ // bind additional events...
151
+ if ( drag.touched )
152
+ $event.add( drag.touched, "touchmove touchend", drag.handler, dd );
153
+ else
154
+ $event.add( document, "mousemove mouseup", drag.handler, dd );
155
+ // helps prevent text selection or scrolling
156
+ if ( !drag.touched || dd.live )
157
+ return false;
158
+ },
159
+
160
+ // returns an interaction object
161
+ interaction: function( elem, dd ){
162
+ var offset = $( elem )[ dd.relative ? "position" : "offset" ]() || { top:0, left:0 };
163
+ return {
164
+ drag: elem,
165
+ callback: new drag.callback(),
166
+ droppable: [],
167
+ offset: offset
168
+ };
169
+ },
170
+
171
+ // handle drag-releatd DOM events
172
+ handler: function( event ){
173
+ // read the data before hijacking anything
174
+ var dd = event.data;
175
+ // handle various events
176
+ switch ( event.type ){
177
+ // mousemove, check distance, start dragging
178
+ case !dd.dragging && 'touchmove':
179
+ event.preventDefault();
180
+ case !dd.dragging && 'mousemove':
181
+ // drag tolerance, x² + y² = distance²
182
+ if ( Math.pow( event.pageX-dd.pageX, 2 ) + Math.pow( event.pageY-dd.pageY, 2 ) < Math.pow( dd.distance, 2 ) )
183
+ break; // distance tolerance not reached
184
+ event.target = dd.target; // force target from "mousedown" event (fix distance issue)
185
+ drag.hijack( event, "dragstart", dd ); // trigger "dragstart"
186
+ if ( dd.propagates ) // "dragstart" not rejected
187
+ dd.dragging = true; // activate interaction
188
+ // mousemove, dragging
189
+ case 'touchmove':
190
+ event.preventDefault();
191
+ case 'mousemove':
192
+ if ( dd.dragging ){
193
+ // trigger "drag"
194
+ drag.hijack( event, "drag", dd );
195
+ if ( dd.propagates ){
196
+ // manage drop events
197
+ if ( dd.drop !== false && $special.drop )
198
+ $special.drop.handler( event, dd ); // "dropstart", "dropend"
199
+ break; // "drag" not rejected, stop
200
+ }
201
+ event.type = "mouseup"; // helps "drop" handler behave
202
+ }
203
+ // mouseup, stop dragging
204
+ case 'touchend':
205
+ case 'mouseup':
206
+ default:
207
+ if ( drag.touched )
208
+ $event.remove( drag.touched, "touchmove touchend", drag.handler ); // remove touch events
209
+ else
210
+ $event.remove( document, "mousemove mouseup", drag.handler ); // remove page events
211
+ if ( dd.dragging ){
212
+ if ( dd.drop !== false && $special.drop )
213
+ $special.drop.handler( event, dd ); // "drop"
214
+ drag.hijack( event, "dragend", dd ); // trigger "dragend"
215
+ }
216
+ drag.textselect( true ); // enable text selection
217
+ // if suppressing click events...
218
+ if ( dd.click === false && dd.dragging )
219
+ $.data( dd.mousedown, "suppress.click", new Date().getTime() + 5 );
220
+ dd.dragging = drag.touched = false; // deactivate element
221
+ break;
222
+ }
223
+ },
224
+
225
+ // re-use event object for custom events
226
+ hijack: function( event, type, dd, x, elem ){
227
+ // not configured
228
+ if ( !dd )
229
+ return;
230
+ // remember the original event and type
231
+ var orig = { event:event.originalEvent, type:event.type },
232
+ // is the event drag related or drog related?
233
+ mode = type.indexOf("drop") ? "drag" : "drop",
234
+ // iteration vars
235
+ result, i = x || 0, ia, $elems, callback,
236
+ len = !isNaN( x ) ? x : dd.interactions.length;
237
+ // modify the event type
238
+ event.type = type;
239
+ // remove the original event
240
+ event.originalEvent = null;
241
+ // initialize the results
242
+ dd.results = [];
243
+ // handle each interacted element
244
+ do if ( ia = dd.interactions[ i ] ){
245
+ // validate the interaction
246
+ if ( type !== "dragend" && ia.cancelled )
247
+ continue;
248
+ // set the dragdrop properties on the event object
249
+ callback = drag.properties( event, dd, ia );
250
+ // prepare for more results
251
+ ia.results = [];
252
+ // handle each element
253
+ $( elem || ia[ mode ] || dd.droppable ).each(function( p, subject ){
254
+ // identify drag or drop targets individually
255
+ callback.target = subject;
256
+ // force propagtion of the custom event
257
+ event.isPropagationStopped = function(){ return false; };
258
+ // handle the event
259
+ result = subject ? $event.dispatch.call( subject, event, callback ) : null;
260
+ // stop the drag interaction for this element
261
+ if ( result === false ){
262
+ if ( mode == "drag" ){
263
+ ia.cancelled = true;
264
+ dd.propagates -= 1;
265
+ }
266
+ if ( type == "drop" ){
267
+ ia[ mode ][p] = null;
268
+ }
269
+ }
270
+ // assign any dropinit elements
271
+ else if ( type == "dropinit" )
272
+ ia.droppable.push( drag.element( result ) || subject );
273
+ // accept a returned proxy element
274
+ if ( type == "dragstart" )
275
+ ia.proxy = $( drag.element( result ) || ia.drag )[0];
276
+ // remember this result
277
+ ia.results.push( result );
278
+ // forget the event result, for recycling
279
+ delete event.result;
280
+ // break on cancelled handler
281
+ if ( type !== "dropinit" )
282
+ return result;
283
+ });
284
+ // flatten the results
285
+ dd.results[ i ] = drag.flatten( ia.results );
286
+ // accept a set of valid drop targets
287
+ if ( type == "dropinit" )
288
+ ia.droppable = drag.flatten( ia.droppable );
289
+ // locate drop targets
290
+ if ( type == "dragstart" && !ia.cancelled )
291
+ callback.update();
292
+ }
293
+ while ( ++i < len )
294
+ // restore the original event & type
295
+ event.type = orig.type;
296
+ event.originalEvent = orig.event;
297
+ // return all handler results
298
+ return drag.flatten( dd.results );
299
+ },
300
+
301
+ // extend the callback object with drag/drop properties...
302
+ properties: function( event, dd, ia ){
303
+ var obj = ia.callback;
304
+ // elements
305
+ obj.drag = ia.drag;
306
+ obj.proxy = ia.proxy || ia.drag;
307
+ // starting mouse position
308
+ obj.startX = dd.pageX;
309
+ obj.startY = dd.pageY;
310
+ // current distance dragged
311
+ obj.deltaX = event.pageX - dd.pageX;
312
+ obj.deltaY = event.pageY - dd.pageY;
313
+ // original element position
314
+ obj.originalX = ia.offset.left;
315
+ obj.originalY = ia.offset.top;
316
+ // adjusted element position
317
+ obj.offsetX = obj.originalX + obj.deltaX;
318
+ obj.offsetY = obj.originalY + obj.deltaY;
319
+ // assign the drop targets information
320
+ obj.drop = drag.flatten( ( ia.drop || [] ).slice() );
321
+ obj.available = drag.flatten( ( ia.droppable || [] ).slice() );
322
+ return obj;
323
+ },
324
+
325
+ // determine is the argument is an element or jquery instance
326
+ element: function( arg ){
327
+ if ( arg && ( arg.jquery || arg.nodeType == 1 ) )
328
+ return arg;
329
+ },
330
+
331
+ // flatten nested jquery objects and arrays into a single dimension array
332
+ flatten: function( arr ){
333
+ return $.map( arr, function( member ){
334
+ return member && member.jquery ? $.makeArray( member ) :
335
+ member && member.length ? drag.flatten( member ) : member;
336
+ });
337
+ },
338
+
339
+ // toggles text selection attributes ON (true) or OFF (false)
340
+ textselect: function( bool ){
341
+ $( document )[ bool ? "unbind" : "bind" ]("selectstart", drag.dontstart )
342
+ .css("MozUserSelect", bool ? "" : "none" );
343
+ // .attr("unselectable", bool ? "off" : "on" )
344
+ document.unselectable = bool ? "off" : "on";
345
+ },
346
+
347
+ // suppress "selectstart" and "ondragstart" events
348
+ dontstart: function(){
349
+ return false;
350
+ },
351
+
352
+ // a callback instance contructor
353
+ callback: function(){}
354
+
355
+ };
356
+
357
+ // callback methods
358
+ drag.callback.prototype = {
359
+ update: function(){
360
+ if ( $special.drop && this.available.length )
361
+ $.each( this.available, function( i ){
362
+ $special.drop.locate( this, i );
363
+ });
364
+ }
365
+ };
366
+
367
+ // patch $.event.$dispatch to allow suppressing clicks
368
+ var $dispatch = $event.dispatch;
369
+ $event.dispatch = function( event ){
370
+ if ( $.data( this, "suppress."+ event.type ) - new Date().getTime() > 0 ){
371
+ $.removeData( this, "suppress."+ event.type );
372
+ return;
373
+ }
374
+ return $dispatch.apply( this, arguments );
375
+ };
376
+
377
+ // event fix hooks for touch events...
378
+ var touchHooks =
379
+ $event.fixHooks.touchstart =
380
+ $event.fixHooks.touchmove =
381
+ $event.fixHooks.touchend =
382
+ $event.fixHooks.touchcancel = {
383
+ props: "clientX clientY pageX pageY screenX screenY".split( " " ),
384
+ filter: function( event, orig ) {
385
+ if ( orig ){
386
+ var touched = ( orig.touches && orig.touches[0] )
387
+ || ( orig.changedTouches && orig.changedTouches[0] )
388
+ || null;
389
+ // iOS webkit: touchstart, touchmove, touchend
390
+ if ( touched )
391
+ $.each( touchHooks.props, function( i, prop ){
392
+ event[ prop ] = touched[ prop ];
393
+ });
394
+ }
395
+ return event;
396
+ }
397
+ };
398
+
399
+ // share the same special event configuration with related events...
400
+ $special.draginit = $special.dragstart = $special.dragend = drag;
401
+
402
+ })( jQuery );