diff options
Diffstat (limited to 'js/dojo/dojox/mobile/app')
| -rw-r--r-- | js/dojo/dojox/mobile/app/AlertDialog.js | 186 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/ImageThumbView.js | 393 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/ImageView.js | 721 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/List.js | 649 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/ListSelector.js | 223 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/SceneAssistant.js | 61 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/SceneController.js | 174 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/StageController.js | 137 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/TextBox.js | 10 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/_FormWidget.js | 293 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/_Widget.js | 34 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/_base.js | 242 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/_event.js | 124 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/compat.js | 15 | ||||
| -rw-r--r-- | js/dojo/dojox/mobile/app/compat.js.uncompressed.js | 169 |
15 files changed, 3431 insertions, 0 deletions
diff --git a/js/dojo/dojox/mobile/app/AlertDialog.js b/js/dojo/dojox/mobile/app/AlertDialog.js new file mode 100644 index 0000000..2b4b738 --- /dev/null +++ b/js/dojo/dojox/mobile/app/AlertDialog.js @@ -0,0 +1,186 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/AlertDialog", ["dijit","dojo","dojox","dojo/require!dijit/_WidgetBase"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.AlertDialog"); +dojo.experimental("dojox.mobile.app.AlertDialog"); +dojo.require("dijit._WidgetBase"); + +dojo.declare("dojox.mobile.app.AlertDialog", dijit._WidgetBase, { + + // title: String + // The title of the AlertDialog + title: "", + + // text: String + // The text message displayed in the AlertDialog + text: "", + + // controller: Object + // The SceneController for the currently active scene + controller: null, + + // buttons: Array + buttons: null, + + defaultButtonLabel: "OK", + + // onChoose: Function + // The callback function that is invoked when a button is tapped. + // If the dialog is cancelled, no parameter is passed to this function. + onChoose: null, + + constructor: function(){ + this.onClick = dojo.hitch(this, this.onClick); + this._handleSelect = dojo.hitch(this, this._handleSelect); + }, + + buildRendering: function(){ + this.domNode = dojo.create("div",{ + "class": "alertDialog" + }); + + // Create the outer dialog body + var dlgBody = dojo.create("div", {"class": "alertDialogBody"}, this.domNode); + + // Create the title + dojo.create("div", {"class": "alertTitle", innerHTML: this.title || ""}, dlgBody); + + // Create the text + dojo.create("div", {"class": "alertText", innerHTML: this.text || ""}, dlgBody); + + // Create the node that encapsulates all the buttons + var btnContainer = dojo.create("div", {"class": "alertBtns"}, dlgBody); + + // If no buttons have been defined, default to a single button saying OK + if(!this.buttons || this.buttons.length == 0){ + this.buttons = [{ + label: this.defaultButtonLabel, + value: "ok", + "class": "affirmative" + }]; + } + + var _this = this; + + // Create each of the buttons + dojo.forEach(this.buttons, function(btnInfo){ + var btn = new dojox.mobile.Button({ + btnClass: btnInfo["class"] || "", + label: btnInfo.label + }); + btn._dialogValue = btnInfo.value; + dojo.place(btn.domNode, btnContainer); + _this.connect(btn, "onClick", _this._handleSelect); + }); + + var viewportSize = this.controller.getWindowSize(); + + // Create the mask that blocks out the rest of the screen + this.mask = dojo.create("div", {"class": "dialogUnderlayWrapper", + innerHTML: "<div class=\"dialogUnderlay\"></div>", + style: { + width: viewportSize.w + "px", + height: viewportSize.h + "px" + } + }, this.controller.assistant.domNode); + + this.connect(this.mask, "onclick", function(){ + _this.onChoose && _this.onChoose(); + _this.hide(); + }); + }, + + postCreate: function(){ + this.subscribe("/dojox/mobile/app/goback", this._handleSelect); + }, + + _handleSelect: function(event){ + // summary: + // Handle the selection of a value + var node; + console.log("handleSelect"); + if(event && event.target){ + node = event.target; + + // Find the widget that was tapped. + while(!dijit.byNode(node)){ + node - node.parentNode; + } + } + + // If an onChoose function was provided, tell it what button + // value was chosen + if(this.onChoose){ + this.onChoose(node ? dijit.byNode(node)._dialogValue: undefined); + } + // Hide the dialog + this.hide(); + }, + + show: function(){ + // summary: + // Show the dialog + this._doTransition(1); + }, + + hide: function(){ + // summary: + // Hide the dialog + this._doTransition(-1); + }, + + _doTransition: function(dir){ + // summary: + // Either shows or hides the dialog. + // dir: + // An integer. If positive, the dialog is shown. If negative, + // the dialog is hidden. + + // TODO: replace this with CSS transitions + + var anim; + var h = dojo.marginBox(this.domNode.firstChild).h; + + + var bodyHeight = this.controller.getWindowSize().h; + console.log("dialog height = " + h, " body height = " + bodyHeight); + + var high = bodyHeight - h; + var low = bodyHeight; + + var anim1 = dojo.fx.slideTo({ + node: this.domNode, + duration: 400, + top: {start: dir < 0 ? high : low, end: dir < 0 ? low: high} + }); + + var anim2 = dojo[dir < 0 ? "fadeOut" : "fadeIn"]({ + node: this.mask, + duration: 400 + }); + + var anim = dojo.fx.combine([anim1, anim2]); + + var _this = this; + + dojo.connect(anim, "onEnd", this, function(){ + if(dir < 0){ + _this.domNode.style.display = "none"; + dojo.destroy(_this.domNode); + dojo.destroy(_this.mask); + } + }); + anim.play(); + }, + + destroy: function(){ + this.inherited(arguments); + dojo.destroy(this.mask); + }, + + + onClick: function(){ + + } +}); +}); diff --git a/js/dojo/dojox/mobile/app/ImageThumbView.js b/js/dojo/dojox/mobile/app/ImageThumbView.js new file mode 100644 index 0000000..045385a --- /dev/null +++ b/js/dojo/dojox/mobile/app/ImageThumbView.js @@ -0,0 +1,393 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/ImageThumbView", ["dijit","dojo","dojox","dojo/require!dijit/_WidgetBase,dojo/string"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.ImageThumbView"); +dojo.experimental("dojox.mobile.app.ImageThumbView"); + +dojo.require("dijit._WidgetBase"); +dojo.require("dojo.string"); + +dojo.declare("dojox.mobile.app.ImageThumbView", dijit._WidgetBase, { + // summary: + // An image thumbnail gallery + + // items: Array + // The data items from which the image urls are retrieved. + // If an item is a string, it is expected to be a URL. Otherwise + // by default it is expected to have a 'url' member. This can + // be configured using the 'urlParam' attribute on this widget. + items: [], + + // urlParam: String + // The paramter name used to retrieve an image url from a JSON object + urlParam: "url", + + labelParam: null, + + itemTemplate: '<div class="mblThumbInner">' + + '<div class="mblThumbOverlay"></div>' + + '<div class="mblThumbMask">' + + '<div class="mblThumbSrc" style="background-image:url(${url})"></div>' + + '</div>' + + '</div>', + + minPadding: 4, + + maxPerRow: 3, + + maxRows: -1, + + baseClass: "mblImageThumbView", + + thumbSize: "medium", + + animationEnabled: true, + + selectedIndex: -1, + + cache: null, + + cacheMustMatch: false, + + clickEvent: "onclick", + + cacheBust: false, + + disableHide: false, + + constructor: function(params, node){ + }, + + postCreate: function(){ + + this.inherited(arguments); + var _this = this; + + var hoverCls = "mblThumbHover"; + + this.addThumb = dojo.hitch(this, this.addThumb); + this.handleImgLoad = dojo.hitch(this, this.handleImgLoad); + this.hideCached = dojo.hitch(this, this.hideCached); + + this._onLoadImages = {}; + + this.cache = []; + this.visibleImages = []; + + this._cacheCounter = 0; + + this.connect(this.domNode, this.clickEvent, function(event){ + var itemNode = _this._getItemNodeFromEvent(event); + + if(itemNode && !itemNode._cached){ + _this.onSelect(itemNode._item, itemNode._index, _this.items); + dojo.query(".selected", this.domNode).removeClass("selected"); + dojo.addClass(itemNode, "selected"); + } + }); + + dojo.addClass(this.domNode, this.thumbSize); + + this.resize(); + this.render(); + }, + + onSelect: function(item, index, items){ + // summary: + // Dummy function that is triggered when an image is selected. + }, + + _setAnimationEnabledAttr: function(value){ + this.animationEnabled = value; + dojo[value ? "addClass" : "removeClass"](this.domNode, "animated"); + }, + + _setItemsAttr: function(items){ + this.items = items || []; + + var urls = {}; + var i; + for(i = 0; i < this.items.length; i++){ + urls[this.items[i][this.urlParam]] = 1; + } + + var clearedUrls = []; + for(var url in this._onLoadImages){ + if(!urls[url] && this._onLoadImages[url]._conn){ + dojo.disconnect(this._onLoadImages[url]._conn); + this._onLoadImages[url].src = null; + clearedUrls.push(url); + } + } + + for(i = 0; i < clearedUrls.length; i++){ + delete this._onLoadImages[url]; + } + + this.render(); + }, + + _getItemNode: function(node){ + while(node && !dojo.hasClass(node, "mblThumb") && node != this.domNode){ + node = node.parentNode; + } + + return (node == this.domNode) ? null : node; + }, + + _getItemNodeFromEvent: function(event){ + if(event.touches && event.touches.length > 0){ + event = event.touches[0]; + } + return this._getItemNode(event.target); + }, + + resize: function(){ + this._thumbSize = null; + + this._size = dojo.contentBox(this.domNode); + + this.disableHide = true; + this.render(); + this.disableHide = false; + }, + + hideCached: function(){ + // summary: + // Hides all cached nodes, so that they're no invisible and overlaying + // other screen elements. + for(var i = 0; i < this.cache.length; i++){ + if (this.cache[i]) { + dojo.style(this.cache[i], "display", "none"); + } + } + }, + + render: function(){ + var i; + var url; + var item; + + var thumb; + while(this.visibleImages && this.visibleImages.length > 0){ + thumb = this.visibleImages.pop(); + this.cache.push(thumb); + + if (!this.disableHide) { + dojo.addClass(thumb, "hidden"); + } + thumb._cached = true; + } + + if(this.cache && this.cache.length > 0){ + setTimeout(this.hideCached, 1000); + } + + if(!this.items || this.items.length == 0){ + return; + } + + for(i = 0; i < this.items.length; i++){ + item = this.items[i]; + url = (dojo.isString(item) ? item : item[this.urlParam]); + + this.addThumb(item, url, i); + + if(this.maxRows > 0 && (i + 1) / this.maxPerRow >= this.maxRows){ + break; + } + } + + if(!this._thumbSize){ + return; + } + + var column = 0; + var row = -1; + + var totalThumbWidth = this._thumbSize.w + (this.padding * 2); + var totalThumbHeight = this._thumbSize.h + (this.padding * 2); + + var nodes = this.thumbNodes = + dojo.query(".mblThumb", this.domNode); + + var pos = 0; + nodes = this.visibleImages; + for(i = 0; i < nodes.length; i++){ + if(nodes[i]._cached){ + continue; + } + + if(pos % this.maxPerRow == 0){ + row ++; + } + column = pos % this.maxPerRow; + + this.place( + nodes[i], + (column * totalThumbWidth) + this.padding, // x position + (row * totalThumbHeight) + this.padding // y position + ); + + if(!nodes[i]._loading){ + dojo.removeClass(nodes[i], "hidden"); + } + + if(pos == this.selectedIndex){ + dojo[pos == this.selectedIndex ? "addClass" : "removeClass"] + (nodes[i], "selected"); + } + pos++; + } + + var numRows = Math.ceil(pos / this.maxPerRow); + + this._numRows = numRows; + + this.setContainerHeight((numRows * (this._thumbSize.h + this.padding * 2))); + }, + + setContainerHeight: function(amount){ + dojo.style(this.domNode, "height", amount + "px"); + }, + + addThumb: function(item, url, index){ + + var thumbDiv; + var cacheHit = false; + if(this.cache.length > 0){ + // Reuse a previously created node if possible + var found = false; + // Search for an image with the same url first + for(var i = 0; i < this.cache.length; i++){ + if(this.cache[i]._url == url){ + thumbDiv = this.cache.splice(i, 1)[0]; + found = true; + break + } + } + + // if no image with the same url is found, just take the last one + if(!thumbDiv && !this.cacheMustMatch){ + thumbDiv = this.cache.pop(); + dojo.removeClass(thumbDiv, "selected"); + } else { + cacheHit = true; + } + } + + if(!thumbDiv){ + + // Create a new thumb + thumbDiv = dojo.create("div", { + "class": "mblThumb hidden", + innerHTML: dojo.string.substitute(this.itemTemplate, { + url: url + }, null, this) + }, this.domNode); + } + + if(this.labelParam) { + var labelNode = dojo.query(".mblThumbLabel", thumbDiv)[0]; + if(!labelNode) { + labelNode = dojo.create("div", { + "class": "mblThumbLabel" + }, thumbDiv); + } + labelNode.innerHTML = item[this.labelParam] || ""; + } + + dojo.style(thumbDiv, "display", ""); + if (!this.disableHide) { + dojo.addClass(thumbDiv, "hidden"); + } + + if (!cacheHit) { + var loader = dojo.create("img", {}); + loader._thumbDiv = thumbDiv; + loader._conn = dojo.connect(loader, "onload", this.handleImgLoad); + loader._url = url; + thumbDiv._loading = true; + + this._onLoadImages[url] = loader; + if (loader) { + loader.src = url; + } + } + this.visibleImages.push(thumbDiv); + + thumbDiv._index = index; + thumbDiv._item = item; + thumbDiv._url = url; + thumbDiv._cached = false; + + if(!this._thumbSize){ + this._thumbSize = dojo.marginBox(thumbDiv); + + if(this._thumbSize.h == 0){ + this._thumbSize.h = 100; + this._thumbSize.w = 100; + } + + if(this.labelParam){ + this._thumbSize.h += 8; + } + + this.calcPadding(); + } + }, + + handleImgLoad: function(event){ + var img = event.target; + dojo.disconnect(img._conn); + dojo.removeClass(img._thumbDiv, "hidden"); + img._thumbDiv._loading = false; + img._conn = null; + + var url = img._url; + if(this.cacheBust){ + url += (url.indexOf("?") > -1 ? "&" : "?") + + "cacheBust=" + (new Date()).getTime() + "_" + (this._cacheCounter++); + } + + dojo.query(".mblThumbSrc", img._thumbDiv) + .style("backgroundImage", "url(" + url + ")"); + + delete this._onLoadImages[img._url]; + }, + + calcPadding: function(){ + var width = this._size.w; + + var thumbWidth = this._thumbSize.w; + + var imgBounds = thumbWidth + this.minPadding; + + this.maxPerRow = Math.floor(width / imgBounds); + + this.padding = Math.floor((width - (thumbWidth * this.maxPerRow)) / (this.maxPerRow * 2)); + }, + + place: function(node, x, y){ + dojo.style(node, { + "-webkit-transform" :"translate(" + x + "px," + y + "px)" + }); + }, + + destroy: function(){ + // Stop the loading of any more images + + var img; + var counter = 0; + for (var url in this._onLoadImages){ + img = this._onLoadImages[url]; + if (img) { + img.src = null; + counter++; + } + } + + this.inherited(arguments); + } +}); +}); diff --git a/js/dojo/dojox/mobile/app/ImageView.js b/js/dojo/dojox/mobile/app/ImageView.js new file mode 100644 index 0000000..d27bdf7 --- /dev/null +++ b/js/dojo/dojox/mobile/app/ImageView.js @@ -0,0 +1,721 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/ImageView", ["dijit","dojo","dojox","dojo/require!dojox/mobile/app/_Widget,dojo/fx/easing"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.ImageView"); +dojo.experimental("dojox.mobile.app.ImageView"); +dojo.require("dojox.mobile.app._Widget"); + +dojo.require("dojo.fx.easing"); + +dojo.declare("dojox.mobile.app.ImageView", dojox.mobile.app._Widget, { + + // zoom: Number + // The current level of zoom. This should not be set manually. + zoom: 1, + + // zoomCenterX: Number + // The X coordinate in the image where the zoom is focused + zoomCenterX: 0, + + // zoomCenterY: Number + // The Y coordinate in the image where the zoom is focused + zoomCenterY: 0, + + // maxZoom: Number + // The highest degree to which an image can be zoomed. For example, + // a maxZoom of 5 means that the image will be 5 times larger than normal + maxZoom: 5, + + // autoZoomLevel: Number + // The degree to which the image is zoomed when auto zoom is invoked. + // The higher the number, the more the image is zoomed in. + autoZoomLevel: 3, + + // disableAutoZoom: Boolean + // Disables auto zoom + disableAutoZoom: false, + + // disableSwipe: Boolean + // Disables the users ability to swipe from one image to the next. + disableSwipe: false, + + // autoZoomEvent: String + // Overrides the default event listened to which invokes auto zoom + autoZoomEvent: null, + + // _leftImg: Node + // The full sized image to the left + _leftImg: null, + + // _centerImg: Node + // The full sized image in the center + _centerImg: null, + + // _rightImg: Node + // The full sized image to the right + _rightImg: null, + + // _leftImg: Node + // The small sized image to the left + _leftSmallImg: null, + + // _centerImg: Node + // The small sized image in the center + _centerSmallImg: null, + + // _rightImg: Node + // The small sized image to the right + _rightSmallImg: null, + + constructor: function(){ + + this.panX = 0; + this.panY = 0; + + this.handleLoad = dojo.hitch(this, this.handleLoad); + this._updateAnimatedZoom = dojo.hitch(this, this._updateAnimatedZoom); + this._updateAnimatedPan = dojo.hitch(this, this._updateAnimatedPan); + this._onAnimPanEnd = dojo.hitch(this, this._onAnimPanEnd); + }, + + buildRendering: function(){ + this.inherited(arguments); + + this.canvas = dojo.create("canvas", {}, this.domNode); + + dojo.addClass(this.domNode, "mblImageView"); + }, + + postCreate: function(){ + this.inherited(arguments); + + this.size = dojo.marginBox(this.domNode); + + dojo.style(this.canvas, { + width: this.size.w + "px", + height: this.size.h + "px" + }); + this.canvas.height = this.size.h; + this.canvas.width = this.size.w; + + var _this = this; + + // Listen to the mousedown/touchstart event. Record the position + // so we can use it to pan the image. + this.connect(this.domNode, "onmousedown", function(event){ + if(_this.isAnimating()){ + return; + } + if(_this.panX){ + _this.handleDragEnd(); + } + + _this.downX = event.targetTouches ? event.targetTouches[0].clientX : event.clientX; + _this.downY = event.targetTouches ? event.targetTouches[0].clientY : event.clientY; + }); + + // record the movement of the mouse. + this.connect(this.domNode, "onmousemove", function(event){ + if(_this.isAnimating()){ + return; + } + if((!_this.downX && _this.downX !== 0) || (!_this.downY && _this.downY !== 0)){ + // If the touch didn't begin on this widget, ignore the movement + return; + } + + if((!_this.disableSwipe && _this.zoom == 1) + || (!_this.disableAutoZoom && _this.zoom != 1)){ + var x = event.targetTouches ? + event.targetTouches[0].clientX : event.pageX; + var y = event.targetTouches ? + event.targetTouches[0].clientY : event.pageY; + + _this.panX = x - _this.downX; + _this.panY = y - _this.downY; + + if(_this.zoom == 1){ + // If not zoomed in, then try to move to the next or prev image + // but only if the mouse has moved more than 10 pixels + // in the X direction + if(Math.abs(_this.panX) > 10){ + _this.render(); + } + }else{ + // If zoomed in, pan the image if the mouse has moved more + // than 10 pixels in either direction. + if(Math.abs(_this.panX) > 10 || Math.abs(_this.panY) > 10){ + _this.render(); + } + } + } + }); + + this.connect(this.domNode, "onmouseout", function(event){ + if(!_this.isAnimating() && _this.panX){ + _this.handleDragEnd(); + } + }); + + this.connect(this.domNode, "onmouseover", function(event){ + _this.downX = _this.downY = null; + }); + + // Set up AutoZoom, which zooms in a fixed amount when the user taps + // a part of the canvas + this.connect(this.domNode, "onclick", function(event){ + if(_this.isAnimating()){ + return; + } + if(_this.downX == null || _this.downY == null){ + return; + } + + var x = (event.targetTouches ? + event.targetTouches[0].clientX : event.pageX); + var y = (event.targetTouches ? + event.targetTouches[0].clientY : event.pageY); + + // If the mouse/finger has moved more than 14 pixels from where it + // started, do not treat it as a click. It is a drag. + if(Math.abs(_this.panX) > 14 || Math.abs(_this.panY) > 14){ + _this.downX = _this.downY = null; + _this.handleDragEnd(); + return; + } + _this.downX = _this.downY = null; + + if(!_this.disableAutoZoom){ + + if(!_this._centerImg || !_this._centerImg._loaded){ + // Do nothing until the image is loaded + return; + } + if(_this.zoom != 1){ + _this.set("animatedZoom", 1); + return; + } + + var pos = dojo._abs(_this.domNode); + + // Translate the clicked point to a point on the source image + var xRatio = _this.size.w / _this._centerImg.width; + var yRatio = _this.size.h / _this._centerImg.height; + + // Do an animated zoom to the point which was clicked. + _this.zoomTo( + ((x - pos.x) / xRatio) - _this.panX, + ((y - pos.y) / yRatio) - _this.panY, + _this.autoZoomLevel); + } + }); + + // Listen for Flick events + dojo.connect(this.domNode, "flick", this, "handleFlick"); + }, + + isAnimating: function(){ + // summary: + // Returns true if an animation is in progress, false otherwise. + return this._anim && this._anim.status() == "playing"; + }, + + handleDragEnd: function(){ + // summary: + // Handles the end of a dragging event. If not zoomed in, it + // determines if the next or previous image should be transitioned + // to. + this.downX = this.downY = null; + console.log("handleDragEnd"); + + if(this.zoom == 1){ + if(!this.panX){ + return; + } + + var leftLoaded = (this._leftImg && this._leftImg._loaded) + || (this._leftSmallImg && this._leftSmallImg._loaded); + var rightLoaded = (this._rightImg && this._rightImg._loaded) + || (this._rightSmallImg && this._rightSmallImg._loaded); + + // Check if the drag has moved the image more than half its length. + // If so, move to either the previous or next image. + var doMove = + !(Math.abs(this.panX) < this._centerImg._baseWidth / 2) && + ( + (this.panX > 0 && leftLoaded ? 1 : 0) || + (this.panX < 0 && rightLoaded ? 1 : 0) + ); + + + if(!doMove){ + // If not moving to another image, animate the sliding of the + // image back into place. + this._animPanTo(0, dojo.fx.easing.expoOut, 700); + }else{ + // Move to another image. + this.moveTo(this.panX); + } + }else{ + if(!this.panX && !this.panY){ + return; + } + // Recenter the zoomed image based on where it was panned to + // previously + this.zoomCenterX -= (this.panX / this.zoom); + this.zoomCenterY -= (this.panY / this.zoom); + + this.panX = this.panY = 0; + } + + }, + + handleFlick: function(event){ + // summary: + // Handle a flick event. + if(this.zoom == 1 && event.duration < 500){ + // Only handle quick flicks here, less than 0.5 seconds + + // If not zoomed in, then check if we should move to the next photo + // or not + if(event.direction == "ltr"){ + this.moveTo(1); + }else if(event.direction == "rtl"){ + this.moveTo(-1); + } + // If an up or down flick occurs, it means nothing so ignore it + this.downX = this.downY = null; + } + }, + + moveTo: function(direction){ + direction = direction > 0 ? 1 : -1; + var toImg; + + if(direction < 1){ + if(this._rightImg && this._rightImg._loaded){ + toImg = this._rightImg; + }else if(this._rightSmallImg && this._rightSmallImg._loaded){ + toImg = this._rightSmallImg; + } + }else{ + if(this._leftImg && this._leftImg._loaded){ + toImg = this._leftImg; + }else if(this._leftSmallImg && this._leftSmallImg._loaded){ + toImg = this._leftSmallImg; + } + } + + this._moveDir = direction; + var _this = this; + + if(toImg && toImg._loaded){ + // If the image is loaded, make a linear animation to show it + this._animPanTo(this.size.w * direction, null, 500, function(){ + _this.panX = 0; + _this.panY = 0; + + if(direction < 0){ + // Moving to show the right image + _this._switchImage("left", "right"); + }else{ + // Moving to show the left image + _this._switchImage("right", "left"); + } + + _this.render(); + _this.onChange(direction * -1); + }); + + }else{ + // If the next image is not loaded, make an animation to + // move the center image to half the width of the widget and back + // again + + console.log("moveTo image not loaded!", toImg); + + this._animPanTo(0, dojo.fx.easing.expoOut, 700); + } + }, + + _switchImage: function(toImg, fromImg){ + var toSmallImgName = "_" + toImg + "SmallImg"; + var toImgName = "_" + toImg + "Img"; + + var fromSmallImgName = "_" + fromImg + "SmallImg"; + var fromImgName = "_" + fromImg + "Img"; + + this[toImgName] = this._centerImg; + this[toSmallImgName] = this._centerSmallImg; + + this[toImgName]._type = toImg; + + if(this[toSmallImgName]){ + this[toSmallImgName]._type = toImg; + } + + this._centerImg = this[fromImgName]; + this._centerSmallImg = this[fromSmallImgName]; + this._centerImg._type = "center"; + + if(this._centerSmallImg){ + this._centerSmallImg._type = "center"; + } + this[fromImgName] = this[fromSmallImgName] = null; + }, + + _animPanTo: function(to, easing, duration, callback){ + this._animCallback = callback; + this._anim = new dojo.Animation({ + curve: [this.panX, to], + onAnimate: this._updateAnimatedPan, + duration: duration || 500, + easing: easing, + onEnd: this._onAnimPanEnd + }); + + this._anim.play(); + return this._anim; + }, + + onChange: function(direction){ + // summary: + // Stub function that can be listened to in order to provide + // new images when the displayed image changes + }, + + _updateAnimatedPan: function(amount){ + this.panX = amount; + this.render(); + }, + + _onAnimPanEnd: function(){ + this.panX = this.panY = 0; + + if(this._animCallback){ + this._animCallback(); + } + }, + + zoomTo: function(centerX, centerY, zoom){ + this.set("zoomCenterX", centerX); + this.set("zoomCenterY", centerY); + + this.set("animatedZoom", zoom); + }, + + render: function(){ + var cxt = this.canvas.getContext('2d'); + + cxt.clearRect(0, 0, this.canvas.width, this.canvas.height); + + // Render the center image + this._renderImg( + this._centerSmallImg, + this._centerImg, + this.zoom == 1 ? (this.panX < 0 ? 1 : this.panX > 0 ? -1 : 0) : 0); + + if(this.zoom == 1 && this.panX != 0){ + if(this.panX > 0){ + // Render the left image, showing the right side of it + this._renderImg(this._leftSmallImg, this._leftImg, 1); + }else{ + // Render the right image, showing the left side of it + this._renderImg(this._rightSmallImg, this._rightImg, -1); + } + } + }, + + _renderImg: function(smallImg, largeImg, panDir){ + // summary: + // Renders a single image + + + // If zoomed, we just display the center img + var img = (largeImg && largeImg._loaded) ? largeImg : smallImg; + + if(!img || !img._loaded){ + // If neither the large or small image is loaded, display nothing + return; + } + var cxt = this.canvas.getContext('2d'); + + var baseWidth = img._baseWidth; + var baseHeight = img._baseHeight; + + // Calculate the size the image would be if there were no bounds + var desiredWidth = baseWidth * this.zoom; + var desiredHeight = baseHeight * this.zoom; + + // Calculate the actual size of the viewable image + var destWidth = Math.min(this.size.w, desiredWidth); + var destHeight = Math.min(this.size.h, desiredHeight); + + + // Calculate the size of the window on the original image to use + var sourceWidth = this.dispWidth = img.width * (destWidth / desiredWidth); + var sourceHeight = this.dispHeight = img.height * (destHeight / desiredHeight); + + var zoomCenterX = this.zoomCenterX - (this.panX / this.zoom); + var zoomCenterY = this.zoomCenterY - (this.panY / this.zoom); + + // Calculate where the center of the view should be + var centerX = Math.floor(Math.max(sourceWidth / 2, + Math.min(img.width - sourceWidth / 2, zoomCenterX))); + var centerY = Math.floor(Math.max(sourceHeight / 2, + Math.min(img.height - sourceHeight / 2, zoomCenterY))); + + + var sourceX = Math.max(0, + Math.round((img.width - sourceWidth)/2 + (centerX - img._centerX)) ); + var sourceY = Math.max(0, + Math.round((img.height - sourceHeight) / 2 + (centerY - img._centerY)) + ); + + var destX = Math.round(Math.max(0, this.canvas.width - destWidth)/2); + var destY = Math.round(Math.max(0, this.canvas.height - destHeight)/2); + + var oldDestWidth = destWidth; + var oldSourceWidth = sourceWidth; + + if(this.zoom == 1 && panDir && this.panX){ + + if(this.panX < 0){ + if(panDir > 0){ + // If the touch is moving left, and the right side of the + // image should be shown, then reduce the destination width + // by the absolute value of panX + destWidth -= Math.abs(this.panX); + destX = 0; + }else if(panDir < 0){ + // If the touch is moving left, and the left side of the + // image should be shown, then set the displayed width + // to the absolute value of panX, less some pixels for + // a padding between images + destWidth = Math.max(1, Math.abs(this.panX) - 5); + destX = this.size.w - destWidth; + } + }else{ + if(panDir > 0){ + // If the touch is moving right, and the right side of the + // image should be shown, then set the destination width + // to the absolute value of the pan, less some pixels for + // padding + destWidth = Math.max(1, Math.abs(this.panX) - 5); + destX = 0; + }else if(panDir < 0){ + // If the touch is moving right, and the left side of the + // image should be shown, then reduce the destination width + // by the widget width minus the absolute value of panX + destWidth -= Math.abs(this.panX); + destX = this.size.w - destWidth; + } + } + + sourceWidth = Math.max(1, + Math.floor(sourceWidth * (destWidth / oldDestWidth))); + + if(panDir > 0){ + // If the right side of the image should be displayed, move + // the sourceX to be the width of the image minus the difference + // between the original sourceWidth and the new sourceWidth + sourceX = (sourceX + oldSourceWidth) - (sourceWidth); + } + sourceX = Math.floor(sourceX); + } + + try{ + + // See https://developer.mozilla.org/en/Canvas_tutorial/Using_images + cxt.drawImage( + img, + Math.max(0, sourceX), + sourceY, + Math.min(oldSourceWidth, sourceWidth), + sourceHeight, + destX, // Xpos + destY, // Ypos + Math.min(oldDestWidth, destWidth), + destHeight + ); + }catch(e){ + console.log("Caught Error",e, + + "type=", img._type, + "oldDestWidth = ", oldDestWidth, + "destWidth", destWidth, + "destX", destX + , "oldSourceWidth=",oldSourceWidth, + "sourceWidth=", sourceWidth, + "sourceX = " + sourceX + ); + } + }, + + _setZoomAttr: function(amount){ + this.zoom = Math.min(this.maxZoom, Math.max(1, amount)); + + if(this.zoom == 1 + && this._centerImg + && this._centerImg._loaded){ + + if(!this.isAnimating()){ + this.zoomCenterX = this._centerImg.width / 2; + this.zoomCenterY = this._centerImg.height / 2; + } + this.panX = this.panY = 0; + } + + this.render(); + }, + + _setZoomCenterXAttr: function(value){ + if(value != this.zoomCenterX){ + if(this._centerImg && this._centerImg._loaded){ + value = Math.min(this._centerImg.width, value); + } + this.zoomCenterX = Math.max(0, Math.round(value)); + } + }, + + _setZoomCenterYAttr: function(value){ + if(value != this.zoomCenterY){ + if(this._centerImg && this._centerImg._loaded){ + value = Math.min(this._centerImg.height, value); + } + this.zoomCenterY = Math.max(0, Math.round(value)); + } + }, + + _setZoomCenterAttr: function(value){ + if(value.x != this.zoomCenterX || value.y != this.zoomCenterY){ + this.set("zoomCenterX", value.x); + this.set("zoomCenterY", value.y); + this.render(); + } + }, + + _setAnimatedZoomAttr: function(amount){ + if(this._anim && this._anim.status() == "playing"){ + return; + } + + this._anim = new dojo.Animation({ + curve: [this.zoom, amount], + onAnimate: this._updateAnimatedZoom, + onEnd: this._onAnimEnd + }); + + this._anim.play(); + }, + + _updateAnimatedZoom: function(amount){ + this._setZoomAttr(amount); + }, + + _setCenterUrlAttr: function(urlOrObj){ + this._setImage("center", urlOrObj); + }, + _setLeftUrlAttr: function(urlOrObj){ + this._setImage("left", urlOrObj); + }, + _setRightUrlAttr: function(urlOrObj){ + this._setImage("right", urlOrObj); + }, + + _setImage: function(name, urlOrObj){ + var smallUrl = null; + + var largeUrl = null; + + if(dojo.isString(urlOrObj)){ + // If the argument is a string, then just load the large url + largeUrl = urlOrObj; + }else{ + largeUrl = urlOrObj.large; + smallUrl = urlOrObj.small; + } + + if(this["_" + name + "Img"] && this["_" + name + "Img"]._src == largeUrl){ + // Identical URL, ignore it + return; + } + + // Just do the large image for now + var largeImg = this["_" + name + "Img"] = new Image(); + largeImg._type = name; + largeImg._loaded = false; + largeImg._src = largeUrl; + largeImg._conn = dojo.connect(largeImg, "onload", this.handleLoad); + + if(smallUrl){ + // If a url to a small version of the image has been provided, + // load that image first. + var smallImg = this["_" + name + "SmallImg"] = new Image(); + smallImg._type = name; + smallImg._loaded = false; + smallImg._conn = dojo.connect(smallImg, "onload", this.handleLoad); + smallImg._isSmall = true; + smallImg._src = smallUrl; + smallImg.src = smallUrl; + } + + // It's important that the large url's src is set after the small image + // to ensure it's loaded second. + largeImg.src = largeUrl; + }, + + handleLoad: function(evt){ + // summary: + // Handles the loading of an image, both the large and small + // versions. A render is triggered as a result of each image load. + + var img = evt.target; + img._loaded = true; + + dojo.disconnect(img._conn); + + var type = img._type; + + switch(type){ + case "center": + this.zoomCenterX = img.width / 2; + this.zoomCenterY = img.height / 2; + break; + } + + var height = img.height; + var width = img.width; + + if(width / this.size.w < height / this.size.h){ + // Fit the height to the height of the canvas + img._baseHeight = this.canvas.height; + img._baseWidth = width / (height / this.size.h); + }else{ + // Fix the width to the width of the canvas + img._baseWidth = this.canvas.width; + img._baseHeight = height / (width / this.size.w); + } + img._centerX = width / 2; + img._centerY = height / 2; + + this.render(); + + this.onLoad(img._type, img._src, img._isSmall); + }, + + onLoad: function(type, url, isSmall){ + // summary: + // Dummy function that is called whenever an image loads. + // type: String + // The position of the image that has loaded, either + // "center", "left" or "right" + // url: String + // The src of the image + // isSmall: Boolean + // True if it is a small version of the image that has loaded, + // false otherwise. + } +}); + +}); diff --git a/js/dojo/dojox/mobile/app/List.js b/js/dojo/dojox/mobile/app/List.js new file mode 100644 index 0000000..21c2c64 --- /dev/null +++ b/js/dojo/dojox/mobile/app/List.js @@ -0,0 +1,649 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/List", ["dijit","dojo","dojox","dojo/require!dojo/string,dijit/_WidgetBase"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.List"); +dojo.experimental("dojox.mobile.app.List"); + +dojo.require("dojo.string"); +dojo.require("dijit._WidgetBase"); + +(function(){ + + var templateCache = {}; + + dojo.declare("dojox.mobile.app.List", dijit._WidgetBase, { + // summary: + // A templated list widget. Given a simple array of data objects + // and a HTML template, it renders a list of elements, with + // support for a swipe delete action. An optional template + // can be provided for when the list is empty. + + // items: Array + // The array of data items that will be rendered. + items: null, + + // itemTemplate: String + // The URL to the HTML file containing the markup for each individual + // data item. + itemTemplate: "", + + // emptyTemplate: String + // The URL to the HTML file containing the HTML to display if there + // are no data items. This is optional. + emptyTemplate: "", + + // dividerTemplate: String + // The URL to the HTML file containing the markup for the dividers + // between groups of list items + dividerTemplate: "", + + // dividerFunction: Function + // Function to create divider elements. This should return a divider + // value for each item in the list + dividerFunction: null, + + // labelDelete: String + // The label to display for the Delete button + labelDelete: "Delete", + + // labelCancel: String + // The label to display for the Cancel button + labelCancel: "Cancel", + + // controller: Object + // + controller: null, + + // autoDelete: Boolean + autoDelete: true, + + // enableDelete: Boolean + enableDelete: true, + + // enableHold: Boolean + enableHold: true, + + // formatters: Object + // A name/value map of functions used to format data for display + formatters: null, + + // _templateLoadCount: Number + // The number of templates remaining to load before the list renders. + _templateLoadCount: 0, + + // _mouseDownPos: Object + // The coordinates of where a mouseDown event was detected + _mouseDownPos: null, + + baseClass: "list", + + constructor: function(){ + this._checkLoadComplete = dojo.hitch(this, this._checkLoadComplete); + this._replaceToken = dojo.hitch(this, this._replaceToken); + this._postDeleteAnim = dojo.hitch(this, this._postDeleteAnim); + }, + + postCreate: function(){ + + var _this = this; + + if(this.emptyTemplate){ + this._templateLoadCount++; + } + if(this.itemTemplate){ + this._templateLoadCount++; + } + if(this.dividerTemplate){ + this._templateLoadCount++; + } + + this.connect(this.domNode, "onmousedown", function(event){ + var touch = event; + if(event.targetTouches && event.targetTouches.length > 0){ + touch = event.targetTouches[0]; + } + + // Find the node that was tapped/clicked + var rowNode = _this._getRowNode(event.target); + + if(rowNode){ + // Add the rows data to the event so it can be picked up + // by any listeners + _this._setDataInfo(rowNode, event); + + // Select and highlight the row + _this._selectRow(rowNode); + + // Record the position that was tapped + _this._mouseDownPos = { + x: touch.pageX, + y: touch.pageY + }; + _this._dragThreshold = null; + } + }); + + this.connect(this.domNode, "onmouseup", function(event){ + // When the mouse/finger comes off the list, + // call the onSelect function and deselect the row. + if(event.targetTouches && event.targetTouches.length > 0){ + event = event.targetTouches[0]; + } + var rowNode = _this._getRowNode(event.target); + + if(rowNode){ + + _this._setDataInfo(rowNode, event); + + if(_this._selectedRow){ + _this.onSelect(rowNode._data, rowNode._idx, rowNode); + } + + this._deselectRow(); + } + }); + + // If swipe-to-delete is enabled, listen for the mouse moving + if(this.enableDelete){ + this.connect(this.domNode, "mousemove", function(event){ + dojo.stopEvent(event); + if(!_this._selectedRow){ + return; + } + var rowNode = _this._getRowNode(event.target); + + // Still check for enableDelete in case it's changed after + // this listener is added. + if(_this.enableDelete && rowNode && !_this._deleting){ + _this.handleDrag(event); + } + }); + } + + // Put the data and index onto each onclick event. + this.connect(this.domNode, "onclick", function(event){ + if(event.touches && event.touches.length > 0){ + event = event.touches[0]; + } + var rowNode = _this._getRowNode(event.target, true); + + if(rowNode){ + _this._setDataInfo(rowNode, event); + } + }); + + // If the mouse or finger moves off the selected row, + // deselect it. + this.connect(this.domNode, "mouseout", function(event){ + if(event.touches && event.touches.length > 0){ + event = event.touches[0]; + } + if(event.target == _this._selectedRow){ + _this._deselectRow(); + } + }); + + // If no item template has been provided, it is an error. + if(!this.itemTemplate){ + throw Error("An item template must be provided to " + this.declaredClass); + } + + // Load the item template + this._loadTemplate(this.itemTemplate, "itemTemplate", this._checkLoadComplete); + + if(this.emptyTemplate){ + // If the optional empty template has been provided, load it. + this._loadTemplate(this.emptyTemplate, "emptyTemplate", this._checkLoadComplete); + } + + if(this.dividerTemplate){ + this._loadTemplate(this.dividerTemplate, "dividerTemplate", this._checkLoadComplete); + } + }, + + handleDrag: function(event){ + // summary: + // Handles rows being swiped for deletion. + var touch = event; + if(event.targetTouches && event.targetTouches.length > 0){ + touch = event.targetTouches[0]; + } + + // Get the distance that the mouse or finger has moved since + // beginning the swipe action. + var diff = touch.pageX - this._mouseDownPos.x; + + var absDiff = Math.abs(diff); + if(absDiff > 10 && !this._dragThreshold){ + // Make the user drag the row 60% of the width to remove it + this._dragThreshold = dojo.marginBox(this._selectedRow).w * 0.6; + if(!this.autoDelete){ + this.createDeleteButtons(this._selectedRow); + } + } + + this._selectedRow.style.left = (absDiff > 10 ? diff : 0) + "px"; + + // If the user has dragged the row more than the threshold, slide + // it off the screen in preparation for deletion. + if(this._dragThreshold && this._dragThreshold < absDiff){ + this.preDelete(diff); + } + }, + + handleDragCancel: function(){ + // summary: + // Handle a drag action being cancelled, for whatever reason. + // Reset handles, remove CSS classes etc. + if(this._deleting){ + return; + } + dojo.removeClass(this._selectedRow, "hold"); + this._selectedRow.style.left = 0; + this._mouseDownPos = null; + this._dragThreshold = null; + + this._deleteBtns && dojo.style(this._deleteBtns, "display", "none"); + }, + + preDelete: function(currentLeftPos){ + // summary: + // Slides the row offscreen before it is deleted + + // TODO: do this with CSS3! + var self = this; + + this._deleting = true; + + dojo.animateProperty({ + node: this._selectedRow, + duration: 400, + properties: { + left: { + end: currentLeftPos + + ((currentLeftPos > 0 ? 1 : -1) * this._dragThreshold * 0.8) + } + }, + onEnd: dojo.hitch(this, function(){ + if(this.autoDelete){ + this.deleteRow(this._selectedRow); + } + }) + }).play(); + }, + + deleteRow: function(row){ + + // First make the row invisible + // Put it back where it came from + dojo.style(row, { + visibility: "hidden", + minHeight: "0px" + }); + dojo.removeClass(row, "hold"); + + this._deleteAnimConn = + this.connect(row, "webkitAnimationEnd", this._postDeleteAnim); + + dojo.addClass(row, "collapsed"); + }, + + _postDeleteAnim: function(event){ + // summary: + // Completes the deletion of a row. + + if(this._deleteAnimConn){ + this.disconnect(this._deleteAnimConn); + this._deleteAnimConn = null; + } + + var row = this._selectedRow; + var sibling = row.nextSibling; + var prevSibling = row.previousSibling; + + // If the previous node is a divider and either this is + // the last element in the list, or the next node is + // also a divider, remove the divider for the deleted section. + if(prevSibling && prevSibling._isDivider){ + if(!sibling || sibling._isDivider){ + prevSibling.parentNode.removeChild(prevSibling); + } + } + + row.parentNode.removeChild(row); + this.onDelete(row._data, row._idx, this.items); + + // Decrement the index of each following row + while(sibling){ + if(sibling._idx){ + sibling._idx--; + } + sibling = sibling.nextSibling; + } + + dojo.destroy(row); + + // Fix up the 'first' and 'last' CSS classes on the rows + dojo.query("> *:not(.buttons)", this.domNode).forEach(this.applyClass); + + this._deleting = false; + this._deselectRow(); + }, + + createDeleteButtons: function(aroundNode){ + // summary: + // Creates the two buttons displayed when confirmation is + // required before deletion of a row. + // aroundNode: + // The DOM node of the row about to be deleted. + var mb = dojo.marginBox(aroundNode); + var pos = dojo._abs(aroundNode, true); + + if(!this._deleteBtns){ + // Create the delete buttons. + this._deleteBtns = dojo.create("div",{ + "class": "buttons" + }, this.domNode); + + this.buttons = []; + + this.buttons.push(new dojox.mobile.Button({ + btnClass: "mblRedButton", + label: this.labelDelete + })); + this.buttons.push(new dojox.mobile.Button({ + btnClass: "mblBlueButton", + label: this.labelCancel + })); + + dojo.place(this.buttons[0].domNode, this._deleteBtns); + dojo.place(this.buttons[1].domNode, this._deleteBtns); + + dojo.addClass(this.buttons[0].domNode, "deleteBtn"); + dojo.addClass(this.buttons[1].domNode, "cancelBtn"); + + this._handleButtonClick = dojo.hitch(this._handleButtonClick); + this.connect(this._deleteBtns, "onclick", this._handleButtonClick); + } + dojo.removeClass(this._deleteBtns, "fade out fast"); + dojo.style(this._deleteBtns, { + display: "", + width: mb.w + "px", + height: mb.h + "px", + top: (aroundNode.offsetTop) + "px", + left: "0px" + }); + }, + + onDelete: function(data, index, array){ + // summary: + // Called when a row is deleted + // data: + // The data related to the row being deleted + // index: + // The index of the data in the total array + // array: + // The array of data used. + + array.splice(index, 1); + + // If the data is empty, rerender in case an emptyTemplate has + // been provided + if(array.length < 1){ + this.render(); + } + }, + + cancelDelete: function(){ + // summary: + // Cancels the deletion of a row. + this._deleting = false; + this.handleDragCancel(); + }, + + _handleButtonClick: function(event){ + // summary: + // Handles the click of one of the deletion buttons, either to + // delete the row or to cancel the deletion. + if(event.touches && event.touches.length > 0){ + event = event.touches[0]; + } + var node = event.target; + if(dojo.hasClass(node, "deleteBtn")){ + this.deleteRow(this._selectedRow); + }else if(dojo.hasClass(node, "cancelBtn")){ + this.cancelDelete(); + }else{ + return; + } + dojo.addClass(this._deleteBtns, "fade out"); + }, + + applyClass: function(node, idx, array){ + // summary: + // Applies the 'first' and 'last' CSS classes to the relevant + // rows. + + dojo.removeClass(node, "first last"); + if(idx == 0){ + dojo.addClass(node, "first"); + } + if(idx == array.length - 1){ + dojo.addClass(node, "last"); + } + }, + + _setDataInfo: function(rowNode, event){ + // summary: + // Attaches the data item and index for each row to any event + // that occurs on that row. + event.item = rowNode._data; + event.index = rowNode._idx; + }, + + onSelect: function(data, index, rowNode){ + // summary: + // Dummy function that is called when a row is tapped + }, + + _selectRow: function(row){ + // summary: + // Selects a row, applies the relevant CSS classes. + if(this._deleting && this._selectedRow && row != this._selectedRow){ + this.cancelDelete(); + } + + if(!dojo.hasClass(row, "row")){ + return; + } + if(this.enableHold || this.enableDelete){ + dojo.addClass(row, "hold"); + } + this._selectedRow = row; + }, + + _deselectRow: function(){ + // summary: + // Deselects a row, and cancels any drag actions that were + // occurring. + if(!this._selectedRow || this._deleting){ + return; + } + this.handleDragCancel(); + dojo.removeClass(this._selectedRow, "hold"); + this._selectedRow = null; + }, + + _getRowNode: function(fromNode, ignoreNoClick){ + // summary: + // Gets the DOM node of the row that is equal to or the parent + // of the node passed to this function. + while(fromNode && !fromNode._data && fromNode != this.domNode){ + if(!ignoreNoClick && dojo.hasClass(fromNode, "noclick")){ + return null; + } + fromNode = fromNode.parentNode; + } + return fromNode == this.domNode ? null : fromNode; + }, + + applyTemplate: function(template, data){ + return dojo._toDom(dojo.string.substitute( + template, data, this._replaceToken, this.formatters || this)); + }, + + render: function(){ + // summary: + // Renders the list. + + // Delete all existing nodes, except the deletion buttons. + dojo.query("> *:not(.buttons)", this.domNode).forEach(dojo.destroy); + + // If there is no data, and an empty template has been provided, + // render it. + if(this.items.length < 1 && this.emptyTemplate){ + dojo.place(dojo._toDom(this.emptyTemplate), this.domNode, "first"); + }else{ + this.domNode.appendChild(this._renderRange(0, this.items.length)); + } + if(dojo.hasClass(this.domNode.parentNode, "mblRoundRect")){ + dojo.addClass(this.domNode.parentNode, "mblRoundRectList") + } + + var divs = dojo.query("> .row", this.domNode); + if(divs.length > 0){ + dojo.addClass(divs[0], "first"); + dojo.addClass(divs[divs.length - 1], "last"); + } + }, + + _renderRange: function(startIdx, endIdx){ + + var rows = []; + var row, i; + var frag = document.createDocumentFragment(); + startIdx = Math.max(0, startIdx); + endIdx = Math.min(endIdx, this.items.length); + + for(i = startIdx; i < endIdx; i++){ + // Create a document fragment containing the templated row + row = this.applyTemplate(this.itemTemplate, this.items[i]); + dojo.addClass(row, 'row'); + row._data = this.items[i]; + row._idx = i; + rows.push(row); + } + if(!this.dividerFunction || !this.dividerTemplate){ + for(i = startIdx; i < endIdx; i++){ + rows[i]._data = this.items[i]; + rows[i]._idx = i; + frag.appendChild(rows[i]); + } + }else{ + var prevDividerValue = null; + var dividerValue; + var divider; + for(i = startIdx; i < endIdx; i++){ + rows[i]._data = this.items[i]; + rows[i]._idx = i; + + dividerValue = this.dividerFunction(this.items[i]); + if(dividerValue && dividerValue != prevDividerValue){ + divider = this.applyTemplate(this.dividerTemplate, { + label: dividerValue, + item: this.items[i] + }); + divider._isDivider = true; + frag.appendChild(divider); + prevDividerValue = dividerValue; + } + frag.appendChild(rows[i]); + } + } + return frag; + }, + + _replaceToken: function(value, key){ + if(key.charAt(0) == '!'){ value = dojo.getObject(key.substr(1), false, _this); } + if(typeof value == "undefined"){ return ""; } // a debugging aide + if(value == null){ return ""; } + + // Substitution keys beginning with ! will skip the transform step, + // in case a user wishes to insert unescaped markup, e.g. ${!foo} + return key.charAt(0) == "!" ? value : + // Safer substitution, see heading "Attribute values" in + // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 + value.toString().replace(/"/g,"""); //TODO: add &? use encodeXML method? + + }, + + _checkLoadComplete: function(){ + // summary: + // Checks if all templates have loaded + this._templateLoadCount--; + + if(this._templateLoadCount < 1 && this.get("items")){ + this.render(); + } + }, + + _loadTemplate: function(url, thisAttr, callback){ + // summary: + // Loads a template + if(!url){ + callback(); + return; + } + + if(templateCache[url]){ + this.set(thisAttr, templateCache[url]); + callback(); + }else{ + var _this = this; + + dojo.xhrGet({ + url: url, + sync: false, + handleAs: "text", + load: function(text){ + templateCache[url] = dojo.trim(text); + _this.set(thisAttr, templateCache[url]); + callback(); + } + }); + } + }, + + + _setFormattersAttr: function(formatters){ + // summary: + // Sets the data items, and causes a rerender of the list + this.formatters = formatters; + }, + + _setItemsAttr: function(items){ + // summary: + // Sets the data items, and causes a rerender of the list + + this.items = items || []; + + if(this._templateLoadCount < 1 && items){ + this.render(); + } + }, + + destroy: function(){ + if(this.buttons){ + dojo.forEach(this.buttons, function(button){ + button.destroy(); + }); + this.buttons = null; + } + + this.inherited(arguments); + } + + }); + +})(); +}); diff --git a/js/dojo/dojox/mobile/app/ListSelector.js b/js/dojo/dojox/mobile/app/ListSelector.js new file mode 100644 index 0000000..b3f6f94 --- /dev/null +++ b/js/dojo/dojox/mobile/app/ListSelector.js @@ -0,0 +1,223 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/ListSelector", ["dijit","dojo","dojox","dojo/require!dojox/mobile/app/_Widget,dojo/fx"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.ListSelector"); +dojo.experimental("dojox.mobile.app.ListSelector"); + +dojo.require("dojox.mobile.app._Widget"); +dojo.require("dojo.fx"); + +dojo.declare("dojox.mobile.app.ListSelector", dojox.mobile.app._Widget, { + + // data: Array + // The array of items to display. Each element in the array + // should have both a label and value attribute, e.g. + // [{label: "Open", value: 1} , {label: "Delete", value: 2}] + data: null, + + // controller: Object + // The current SceneController widget. + controller: null, + + // onChoose: Function + // The callback function for when an item is selected + onChoose: null, + + destroyOnHide: false, + + _setDataAttr: function(data){ + this.data = data; + + if(this.data){ + this.render(); + } + }, + + postCreate: function(){ + dojo.addClass(this.domNode, "listSelector"); + + var _this = this; + + this.connect(this.domNode, "onclick", function(event){ + if(!dojo.hasClass(event.target, "listSelectorRow")){ + return; + } + + if(_this.onChoose){ + _this.onChoose(_this.data[event.target._idx].value); + } + _this.hide(); + }); + + this.connect(this.domNode, "onmousedown", function(event){ + if(!dojo.hasClass(event.target, "listSelectorRow")){ + return; + } + dojo.addClass(event.target, "listSelectorRow-selected"); + }); + + this.connect(this.domNode, "onmouseup", function(event){ + if(!dojo.hasClass(event.target, "listSelectorRow")){ + return; + } + dojo.removeClass(event.target, "listSelectorRow-selected"); + }); + + this.connect(this.domNode, "onmouseout", function(event){ + if(!dojo.hasClass(event.target, "listSelectorRow")){ + return; + } + dojo.removeClass(event.target, "listSelectorRow-selected"); + }); + + var viewportSize = this.controller.getWindowSize(); + + this.mask = dojo.create("div", {"class": "dialogUnderlayWrapper", + innerHTML: "<div class=\"dialogUnderlay\"></div>" + }, this.controller.assistant.domNode); + + this.connect(this.mask, "onclick", function(){ + _this.onChoose && _this.onChoose(); + _this.hide(); + }); + }, + + show: function(fromNode){ + + // Using dojo.fx here. Must figure out how to do this with CSS animations!! + var startPos; + + var windowSize = this.controller.getWindowSize(); + var fromNodePos; + if(fromNode){ + fromNodePos = dojo._abs(fromNode); + startPos = fromNodePos; + }else{ + startPos.x = windowSize.w / 2; + startPos.y = 200; + } + console.log("startPos = ", startPos); + + dojo.style(this.domNode, { + opacity: 0, + display: "", + width: Math.floor(windowSize.w * 0.8) + "px" + }); + + var maxWidth = 0; + dojo.query(">", this.domNode).forEach(function(node){ + dojo.style(node, { + "float": "left" + }); + maxWidth = Math.max(maxWidth, dojo.marginBox(node).w); + dojo.style(node, { + "float": "none" + }); + }); + maxWidth = Math.min(maxWidth, Math.round(windowSize.w * 0.8)) + + dojo.style(this.domNode, "paddingLeft") + + dojo.style(this.domNode, "paddingRight") + + 1; + + dojo.style(this.domNode, "width", maxWidth + "px"); + var targetHeight = dojo.marginBox(this.domNode).h; + + var _this = this; + + + var targetY = fromNodePos ? + Math.max(30, fromNodePos.y - targetHeight - 10) : + this.getScroll().y + 30; + + console.log("fromNodePos = ", fromNodePos, " targetHeight = ", targetHeight, + " targetY = " + targetY, " startPos ", startPos); + + + var anim1 = dojo.animateProperty({ + node: this.domNode, + duration: 400, + properties: { + width: {start: 1, end: maxWidth}, + height: {start: 1, end: targetHeight}, + top: {start: startPos.y, end: targetY}, + left: {start: startPos.x, end: (windowSize.w/2 - maxWidth/2)}, + opacity: {start: 0, end: 1}, + fontSize: {start: 1} + }, + onEnd: function(){ + dojo.style(_this.domNode, "width", "inherit"); + } + }); + var anim2 = dojo.fadeIn({ + node: this.mask, + duration: 400 + }); + dojo.fx.combine([anim1, anim2]).play(); + + }, + + hide: function(){ + // Using dojo.fx here. Must figure out how to do this with CSS animations!! + + var _this = this; + + var anim1 = dojo.animateProperty({ + node: this.domNode, + duration: 500, + properties: { + width: {end: 1}, + height: {end: 1}, + opacity: {end: 0}, + fontSize: {end: 1} + }, + onEnd: function(){ + if(_this.get("destroyOnHide")){ + _this.destroy(); + } + } + }); + + var anim2 = dojo.fadeOut({ + node: this.mask, + duration: 400 + }); + dojo.fx.combine([anim1, anim2]).play(); + }, + + render: function(){ + // summary: + // Renders + + dojo.empty(this.domNode); + dojo.style(this.domNode, "opacity", 0); + + var row; + + for(var i = 0; i < this.data.length; i++){ + // Create each row and add any custom classes. Also set the _idx property. + row = dojo.create("div", { + "class": "listSelectorRow " + (this.data[i].className || ""), + innerHTML: this.data[i].label + }, this.domNode); + + row._idx = i; + + if(i == 0){ + dojo.addClass(row, "first"); + } + if(i == this.data.length - 1){ + dojo.addClass(row, "last"); + } + + } + }, + + + destroy: function(){ + this.inherited(arguments); + dojo.destroy(this.mask); + } + +}); + +}); diff --git a/js/dojo/dojox/mobile/app/SceneAssistant.js b/js/dojo/dojox/mobile/app/SceneAssistant.js new file mode 100644 index 0000000..2b192bf --- /dev/null +++ b/js/dojo/dojox/mobile/app/SceneAssistant.js @@ -0,0 +1,61 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/SceneAssistant", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.SceneAssistant"); +dojo.experimental("dojox.mobile.app.SceneAssistant"); + +dojo.declare("dojox.mobile.app.SceneAssistant", null, { + // summary: + // The base class for all scene assistants. + + constructor: function(){ + + }, + + setup: function(){ + // summary: + // Called to set up the widget. The UI is not visible at this time + + }, + + activate: function(params){ + // summary: + // Called each time the scene becomes visible. This can be as a result + // of a new scene being created, or a subsequent scene being destroyed + // and control transferring back to this scene assistant. + // params: + // Optional paramters, only passed when a subsequent scene pops itself + // off the stack and passes back data. + }, + + deactivate: function(){ + // summary: + // Called each time the scene becomes invisible. This can be as a result + // of it being popped off the stack and destroyed, + // or another scene being created and pushed on top of it on the stack + }, + + destroy: function(){ + + var children = + dojo.query("> [widgetId]", this.containerNode).map(dijit.byNode); + dojo.forEach(children, function(child){ child.destroyRecursive(); }); + + this.disconnect(); + }, + + connect: function(obj, method, callback){ + if(!this._connects){ + this._connects = []; + } + this._connects.push(dojo.connect(obj, method, callback)); + }, + + disconnect: function(){ + dojo.forEach(this._connects, dojo.disconnect); + this._connects = []; + } +}); + + +}); diff --git a/js/dojo/dojox/mobile/app/SceneController.js b/js/dojo/dojox/mobile/app/SceneController.js new file mode 100644 index 0000000..30f0788 --- /dev/null +++ b/js/dojo/dojox/mobile/app/SceneController.js @@ -0,0 +1,174 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/SceneController", ["dijit","dojo","dojox","dojo/require!dojox/mobile/_base"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.SceneController"); +dojo.experimental("dojox.mobile.app.SceneController"); +dojo.require("dojox.mobile._base"); + +(function(){ + + var app = dojox.mobile.app; + + var templates = {}; + + dojo.declare("dojox.mobile.app.SceneController", dojox.mobile.View, { + + stageController: null, + + keepScrollPos: false, + + init: function(sceneName, params){ + // summary: + // Initializes the scene by loading the HTML template and code, if it has + // not already been loaded + + this.sceneName = sceneName; + this.params = params; + var templateUrl = app.resolveTemplate(sceneName); + + this._deferredInit = new dojo.Deferred(); + + if(templates[sceneName]){ + // If the template has been cached, do not load it again. + this._setContents(templates[sceneName]); + }else{ + // Otherwise load the template + dojo.xhrGet({ + url: templateUrl, + handleAs: "text" + }).addCallback(dojo.hitch(this, this._setContents)); + } + + return this._deferredInit; + }, + + _setContents: function(templateHtml){ + // summary: + // Sets the content of the View, and invokes either the loading or + // initialization of the scene assistant. + templates[this.sceneName] = templateHtml; + + this.domNode.innerHTML = "<div>" + templateHtml + "</div>"; + + var sceneAssistantName = ""; + + var nameParts = this.sceneName.split("-"); + + for(var i = 0; i < nameParts.length; i++){ + sceneAssistantName += nameParts[i].substring(0, 1).toUpperCase() + + nameParts[i].substring(1); + } + sceneAssistantName += "Assistant"; + this.sceneAssistantName = sceneAssistantName; + + var _this = this; + + dojox.mobile.app.loadResourcesForScene(this.sceneName, function(){ + + console.log("All resources for ",_this.sceneName," loaded"); + + var assistant; + if(typeof(dojo.global[sceneAssistantName]) != "undefined"){ + _this._initAssistant(); + }else{ + var assistantUrl = app.resolveAssistant(_this.sceneName); + + dojo.xhrGet({ + url: assistantUrl, + handleAs: "text" + }).addCallback(function(text){ + try{ + dojo.eval(text); + }catch(e){ + console.log("Error initializing code for scene " + _this.sceneName + + '. Please check for syntax errors'); + throw e; + } + _this._initAssistant(); + }); + } + }); + + }, + + _initAssistant: function(){ + // summary: + // Initializes the scene assistant. At this point, the View is + // populated with the HTML template, and the scene assistant type + // is declared. + + console.log("Instantiating the scene assistant " + this.sceneAssistantName); + + var cls = dojo.getObject(this.sceneAssistantName); + + if(!cls){ + throw Error("Unable to resolve scene assistant " + + this.sceneAssistantName); + } + + this.assistant = new cls(this.params); + + this.assistant.controller = this; + this.assistant.domNode = this.domNode.firstChild; + + this.assistant.setup(); + + this._deferredInit.callback(); + }, + + query: function(selector, node){ + // summary: + // Queries for DOM nodes within either the node passed in as an argument + // or within this view. + + return dojo.query(selector, node || this.domNode) + }, + + parse: function(node){ + var widgets = this._widgets = + dojox.mobile.parser.parse(node || this.domNode, { + controller: this + }); + + // Tell all widgets what their controller is. + for(var i = 0; i < widgets.length; i++){ + widgets[i].set("controller", this); + } + }, + + getWindowSize: function(){ + // TODO, this needs cross browser testing + + return { + w: dojo.global.innerWidth, + h: dojo.global.innerHeight + } + }, + + showAlertDialog: function(props){ + + var size = dojo.marginBox(this.assistant.domNode); + var dialog = new dojox.mobile.app.AlertDialog( + dojo.mixin(props, {controller: this})); + this.assistant.domNode.appendChild(dialog.domNode); + + console.log("Appended " , dialog.domNode, " to ", this.assistant.domNode); + dialog.show(); + }, + + popupSubMenu: function(info){ + var widget = new dojox.mobile.app.ListSelector({ + controller: this, + destroyOnHide: true, + onChoose: info.onChoose + }); + + this.assistant.domNode.appendChild(widget.domNode); + + widget.set("data", info.choices); + widget.show(info.fromNode); + } + }); + +})(); +}); diff --git a/js/dojo/dojox/mobile/app/StageController.js b/js/dojo/dojox/mobile/app/StageController.js new file mode 100644 index 0000000..1a6288b --- /dev/null +++ b/js/dojo/dojox/mobile/app/StageController.js @@ -0,0 +1,137 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/StageController", ["dijit","dojo","dojox","dojo/require!dojox/mobile/app/SceneController"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.StageController"); +dojo.experimental("dojox.mobile.app.StageController"); + +dojo.require("dojox.mobile.app.SceneController"); + +dojo.declare("dojox.mobile.app.StageController", null,{ + + // scenes: Array + // The list of scenes currently in existance in the app. + scenes: null, + + effect: "fade", + + constructor: function(node){ + this.domNode = node; + this.scenes = []; + + if(dojo.config.mobileAnim){ + this.effect = dojo.config.mobileAnim; + } + }, + + getActiveSceneController: function(){ + return this.scenes[this.scenes.length - 1]; + }, + + pushScene: function(sceneName, params){ + if(this._opInProgress){ + return; + } + this._opInProgress = true; + + // Push new scenes as the first element on the page. + var node = dojo.create("div", { + "class": "scene-wrapper", + style: { + visibility: "hidden" + } + }, this.domNode); + + var controller = new dojox.mobile.app.SceneController({}, node); + + if(this.scenes.length > 0){ + this.scenes[this.scenes.length -1].assistant.deactivate(); + } + + this.scenes.push(controller); + + var _this = this; + + dojo.forEach(this.scenes, this.setZIndex); + + controller.stageController = this; + + controller.init(sceneName, params).addCallback(function(){ + + if(_this.scenes.length == 1){ + controller.domNode.style.visibility = "visible"; + _this.scenes[_this.scenes.length - 1].assistant.activate(params); + _this._opInProgress = false; + }else{ + _this.scenes[_this.scenes.length - 2] + .performTransition( + _this.scenes[_this.scenes.length - 1].domNode, + 1, + _this.effect, + null, + function(){ + // When the scene is ready, activate it. + _this.scenes[_this.scenes.length - 1].assistant.activate(params); + _this._opInProgress = false; + }); + } + }); + }, + + setZIndex: function(controller, idx){ + dojo.style(controller.domNode, "zIndex", idx + 1); + }, + + popScene: function(data){ + // performTransition: function(/*String*/moveTo, /*Number*/dir, /*String*/transition, + // /*Object|null*/context, /*String|Function*/method /*optional args*/){ + if(this._opInProgress){ + return; + } + + var _this = this; + if(this.scenes.length > 1){ + + this._opInProgress = true; + this.scenes[_this.scenes.length - 2].assistant.activate(data); + this.scenes[_this.scenes.length - 1] + .performTransition( + _this.scenes[this.scenes.length - 2].domNode, + -1, + this.effect, + null, + function(){ + // When the scene is no longer visible, destroy it + _this._destroyScene(_this.scenes[_this.scenes.length - 1]); + _this.scenes.splice(_this.scenes.length - 1, 1); + _this._opInProgress = false; + }); + }else{ + console.log("cannot pop the scene if there is just one"); + } + }, + + popScenesTo: function(sceneName, data){ + if(this._opInProgress){ + return; + } + + while(this.scenes.length > 2 && + this.scenes[this.scenes.length - 2].sceneName != sceneName){ + this._destroyScene(this.scenes[this.scenes.length - 2]); + this.scenes.splice(this.scenes.length - 2, 1); + } + + this.popScene(data); + }, + + _destroyScene: function(scene){ + scene.assistant.deactivate(); + scene.assistant.destroy(); + scene.destroyRecursive(); + } + + +}); + + +}); diff --git a/js/dojo/dojox/mobile/app/TextBox.js b/js/dojo/dojox/mobile/app/TextBox.js new file mode 100644 index 0000000..d1c2bb2 --- /dev/null +++ b/js/dojo/dojox/mobile/app/TextBox.js @@ -0,0 +1,10 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/TextBox", ["dijit","dojo","dojox","dojo/require!dojox/mobile/TextBox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.TextBox"); +dojo.deprecated("dojox.mobile.app.TextBox is deprecated", "dojox.mobile.app.TextBox moved to dojox.mobile.TextBox", 1.8); + +dojo.require("dojox.mobile.TextBox"); + +dojox.mobile.app.TextBox = dojox.mobile.TextBox; +}); diff --git a/js/dojo/dojox/mobile/app/_FormWidget.js b/js/dojo/dojox/mobile/app/_FormWidget.js new file mode 100644 index 0000000..1a6c8cd --- /dev/null +++ b/js/dojo/dojox/mobile/app/_FormWidget.js @@ -0,0 +1,293 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/_FormWidget", ["dijit","dojo","dojox","dojo/require!dojo/window,dijit/_WidgetBase,dijit/focus"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app._FormWidget"); +dojo.experimental("dojox.mobile.app._FormWidget"); + +dojo.require("dojo.window"); + +dojo.require("dijit._WidgetBase"); +dojo.require("dijit.focus"); // dijit.focus() + +dojo.declare("dojox.mobile.app._FormWidget", dijit._WidgetBase, { + // summary: + // Base class for widgets corresponding to native HTML elements such as <checkbox> or <button>, + // which can be children of a <form> node or a `dojox.mobile.app.Form` widget. + // + // description: + // Represents a single HTML element. + // All these widgets should have these attributes just like native HTML input elements. + // You can set them during widget construction or afterwards, via `dijit._WidgetBase.attr`. + // + // They also share some common methods. + + // name: String + // Name used when submitting form; same as "name" attribute or plain HTML elements + name: "", + + // alt: String + // Corresponds to the native HTML <input> element's attribute. + alt: "", + + // value: String + // Corresponds to the native HTML <input> element's attribute. + value: "", + + // type: String + // Corresponds to the native HTML <input> element's attribute. + type: "text", + + // disabled: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "disabled='disabled'", or just "disabled". + disabled: false, + + // intermediateChanges: Boolean + // Fires onChange for each value change or only on demand + intermediateChanges: false, + + // scrollOnFocus: Boolean + // On focus, should this widget scroll into view? + scrollOnFocus: false, + + // These mixins assume that the focus node is an INPUT, as many but not all _FormWidgets are. + attributeMap: dojo.delegate(dijit._WidgetBase.prototype.attributeMap, { + value: "focusNode", + id: "focusNode", + alt: "focusNode", + title: "focusNode" + }), + + postMixInProperties: function(){ + // Setup name=foo string to be referenced from the template (but only if a name has been specified) + // Unfortunately we can't use attributeMap to set the name due to IE limitations, see #8660 + // Regarding escaping, see heading "Attribute values" in + // http://www.w3.org/TR/REC-html40/appendix/notes.html#h-B.3.2 + this.nameAttrSetting = this.name ? ('name="' + this.name.replace(/'/g, """) + '"') : ''; + this.inherited(arguments); + }, + + postCreate: function(){ + this.inherited(arguments); + this.connect(this.domNode, "onmousedown", "_onMouseDown"); + }, + + _setDisabledAttr: function(/*Boolean*/ value){ + this.disabled = value; + dojo.attr(this.focusNode, 'disabled', value); + if(this.valueNode){ + dojo.attr(this.valueNode, 'disabled', value); + } + }, + + _onFocus: function(e){ + if(this.scrollOnFocus){ + dojo.window.scrollIntoView(this.domNode); + } + this.inherited(arguments); + }, + + isFocusable: function(){ + // summary: + // Tells if this widget is focusable or not. Used internally by dijit. + // tags: + // protected + return !this.disabled && !this.readOnly + && this.focusNode && (dojo.style(this.domNode, "display") != "none"); + }, + + focus: function(){ + // summary: + // Put focus on this widget + this.focusNode.focus(); + }, + + compare: function(/*anything*/val1, /*anything*/val2){ + // summary: + // Compare 2 values (as returned by attr('value') for this widget). + // tags: + // protected + if(typeof val1 == "number" && typeof val2 == "number"){ + return (isNaN(val1) && isNaN(val2)) ? 0 : val1 - val2; + }else if(val1 > val2){ + return 1; + }else if(val1 < val2){ + return -1; + }else{ + return 0; + } + }, + + onChange: function(newValue){ + // summary: + // Callback when this widget's value is changed. + // tags: + // callback + }, + + // _onChangeActive: [private] Boolean + // Indicates that changes to the value should call onChange() callback. + // This is false during widget initialization, to avoid calling onChange() + // when the initial value is set. + _onChangeActive: false, + + _handleOnChange: function(/*anything*/ newValue, /* Boolean? */ priorityChange){ + // summary: + // Called when the value of the widget is set. Calls onChange() if appropriate + // newValue: + // the new value + // priorityChange: + // For a slider, for example, dragging the slider is priorityChange==false, + // but on mouse up, it's priorityChange==true. If intermediateChanges==true, + // onChange is only called form priorityChange=true events. + // tags: + // private + this._lastValue = newValue; + if(this._lastValueReported == undefined && (priorityChange === null || !this._onChangeActive)){ + // this block executes not for a change, but during initialization, + // and is used to store away the original value (or for ToggleButton, the original checked state) + this._resetValue = this._lastValueReported = newValue; + } + if((this.intermediateChanges || priorityChange || priorityChange === undefined) && + ((typeof newValue != typeof this._lastValueReported) || + this.compare(newValue, this._lastValueReported) != 0)){ + this._lastValueReported = newValue; + if(this._onChangeActive){ + if(this._onChangeHandle){ + clearTimeout(this._onChangeHandle); + } + // setTimout allows hidden value processing to run and + // also the onChange handler can safely adjust focus, etc + this._onChangeHandle = setTimeout(dojo.hitch(this, + function(){ + this._onChangeHandle = null; + this.onChange(newValue); + }), 0); // try to collapse multiple onChange's fired faster than can be processed + } + } + }, + + create: function(){ + // Overrides _Widget.create() + this.inherited(arguments); + this._onChangeActive = true; + }, + + destroy: function(){ + if(this._onChangeHandle){ // destroy called before last onChange has fired + clearTimeout(this._onChangeHandle); + this.onChange(this._lastValueReported); + } + this.inherited(arguments); + }, + + _onMouseDown: function(e){ + // If user clicks on the button, even if the mouse is released outside of it, + // this button should get focus (to mimics native browser buttons). + // This is also needed on chrome because otherwise buttons won't get focus at all, + // which leads to bizarre focus restore on Dialog close etc. + if(this.isFocusable()){ + // Set a global event to handle mouseup, so it fires properly + // even if the cursor leaves this.domNode before the mouse up event. + var mouseUpConnector = this.connect(dojo.body(), "onmouseup", function(){ + if(this.isFocusable()){ + this.focus(); + } + this.disconnect(mouseUpConnector); + }); + } + }, + + selectInputText: function(/*DomNode*/element, /*Number?*/ start, /*Number?*/ stop){ + // summary: + // Select text in the input element argument, from start (default 0), to stop (default end). + + // TODO: use functions in _editor/selection.js? + var _window = dojo.global; + var _document = dojo.doc; + element = dojo.byId(element); + if(isNaN(start)){ start = 0; } + if(isNaN(stop)){ stop = element.value ? element.value.length : 0; } + dijit.focus(element); + + if(_window["getSelection"] && element.setSelectionRange){ + element.setSelectionRange(start, stop); + } + } +}); + +dojo.declare("dojox.mobile.app._FormValueWidget", dojox.mobile.app._FormWidget, +{ + // summary: + // Base class for widgets corresponding to native HTML elements such as <input> or <select> that have user changeable values. + // description: + // Each _FormValueWidget represents a single input value, and has a (possibly hidden) <input> element, + // to which it serializes it's input value, so that form submission (either normal submission or via FormBind?) + // works as expected. + + // Don't attempt to mixin the 'type', 'name' attributes here programatically -- they must be declared + // directly in the template as read by the parser in order to function. IE is known to specifically + // require the 'name' attribute at element creation time. See #8484, #8660. + // TODO: unclear what that {value: ""} is for; FormWidget.attributeMap copies value to focusNode, + // so maybe {value: ""} is so the value *doesn't* get copied to focusNode? + // Seems like we really want value removed from attributeMap altogether + // (although there's no easy way to do that now) + + // readOnly: Boolean + // Should this widget respond to user input? + // In markup, this is specified as "readOnly". + // Similar to disabled except readOnly form values are submitted. + readOnly: false, + + attributeMap: dojo.delegate(dojox.mobile.app._FormWidget.prototype.attributeMap, { + value: "", + readOnly: "focusNode" + }), + + _setReadOnlyAttr: function(/*Boolean*/ value){ + this.readOnly = value; + dojo.attr(this.focusNode, 'readOnly', value); + }, + + postCreate: function(){ + this.inherited(arguments); + + // Update our reset value if it hasn't yet been set (because this.set() + // is only called when there *is* a value) + if(this._resetValue === undefined){ + this._resetValue = this.value; + } + }, + + _setValueAttr: function(/*anything*/ newValue, /*Boolean, optional*/ priorityChange){ + // summary: + // Hook so attr('value', value) works. + // description: + // Sets the value of the widget. + // If the value has changed, then fire onChange event, unless priorityChange + // is specified as null (or false?) + this.value = newValue; + this._handleOnChange(newValue, priorityChange); + }, + + _getValueAttr: function(){ + // summary: + // Hook so attr('value') works. + return this._lastValue; + }, + + undo: function(){ + // summary: + // Restore the value to the last value passed to onChange + this._setValueAttr(this._lastValueReported, false); + }, + + reset: function(){ + // summary: + // Reset the widget's value to what it was at initialization time + this._hasBeenBlurred = false; + this._setValueAttr(this._resetValue, true); + } +}); + +}); diff --git a/js/dojo/dojox/mobile/app/_Widget.js b/js/dojo/dojox/mobile/app/_Widget.js new file mode 100644 index 0000000..7073df5 --- /dev/null +++ b/js/dojo/dojox/mobile/app/_Widget.js @@ -0,0 +1,34 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/_Widget", ["dijit","dojo","dojox","dojo/require!dijit/_WidgetBase"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app._Widget"); +dojo.experimental("dojox.mobile.app._Widget"); + +dojo.require("dijit._WidgetBase"); + +dojo.declare("dojox.mobile.app._Widget", dijit._WidgetBase, { + // summary: + // The base mobile app widget. + + getScroll: function(){ + // summary: + // Returns the scroll position. + return { + x: dojo.global.scrollX, + y: dojo.global.scrollY + }; + }, + + connect: function(target, event, fn){ + if(event.toLowerCase() == "dblclick" + || event.toLowerCase() == "ondblclick"){ + + if(dojo.global["Mojo"]){ + // Handle webOS tap event + return this.connect(target, Mojo.Event.tap, fn); + } + } + return this.inherited(arguments); + } +}); +}); diff --git a/js/dojo/dojox/mobile/app/_base.js b/js/dojo/dojox/mobile/app/_base.js new file mode 100644 index 0000000..c5d92a3 --- /dev/null +++ b/js/dojo/dojox/mobile/app/_base.js @@ -0,0 +1,242 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/_base", ["dijit","dojo","dojox","dojo/require!dijit/_base,dijit/_WidgetBase,dojox/mobile,dojox/mobile/parser,dojox/mobile/Button,dojox/mobile/app/_event,dojox/mobile/app/_Widget,dojox/mobile/app/StageController,dojox/mobile/app/SceneController,dojox/mobile/app/SceneAssistant,dojox/mobile/app/AlertDialog,dojox/mobile/app/List,dojox/mobile/app/ListSelector,dojox/mobile/app/TextBox,dojox/mobile/app/ImageView,dojox/mobile/app/ImageThumbView"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app._base"); +dojo.experimental("dojox.mobile.app._base"); + +dojo.require("dijit._base"); +dojo.require("dijit._WidgetBase"); +dojo.require("dojox.mobile"); +dojo.require("dojox.mobile.parser"); +dojo.require("dojox.mobile.Button"); + +dojo.require("dojox.mobile.app._event"); +dojo.require("dojox.mobile.app._Widget"); +dojo.require("dojox.mobile.app.StageController"); +dojo.require("dojox.mobile.app.SceneController"); +dojo.require("dojox.mobile.app.SceneAssistant"); +dojo.require("dojox.mobile.app.AlertDialog"); +dojo.require("dojox.mobile.app.List"); +dojo.require("dojox.mobile.app.ListSelector"); +dojo.require("dojox.mobile.app.TextBox"); +dojo.require("dojox.mobile.app.ImageView"); +dojo.require("dojox.mobile.app.ImageThumbView"); + +(function(){ + + var stageController; + var appInfo; + + var jsDependencies = [ + "dojox.mobile", + "dojox.mobile.parser" + ]; + + var loadedResources = {}; + var loadingDependencies; + + var rootNode; + + var sceneResources = []; + + // Load the required resources asynchronously, since not all mobile OSes + // support dojo.require and sync XHR + function loadResources(resources, callback){ + // summary: + // Loads one or more JavaScript files asynchronously. When complete, + // the first scene is pushed onto the stack. + // resources: + // An array of module names, e.g. 'dojox.mobile.AlertDialog' + + var resource; + var url; + + do { + resource = resources.pop(); + if (resource.source) { + url = resource.source; + }else if (resource.module) { + url= dojo.moduleUrl(resource.module)+".js"; + }else { + console.log("Error: invalid JavaScript resource " + dojo.toJson(resource)); + return; + } + }while (resources.length > 0 && loadedResources[url]); + + if(resources.length < 1 && loadedResources[url]){ + // All resources have already been loaded + callback(); + return; + } + + dojo.xhrGet({ + url: url, + sync: false + }).addCallbacks(function(text){ + dojo["eval"](text); + loadedResources[url] = true; + if(resources.length > 0){ + loadResources(resources, callback); + }else{ + callback(); + } + }, + function(){ + console.log("Failed to load resource " + url); + }); + } + + var pushFirstScene = function(){ + // summary: + // Pushes the first scene onto the stack. + + stageController = new dojox.mobile.app.StageController(rootNode); + var defaultInfo = { + id: "com.test.app", + version: "1.0.0", + initialScene: "main" + }; + + // If the application info has been defined, as it should be, + // use it. + if(dojo.global["appInfo"]){ + dojo.mixin(defaultInfo, dojo.global["appInfo"]); + } + appInfo = dojox.mobile.app.info = defaultInfo; + + // Set the document title from the app info title if it exists + if(appInfo.title){ + var titleNode = dojo.query("head title")[0] || + dojo.create("title", {},dojo.query("head")[0]); + document.title = appInfo.title; + } + + stageController.pushScene(appInfo.initialScene); + }; + + var initBackButton = function(){ + var hasNativeBack = false; + if(dojo.global.BackButton){ + // Android phonegap support + BackButton.override(); + dojo.connect(document, 'backKeyDown', function(e) { + dojo.publish("/dojox/mobile/app/goback"); + }); + hasNativeBack = true; + }else if(dojo.global.Mojo){ + // TODO: add webOS support + } + if(hasNativeBack){ + dojo.addClass(dojo.body(), "mblNativeBack"); + } + }; + + dojo.mixin(dojox.mobile.app, { + init: function(node){ + // summary: + // Initializes the mobile app. Creates the + + rootNode = node || dojo.body(); + dojox.mobile.app.STAGE_CONTROLLER_ACTIVE = true; + + dojo.subscribe("/dojox/mobile/app/goback", function(){ + stageController.popScene(); + }); + + dojo.subscribe("/dojox/mobile/app/alert", function(params){ + dojox.mobile.app.getActiveSceneController().showAlertDialog(params); + }); + + dojo.subscribe("/dojox/mobile/app/pushScene", function(sceneName, params){ + stageController.pushScene(sceneName, params || {}); + }); + + // Get the list of files to load per scene/view + dojo.xhrGet({ + url: "view-resources.json", + load: function(data){ + var resources = []; + + if(data){ + // Should be an array + sceneResources = data = dojo.fromJson(data); + + // Get the list of files to load that have no scene + // specified, and therefore should be loaded on + // startup + for(var i = 0; i < data.length; i++){ + if(!data[i].scene){ + resources.push(data[i]); + } + } + } + if(resources.length > 0){ + loadResources(resources, pushFirstScene); + }else{ + pushFirstScene(); + } + }, + error: pushFirstScene + }); + + initBackButton(); + }, + + getActiveSceneController: function(){ + // summary: + // Gets the controller for the active scene. + + return stageController.getActiveSceneController(); + }, + + getStageController: function(){ + // summary: + // Gets the stage controller. + return stageController; + }, + + loadResources: function(resources, callback){ + loadResources(resources, callback); + }, + + loadResourcesForScene: function(sceneName, callback){ + var resources = []; + + // Get the list of files to load that have no scene + // specified, and therefore should be loaded on + // startup + for(var i = 0; i < sceneResources.length; i++){ + if(sceneResources[i].scene == sceneName){ + resources.push(sceneResources[i]); + } + } + + if(resources.length > 0){ + loadResources(resources, callback); + }else{ + callback(); + } + }, + + resolveTemplate: function(sceneName){ + // summary: + // Given the name of a scene, returns the path to it's template + // file. For example, for a scene named 'main', the file + // returned is 'app/views/main/main-scene.html' + // This function can be overridden if it is desired to have + // a different name to file mapping. + return "app/views/" + sceneName + "/" + sceneName + "-scene.html"; + }, + + resolveAssistant: function(sceneName){ + // summary: + // Given the name of a scene, returns the path to it's assistant + // file. For example, for a scene named 'main', the file + // returned is 'app/assistants/main-assistant.js' + // This function can be overridden if it is desired to have + // a different name to file mapping. + return "app/assistants/" + sceneName + "-assistant.js"; + } + }); +})(); +}); diff --git a/js/dojo/dojox/mobile/app/_event.js b/js/dojo/dojox/mobile/app/_event.js new file mode 100644 index 0000000..f388db2 --- /dev/null +++ b/js/dojo/dojox/mobile/app/_event.js @@ -0,0 +1,124 @@ +//>>built +// wrapped by build app +define("dojox/mobile/app/_event", ["dijit","dojo","dojox"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app._event"); +dojo.experimental("dojox.mobile.app._event.js"); + +dojo.mixin(dojox.mobile.app, { + eventMap: {}, + + connectFlick: function(target, context, method){ + // summary: + // Listens for a flick event on a DOM node. If the mouse/touch + // moves more than 15 pixels in any given direction it is a flick. + // The synthetic event fired specifies the direction as + // <ul> + // <li><b>'ltr'</b> Left To Right</li> + // <li><b>'rtl'</b> Right To Left</li> + // <li><b>'ttb'</b> Top To Bottom</li> + // <li><b>'btt'</b> Bottom To Top</li> + // </ul> + // target: Node + // The DOM node to connect to + + var startX; + var startY; + var isFlick = false; + + var currentX; + var currentY; + + var connMove; + var connUp; + + var direction; + + var time; + + // Listen to to the mousedown/touchstart event + var connDown = dojo.connect("onmousedown", target, function(event){ + isFlick = false; + startX = event.targetTouches ? event.targetTouches[0].clientX : event.clientX; + startY = event.targetTouches ? event.targetTouches[0].clientY : event.clientY; + + time = (new Date()).getTime(); + + connMove = dojo.connect(target, "onmousemove", onMove); + connUp = dojo.connect(target, "onmouseup", onUp); + }); + + // The function that handles the mousemove/touchmove event + var onMove = function(event){ + dojo.stopEvent(event); + + currentX = event.targetTouches ? event.targetTouches[0].clientX : event.clientX; + currentY = event.targetTouches ? event.targetTouches[0].clientY : event.clientY; + if(Math.abs(Math.abs(currentX) - Math.abs(startX)) > 15){ + isFlick = true; + + direction = (currentX > startX) ? "ltr" : "rtl"; + }else if(Math.abs(Math.abs(currentY) - Math.abs(startY)) > 15){ + isFlick = true; + + direction = (currentY > startY) ? "ttb" : "btt"; + } + }; + + var onUp = function(event){ + dojo.stopEvent(event); + + connMove && dojo.disconnect(connMove); + connUp && dojo.disconnect(connUp); + + if(isFlick){ + var flickEvt = { + target: target, + direction: direction, + duration: (new Date()).getTime() - time + }; + if(context && method){ + context[method](flickEvt); + }else{ + method(flickEvt); + } + } + }; + + } +}); + +dojox.mobile.app.isIPhone = (dojo.isSafari + && (navigator.userAgent.indexOf("iPhone") > -1 || + navigator.userAgent.indexOf("iPod") > -1 + )); +dojox.mobile.app.isWebOS = (navigator.userAgent.indexOf("webOS") > -1); +dojox.mobile.app.isAndroid = (navigator.userAgent.toLowerCase().indexOf("android") > -1); + +if(dojox.mobile.app.isIPhone || dojox.mobile.app.isAndroid){ + // We are touchable. + // Override the dojo._connect function to replace mouse events with touch events + + dojox.mobile.app.eventMap = { + onmousedown: "ontouchstart", + mousedown: "ontouchstart", + onmouseup: "ontouchend", + mouseup: "ontouchend", + onmousemove: "ontouchmove", + mousemove: "ontouchmove" + }; + +} +dojo._oldConnect = dojo._connect; +dojo._connect = function(obj, event, context, method, dontFix){ + event = dojox.mobile.app.eventMap[event] || event; + if(event == "flick" || event == "onflick"){ + if(dojo.global["Mojo"]){ + event = Mojo.Event.flick; + } else{ + return dojox.mobile.app.connectFlick(obj, context, method); + } + } + + return dojo._oldConnect(obj, event, context, method, dontFix); +}; +}); diff --git a/js/dojo/dojox/mobile/app/compat.js b/js/dojo/dojox/mobile/app/compat.js new file mode 100644 index 0000000..2b4f1e7 --- /dev/null +++ b/js/dojo/dojox/mobile/app/compat.js @@ -0,0 +1,15 @@ +/* + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Available via Academic Free License >= 2.1 OR the modified BSD license. + see: http://dojotoolkit.org/license for details +*/ + +/* + This is an optimized version of Dojo, built for deployment and not for + development. To get sources and documentation, please visit: + + http://dojotoolkit.org +*/ + +//>>built +require({cache:{"dojox/main":function(){define(["dojo/_base/kernel"],function(_1){return _1.dojox;});},"dojox/mobile/compat":function(){define(["dojo/_base/lang","dojo/_base/sniff"],function(_2,_3){var dm=_2.getObject("dojox.mobile",true);if(!_3("webkit")){var s="dojox/mobile/_compat";require([s]);}return dm;});},"dijit/main":function(){define("dijit/main",["dojo/_base/kernel"],function(_4){return _4.dijit;});}}});require(["dojo/i18n"],function(_5){_5._preloadLocalizations("dojox/mobile/app/nls/compat",[]);});define("dojox/mobile/app/compat",["dijit","dojo","dojox","dojo/require!dojox/mobile/compat"],function(_6,_7,_8){_7.provide("dojox.mobile.app.compat");_7.require("dojox.mobile.compat");_7.extend(_8.mobile.app.AlertDialog,{_doTransition:function(_9){var h=_7.marginBox(this.domNode.firstChild).h;var _a=this.controller.getWindowSize().h;var _b=_a-h;var _c=_a;var _d=_7.fx.slideTo({node:this.domNode,duration:400,top:{start:_9<0?_b:_c,end:_9<0?_c:_b}});var _e=_7[_9<0?"fadeOut":"fadeIn"]({node:this.mask,duration:400});var _f=_7.fx.combine([_d,_e]);var _10=this;_7.connect(_f,"onEnd",this,function(){if(_9<0){_10.domNode.style.display="none";_7.destroy(_10.domNode);_7.destroy(_10.mask);}});_f.play();}});_7.extend(_8.mobile.app.List,{deleteRow:function(){var row=this._selectedRow;_7.style(row,{visibility:"hidden",minHeight:"0px"});_7.removeClass(row,"hold");var _11=_7.contentBox(row).h;_7.animateProperty({node:row,duration:800,properties:{height:{start:_11,end:1},paddingTop:{end:0},paddingBottom:{end:0}},onEnd:this._postDeleteAnim}).play();}});if(_8.mobile.app.ImageView&&!_7.create("canvas").getContext){_7.extend(_8.mobile.app.ImageView,{buildRendering:function(){this.domNode.innerHTML="ImageView widget is not supported on this browser."+"Please try again with a modern browser, e.g. "+"Safari, Chrome or Firefox";this.canvas={};},postCreate:function(){}});}if(_8.mobile.app.ImageThumbView){_7.extend(_8.mobile.app.ImageThumbView,{place:function(_12,x,y){_7.style(_12,{top:y+"px",left:x+"px",visibility:"visible"});}});}});
\ No newline at end of file diff --git a/js/dojo/dojox/mobile/app/compat.js.uncompressed.js b/js/dojo/dojox/mobile/app/compat.js.uncompressed.js new file mode 100644 index 0000000..a0ef1bd --- /dev/null +++ b/js/dojo/dojox/mobile/app/compat.js.uncompressed.js @@ -0,0 +1,169 @@ +/* + Copyright (c) 2004-2011, The Dojo Foundation All Rights Reserved. + Available via Academic Free License >= 2.1 OR the modified BSD license. + see: http://dojotoolkit.org/license for details +*/ + +/* + This is an optimized version of Dojo, built for deployment and not for + development. To get sources and documentation, please visit: + + http://dojotoolkit.org +*/ + +//>>built +require({cache:{ +'dojox/main':function(){ +define(["dojo/_base/kernel"], function(dojo) { + // module: + // dojox/main + // summary: + // The dojox package main module; dojox package is somewhat unusual in that the main module currently just provides an empty object. + + return dojo.dojox; +}); +}, +'dojox/mobile/compat':function(){ +define([ + "dojo/_base/lang", + "dojo/_base/sniff" +], function(lang, has){ + var dm = lang.getObject("dojox.mobile", true); + if(!has("webkit")){ + var s = "dojox/mobile/_compat"; // assign to a variable so as not to be picked up by the build tool + require([s]); + } + return dm; +}); + +}, +'dijit/main':function(){ +define("dijit/main", [ + "dojo/_base/kernel" +], function(dojo){ + // module: + // dijit + // summary: + // The dijit package main module + + return dojo.dijit; +}); + +}}}); + +require(["dojo/i18n"], function(i18n){ +i18n._preloadLocalizations("dojox/mobile/app/nls/compat", []); +}); +// wrapped by build app +define("dojox/mobile/app/compat", ["dijit","dojo","dojox","dojo/require!dojox/mobile/compat"], function(dijit,dojo,dojox){ +dojo.provide("dojox.mobile.app.compat"); +dojo.require("dojox.mobile.compat"); + +// summary: +// CSS3 compatibility module for apps +// description: +// This module provides support for some of the CSS3 features to djMobile +// for non-CSS3 browsers, such as IE or Firefox. +// If you load this module, it directly replaces some of the methods of +// djMobile instead of subclassing. This way, html pages remains the same +// regardless of whether this compatibility module is used or not. +// Recommended usage is as follows. the code below loads dojox.mobile.compat +// only when isWebKit is true. +// +// dojo.require("dojox.mobile"); +// dojo.requireIf(!dojo.isWebKit, "dojox.mobile.appCompat"); + +dojo.extend(dojox.mobile.app.AlertDialog, { + _doTransition: function(dir){ + console.log("in _doTransition and this = ", this); + + var h = dojo.marginBox(this.domNode.firstChild).h; + + var bodyHeight = this.controller.getWindowSize().h; + + var high = bodyHeight - h; + var low = bodyHeight; + + var anim1 = dojo.fx.slideTo({ + node: this.domNode, + duration: 400, + top: {start: dir < 0 ? high : low, end: dir < 0 ? low: high} + }); + + var anim2 = dojo[dir < 0 ? "fadeOut" : "fadeIn"]({ + node: this.mask, + duration: 400 + }); + + var anim = dojo.fx.combine([anim1, anim2]); + + var _this = this; + + dojo.connect(anim, "onEnd", this, function(){ + if(dir < 0){ + _this.domNode.style.display = "none"; + dojo.destroy(_this.domNode); + dojo.destroy(_this.mask); + } + }); + anim.play(); + } +}); + +dojo.extend(dojox.mobile.app.List, { + deleteRow: function(){ + console.log("deleteRow in compat mode", row); + + var row = this._selectedRow; + // First make the row invisible + // Put it back where it came from + dojo.style(row, { + visibility: "hidden", + minHeight: "0px" + }); + dojo.removeClass(row, "hold"); + + + // Animate reducing it's height to zero, then delete the data from the + // array + var height = dojo.contentBox(row).h; + dojo.animateProperty({ + node: row, + duration: 800, + properties: { + height: {start: height, end: 1}, + paddingTop: {end: 0}, + paddingBottom: {end: 0} + }, + onEnd: this._postDeleteAnim + }).play(); + } +}); + +if(dojox.mobile.app.ImageView && !dojo.create("canvas").getContext){ + dojo.extend(dojox.mobile.app.ImageView, { + buildRendering: function(){ + this.domNode.innerHTML = + "ImageView widget is not supported on this browser." + + "Please try again with a modern browser, e.g. " + + "Safari, Chrome or Firefox"; + this.canvas = {}; + }, + + postCreate: function(){} + }); +} + +if(dojox.mobile.app.ImageThumbView){ + dojo.extend(dojox.mobile.app.ImageThumbView, { + place: function(node, x, y){ + dojo.style(node, { + top: y + "px", + left: x + "px", + visibility: "visible" + }); + } + }) +} + +}); |
