1 | define([ |
---|
2 | "dojo/_base/array", |
---|
3 | "dojo/_base/declare", |
---|
4 | "dojo/_base/lang", |
---|
5 | "dojo/_base/window", |
---|
6 | "dojo/dom-class", |
---|
7 | "dojo/touch", |
---|
8 | "dijit/registry", |
---|
9 | "dijit/_Contained", |
---|
10 | "dijit/_Container", |
---|
11 | "dijit/_WidgetBase", |
---|
12 | "./TransitionEvent", |
---|
13 | "./iconUtils", |
---|
14 | "./sniff", |
---|
15 | "dojo/has!dojo-bidi?dojox/mobile/bidi/_ItemBase" |
---|
16 | ], function(array, declare, lang, win, domClass, touch, registry, Contained, Container, WidgetBase, TransitionEvent, iconUtils, has, BidiItemBase){ |
---|
17 | |
---|
18 | // module: |
---|
19 | // dojox/mobile/_ItemBase |
---|
20 | |
---|
21 | var _ItemBase = declare(has("dojo-bidi") ? "dojox.mobile._NonBidiItemBase" : "dojox.mobile._ItemBase", [WidgetBase, Container, Contained], { |
---|
22 | // summary: |
---|
23 | // A base class for item classes (e.g. ListItem, IconItem, etc.). |
---|
24 | // description: |
---|
25 | // _ItemBase is a base class for widgets that have capability to |
---|
26 | // make a view transition when clicked. |
---|
27 | |
---|
28 | // icon: String |
---|
29 | // An icon image to display. The value can be either a path for an |
---|
30 | // image file or a class name of a DOM button. If icon is not |
---|
31 | // specified, the iconBase parameter of the parent widget is used. |
---|
32 | icon: "", |
---|
33 | |
---|
34 | // iconPos: String |
---|
35 | // The position of an aggregated icon. IconPos is comma separated |
---|
36 | // values like top,left,width,height (ex. "0,0,29,29"). If iconPos |
---|
37 | // is not specified, the iconPos parameter of the parent widget is |
---|
38 | // used. |
---|
39 | iconPos: "", // top,left,width,height (ex. "0,0,29,29") |
---|
40 | |
---|
41 | // alt: String |
---|
42 | // An alternate text for the icon image. |
---|
43 | alt: "", |
---|
44 | |
---|
45 | // href: String |
---|
46 | // A URL of another web page to go to. |
---|
47 | href: "", |
---|
48 | |
---|
49 | // hrefTarget: String |
---|
50 | // A target that specifies where to open a page specified by |
---|
51 | // href. The value will be passed to the 2nd argument of |
---|
52 | // window.open(). |
---|
53 | hrefTarget: "", |
---|
54 | |
---|
55 | // moveTo: String |
---|
56 | // The id of the transition destination view which resides in the |
---|
57 | // current page. |
---|
58 | // |
---|
59 | // If the value has a hash sign ('#') before the id (e.g. #view1) |
---|
60 | // and the dojo/hash module is loaded by the user application, the |
---|
61 | // view transition updates the hash in the browser URL so that the |
---|
62 | // user can bookmark the destination view. In this case, the user |
---|
63 | // can also use the browser's back/forward button to navigate |
---|
64 | // through the views in the browser history. |
---|
65 | // |
---|
66 | // If null, transitions to a blank view. |
---|
67 | // If '#', returns immediately without transition. |
---|
68 | moveTo: "", |
---|
69 | |
---|
70 | // scene: String |
---|
71 | // The name of a scene. Used from dojox/mobile/app. |
---|
72 | scene: "", |
---|
73 | |
---|
74 | // clickable: Boolean |
---|
75 | // If true, this item becomes clickable even if a transition |
---|
76 | // destination (moveTo, etc.) is not specified. |
---|
77 | clickable: false, |
---|
78 | |
---|
79 | // url: String |
---|
80 | // A URL of an html fragment page or JSON data that represents a |
---|
81 | // new view content. The view content is loaded with XHR and |
---|
82 | // inserted in the current page. Then a view transition occurs to |
---|
83 | // the newly created view. The view is cached so that subsequent |
---|
84 | // requests would not load the content again. |
---|
85 | url: "", |
---|
86 | |
---|
87 | // urlTarget: String |
---|
88 | // Node id under which a new view will be created according to the |
---|
89 | // url parameter. If not specified, The new view will be created as |
---|
90 | // a sibling of the current view. |
---|
91 | urlTarget: "", |
---|
92 | |
---|
93 | // back: Boolean |
---|
94 | // If true, history.back() is called when clicked. |
---|
95 | back: false, |
---|
96 | |
---|
97 | // transition: String |
---|
98 | // A type of animated transition effect. You can choose from the |
---|
99 | // standard transition types, "slide", "fade", "flip", or from the |
---|
100 | // extended transition types, "cover", "coverv", "dissolve", |
---|
101 | // "reveal", "revealv", "scaleIn", "scaleOut", "slidev", |
---|
102 | // "swirl", "zoomIn", "zoomOut", "cube", and "swap". If "none" is |
---|
103 | // specified, transition occurs immediately without animation. |
---|
104 | transition: "", |
---|
105 | |
---|
106 | // transitionDir: Number |
---|
107 | // The transition direction. If 1, transition forward. If -1, |
---|
108 | // transition backward. For example, the slide transition slides |
---|
109 | // the view from right to left when dir == 1, and from left to |
---|
110 | // right when dir == -1. |
---|
111 | transitionDir: 1, |
---|
112 | |
---|
113 | // transitionOptions: Object |
---|
114 | // A hash object that holds transition options. |
---|
115 | transitionOptions: null, |
---|
116 | |
---|
117 | // callback: Function|String |
---|
118 | // A callback function that is called when the transition has been |
---|
119 | // finished. A function reference, or name of a function in |
---|
120 | // context. |
---|
121 | callback: null, |
---|
122 | |
---|
123 | // label: String |
---|
124 | // A label of the item. If the label is not specified, innerHTML is |
---|
125 | // used as a label. |
---|
126 | label: "", |
---|
127 | |
---|
128 | // toggle: Boolean |
---|
129 | // If true, the item acts like a toggle button. |
---|
130 | toggle: false, |
---|
131 | |
---|
132 | // selected: Boolean |
---|
133 | // If true, the item is highlighted to indicate it is selected. |
---|
134 | selected: false, |
---|
135 | |
---|
136 | // tabIndex: String |
---|
137 | // Tabindex setting for the item so users can hit the tab key to |
---|
138 | // focus on it. |
---|
139 | tabIndex: "0", |
---|
140 | |
---|
141 | // _setTabIndexAttr: [private] String |
---|
142 | // Sets tabIndex to domNode. |
---|
143 | _setTabIndexAttr: "", |
---|
144 | |
---|
145 | /* internal properties */ |
---|
146 | |
---|
147 | // paramsToInherit: String |
---|
148 | // Comma separated parameters to inherit from the parent. |
---|
149 | paramsToInherit: "transition,icon", |
---|
150 | |
---|
151 | // _selStartMethod: String |
---|
152 | // Specifies how the item enters the selected state. |
---|
153 | // |
---|
154 | // - "touch": Use touch events to enter the selected state. |
---|
155 | // - "none": Do not change the selected state. |
---|
156 | _selStartMethod: "none", // touch or none |
---|
157 | |
---|
158 | // _selEndMethod: String |
---|
159 | // Specifies how the item leaves the selected state. |
---|
160 | // |
---|
161 | // - "touch": Use touch events to leave the selected state. |
---|
162 | // - "timer": Use setTimeout to leave the selected state. |
---|
163 | // - "none": Do not change the selected state. |
---|
164 | _selEndMethod: "none", // touch, timer, or none |
---|
165 | |
---|
166 | // _delayedSelection: Boolean |
---|
167 | // If true, selection is delayed 100ms and canceled if dragged in |
---|
168 | // order to avoid selection when flick operation is performed. |
---|
169 | _delayedSelection: false, |
---|
170 | |
---|
171 | // _duration: Number |
---|
172 | // Duration of selection, milliseconds. |
---|
173 | _duration: 800, |
---|
174 | |
---|
175 | // _handleClick: Boolean |
---|
176 | // If true, this widget listens to touch events. |
---|
177 | _handleClick: true, |
---|
178 | |
---|
179 | buildRendering: function(){ |
---|
180 | this.inherited(arguments); |
---|
181 | this._isOnLine = this.inheritParams(); |
---|
182 | }, |
---|
183 | |
---|
184 | startup: function(){ |
---|
185 | if(this._started){ return; } |
---|
186 | if(!this._isOnLine){ |
---|
187 | this.inheritParams(); |
---|
188 | } |
---|
189 | this._updateHandles(); |
---|
190 | this.inherited(arguments); |
---|
191 | }, |
---|
192 | |
---|
193 | inheritParams: function(){ |
---|
194 | // summary: |
---|
195 | // Copies from the parent the values of parameters specified |
---|
196 | // by the property paramsToInherit. |
---|
197 | var parent = this.getParent(); |
---|
198 | if(parent){ |
---|
199 | array.forEach(this.paramsToInherit.split(/,/), function(p){ |
---|
200 | if(p.match(/icon/i)){ |
---|
201 | var base = p + "Base", pos = p + "Pos"; |
---|
202 | if(this[p] && parent[base] && |
---|
203 | parent[base].charAt(parent[base].length - 1) === '/'){ |
---|
204 | this[p] = parent[base] + this[p]; |
---|
205 | } |
---|
206 | if(!this[p]){ this[p] = parent[base]; } |
---|
207 | if(!this[pos]){ this[pos] = parent[pos]; } |
---|
208 | } |
---|
209 | if(!this[p]){ this[p] = parent[p]; } |
---|
210 | }, this); |
---|
211 | } |
---|
212 | return !!parent; |
---|
213 | }, |
---|
214 | |
---|
215 | _updateHandles: function(){ |
---|
216 | // tags: |
---|
217 | // private |
---|
218 | if(this._handleClick && this._selStartMethod === "touch"){ |
---|
219 | if(!this._onTouchStartHandle){ |
---|
220 | this._onTouchStartHandle = this.connect(this.domNode, touch.press, "_onTouchStart"); |
---|
221 | } |
---|
222 | }else{ |
---|
223 | if(this._onTouchStartHandle){ |
---|
224 | this.disconnect(this._onTouchStartHandle); |
---|
225 | this._onTouchStartHandle = null; |
---|
226 | } |
---|
227 | } |
---|
228 | }, |
---|
229 | |
---|
230 | getTransOpts: function(){ |
---|
231 | // summary: |
---|
232 | // Copies from the parent and returns the values of parameters |
---|
233 | // specified by the property paramsToInherit. |
---|
234 | var opts = this.transitionOptions || {}; |
---|
235 | array.forEach(["moveTo", "href", "hrefTarget", "url", "target", |
---|
236 | "urlTarget", "scene", "transition", "transitionDir"], function(p){ |
---|
237 | opts[p] = opts[p] || this[p]; |
---|
238 | }, this); |
---|
239 | return opts; // Object |
---|
240 | }, |
---|
241 | |
---|
242 | userClickAction: function(/*Event*/ /*===== e =====*/){ |
---|
243 | // summary: |
---|
244 | // User-defined click action. |
---|
245 | }, |
---|
246 | |
---|
247 | defaultClickAction: function(/*Event*/e){ |
---|
248 | // summary: |
---|
249 | // The default action of this item. |
---|
250 | this.handleSelection(e); |
---|
251 | if(this.userClickAction(e) === false){ return; } // user's click action |
---|
252 | this.makeTransition(e); |
---|
253 | }, |
---|
254 | |
---|
255 | handleSelection: function(/*Event*/e){ |
---|
256 | // summary: |
---|
257 | // Handles this items selection state. |
---|
258 | |
---|
259 | // Before transitioning, we want the visual effect of selecting the item. |
---|
260 | // To ensure this effect happens even if _delayedSelection is true: |
---|
261 | if(this._delayedSelection){ |
---|
262 | this.set("selected", true); |
---|
263 | } // the item will be deselected after transition. |
---|
264 | |
---|
265 | if(this._onTouchEndHandle){ |
---|
266 | this.disconnect(this._onTouchEndHandle); |
---|
267 | this._onTouchEndHandle = null; |
---|
268 | } |
---|
269 | |
---|
270 | var p = this.getParent(); |
---|
271 | if(this.toggle){ |
---|
272 | this.set("selected", !this._currentSel); |
---|
273 | }else if(p && p.selectOne){ |
---|
274 | this.set("selected", true); |
---|
275 | }else{ |
---|
276 | if(this._selEndMethod === "touch"){ |
---|
277 | this.set("selected", false); |
---|
278 | }else if(this._selEndMethod === "timer"){ |
---|
279 | this.defer(function(){ |
---|
280 | this.set("selected", false); |
---|
281 | }, this._duration); |
---|
282 | } |
---|
283 | } |
---|
284 | }, |
---|
285 | |
---|
286 | makeTransition: function(/*Event*/e){ |
---|
287 | // summary: |
---|
288 | // Makes a transition. |
---|
289 | if(this.back && history){ |
---|
290 | history.back(); |
---|
291 | return; |
---|
292 | } |
---|
293 | if (this.href && this.hrefTarget && this.hrefTarget != "_self") { |
---|
294 | win.global.open(this.href, this.hrefTarget || "_blank"); |
---|
295 | this._onNewWindowOpened(e); |
---|
296 | return; |
---|
297 | } |
---|
298 | var opts = this.getTransOpts(); |
---|
299 | var doTransition = |
---|
300 | !!(opts.moveTo || opts.href || opts.url || opts.target || opts.scene); |
---|
301 | if(this._prepareForTransition(e, doTransition ? opts : null) === false){ return; } |
---|
302 | if(doTransition){ |
---|
303 | this.setTransitionPos(e); |
---|
304 | new TransitionEvent(this.domNode, opts, e).dispatch(); |
---|
305 | } |
---|
306 | }, |
---|
307 | |
---|
308 | _onNewWindowOpened: function(/*Event*/ /*===== e =====*/){ |
---|
309 | // summary: |
---|
310 | // Subclasses may want to implement it. |
---|
311 | }, |
---|
312 | |
---|
313 | _prepareForTransition: function(/*Event*/e, /*Object*/transOpts){ |
---|
314 | // summary: |
---|
315 | // Subclasses may want to implement it. |
---|
316 | }, |
---|
317 | |
---|
318 | _onTouchStart: function(e){ |
---|
319 | // tags: |
---|
320 | // private |
---|
321 | if(this.getParent().isEditing || this.onTouchStart(e) === false){ return; } // user's touchStart action |
---|
322 | if(!this._onTouchEndHandle && this._selStartMethod === "touch"){ |
---|
323 | // Connect to the entire window. Otherwise, fail to receive |
---|
324 | // events if operation is performed outside this widget. |
---|
325 | // Expose both connect handlers in case the user has interest. |
---|
326 | this._onTouchMoveHandle = this.connect(win.body(), touch.move, "_onTouchMove"); |
---|
327 | this._onTouchEndHandle = this.connect(win.body(), touch.release, "_onTouchEnd"); |
---|
328 | } |
---|
329 | this.touchStartX = e.touches ? e.touches[0].pageX : e.clientX; |
---|
330 | this.touchStartY = e.touches ? e.touches[0].pageY : e.clientY; |
---|
331 | this._currentSel = this.selected; |
---|
332 | |
---|
333 | if(this._delayedSelection){ |
---|
334 | // so as not to make selection when the user flicks on ScrollableView |
---|
335 | this._selTimer = this.defer(function(){ |
---|
336 | this.set("selected", true); |
---|
337 | }, 100); |
---|
338 | }else{ |
---|
339 | this.set("selected", true); |
---|
340 | } |
---|
341 | }, |
---|
342 | |
---|
343 | onTouchStart: function(/*Event*/ /*===== e =====*/){ |
---|
344 | // summary: |
---|
345 | // User-defined function to handle touchStart events. |
---|
346 | // tags: |
---|
347 | // callback |
---|
348 | }, |
---|
349 | |
---|
350 | _onTouchMove: function(e){ |
---|
351 | // tags: |
---|
352 | // private |
---|
353 | var x = e.touches ? e.touches[0].pageX : e.clientX; |
---|
354 | var y = e.touches ? e.touches[0].pageY : e.clientY; |
---|
355 | if(Math.abs(x - this.touchStartX) >= 4 || |
---|
356 | Math.abs(y - this.touchStartY) >= 4){ // dojox/mobile/scrollable.threshold |
---|
357 | this.cancel(); |
---|
358 | var p = this.getParent(); |
---|
359 | if(p && p.selectOne){ |
---|
360 | this._prevSel && this._prevSel.set("selected", true); |
---|
361 | }else{ |
---|
362 | this.set("selected", false); |
---|
363 | } |
---|
364 | } |
---|
365 | }, |
---|
366 | |
---|
367 | _disconnect: function(){ |
---|
368 | // tags: |
---|
369 | // private |
---|
370 | this.disconnect(this._onTouchMoveHandle); |
---|
371 | this.disconnect(this._onTouchEndHandle); |
---|
372 | this._onTouchMoveHandle = this._onTouchEndHandle = null; |
---|
373 | }, |
---|
374 | |
---|
375 | cancel: function(){ |
---|
376 | // summary: |
---|
377 | // Cancels an ongoing selection (if any). |
---|
378 | if(this._selTimer){ |
---|
379 | this._selTimer.remove(); |
---|
380 | this._selTimer = null; |
---|
381 | } |
---|
382 | this._disconnect(); |
---|
383 | }, |
---|
384 | |
---|
385 | _onTouchEnd: function(e){ |
---|
386 | // tags: |
---|
387 | // private |
---|
388 | if(!this._selTimer && this._delayedSelection){ return; } |
---|
389 | this.cancel(); |
---|
390 | this._onClick(e); |
---|
391 | }, |
---|
392 | |
---|
393 | setTransitionPos: function(e){ |
---|
394 | // summary: |
---|
395 | // Stores the clicked position for later use. |
---|
396 | // description: |
---|
397 | // Some of the transition animations (e.g. ScaleIn) need the |
---|
398 | // clicked position. |
---|
399 | var w = this; |
---|
400 | while(true){ |
---|
401 | w = w.getParent(); |
---|
402 | if(!w || domClass.contains(w.domNode, "mblView")){ break; } |
---|
403 | } |
---|
404 | if(w){ |
---|
405 | w.clickedPosX = e.clientX; |
---|
406 | w.clickedPosY = e.clientY; |
---|
407 | } |
---|
408 | }, |
---|
409 | |
---|
410 | transitionTo: function(/*String|Object*/moveTo, /*String*/href, /*String*/url, /*String*/scene){ |
---|
411 | // summary: |
---|
412 | // Performs a view transition. |
---|
413 | // description: |
---|
414 | // Given a transition destination, this method performs a view |
---|
415 | // transition. This method is typically called when this item |
---|
416 | // is clicked. |
---|
417 | var opts = (moveTo && typeof(moveTo) === "object") ? moveTo : |
---|
418 | {moveTo: moveTo, href: href, url: url, scene: scene, |
---|
419 | transition: this.transition, transitionDir: this.transitionDir}; |
---|
420 | new TransitionEvent(this.domNode, opts).dispatch(); |
---|
421 | }, |
---|
422 | |
---|
423 | _setIconAttr: function(icon){ |
---|
424 | // tags: |
---|
425 | // private |
---|
426 | if(!this._isOnLine){ |
---|
427 | // record the value to be able to reapply it (see the code in the startup method) |
---|
428 | this._pendingIcon = icon; |
---|
429 | return; |
---|
430 | } // icon may be invalid because inheritParams is not called yet |
---|
431 | this._set("icon", icon); |
---|
432 | this.iconNode = iconUtils.setIcon(icon, this.iconPos, this.iconNode, this.alt, this.iconParentNode, this.refNode, this.position); |
---|
433 | }, |
---|
434 | |
---|
435 | _setLabelAttr: function(/*String*/text){ |
---|
436 | // tags: |
---|
437 | // private |
---|
438 | this._set("label", text); |
---|
439 | this.labelNode.innerHTML = this._cv ? this._cv(text) : text; |
---|
440 | }, |
---|
441 | |
---|
442 | _setSelectedAttr: function(/*Boolean*/selected){ |
---|
443 | // summary: |
---|
444 | // Makes this widget in the selected or unselected state. |
---|
445 | // description: |
---|
446 | // Subclass should override. |
---|
447 | // tags: |
---|
448 | // private |
---|
449 | if(selected){ |
---|
450 | var p = this.getParent(); |
---|
451 | if(p && p.selectOne){ |
---|
452 | // deselect the currently selected item |
---|
453 | var arr = array.filter(p.getChildren(), function(w){ |
---|
454 | return w.selected; |
---|
455 | }); |
---|
456 | array.forEach(arr, function(c){ |
---|
457 | this._prevSel = c; |
---|
458 | c.set("selected", false); |
---|
459 | }, this); |
---|
460 | } |
---|
461 | } |
---|
462 | this._set("selected", selected); |
---|
463 | } |
---|
464 | }); |
---|
465 | return has("dojo-bidi") ? declare("dojox.mobile._ItemBase", [_ItemBase, BidiItemBase]) : _ItemBase; |
---|
466 | }); |
---|