1 | define([ |
---|
2 | "dojo/_base/array", |
---|
3 | "dojo/_base/connect", |
---|
4 | "dojo/_base/declare", |
---|
5 | "dojo/_base/event", |
---|
6 | "dojo/_base/lang", |
---|
7 | "dojo/_base/window", |
---|
8 | "dojo/dom-geometry", |
---|
9 | "dojo/dom-style", |
---|
10 | "dojo/touch", |
---|
11 | "dijit/registry", |
---|
12 | "./IconItem", |
---|
13 | "./sniff", |
---|
14 | "./viewRegistry", |
---|
15 | "./_css3" |
---|
16 | ], function(array, connect, declare, event, lang, win, domGeometry, domStyle, touch, registry, IconItem, has, viewRegistry, css3){ |
---|
17 | |
---|
18 | // module: |
---|
19 | // dojox/mobile/_EditableIconMixin |
---|
20 | |
---|
21 | return declare("dojox.mobile._EditableIconMixin", null, { |
---|
22 | // summary: |
---|
23 | // A mixin for IconContainer to make it editable. |
---|
24 | |
---|
25 | deleteIconForEdit: "mblDomButtonBlackCircleCross", |
---|
26 | threshold: 4, // drag threshold value in pixels |
---|
27 | |
---|
28 | destroy: function(){ |
---|
29 | // summary: |
---|
30 | // Destroys the container. |
---|
31 | if(this._blankItem){ |
---|
32 | this._blankItem.destroy(); |
---|
33 | } |
---|
34 | this.inherited(arguments); |
---|
35 | }, |
---|
36 | |
---|
37 | startEdit: function(){ |
---|
38 | // summary: |
---|
39 | // Starts the editing. |
---|
40 | if(!this.editable || this.isEditing){ return; } |
---|
41 | |
---|
42 | this.isEditing = true; |
---|
43 | if(!this._handles){ |
---|
44 | this._handles = [ |
---|
45 | this.connect(this.domNode, css3.name("transitionStart"), "_onTransitionStart"), |
---|
46 | this.connect(this.domNode, css3.name("transitionEnd"), "_onTransitionEnd") |
---|
47 | ]; |
---|
48 | } |
---|
49 | |
---|
50 | var count = 0; |
---|
51 | array.forEach(this.getChildren(), function(w){ |
---|
52 | this.defer(function(){ |
---|
53 | w.set("deleteIcon", this.deleteIconForEdit); |
---|
54 | if(w.deleteIconNode){ |
---|
55 | w._deleteHandle = this.connect(w.deleteIconNode, "onclick", "_deleteIconClicked"); |
---|
56 | } |
---|
57 | w.highlight(0); |
---|
58 | }, 15*count++); |
---|
59 | }, this); |
---|
60 | |
---|
61 | connect.publish("/dojox/mobile/startEdit", [this]); // pubsub |
---|
62 | this.onStartEdit(); // callback |
---|
63 | }, |
---|
64 | |
---|
65 | endEdit: function(){ |
---|
66 | // summary: |
---|
67 | // Ends the editing. |
---|
68 | if(!this.isEditing){ return; } |
---|
69 | |
---|
70 | array.forEach(this.getChildren(), function(w){ |
---|
71 | w.unhighlight(); |
---|
72 | if(w._deleteHandle){ |
---|
73 | this.disconnect(w._deleteHandle); |
---|
74 | w._deleteHandle = null; |
---|
75 | } |
---|
76 | w.set("deleteIcon", ""); |
---|
77 | }, this); |
---|
78 | |
---|
79 | this._movingItem = null; |
---|
80 | if(this._handles){ |
---|
81 | array.forEach(this._handles, this.disconnect, this); |
---|
82 | this._handles = null; |
---|
83 | } |
---|
84 | |
---|
85 | connect.publish("/dojox/mobile/endEdit", [this]); // pubsub |
---|
86 | this.onEndEdit(); // callback |
---|
87 | this.isEditing = false; |
---|
88 | }, |
---|
89 | |
---|
90 | scaleItem: function(/*Widget*/widget, /*Number*/ratio){ |
---|
91 | // summary: |
---|
92 | // Scales an item according to the specified ratio. |
---|
93 | domStyle.set(widget.domNode, css3.add({}, { |
---|
94 | transition: has("android") ? "" : css3.name("transform", true) + " .1s ease-in-out", |
---|
95 | transform: ratio == 1 ? "" : "scale(" + ratio + ")" |
---|
96 | })); |
---|
97 | }, |
---|
98 | |
---|
99 | _onTransitionStart: function(e){ |
---|
100 | // tags: |
---|
101 | // private |
---|
102 | event.stop(e); |
---|
103 | }, |
---|
104 | |
---|
105 | _onTransitionEnd: function(e){ |
---|
106 | // tags: |
---|
107 | // private |
---|
108 | event.stop(e); |
---|
109 | var w = registry.getEnclosingWidget(e.target); |
---|
110 | w._moving = false; |
---|
111 | domStyle.set(w.domNode, css3.name("transition"), ""); |
---|
112 | }, |
---|
113 | |
---|
114 | _onTouchStart: function(e){ |
---|
115 | // tags: |
---|
116 | // private |
---|
117 | if(!this._blankItem){ |
---|
118 | this._blankItem = new IconItem(); |
---|
119 | this._blankItem.domNode.style.visibility = "hidden"; |
---|
120 | this._blankItem._onClick = function(){}; |
---|
121 | } |
---|
122 | var item = this._movingItem = registry.getEnclosingWidget(e.target); |
---|
123 | var iconPressed = false; |
---|
124 | var n; |
---|
125 | for(n = e.target; n !== item.domNode; n = n.parentNode){ |
---|
126 | if(n === item.iconNode){ |
---|
127 | iconPressed = true; |
---|
128 | break; |
---|
129 | } |
---|
130 | } |
---|
131 | if(!iconPressed){ return; } |
---|
132 | |
---|
133 | if(!this._conn){ |
---|
134 | this._conn = [ |
---|
135 | this.connect(this.domNode, touch.move, "_onTouchMove"), |
---|
136 | this.connect(win.doc, touch.release, "_onTouchEnd") |
---|
137 | ]; |
---|
138 | } |
---|
139 | this._touchStartPosX = e.touches ? e.touches[0].pageX : e.pageX; |
---|
140 | this._touchStartPosY = e.touches ? e.touches[0].pageY : e.pageY; |
---|
141 | if(this.isEditing){ |
---|
142 | this._onDragStart(e); |
---|
143 | }else{ |
---|
144 | // set timer to detect long press |
---|
145 | this._pressTimer = this.defer(function(){ |
---|
146 | this.startEdit(); |
---|
147 | this._onDragStart(e); |
---|
148 | }, 1000); |
---|
149 | } |
---|
150 | }, |
---|
151 | |
---|
152 | _onDragStart: function(e){ |
---|
153 | // tags: |
---|
154 | // private |
---|
155 | this._dragging = true; |
---|
156 | |
---|
157 | var movingItem = this._movingItem; |
---|
158 | if(movingItem.get("selected")){ |
---|
159 | movingItem.set("selected", false); |
---|
160 | } |
---|
161 | this.scaleItem(movingItem, 1.1); |
---|
162 | |
---|
163 | var x = e.touches ? e.touches[0].pageX : e.pageX; |
---|
164 | var y = e.touches ? e.touches[0].pageY : e.pageY; |
---|
165 | |
---|
166 | var enclosingScrollable = viewRegistry.getEnclosingScrollable(movingItem.domNode); |
---|
167 | var dx = 0; |
---|
168 | var dy = 0; |
---|
169 | if(enclosingScrollable){ // this node is placed inside a scrollable |
---|
170 | var pos = enclosingScrollable.getPos(); |
---|
171 | dx = pos.x; |
---|
172 | dy = pos.y; |
---|
173 | event.stop(e); |
---|
174 | } |
---|
175 | |
---|
176 | var startPos = this._startPos = domGeometry.position(movingItem.domNode, true); |
---|
177 | this._offsetPos = { |
---|
178 | x: startPos.x - x - dx, |
---|
179 | y: startPos.y - y - dy |
---|
180 | }; |
---|
181 | |
---|
182 | this._startIndex = this.getIndexOfChild(movingItem); |
---|
183 | this.addChild(this._blankItem, this._startIndex); |
---|
184 | this.moveChild(movingItem, this.getChildren().length); |
---|
185 | domStyle.set(movingItem.domNode, { |
---|
186 | position: "absolute", |
---|
187 | top: (startPos.y - dy) + "px", |
---|
188 | left: (startPos.x - dx) + "px", |
---|
189 | zIndex: 100 |
---|
190 | }); |
---|
191 | }, |
---|
192 | |
---|
193 | _onTouchMove: function(e){ |
---|
194 | // tags: |
---|
195 | // private |
---|
196 | var x = e.touches ? e.touches[0].pageX : e.pageX; |
---|
197 | var y = e.touches ? e.touches[0].pageY : e.pageY; |
---|
198 | if(this._dragging){ |
---|
199 | domStyle.set(this._movingItem.domNode, { |
---|
200 | top: (this._offsetPos.y + y) + "px", |
---|
201 | left: (this._offsetPos.x + x) + "px" |
---|
202 | }); |
---|
203 | this._detectOverlap({x: x, y: y}); |
---|
204 | event.stop(e); |
---|
205 | }else{ |
---|
206 | var dx = Math.abs(this._touchStartPosX - x); |
---|
207 | var dy = Math.abs(this._touchStartPosY - y); |
---|
208 | if (dx > this.threshold || dy > this.threshold) { |
---|
209 | this._clearPressTimer(); |
---|
210 | } |
---|
211 | } |
---|
212 | }, |
---|
213 | |
---|
214 | _onTouchEnd: function(e){ |
---|
215 | // tags: |
---|
216 | // private |
---|
217 | this._clearPressTimer(); |
---|
218 | if(this._conn){ |
---|
219 | array.forEach(this._conn, this.disconnect, this); |
---|
220 | this._conn = null; |
---|
221 | } |
---|
222 | |
---|
223 | if(this._dragging){ |
---|
224 | this._dragging = false; |
---|
225 | |
---|
226 | var movingItem = this._movingItem; |
---|
227 | this.scaleItem(movingItem, 1.0); |
---|
228 | domStyle.set(movingItem.domNode, { |
---|
229 | position: "", |
---|
230 | top: "", |
---|
231 | left: "", |
---|
232 | zIndex: "" |
---|
233 | }); |
---|
234 | var startIndex = this._startIndex; |
---|
235 | var endIndex = this.getIndexOfChild(this._blankItem); |
---|
236 | this.moveChild(movingItem, endIndex); |
---|
237 | this.removeChild(this._blankItem); |
---|
238 | connect.publish("/dojox/mobile/moveIconItem", [this, movingItem, startIndex, endIndex]); // pubsub |
---|
239 | this.onMoveItem(movingItem, startIndex, endIndex); // callback |
---|
240 | } |
---|
241 | }, |
---|
242 | |
---|
243 | _clearPressTimer: function(){ |
---|
244 | // tags: |
---|
245 | // private |
---|
246 | if(this._pressTimer){ |
---|
247 | this._pressTimer.remove(); |
---|
248 | this._pressTimer = null; |
---|
249 | } |
---|
250 | }, |
---|
251 | |
---|
252 | _detectOverlap: function(/*Object*/point){ |
---|
253 | // tags: |
---|
254 | // private |
---|
255 | var children = this.getChildren(), |
---|
256 | blankItem = this._blankItem, |
---|
257 | blankPos = domGeometry.position(blankItem.domNode, true), |
---|
258 | blankIndex = this.getIndexOfChild(blankItem), |
---|
259 | dir = 1, |
---|
260 | i, w, pos; |
---|
261 | if(this._contains(point, blankPos)){ |
---|
262 | return; |
---|
263 | }else if(point.y < blankPos.y || (point.y <= blankPos.y + blankPos.h && point.x < blankPos.x)){ |
---|
264 | dir = -1; |
---|
265 | } |
---|
266 | for(i = blankIndex + dir; i>=0 && i<children.length-1; i += dir){ |
---|
267 | w = children[i]; |
---|
268 | if(w._moving){ continue; } |
---|
269 | pos = domGeometry.position(w.domNode, true); |
---|
270 | if(this._contains(point, pos)){ |
---|
271 | this.defer(function(){ |
---|
272 | this.moveChildWithAnimation(blankItem, dir == 1 ? i+1 : i); |
---|
273 | }); |
---|
274 | break; |
---|
275 | }else if((dir == 1 && pos.y > point.y) || (dir == -1 && pos.y + pos.h < point.y)){ |
---|
276 | break; |
---|
277 | } |
---|
278 | } |
---|
279 | }, |
---|
280 | |
---|
281 | _contains: function(point, pos){ |
---|
282 | // tags: |
---|
283 | // private |
---|
284 | return pos.x < point.x && point.x < pos.x + pos.w && pos.y < point.y && point.y < pos.y + pos.h; |
---|
285 | }, |
---|
286 | |
---|
287 | _animate: function(/*int*/from, /*int*/to){ |
---|
288 | // tags: |
---|
289 | // private |
---|
290 | if(from == to) { return; } |
---|
291 | var dir = from < to ? 1 : -1; |
---|
292 | var children = this.getChildren(); |
---|
293 | var posArray = []; |
---|
294 | var i; |
---|
295 | for(i=from; i!=to; i+=dir){ |
---|
296 | posArray.push({ |
---|
297 | t: (children[i+dir].domNode.offsetTop - children[i].domNode.offsetTop) + "px", |
---|
298 | l: (children[i+dir].domNode.offsetLeft - children[i].domNode.offsetLeft) + "px" |
---|
299 | }); |
---|
300 | } |
---|
301 | for(i=from, j=0; i!=to; i+=dir, j++){ |
---|
302 | var w = children[i]; |
---|
303 | w._moving = true; |
---|
304 | domStyle.set(w.domNode, { |
---|
305 | top: posArray[j].t, |
---|
306 | left: posArray[j].l |
---|
307 | }); |
---|
308 | this.defer(lang.hitch(w, function(){ |
---|
309 | domStyle.set(this.domNode, css3.add({ |
---|
310 | top: "0px", |
---|
311 | left: "0px" |
---|
312 | }, { |
---|
313 | transition: "top .3s ease-in-out, left .3s ease-in-out" |
---|
314 | })); |
---|
315 | }), j*10); |
---|
316 | } |
---|
317 | }, |
---|
318 | |
---|
319 | removeChildWithAnimation: function(/*Widget|Number*/widget){ |
---|
320 | // summary: |
---|
321 | // Removes the given child with animation. |
---|
322 | var index = (typeof widget === "number") ? widget : this.getIndexOfChild(widget); |
---|
323 | this.removeChild(widget); |
---|
324 | |
---|
325 | // Show remove animation |
---|
326 | if(this._blankItem){ |
---|
327 | // #16868 - no _blankItem if calling deleteItem() programmatically, that is |
---|
328 | // without _onTouchStart() being called. |
---|
329 | this.addChild(this._blankItem); |
---|
330 | } |
---|
331 | this._animate(index, this.getChildren().length - 1); |
---|
332 | if(this._blankItem){ |
---|
333 | this.removeChild(this._blankItem); |
---|
334 | } |
---|
335 | }, |
---|
336 | |
---|
337 | moveChild: function(/*Widget|Number*/widget, /*Number?*/insertIndex){ |
---|
338 | // summary: |
---|
339 | // Moves a child without animation. |
---|
340 | this.addChild(widget, insertIndex); |
---|
341 | this.paneContainerWidget.addChild(widget.paneWidget, insertIndex); |
---|
342 | }, |
---|
343 | |
---|
344 | moveChildWithAnimation: function(/*Widget|Number*/widget, /*Number?*/insertIndex){ |
---|
345 | // summary: |
---|
346 | // Moves a child with animation. |
---|
347 | var index = this.getIndexOfChild(this._blankItem); |
---|
348 | this.moveChild(widget, insertIndex); |
---|
349 | |
---|
350 | // Show move animation |
---|
351 | this._animate(index, insertIndex); |
---|
352 | }, |
---|
353 | |
---|
354 | _deleteIconClicked: function(e){ |
---|
355 | // summary: |
---|
356 | // Internal handler for click events. |
---|
357 | // tags: |
---|
358 | // private |
---|
359 | if(this.deleteIconClicked(e) === false){ return; } // user's click action |
---|
360 | var item = registry.getEnclosingWidget(e.target); |
---|
361 | this.deleteItem(item); |
---|
362 | }, |
---|
363 | |
---|
364 | deleteIconClicked: function(/*Event*/ /*===== e =====*/){ |
---|
365 | // summary: |
---|
366 | // User-defined function to handle clicks for the delete icon. |
---|
367 | // tags: |
---|
368 | // callback |
---|
369 | }, |
---|
370 | |
---|
371 | deleteItem: function(/*Widget*/item){ |
---|
372 | // summary: |
---|
373 | // Deletes the given item. |
---|
374 | if(item._deleteHandle){ |
---|
375 | this.disconnect(item._deleteHandle); |
---|
376 | } |
---|
377 | this.removeChildWithAnimation(item); |
---|
378 | |
---|
379 | connect.publish("/dojox/mobile/deleteIconItem", [this, item]); // pubsub |
---|
380 | this.onDeleteItem(item); // callback |
---|
381 | |
---|
382 | item.destroy(); |
---|
383 | }, |
---|
384 | |
---|
385 | onDeleteItem: function(/*Widget*/item){ |
---|
386 | // summary: |
---|
387 | // Stub function to connect to from your application. |
---|
388 | }, |
---|
389 | |
---|
390 | onMoveItem: function(/*Widget*/item, /*int*/from, /*int*/to){ |
---|
391 | // summary: |
---|
392 | // Stub function to connect to from your application. |
---|
393 | }, |
---|
394 | |
---|
395 | onStartEdit: function(){ |
---|
396 | // summary: |
---|
397 | // Stub function to connect to from your application. |
---|
398 | }, |
---|
399 | |
---|
400 | onEndEdit: function(){ |
---|
401 | // summary: |
---|
402 | // Stub function to connect to from your application. |
---|
403 | }, |
---|
404 | |
---|
405 | _setEditableAttr: function(/*Boolean*/editable){ |
---|
406 | // tags: |
---|
407 | // private |
---|
408 | this._set("editable", editable); |
---|
409 | if(editable && !this._touchStartHandle){ // Allow users to start editing by long press on IconItems |
---|
410 | this._touchStartHandle = this.connect(this.domNode, touch.press, "_onTouchStart"); |
---|
411 | }else if(!editable && this._touchStartHandle){ |
---|
412 | this.disconnect(this._touchStartHandle); |
---|
413 | this._touchStartHandle = null; |
---|
414 | } |
---|
415 | } |
---|
416 | }); |
---|
417 | }); |
---|