summaryrefslogtreecommitdiff
path: root/js/dojo/dojox/mobile/app
diff options
context:
space:
mode:
Diffstat (limited to 'js/dojo/dojox/mobile/app')
-rw-r--r--js/dojo/dojox/mobile/app/AlertDialog.js186
-rw-r--r--js/dojo/dojox/mobile/app/ImageThumbView.js393
-rw-r--r--js/dojo/dojox/mobile/app/ImageView.js721
-rw-r--r--js/dojo/dojox/mobile/app/List.js649
-rw-r--r--js/dojo/dojox/mobile/app/ListSelector.js223
-rw-r--r--js/dojo/dojox/mobile/app/SceneAssistant.js61
-rw-r--r--js/dojo/dojox/mobile/app/SceneController.js174
-rw-r--r--js/dojo/dojox/mobile/app/StageController.js137
-rw-r--r--js/dojo/dojox/mobile/app/TextBox.js10
-rw-r--r--js/dojo/dojox/mobile/app/_FormWidget.js293
-rw-r--r--js/dojo/dojox/mobile/app/_Widget.js34
-rw-r--r--js/dojo/dojox/mobile/app/_base.js242
-rw-r--r--js/dojo/dojox/mobile/app/_event.js124
-rw-r--r--js/dojo/dojox/mobile/app/compat.js15
-rw-r--r--js/dojo/dojox/mobile/app/compat.js.uncompressed.js169
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,"&quot;"); //TODO: add &amp? 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, "&quot;") + '"') : '';
+ 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"
+ });
+ }
+ })
+}
+
+});