dataclips 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (158) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.rdoc +3 -0
  4. data/Rakefile +34 -0
  5. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.eot +0 -0
  6. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.svg +288 -0
  7. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.ttf +0 -0
  8. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.woff +0 -0
  9. data/app/assets/fonts/bootstrap/glyphicons-halflings-regular.woff2 +0 -0
  10. data/app/assets/images/dataclips/slickgrid/sort-asc.png +0 -0
  11. data/app/assets/images/dataclips/slickgrid/sort-desc.png +0 -0
  12. data/app/assets/javascripts/dataclips/application.js +26 -0
  13. data/app/assets/javascripts/dataclips/backbone.js +1608 -0
  14. data/app/assets/javascripts/dataclips/dataclips.js.coffee +168 -0
  15. data/app/assets/javascripts/dataclips/formatters.js.coffee +14 -0
  16. data/app/assets/javascripts/dataclips/namespace.js.coffee +1 -0
  17. data/app/assets/javascripts/dataclips/record.js.coffee +39 -0
  18. data/app/assets/javascripts/dataclips/slickgrid.js +5 -0
  19. data/app/assets/javascripts/dataclips/slickgrid/jquery.event.drag-2.2.js +402 -0
  20. data/app/assets/javascripts/dataclips/slickgrid/plugins/slick.rowselectionmodel.js +187 -0
  21. data/app/assets/javascripts/dataclips/slickgrid/slick.core.js +458 -0
  22. data/app/assets/javascripts/dataclips/slickgrid/slick.dataview.js +1063 -0
  23. data/app/assets/javascripts/dataclips/slickgrid/slick.grid.js +3309 -0
  24. data/app/assets/javascripts/dataclips/underscore.js +1416 -0
  25. data/app/assets/stylesheets/bootstrap/_alerts.scss +68 -0
  26. data/app/assets/stylesheets/bootstrap/_badges.scss +63 -0
  27. data/app/assets/stylesheets/bootstrap/_breadcrumbs.scss +26 -0
  28. data/app/assets/stylesheets/bootstrap/_button-groups.scss +243 -0
  29. data/app/assets/stylesheets/bootstrap/_buttons.scss +160 -0
  30. data/app/assets/stylesheets/bootstrap/_carousel.scss +267 -0
  31. data/app/assets/stylesheets/bootstrap/_close.scss +35 -0
  32. data/app/assets/stylesheets/bootstrap/_code.scss +69 -0
  33. data/app/assets/stylesheets/bootstrap/_component-animations.scss +38 -0
  34. data/app/assets/stylesheets/bootstrap/_dropdowns.scss +213 -0
  35. data/app/assets/stylesheets/bootstrap/_forms.scss +548 -0
  36. data/app/assets/stylesheets/bootstrap/_glyphicons.scss +234 -0
  37. data/app/assets/stylesheets/bootstrap/_grid.scss +84 -0
  38. data/app/assets/stylesheets/bootstrap/_input-groups.scss +166 -0
  39. data/app/assets/stylesheets/bootstrap/_jumbotron.scss +49 -0
  40. data/app/assets/stylesheets/bootstrap/_labels.scss +66 -0
  41. data/app/assets/stylesheets/bootstrap/_list-group.scss +124 -0
  42. data/app/assets/stylesheets/bootstrap/_media.scss +47 -0
  43. data/app/assets/stylesheets/bootstrap/_mixins.scss +39 -0
  44. data/app/assets/stylesheets/bootstrap/_modals.scss +148 -0
  45. data/app/assets/stylesheets/bootstrap/_navbar.scss +662 -0
  46. data/app/assets/stylesheets/bootstrap/_navs.scss +244 -0
  47. data/app/assets/stylesheets/bootstrap/_normalize.scss +427 -0
  48. data/app/assets/stylesheets/bootstrap/_pager.scss +54 -0
  49. data/app/assets/stylesheets/bootstrap/_pagination.scss +88 -0
  50. data/app/assets/stylesheets/bootstrap/_panels.scss +261 -0
  51. data/app/assets/stylesheets/bootstrap/_popovers.scss +135 -0
  52. data/app/assets/stylesheets/bootstrap/_print.scss +107 -0
  53. data/app/assets/stylesheets/bootstrap/_progress-bars.scss +87 -0
  54. data/app/assets/stylesheets/bootstrap/_responsive-embed.scss +35 -0
  55. data/app/assets/stylesheets/bootstrap/_responsive-utilities.scss +174 -0
  56. data/app/assets/stylesheets/bootstrap/_scaffolding.scss +150 -0
  57. data/app/assets/stylesheets/bootstrap/_tables.scss +234 -0
  58. data/app/assets/stylesheets/bootstrap/_theme.scss +272 -0
  59. data/app/assets/stylesheets/bootstrap/_thumbnails.scss +38 -0
  60. data/app/assets/stylesheets/bootstrap/_tooltip.scss +103 -0
  61. data/app/assets/stylesheets/bootstrap/_type.scss +298 -0
  62. data/app/assets/stylesheets/bootstrap/_utilities.scss +56 -0
  63. data/app/assets/stylesheets/bootstrap/_variables.scss +864 -0
  64. data/app/assets/stylesheets/bootstrap/_wells.scss +29 -0
  65. data/app/assets/stylesheets/bootstrap/mixins/_alerts.scss +14 -0
  66. data/app/assets/stylesheets/bootstrap/mixins/_background-variant.scss +11 -0
  67. data/app/assets/stylesheets/bootstrap/mixins/_border-radius.scss +18 -0
  68. data/app/assets/stylesheets/bootstrap/mixins/_buttons.scss +52 -0
  69. data/app/assets/stylesheets/bootstrap/mixins/_center-block.scss +7 -0
  70. data/app/assets/stylesheets/bootstrap/mixins/_clearfix.scss +22 -0
  71. data/app/assets/stylesheets/bootstrap/mixins/_forms.scss +88 -0
  72. data/app/assets/stylesheets/bootstrap/mixins/_gradients.scss +58 -0
  73. data/app/assets/stylesheets/bootstrap/mixins/_grid-framework.scss +81 -0
  74. data/app/assets/stylesheets/bootstrap/mixins/_grid.scss +122 -0
  75. data/app/assets/stylesheets/bootstrap/mixins/_hide-text.scss +21 -0
  76. data/app/assets/stylesheets/bootstrap/mixins/_image.scss +33 -0
  77. data/app/assets/stylesheets/bootstrap/mixins/_labels.scss +12 -0
  78. data/app/assets/stylesheets/bootstrap/mixins/_list-group.scss +31 -0
  79. data/app/assets/stylesheets/bootstrap/mixins/_nav-divider.scss +10 -0
  80. data/app/assets/stylesheets/bootstrap/mixins/_nav-vertical-align.scss +9 -0
  81. data/app/assets/stylesheets/bootstrap/mixins/_opacity.scss +8 -0
  82. data/app/assets/stylesheets/bootstrap/mixins/_pagination.scss +23 -0
  83. data/app/assets/stylesheets/bootstrap/mixins/_panels.scss +24 -0
  84. data/app/assets/stylesheets/bootstrap/mixins/_progress-bar.scss +10 -0
  85. data/app/assets/stylesheets/bootstrap/mixins/_reset-filter.scss +8 -0
  86. data/app/assets/stylesheets/bootstrap/mixins/_resize.scss +6 -0
  87. data/app/assets/stylesheets/bootstrap/mixins/_responsive-visibility.scss +21 -0
  88. data/app/assets/stylesheets/bootstrap/mixins/_size.scss +10 -0
  89. data/app/assets/stylesheets/bootstrap/mixins/_tab-focus.scss +9 -0
  90. data/app/assets/stylesheets/bootstrap/mixins/_table-row.scss +28 -0
  91. data/app/assets/stylesheets/bootstrap/mixins/_text-emphasis.scss +11 -0
  92. data/app/assets/stylesheets/bootstrap/mixins/_text-overflow.scss +8 -0
  93. data/app/assets/stylesheets/bootstrap/mixins/_vendor-prefixes.scss +222 -0
  94. data/app/assets/stylesheets/dataclips/_bootstrap.scss +53 -0
  95. data/app/assets/stylesheets/dataclips/application.css +18 -0
  96. data/app/assets/stylesheets/dataclips/slickgrid/slickgrid.sass +341 -0
  97. data/app/controllers/dataclips/application_controller.rb +31 -0
  98. data/app/controllers/dataclips/clips_controller.rb +46 -0
  99. data/app/controllers/dataclips/insights_controller.rb +45 -0
  100. data/app/helpers/dataclips/application_helper.rb +4 -0
  101. data/app/models/dataclips/clip.rb +79 -0
  102. data/app/models/dataclips/insight.rb +18 -0
  103. data/app/models/dataclips/sql_query.rb +36 -0
  104. data/app/views/dataclips/application/_boolean.html.erb +1 -0
  105. data/app/views/dataclips/application/_date.html.erb +22 -0
  106. data/app/views/dataclips/application/_datetime.html.erb +25 -0
  107. data/app/views/dataclips/application/_float.html.erb +7 -0
  108. data/app/views/dataclips/application/_integer.html.erb +7 -0
  109. data/app/views/dataclips/application/_text.html.erb +10 -0
  110. data/app/views/dataclips/application/_time.html.erb +23 -0
  111. data/app/views/dataclips/clips/edit.html.erb +14 -0
  112. data/app/views/dataclips/clips/show.html.erb +109 -0
  113. data/app/views/dataclips/insights/show.html.erb +99 -0
  114. data/app/views/layouts/dataclips/application.html.erb +14 -0
  115. data/config/initializers/assets.rb +1 -0
  116. data/config/routes.rb +9 -0
  117. data/db/migrate/20150101143530_create_dataclips_insights.rb +12 -0
  118. data/lib/dataclips.rb +47 -0
  119. data/lib/dataclips/engine.rb +29 -0
  120. data/lib/dataclips/version.rb +3 -0
  121. data/lib/tasks/dataclips_tasks.rake +4 -0
  122. data/test/dataclips_test.rb +7 -0
  123. data/test/dummy/README.rdoc +28 -0
  124. data/test/dummy/Rakefile +6 -0
  125. data/test/dummy/app/assets/javascripts/application.js +13 -0
  126. data/test/dummy/app/assets/stylesheets/application.css +15 -0
  127. data/test/dummy/app/controllers/application_controller.rb +5 -0
  128. data/test/dummy/app/helpers/application_helper.rb +2 -0
  129. data/test/dummy/app/views/layouts/application.html.erb +14 -0
  130. data/test/dummy/bin/bundle +3 -0
  131. data/test/dummy/bin/rails +4 -0
  132. data/test/dummy/bin/rake +4 -0
  133. data/test/dummy/config.ru +4 -0
  134. data/test/dummy/config/application.rb +23 -0
  135. data/test/dummy/config/boot.rb +5 -0
  136. data/test/dummy/config/database.yml +25 -0
  137. data/test/dummy/config/environment.rb +5 -0
  138. data/test/dummy/config/environments/development.rb +37 -0
  139. data/test/dummy/config/environments/production.rb +78 -0
  140. data/test/dummy/config/environments/test.rb +39 -0
  141. data/test/dummy/config/initializers/assets.rb +8 -0
  142. data/test/dummy/config/initializers/backtrace_silencers.rb +7 -0
  143. data/test/dummy/config/initializers/cookies_serializer.rb +3 -0
  144. data/test/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  145. data/test/dummy/config/initializers/inflections.rb +16 -0
  146. data/test/dummy/config/initializers/mime_types.rb +4 -0
  147. data/test/dummy/config/initializers/session_store.rb +3 -0
  148. data/test/dummy/config/initializers/wrap_parameters.rb +14 -0
  149. data/test/dummy/config/locales/en.yml +23 -0
  150. data/test/dummy/config/routes.rb +4 -0
  151. data/test/dummy/config/secrets.yml +22 -0
  152. data/test/dummy/public/404.html +67 -0
  153. data/test/dummy/public/422.html +67 -0
  154. data/test/dummy/public/500.html +66 -0
  155. data/test/dummy/public/favicon.ico +0 -0
  156. data/test/integration/navigation_test.rb +10 -0
  157. data/test/test_helper.rb +17 -0
  158. metadata +348 -0
@@ -0,0 +1,168 @@
1
+ class Dataclips.View extends Backbone.View
2
+ events:
3
+ "input input[type=text]": _.debounce (event) ->
4
+ @filterArgs.set(event.target.name, $.trim(event.target.value))
5
+
6
+ "change input.float[type=number]": _.debounce (event) ->
7
+ value = parseFloat(event.target.value)
8
+ if _.isNaN(value)
9
+ @filterArgs.unset(event.target.name)
10
+ else
11
+ @filterArgs.set(event.target.name, value)
12
+
13
+ "change input.integer[type=number]": _.debounce (event) ->
14
+ value = parseInt(event.target.value)
15
+ if _.isNaN(value)
16
+ @filterArgs.unset(event.target.name)
17
+ else
18
+ @filterArgs.set(event.target.name, value)
19
+
20
+ "dp.change .input-group": _.debounce (event) ->
21
+ value = event.date
22
+ attrName = $(event.target).attr("rel")
23
+ if value?
24
+ @filterArgs.set(attrName, value)
25
+ else
26
+ @filterArgs.unset(attrName)
27
+
28
+ render: ->
29
+ @filterArgs = new Backbone.Model
30
+
31
+ options =
32
+ enableColumnReorder: false
33
+ forceFitColumns: true
34
+
35
+ dataView = new Slick.Data.DataView()
36
+ dataView.setFilterArgs(@filterArgs.toJSON())
37
+
38
+ @listenTo @filterArgs, "change", (model, data) ->
39
+ dataView.setFilterArgs(model.attributes)
40
+ dataView.refresh()
41
+
42
+ columns = []
43
+
44
+ _.each Dataclips.config.schema, (options, attr) ->
45
+ formatter = if options.formatter?
46
+ options.formatter
47
+ else
48
+ options.type
49
+
50
+ columns.push
51
+ focusable: true
52
+ field: attr
53
+ id: attr
54
+ name: Dataclips.config.headers[attr]
55
+ sortable: options.sortable?
56
+ cssClass: options.type
57
+ headerCssClass: options.type
58
+ formatter: Dataclips.Formatters[formatter]
59
+ width: options.width
60
+
61
+ grid = new Slick.Grid("#grid", dataView, columns, options)
62
+
63
+ # grid.onSelectedRowsChanged.subscribe (e, args) ->
64
+ # console.log(grid.getSelectedRows())
65
+
66
+ # grid.setSelectionModel(new Slick.RowSelectionModel)
67
+
68
+ grid.onSort.subscribe (e, args) ->
69
+ sortcol = args.sortCol.field
70
+
71
+ compareByColumn = (a, b) ->
72
+ x = a[sortcol] || ""
73
+ y = b[sortcol] || ""
74
+ if x is y
75
+ 0
76
+ else
77
+ if x > y then 1 else -1
78
+
79
+ dataView.sort(compareByColumn, args.sortAsc)
80
+
81
+ textFilter = (item, attr, query) ->
82
+ return true unless query
83
+ return true if _.isEmpty query.trim()
84
+ item[attr]?.toLowerCase().indexOf(query.toLowerCase()) != -1
85
+
86
+ numericFilter = (item, attr, range) ->
87
+ value = item[attr]
88
+ return true if value is undefined
89
+ if range.from? || range.to?
90
+ gte = (from) ->
91
+ return true if from is undefined
92
+ value >= from
93
+
94
+ lte = (to) ->
95
+ return true if to is undefined
96
+ value <= to
97
+
98
+ gte(range.from) && lte(range.to)
99
+ else
100
+ true
101
+
102
+ dateFilter = (item, attr, range) ->
103
+ value = item[attr]
104
+ return true if value is undefined
105
+ if range.from? || range.to?
106
+ gte = (from) ->
107
+ return true if from is undefined
108
+ value >= from
109
+
110
+ lte = (to) ->
111
+ return true if to is undefined
112
+ value <= to
113
+
114
+ gte(range.from) && lte(range.to)
115
+ else
116
+ true
117
+
118
+ exactMatcher = (item, attr, query) ->
119
+ return true unless query
120
+ return true if _.isEmpty query.trim()
121
+ item[attr] is query
122
+
123
+ dataView.setFilter (item, args) ->
124
+ _.all Dataclips.config.schema, (options, attr) ->
125
+ switch options.type
126
+ when "text"
127
+ textFilter(item, attr, args[attr])
128
+ when "integer", "float", "decimal"
129
+ numericFilter(item, attr, {
130
+ from: args["#{attr}_from"],
131
+ to: args["#{attr}_to"]
132
+ })
133
+ when "datetime", "date"
134
+ dateFilter(item, attr, {
135
+ from: args["#{attr}_from"],
136
+ to: args["#{attr}_to"]
137
+ })
138
+ else
139
+ true
140
+
141
+ dataView.onRowCountChanged.subscribe (e, args) ->
142
+ grid.updateRowCount()
143
+ grid.render()
144
+
145
+ dataView.onRowsChanged.subscribe (e, args) ->
146
+ grid.invalidateRows(args.rows)
147
+ grid.render()
148
+
149
+
150
+ dataView.onPagingInfoChanged.subscribe (e, args) ->
151
+ $("span.count").text(args.totalRows)
152
+
153
+ updateDataView = (data) ->
154
+ dataView.beginUpdate()
155
+ dataView.setItems(data)
156
+ dataView.endUpdate()
157
+
158
+ @listenTo @collection, "batchInsert", ->
159
+ updateDataView(@collection.toJSON())
160
+
161
+ Dataclips.run = ->
162
+ collection = new Dataclips.Records
163
+ collection.url = @config.url
164
+
165
+ view = new Dataclips.View(el: "#dataclip", collection: collection)
166
+
167
+ view.render()
168
+ collection.fetchInBatches(@config.params)
@@ -0,0 +1,14 @@
1
+ Dataclips.Formatters =
2
+ text: (row, cell, value, columnDef, context) -> value
3
+ integer: (row, cell, value, columnDef, context) -> value
4
+ float: (row, cell, value, columnDef, context) -> value
5
+ decimal: (row, cell, value, columnDef, context) -> value
6
+ date: (row, cell, value, columnDef, context) -> value.format('L')
7
+ time: (row, cell, value, columnDef, context) -> value.format('h:mm:ss')
8
+ datetime: (row, cell, value, columnDef, context) -> value.format('L HH:mm:ss')
9
+ binary: (row, cell, value, columnDef, context) -> value
10
+ boolean: (row, cell, value, columnDef, context) ->
11
+ if value is true then "&#9679" else "&#9675;"
12
+
13
+ email: (row, cell, value, columnDef, context) ->
14
+ "<a href='mailto:#{value}'>#{value}</a>"
@@ -0,0 +1 @@
1
+ window.Dataclips = {}
@@ -0,0 +1,39 @@
1
+ class Dataclips.Record extends Backbone.Model
2
+ parse: (options) ->
3
+ attributes = _.reduce options, (memo, value, key) ->
4
+ memo[key] = switch Dataclips.config.schema[key].type
5
+ when "datetime", "time", "date"
6
+ moment(value)
7
+ else
8
+ value
9
+
10
+ memo
11
+ , {}
12
+
13
+ attributes.id = @cid
14
+ super(attributes)
15
+
16
+
17
+ class Dataclips.Records extends Backbone.Collection
18
+ model: Dataclips.Record
19
+ fetchInBatches: (defaultParams = {}) ->
20
+
21
+ fetchNextPage = (collection, current_page, total_pages) ->
22
+ if current_page < total_pages
23
+ collection.fetch
24
+ data: _({page: current_page + 1}).extend(defaultParams),
25
+ remove: false,
26
+ success: (collection, data) ->
27
+ collection.trigger "batchInsert", data.orders
28
+ fetchNextPage(collection, data.page, data.total_pages)
29
+
30
+ @fetch
31
+ data: defaultParams
32
+ success: (collection, data) ->
33
+ $("span.total_entries").text(data.total_entries)
34
+ collection.trigger "batchInsert", data.records
35
+ fetchNextPage(collection, data.page, data.total_pages)
36
+ error: (collection, response) ->
37
+ alert(response.responseText)
38
+
39
+ parse: (data) -> data.records
@@ -0,0 +1,5 @@
1
+ //= require ./slickgrid/jquery.event.drag-2.2
2
+ //= require ./slickgrid/slick.core
3
+ //= require ./slickgrid/slick.grid
4
+ //= require ./slickgrid/slick.dataview
5
+ //= require ./slickgrid/plugins/slick.rowselectionmodel
@@ -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 );