[483] | 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 | }); |
---|