1 if (Garmin == undefined) var Garmin = {};
  2 /** Copyright � 2007 Garmin Ltd. or its subsidiaries.
  3  *
  4  * Licensed under the Apache License, Version 2.0 (the 'License')
  5  * you may not use this file except in compliance with the License.
  6  * You may obtain a copy of the License at
  7  *
  8  *    http://www.apache.org/licenses/LICENSE-2.0
  9  *
 10  * Unless required by applicable law or agreed to in writing, software
 11  * distributed under the License is distributed on an 'AS IS' BASIS,
 12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 13  * See the License for the specific language governing permissions and
 14  * limitations under the License.
 15  * 
 16  * A high-level UI widget for talking with Garmin Devices.
 17  * 
 18  * @fileoverview GarminDeviceDisplay.js  
 19  * @author Michael Bina michael.bina at garmin.com, Diana Chow diana.chow at garmin.com
 20  * @version 1.4.4
 21  */
 22  
 23 /** Provides the easiest avenue for getting a working instance of the plug-in onto your page.
 24  * Generates the UI elements and places them on the page.
 25  *
 26  * @class Garmin.DeviceDisplay
 27  * 
 28  * requires Prototype
 29  * @requires Garmin.DeviceControl
 30  * @namespace Garmin
 31  */
 32 Garmin.DeviceDisplay= function(mainElement, options){}; //just here for jsdoc
 33 Garmin.DeviceDisplay = Class.create();
 34 Garmin.DeviceDisplay.prototype = {
 35 
 36     /** Constructor.
 37 	 * @constructor 
 38 	 * @param String mainElement - id of the element in which to generate the contents
 39 	 * 					(can also be a reference to the dom element itself).
 40 	 * @param Object options - Object with options (see {@link Garmin.DeviceDisplayDefaultOptions} for descriptions of possible options).
 41      */
 42 	initialize: function(mainElement, options) {
 43 		if(typeof(mainElement) == "string") {
 44 			this.mainElement = $(mainElement);
 45 		} else {
 46 			this.mainElement = mainElement;
 47 		}
 48 		
 49 		if(this.mainElement != null) {
 50 			this.options = null;
 51 			this.setOptions(options);
 52 
 53 			this.garminController = null;
 54 			this.garminRemoteTransfer = null;
 55 			this.activities = new Array();
 56 			this.devices = new Array();
 57 			this.factory = null;
 58         	this.tracks = null;
 59 	        this.waypoints = null;
 60 
 61 			this.activityDirectory = null; 					// Array of activity ID strings in the directory
 62 			this.activityQueue = null; 						// Queue of activity IDs to sync events
 63 			this.numQueuedActivities = null;                // Number of total queued activities for status reporting 
 64 			this.uploadData = null;                         // Payload element for upload data
 65 			this.activityMatcher = null;                // The activity filter for synchronizing activities
 66 			
 67 			this.currentActivity = null; 					// The top of the activity queue and/or the activity being processed.
 68 			this.finishedFirstActivity = false;
 69 			this.xhr = null;                             // The XHR object (see GarminRemoteTransfer)
 70 			this.advancedUploadMode = true;              // Internal option to show the activity selection table on upload
 71 			
 72 			this.error = null;
 73 			
 74 			this._generateElements();
 75 			
 76 			if (this.options.unlockOnPageLoad) {
 77 				this.getController(true);
 78 			}
 79 			if (!this.error && this.options.autoFindDevices) {
 80 				this.startFindDevices();
 81 			}
 82 		}
 83 	},
 84 
 85     ////////////////////////// UI GENERATION METHODS ///////////////////////////
 86     
 87     /* Primary UI build method.
 88      * @private  
 89      */
 90 	_generateElements: function() {
 91 		if (BrowserSupport.isBrowserSupported() || !this.options.hideIfBrowserNotSupported) {
 92 			this._generateStatusElement();
 93 			if(this.options.showFindDevicesElement) {
 94 				this._generateFindDevicesElement();
 95 			}
 96 			if(this.options.showReadDataElement) {
 97 				this._generateReadDataElement();
 98 			}
 99 			if(this.options.showActivityDirectoryElement) {
100 				this._generateActivityDirectoryElement();	
101 			}
102 			if(this.options.showWriteDataElement) {
103 				this._generateWriteDataElement();
104 			}
105 			if(this.options.showSendDataElement) {
106 				this._generateSendDataElement();
107 			}
108 			if(this.options.showAboutElement) {
109     			this._generateAboutElement();
110 			}
111 			this.resetUI();
112 		}
113 	},
114 
115     /** Resets UI widgets based on state of application.
116      */ 
117 	resetUI: function() {
118 		//console.debug("Display.resetUI")		
119 	    this.hideProgressBar();
120 	    
121 		var noDevicesAvailable = this.garminController ? (this.getController().numDevices==0) : true;
122 		if(this.options.showFindDevicesElement) {
123 			if (this.findDevicesButton)
124 				this.findDevicesButton.disabled = false;
125 			if (this.deviceSelectInput)		
126 				this.deviceSelectInput.disabled = noDevicesAvailable;
127 			if (this.cancelFindDevicesButton)
128 				this.cancelFindDevicesButton.disabled = true;
129 			if (this.readDataTypesSelect) 
130 				this.readDataTypesSelect.disabled = false;
131 		}
132 		if(this.options.showReadDataElement) {
133 			if (this.readDataButton) 
134 				this.readDataButton.disabled = noDevicesAvailable;
135 			if (this.cancelReadDataButton)
136 				this.cancelReadDataButton.disabled = true;
137     		if(this.loadingContentElement) {
138     		    this.loadingContentElement.hide();
139     		}
140 		}
141 		if(this.options.showWriteDataElement) {
142 			if (this.writeDataButton)
143 				this.writeDataButton.disabled = noDevicesAvailable;
144 			if (this.cancelWriteDataButton)
145 				this.cancelWriteDataButton.disabled = true;
146 		}
147 	},
148 	
149 
150     /* Build status UI components.
151      * @private
152      */
153 	_generateStatusElement: function() {
154 		this.statusElement = document.createElement("div");
155 		Element.extend(this.statusElement);
156 		this.statusElement.id = this.options.statusElementId;
157 		this.statusElement.addClassName(this.options.elementClassName);
158 		this.mainElement.appendChild(this.statusElement);
159 
160 		// Status text
161 		this.statusText = document.createElement("div");
162 		Element.extend(this.statusText);
163 		this.statusText.id = this.options.statusTextId;
164 		this.statusElement.appendChild(this.statusText);
165 
166         // Progress bars
167         this._generateProgressBars();
168 	},
169 	
170 	/* Build status progress bar UI components.
171 	 * @private
172 	 */
173 	_generateProgressBars: function() {
174 		// Device transfer progress bar
175 		this.progressBar = document.createElement("div");
176 		Element.extend(this.progressBar);
177 		this.progressBar.id = this.options.progressBarId;
178 		this.progressBar.className = this.options.progressBarClass;
179 		this.progressBarBack = document.createElement("div");
180 		Element.extend(this.progressBarBack);
181 		this.progressBarBack.id = this.options.progressBarBackId;
182 		this.progressBarBack.addClassName(this.options.progressBarBackClass);
183 		this.progressBarBack.innerHTML = '<span/>';
184 		this.progressBar.appendChild(this.progressBarBack);
185 		this.progressBarDisplay = document.createElement("div");
186 		Element.extend(this.progressBarDisplay);
187 		this.progressBarDisplay.id = this.options.progressBarDisplayId;
188 		this.progressBarDisplay.addClassName(this.options.progressBarDisplayClass);
189 		this.progressBarDisplay.innerHTML = '<span/>';
190 		this.progressBar.appendChild(this.progressBarDisplay);
191 		this.progressBar.hide();
192 		this.statusElement.appendChild(this.progressBar);
193 		
194 		// Device transfer progress bar text
195 		this.progressBarText = document.createElement("div");
196 		Element.extend(this.progressBarText);
197 		this.progressBarText.id = this.options.progressBarTextId;
198 		this.progressBarText.className = this.options.progressBarTextClass;
199 		this.progressBar.appendChild(this.progressBarText);
200 		
201 		// Upload progress bar
202 		this.uploadProgressBar = document.createElement("div");
203 		Element.extend(this.uploadProgressBar);
204 		this.uploadProgressBar.id = this.options.uploadProgressBarId;
205 		this.uploadProgressBar.className = this.options.uploadProgressBarClass;
206 		this.uploadProgressBarBack = document.createElement("div");
207 		Element.extend(this.uploadProgressBarBack);
208 		this.uploadProgressBarBack.id = this.options.uploadProgressBarBackId;
209 		this.uploadProgressBarBack.addClassName(this.options.uploadProgressBarBackClass);
210 		this.uploadProgressBarBack.innerHTML = '<span/>';
211 		this.uploadProgressBar.appendChild(this.uploadProgressBarBack);
212 		this.uploadProgressBarDisplay = document.createElement("div");
213 		Element.extend(this.uploadProgressBarDisplay);
214 		this.uploadProgressBarDisplay.id = this.options.uploadProgressBarDisplayId;
215 		this.uploadProgressBarDisplay.addClassName(this.options.uploadProgressBarDisplayClass);
216 		this.uploadProgressBarDisplay.innerHTML = '<span/>';
217 		this.uploadProgressBar.appendChild(this.uploadProgressBarDisplay);
218 		this.uploadProgressBar.hide();
219 		this.statusElement.appendChild(this.uploadProgressBar);
220 		
221 		// Upload progress bar text
222 		this.uploadProgressBarText = document.createElement("div");
223 		Element.extend(this.uploadProgressBarText);
224 		this.uploadProgressBarText.id = this.options.uploadProgressBarTextId;
225 		this.uploadProgressBarText.className = this.options.uploadProgressBarTextClass;
226 		this.uploadProgressBar.appendChild(this.uploadProgressBarText);
227 		
228 		//TODO This is totally the wrong place to put this.  Move this out somewhere.
229 		this.cancelUploadButton = new Element(this.options.useLinks ? 'div' : 'input', {
230 		    id: this.options.cancelUploadButtonId,
231 		    className: this.options.cancelUploadButtonClass
232 		    });
233 		if (this.options.useLinks) {
234 			this.cancelUploadButton.update('<a href="#">'+this.options.cancelUploadButtonText+'</a>');
235 		} else {
236 			this.cancelUploadButton.type = "button";
237 			this.cancelUploadButton.value = this.options.cancelUploadButtonText;
238 		}
239         this.cancelUploadButton.onclick = function() {
240         	this.resetUI();
241          	this.hideProgressBar();
242          	
243          	// Kill the string of event handlers
244         	this.garminRemoteTransfer.abortRequest();  
245     	    
246     	    try {
247             	// Update the status of the active upload.
248                 this.options.afterFinishSendData.call(this, 
249                     this.xhr, 
250                     this.currentActivity ? $(this.currentActivity.replace(/Checkbox/,"Status")) : null, 
251                     this);
252             } catch (error) {
253 		        this.handleException(error);
254 	        }
255         	
256         	// Clear the queue.  Affects cancel and progress.
257             this.activityQueue = null;
258 //         	this.clearActivityQueue();  // Clearing does not work for whatever reason... timing/async
259         	
260 	        // Go to the finished screen
261         	this.getController()._broadcaster.dispatch("onFinishUploads", { display: this });
262         }.bind(this)
263 		this.uploadProgressBar.insert(this.cancelUploadButton);
264 	},
265 	
266 	_createElement: function(id, text, type, parent) {
267 		var elem = document.createElement(type);
268 		Element.extend(elem);
269 		if (type=="a") {
270 			elem.href = location;
271 			elem.innerHTML = text;
272 		} else if (type=="button"){
273 			elem.type = type;
274 			elem.value = text;
275 		}
276 		elem.id = id;
277 		parent.appendChild(elem);		
278 		return elem;
279 	},
280 	
281 	/** Build device browser list, a singleton.  This list will be juxtaposed with the activity directory.
282 	 * This is a different list than the default device select drop down.  It adds on the computer file browser
283 	 * as well.
284 	 * @private
285 	 */
286 	generateDeviceBrowserElement: function(devices) {
287 	    
288 	    if( this.deviceBrowserElement != null) {
289 	        throw new Error("Unable to generate device browser because an instance already exists.");
290 	    }
291         
292         this.deviceBrowserElement = new Element('div', { 
293             id: this.options.deviceBrowserElementId,
294             className: this.options.deviceBrowserElementClass 
295             });
296         this.deviceBrowserLabel = new Element('div', { 
297             id: this.options.deviceBrowserLabelId,            
298             className: this.options.deviceBrowserLabelClass
299             }).update(this.options.deviceBrowserLabel);
300         this.deviceBrowserElement.insert(this.deviceBrowserLabel);
301     
302 		this.deviceBrowserList = document.createElement("ul");
303 		Element.extend(this.deviceBrowserList);
304 		this.deviceBrowserList.id = this.options.deviceBrowserListId;
305 		
306 		this.deviceBrowserElement.appendChild(this.deviceBrowserList);
307 		this.deviceBrowserElement.hide();
308 		this.mainElement.appendChild(this.deviceBrowserElement);
309 		
310 		// Fill up the list with real data
311 		this._populateDeviceList(this.deviceBrowserList, this.options.afterSelectDevice ? this.options.afterSelectDevice : 
312             function(selectedDeviceNumber, devices, deviceXml){
313     		    if (this.options.readDataTypes != null) {
314             		this.readFromDevice(this.options.readDataTypes);
315 //            		this.readSpecificTypeFromDevice(this.options.readDataType);
316             	} 
317     		});
318 		
319 		if( this.options.uploadSelectedActivities && this.options.showBrowseComputer ) {
320     		this._generateBrowseComputerElement();
321     		
322     		// Add My Computer to the list
323     		var itemLink;
324     	    var listItem;
325             listItem = document.createElement("li");
326             Element.extend(listItem);
327             listItem.className = "unselected";
328             itemLink = document.createElement("a");
329             Element.extend(itemLink);
330             itemLink.href = "#";
331             itemLink.innerHTML = this.options.browseComputerLabel;
332             itemLink.onclick = function(deviceListElement) {
333                 this._displayBrowseComputer(deviceListElement);
334             }.bind(this, this.deviceBrowserList)
335             listItem.appendChild(itemLink);
336             this.deviceBrowserList.appendChild(listItem);
337 		}
338 	},
339 	
340 	/* Displays the Browse Computer element and updates the device list accordingly.
341 	 * @private
342 	 * @param deviceListElement {Element}
343 	 */
344 	_displayBrowseComputer: function(deviceListElement) {
345 	    // Mark My Computer as selected
346         var browseComputerItem = deviceListElement.childNodes[this.devices.length];
347         browseComputerItem.className = "selected";
348         
349         // Stop any existing reads and hide stuffs
350         if(this.getController() && this.isUnlocked()) { // browsing computer does not require valid plugin
351             this.getController().cancelReadFromDevice();
352         }
353         
354         // Mark all the rest of the devices as unselected
355         if( this.devices != null) {
356             for(var j=0; j< this.devices.length; j++) {
357                var listItem = deviceListElement.childNodes[j];
358                listItem.className = "unselected";
359             }
360         }
361         
362         // The callback function has to take the parameter!  Even if it ignores it.
363         this.activityDirectoryElement.hide();
364         this.statusElement.hide();
365         this.browseComputerElement.show();
366 	},
367 	
368 	/* Generates the browse computer element, an iframe that contains
369 	 * the manual upload page.
370 	 * @private 
371 	 */
372 	_generateBrowseComputerElement: function() {
373 	    this.browseComputerElement = document.createElement("div");
374         Element.extend(this.browseComputerElement);
375         this.browseComputerElement.id = this.options.browseComputerElementId;
376         this.browseComputerElement.className = this.options.browseComputerElementClass;
377 		
378 		var browseComputerTitle = document.createElement("div");
379 		browseComputerTitle.id = 'manualUploadTitle';
380 		browseComputerTitle.innerHTML = this.options.browseComputerLabel;
381 		this.browseComputerElement.appendChild(browseComputerTitle);
382 		
383 		var browseComputerContent = document.createElement("iframe");
384 		Element.extend(browseComputerContent);
385 		browseComputerContent.id = browseComputerContent.name = this.options.browseComputerElementId + 'Contents';
386 		browseComputerContent.src = this.options.browseComputerContentUrl;
387 		
388 		this.browseComputerElement.appendChild(browseComputerContent);
389 		this.browseComputerElement.hide();
390 		this.mainElement.appendChild(this.browseComputerElement);
391 		
392 		// Have to set these after append for IE :E
393 		// This doesn't work anyway!! I hate IE!!
394 		browseComputerContent.setAttribute('frameborder', '0'); 
395 		browseComputerContent.setAttribute('allowtransparency', 'true');
396 	},
397 	
398 	/* Generates the loading screen as the passed in element is loading.
399 	 * @private 
400 	 */
401 	_generateLoadingContent: function(loadingElement) {
402 	    if( this.loadingContentElement != null) {
403 	        throw new Error("Unable to generate loading screen because an instance already exists.");
404 	    }
405 	    // 'Loading' display
406         this.loadingContentElement = document.createElement("div");
407         Element.extend(this.loadingContentElement);
408         this.loadingContentElement.className = "shortStatus";
409         this.loadingContentElement.innerHTML = this.evaluateTemplate(this.options.loadingContentText, {deviceName:this.getShortDeviceName(this.getCurrentDevice())});
410         loadingElement.appendChild(this.loadingContentElement);
411         
412         this.showProgressBar();
413 	},
414 	
415 	/* Update the content inside of the loading content element and display it.
416 	 */
417 	_updateLoadingContent: function(content) {
418 	    // Update the device name displayed
419 	    if(this.loadingContentElement != null) {
420             this.loadingContentElement.update(content);
421             this.loadingContentElement.show();
422 	    }
423 	},
424 	
425     /* Build find device UI components.
426      * @private 
427      */
428 	_generateFindDevicesElement: function() {
429 		this.findDevicesElement = document.createElement("div");
430 		Element.extend(this.findDevicesElement);
431 		this.findDevicesElement.id = this.options.findDevicesElementId;
432 		this.findDevicesElement.addClassName(this.options.elementClassName);
433 		this.mainElement.appendChild(this.findDevicesElement);
434 
435 		// Find devices button
436 		if( this.options.showFindDevicesButton) {
437 			this.findDevicesButton = document.createElement( this.options.useLinks ? "div" : "input" );
438 			Element.extend(this.findDevicesButton);
439 			if (this.options.useLinks) {
440 				this.findDevicesButton.innerHTML = '<a href="#">'+this.options.findDevicesButtonText+'</a>';
441 			} else {
442 				this.findDevicesButton.type = "button";
443 				this.findDevicesButton.value = this.options.findDevicesButtonText;
444 			}
445 			this.findDevicesButton.id = this.options.findDevicesButtonId;
446 			this.findDevicesButton.addClassName(this.options.actionButtonClassName);
447 			this.findDevicesElement.appendChild(this.findDevicesButton);		
448 	        this.findDevicesButton.onclick = function() {
449 	        	this.startFindDevices();
450 	        }.bind(this)
451 		}
452 		
453 		if(!this.options.showFindDevicesElementOnLoad) {
454 			if( this.findDevicesElement) {
455 				Element.hide(this.findDevicesElement);
456 			}
457 		}
458 
459 		// Cancel Find devices button
460 		if (this.options.showCancelFindDevicesButton) {
461 			this.cancelFindDevicesButton = document.createElement( this.options.useLinks ? "div" : "input" );
462 			Element.extend(this.cancelFindDevicesButton);
463 			if (this.options.useLinks) {
464 				this.cancelFindDevicesButton.innerHTML = '<a href="#">'+this.options.cancelFindDevicesButtonText+'</a>';
465 			} else {
466 				this.cancelFindDevicesButton.type = "button";
467 				this.cancelFindDevicesButton.value = this.options.cancelFindDevicesButtonText;
468 			}
469 			this.cancelFindDevicesButton.id = this.options.cancelFindDevicesButtonId;
470 			this.cancelFindDevicesButton.addClassName(this.options.actionButtonClassName);
471 			this.cancelFindDevicesButton.disabled = true;
472 	        this.cancelFindDevicesButton.onclick = function() {
473 	        	this.cancelFindDevices();
474 	        }.bind(this)
475 			this.findDevicesElement.appendChild(this.cancelFindDevicesButton);
476 		}
477 		
478 		if (!this.options.showDeviceButtonsOnLoad) {
479 			if (this.findDevicesButton) {
480 				Element.hide(this.findDevicesButton);
481 			}
482 			if (this.cancelFindDevicesButton)				
483 				Element.hide(this.cancelFindDevicesButton);			
484 		}
485 
486 		// Device select drop-down list
487 		this.deviceSelectElement = document.createElement("div");
488 		Element.extend(this.deviceSelectElement);
489 		this.deviceSelectElement.id = this.options.deviceSelectElementId;
490 		this.deviceSelectElement.innerHTML = '<div id="' + this.options.deviceSelectLabelId + '">' + this.options.deviceSelectLabel + '</div>';
491 		this.findDevicesElement.appendChild(this.deviceSelectElement);
492 
493 		this.deviceSelectInput = document.createElement( this.options.useDeviceSelectList ? "ul" : "select");
494 		Element.extend(this.deviceSelectInput);
495 		this.deviceSelectInput.id = this.options.deviceSelectId;
496 		this.deviceSelectInput.disabled = true;
497 		
498 		if (!this.options.showDeviceSelectOnLoad || !this.options.showDeviceSelectOnSingle || this.options.autoSelectFirstDevice) {
499 			Element.hide(this.deviceSelectElement);	
500 		}
501 		
502 		/* Browse computer */
503         this.browseComputerButton = new Element(this.options.useLinks ? "div" : "input", {
504             id: this.options.browseComputerButtonId
505             , className: this.options.browseComputerButtonClass
506             });
507         this.browseComputerButton.onclick = function(){
508             if( this.deviceBrowserList == null) {
509                 this.generateDeviceBrowserElement(this.devices)
510             };
511             this.findDevicesElement.hide();
512             this.readDataElement.hide();
513             this.deviceBrowserElement.show();         
514             this._displayBrowseComputer(this.deviceBrowserList);
515           }.bind(this)
516         if (this.options.useLinks) {
517 			this.browseComputerButton.innerHTML = '<a href="#">'+this.options.browseComputerButtonText+'</a>';
518 		} else {
519 			this.browseComputerButton.type = "button";
520 			this.browseComputerButton.value = this.options.browseComputerButtonText;
521 		}
522         if(!this.options.uploadSelectedActivities || !this.options.showBrowseComputer ) {
523             this.browseComputerButton.hide();
524         }
525 		this.findDevicesElement.appendChild(this.browseComputerButton);
526 	},
527 	
528 	_generateSendDataElement: function() {
529 		this.sendDataElement = document.createElement("div");
530 		Element.extend(this.sendDataElement);
531 		this.sendDataElement.id = this.options.sendDataElementId;
532 		this.sendDataElement.addClassName(this.options.elementClassName);
533 		this.mainElement.appendChild(this.sendDataElement);
534 
535 		this.sendDataButton = document.createElement( this.options.useLinks ? "div" : "input" );
536 		Element.extend(this.sendDataButton);
537 		if (this.options.useLinks) {
538 			this.sendDataButton.innerHTML = '<a href="#">'+this.options.sendDataButtonText+'</a>';
539 		} else {
540 			this.sendDataButton.type = "button";
541 			this.sendDataButton.value = this.options.sendDataButtonText;
542 		}
543 		this.sendDataButton.id = this.options.sendDataButtonId;
544 		this.sendDataButton.addClassName(this.options.actionButtonClassName);
545         this.sendDataButton.onclick = function() {
546         	this.setStatus(    
547         	   this.evaluateTemplate(
548         	       this.options.sendingDataToServer, 
549         	       {deviceName:this.getShortDeviceName(this.getCurrentDevice())}
550         	   )
551 	        );
552         	Element.hide(this.findDevicesElement);
553 	        Element.hide(this.sendDataElement);
554         	this.showProgressBar();
555 	        
556 	        setTimeout(function(){this.postToServer()}.bind(this),1000);
557 	        return false;
558         }.bind(this)
559 		this.sendDataElement.appendChild(this.sendDataButton);
560 		
561 		if(this.options.showSendDataElementOnDeviceFound) {
562 			Element.hide(this.sendDataElement);
563 		}	
564 	},
565 	
566 	/** Post data to an external server.  Request options should be provided by the 
567 	 * parameters element in {@link Garmin.DeviceDisplayDefaultOptions.sendDataOptions} 
568 	 * 
569 	 * @see Garmin.DeviceDisplayDefaultOptions.sendDataOptions 
570 	 * @see Garmin.DeviceDisplay.handleException
571 	 * @version 1.6
572 	 * @param callback {function} function executed after onSuccess of the AJAX request.  If failure, exception is thrown {@link Garmin.DeviceDisplay.handleException}
573 	 */
574 	postToServer: function(callback) {
575     	var error;
576     	var exceptionName = 'RemoteTransferException';
577     	
578     	// getSendOptions overwrites those already set in sendDataOptions
579     	if (this.options.sendDataOptions != null) {
580     	    if (this.options.getSendOptions != null) {
581     	        this.options.sendDataOptions = this.options.getSendOptions.call(this, this.options.sendDataOptions, this.garminController.getCurrentDeviceXml(), this.readDataString);
582     	    }
583 		}
584     	
585 		this.options.sendDataOptions.onSuccess = function(xhr) {
586 			this.xhr = xhr;
587 			if( this.options.afterFinishSendData != null) {
588 				try {
589 					this.options.afterFinishSendData.call(this, 
590 						this.xhr, 
591 						this.currentActivity ? $(this.currentActivity.replace(/Checkbox/, "Status")) : null,
592 						this.activityDirectory, 
593 						this);
594 				} catch (error) {
595 					this.handleException(error);
596 				}
597 			}
598 			callback.call(this);
599 		}.bind(this);
600 		
601 		this.options.sendDataOptions.onComplete = function(xhr) {
602 			// Cross domain...
603 			if( xhr == null) {
604 				error = new Error(Garmin.RemoteTransfer.MESSAGES.generalException);
605 				error.name = exceptionName;
606 				this.handleException(error);
607 				throw new Error(Garmin.RemoteTransfer.MESSAGES.noResponseException);
608 			}
609 		}.bind(this);
610 		
611 		this.options.sendDataOptions.onFailure = function(xhr) {
612 			error = new Error(xhr.statusText);
613 			error.name = exceptionName;
614 			this.handleException(error);
615 		}.bind(this);
616 		
617 		// Make the request
618 		this.apiResponse = this.garminRemoteTransfer.openRequest(this.options.sendDataUrl, this.options.sendDataOptions);
619 	},
620 	
621     /* Build read data UI components.
622      * @private
623      */
624 	_generateReadDataElement: function() {
625 		this.readDataElement = document.createElement("div");
626 		Element.extend(this.readDataElement);
627 		this.readDataElement.id = this.options.readDataElementId;
628 		this.readDataElement.addClassName(this.options.elementClassName);
629 		this.mainElement.appendChild(this.readDataElement);
630 
631 		this.readDataButton = document.createElement( this.options.useLinks ? "div" : "input" );
632 		Element.extend(this.readDataButton);
633 		if (this.options.useLinks) {
634 			this.readDataButton.innerHTML = '<a href="#">'+this.options.readDataButtonText+'</a>';
635 		} else {
636 			this.readDataButton.type = "button";
637 			this.readDataButton.value = this.options.readDataButtonText;
638 		}
639 		this.readDataButton.id = this.options.readDataButtonId;
640 		this.readDataButton.addClassName(this.options.actionButtonClassName);
641 		this.readDataButton.disabled = true;
642         this.readDataButton.onclick = function() {
643             var isSupportedDevice = true;
644         	if( this.options.restrictByDevice.length > 0){
645 				isSupportedDevice = this._restrictByDevice();
646 			}						
647 			
648 			if( isSupportedDevice) {
649             	if( this.options.autoHideUnusedElements ) {
650             		if(this.findDevicesElement) Element.hide(this.findDevicesElement);
651             		if(this.readDataElement) Element.hide(this.readDataElement);
652             		if(this.deviceSelectElement) Element.hide(this.deviceSelectElement);
653             		if(this.activityDirectoryElement) Element.hide(this.activityDirectoryElement);
654             	}
655             	this.readDataButton.disabled = true;
656             	this.cancelReadDataButton.disabled = false;
657             	this.showProgressBar();
658             	if (this.options.showReadDataTypesSelect) {
659             		this.readSpecificTypeFromDevice(this.readDataTypesSelect.value);
660             	} else if (this.options.readDataTypes != null) {				
661 					this.readFromDevice(this.options.readDataTypes);					
662             	} else {
663 					this.readFromDevice(new Array(this.options.readDataType));
664 				}
665 			}
666         }.bind(this)
667 		this.readDataElement.appendChild(this.readDataButton);
668 		if(!this.options.showReadDataButton) {
669             Element.hide(this.readDataButton);
670 		}
671 
672 		this.cancelReadDataButton = document.createElement( this.options.useLinks ? "div" : "input" );
673 		Element.extend(this.cancelReadDataButton);
674 		if (this.options.useLinks) {
675 			this.cancelReadDataButton.innerHTML = '<a href="#">'+this.options.cancelReadDataButtonText+'</a>';
676 		} else {
677 			this.cancelReadDataButton.type = "button";
678 			this.cancelReadDataButton.value = this.options.cancelReadDataButtonText;
679 		}
680 		this.cancelReadDataButton.id = this.options.cancelReadDataButtonId;
681 		this.cancelReadDataButton.addClassName(this.options.actionButtonClassName);
682 		this.cancelReadDataButton.disabled = true;
683         this.cancelReadDataButton.onclick = function() {
684         	this.resetUI();
685          	this.hideProgressBar();
686         	this.getController().cancelReadFromDevice();
687         }.bind(this)
688 		this.readDataElement.appendChild(this.cancelReadDataButton);
689 
690 		if(!this.options.showCancelReadDataButton) {
691 			Element.hide(this.cancelReadDataButton);
692 		}
693 		
694 		/* Upload without showing selection table */
695 		this.uploadNewButton = document.createElement( this.options.useLinks ? "div" : "input" );
696 		Element.extend(this.uploadNewButton);
697 		if (this.options.useLinks) {
698 			this.uploadNewButton.innerHTML = '<a href="#">'+this.options.uploadNewButtonText+'</a>';
699 		} else {
700 			this.uploadNewButton.type = "button";
701 			this.uploadNewButton.value = this.options.uploadNewButtonText;
702 		}
703 		this.uploadNewButton.id = this.options.uploadNewButtonId;
704 		this.uploadNewButton.addClassName(this.options.actionButtonClassName);
705         this.uploadNewButton.onclick = function() {
706 			
707 			var isSupportedDevice = true;
708         	if( this.options.restrictByDevice.length > 0){
709 				isSupportedDevice = this._restrictByDevice();
710 			}						
711 			
712 			if( isSupportedDevice) {
713 	        	if( this.options.autoHideUnusedElements ) {
                    this.advancedUploadMode = false;
714 	        		this.readDataElement.hide();
715 	        		this.browseComputerButton.hide();
716 	        		this.uploadProgressBar.hide();
717 	        		if(this.changeDeviceElement != null) { this.changeDeviceElement.hide(); }
718 	        		if(this.connectedDevices != null) { this.connectedDevices.hide(); }
719 	        		if(this.deviceSelectInput != null) {this.deviceSelectInput.hide();}
720 	        		
721 	        		if( this.loadingContentElement == null) {
722                         this._generateLoadingContent(this.statusElement);
723                         this.loadingContentElement.className = "longStatus";
724                         this.progressBar.className = "longProgressBar";
725                         this.progressBarText.className = "longProgressText";
726 	        		}
727 	        	}
728 	        	this.readDataButton.disabled = true;
729 	        	this.cancelReadDataButton.disabled = false;
730 	        	
731 	        	if (this.options.showReadDataTypesSelect) {
732 	        		this.readSpecificTypeFromDevice(this.readDataTypesSelect.value);
733 	        	} else if (this.options.readDataTypes != null) {					
734             	    this.readFromDevice(this.options.readDataTypes);
735             	} else {
736 					this.readFromDevice(new Array(this.options.readDataType));
737 				} 	        	
738 			}
739         }.bind(this)
740 		this.readDataElement.appendChild(this.uploadNewButton);
741 
742 		if(!this.options.showUploadNewButton) {
743 			Element.hide(this.uploadNewButton);
744 		}
745 
746 		if(this.options.showReadDataTypesSelect) {
747 			this.readDataTypesSelect = document.createElement("select");
748 			Element.extend(this.readDataTypesSelect);
749 			this.readDataTypesSelect.id = this.options.readDataTypeSelectId;
750 			this.readDataTypesSelect.disabled = true;
751 			this.readDataElement.appendChild(this.readDataTypesSelect);
752 
753 			// TODO: need a more elegant way of adding options
754 			this.readDataTypesSelect.options[0] = new Option(this.options.gpsData, Garmin.DeviceControl.FILE_TYPES.gpx);
755 			this.readDataTypesSelect.options[1] = new Option(this.options.trainingData, Garmin.DeviceControl.FILE_TYPES.tcx);			
756 		}
757 		
758 		if(this.options.showReadRoutesSelect) {
759 			this.readRoutesElement = document.createElement("div");
760 			Element.extend(this.readRoutesElement);
761 			this.readRoutesElement.id = this.options.readRoutesElementId;
762 			this.readRoutesElement.addClassName(this.options.readResultsElementClass);
763 			this.readRoutesElement.innerHTML = "<span id=\"" + this.options.readRoutesSelectLabelId + "\">" + this.options.readRoutesSelectLabel + "</span>";
764 
765 			this.readRoutesSelect = document.createElement("select");
766 			Element.extend(this.readRoutesSelect);
767 			this.readRoutesSelect.id = this.options.readRoutesSelectId;
768 			this.readRoutesSelect.addClassName(this.options.readResultsSelectClass);
769 			this.readRoutesSelect.disabled = true;
770 			this.readRoutesSelect.onchange = function() {
771 				this.displayTrack( this._seriesFromSelect(this.readRoutesSelect) );
772 			}.bind(this);
773 			this.readRoutesElement.appendChild(this.readRoutesSelect);
774 			this.readDataElement.appendChild(this.readRoutesElement);
775 			
776 			if(!this.options.showReadResultsSelectOnLoad) {
777 				Element.hide(this.readRoutesElement);
778 			}
779 		}
780 
781 		if(this.options.showReadTracksSelect) {
782 			this.readTracksElement = document.createElement("div");
783 			Element.extend(this.readTracksElement);
784 			this.readTracksElement.id = this.options.readTracksElementId;
785 			this.readTracksElement.addClassName(this.options.readResultsElementClass);
786 			this.readTracksElement.innerHTML = "<span id=\"" + this.options.readTracksSelectLabelId + "\">" + this.options.readTracksSelectLabel + "</span>";
787 
788 			this.readTracksSelect = document.createElement("select");
789 			Element.extend(this.readTracksSelect);
790 			this.readTracksSelect.id = this.options.readTracksSelectId;
791 			this.readTracksSelect.addClassName(this.options.readResultsSelectClass);
792 			this.readTracksSelect.disabled = true;
793 			this.readTracksSelect.onchange = function() {
794 				this.displayTrack( this._seriesFromSelect(this.readTracksSelect) );
795 			}.bind(this);
796 			this.readTracksElement.appendChild(this.readTracksSelect);
797 			this.readDataElement.appendChild(this.readTracksElement);
798 			
799 			if(!this.options.showReadResultsSelectOnLoad) {
800 				Element.hide(this.readTracksElement);
801 			}
802 		}
803 		
804 		if(this.options.showReadWaypointsSelect) {
805 			this.readWaypointsElement = document.createElement("div");
806 			Element.extend(this.readWaypointsElement);
807 			this.readWaypointsElement.id = this.options.readWaypointsElementId;
808 			this.readWaypointsElement.addClassName(this.options.readResultsElementClass);
809 			this.readWaypointsElement.innerHTML = "<span id=\"" + this.options.readWaypointsSelectLabelId + "\">" + this.options.readWaypointsSelectLabel + "</span>";
810 
811 			this.readWaypointsSelect = document.createElement("select");
812 			Element.extend(this.readWaypointsSelect);
813 			this.readWaypointsSelect.id = this.options.readWaypointsSelectId;
814 			this.readWaypointsSelect.addClassName(this.options.readResultsSelectClass);
815 			this.readWaypointsSelect.disabled = true;
816 			this.readWaypointsSelect.onchange = function() {
817 				this.displayWaypoint( this._seriesFromSelect(this.readWaypointsSelect) );
818 			}.bind(this);
819 			this.readWaypointsElement.appendChild(this.readWaypointsSelect);
820 			this.readDataElement.appendChild(this.readWaypointsElement);
821 			
822 			if(!this.options.showReadResultsSelectOnLoad) {
823 				Element.hide(this.readWaypointsElement);
824 			}
825 		}
826 		
827 		// Read Tracks Google Map
828 		if(this.options.showReadGoogleMap) {
829 			this.readGoogleMap = document.createElement("div");
830 			Element.extend(this.readGoogleMap);
831 			this.readGoogleMap.id = this.options.readGoogleMapId;
832 			this.readGoogleMap.addClassName(this.options.readResultsElementClass);
833 			this.readDataElement.appendChild(this.readGoogleMap);			
834 			this.readMapController = new Garmin.MapController(this.options.readGoogleMapId);
835 		}
836 		
837 		if(this.options.showReadDataElementOnDeviceFound) {
838 			Element.hide(this.readDataElement);
839 		}
840 	},
841 
842     /* Generates the activity directory element.  Only one instance of the directory
843      * can exist on a page.
844      *  
845      * @private 
846      */
847 	_generateActivityDirectoryElement: function() {
848 	    if( this.activityDirectoryElement != null) {
849 	        throw new Error("Unable to generate activity directory because an instance already exists.");
850 	    }
851 		// Create the container div to hold the directory elements
852 		this.activityDirectoryElement = document.createElement("div");
853 		Element.extend(this.activityDirectoryElement);
854 		this.activityDirectoryElement.id = this.options.activityDirectoryElementId;
855 		this.activityDirectoryElement.addClassName(this.options.activityDirectoryClass);
856 		this.activityDirectoryElement.hide();
857 		
858 		this.mainElement.appendChild(this.activityDirectoryElement);
859 	},
860 	
861 	_generateActivityTableElement: function() {
862 		if( this.activityTable != null ) {
863 		    throw new Error("Unable to generate activity table with id " + this.options.activityTableId + " because an instance already exists.");
864 		}
865 		this.activities = null;
866 		
867         this._generateActivityTableHeader();
868 		
869 		// Create container div that holds the table data only
870 		this.activityDirectoryData = document.createElement("div");
871 		Element.extend(this.activityDirectoryData);
872 		this.activityDirectoryData.id = this.options.activityDirectoryDataId;
873 		this.activityDirectoryElement.appendChild(this.activityDirectoryData);
874 		
875 		// Create the table
876 		this.activityTable = document.createElement("table");
877 		Element.extend(this.activityTable);
878 		this.activityTable.id = this.options.activityTableId;
879 		this.activityTable.setAttribute('cellspacing','0');
880 		this.activityTable.setAttribute('cellpadding','0');
881 		this.activityDirectoryData.appendChild(this.activityTable);
882 		
883 		this.readSelectedButton = document.createElement( this.options.useLinks ? "div" : "input" );
884 		Element.extend(this.readSelectedButton);
885 		if (this.options.useLinks) {
886 			this.readSelectedButton.innerHTML = '<a href="#">'+this.options.readSelectedButtonText+'</a>';
887 		} else {
888 			this.readSelectedButton.type = "button";
889 			this.readSelectedButton.value = this.options.readSelectedButtonText;
890 		}
891 		this.readSelectedButton.id = this.options.readSelectedButtonId;
892 		this.readSelectedButton.addClassName(this.options.actionButtonClassName);
893 		this.readSelectedButton.disabled = false;
894         this.readSelectedButton.onclick = function() {
895            // Read activities filtered by the API (including user selected activities) 
896     	   this.readFilteredActivities();
897         }.bind(this);
898         this.activityDirectoryElement.appendChild(this.readSelectedButton);
899 	},
900 	
901 	/* Generate the singleton activity table header for the activity directory.
902 	 * The visible elements in the table are added dynamically after the directory is read.
903 	 * See _addToActivityTableHeader().
904 	 * 
905 	 * @private
906 	 */
907 	_generateActivityTableHeader: function() {
908 	    if( this.activityTableHeader != null) {
909 	        throw new Error("Unable to generate activity table header: Instance of the activity table header already exists.");
910 	    }
911 		
912 		// Create the table header
913 		this.activityTableHeader = document.createElement("table");
914 		Element.extend(this.activityTableHeader);
915 		this.activityTableHeader.id = this.options.activityTableHeaderId;
916 		this.activityTableHeader.setAttribute('cellspacing','0');
917 		this.activityTableHeader.setAttribute('cellpadding','0');
918 		
919 		this.activityDirectoryElement.appendChild(this.activityTableHeader);
920 	},
921 	
922     /* Build write data UI components.
923      * @private
924      */
925 	_generateWriteDataElement: function() {
926 		this.writeDataElement = document.createElement("div");
927 		Element.extend(this.writeDataElement);
928 		this.writeDataElement.id = this.options.writeDataElementId;
929 		this.writeDataElement.addClassName(this.options.elementClassName);
930 		this.mainElement.appendChild(this.writeDataElement);
931 
932 		if (!this.options.getWriteData && !this.options.getGpiWriteDescription && !this.options.getBinaryWriteDescription)
933 			throw new Error("Can't write data because getWriteData() function nor getGpiWriteDescription() is defined");
934 		this.writeDataButton = document.createElement( this.options.useLinks ? "div" : "input" );
935 		Element.extend(this.writeDataButton);
936 		if (this.options.useLinks) {
937 			this.writeDataButton.innerHTML = '<a href="#">'+this.options.writeDataButtonText+'</a>';
938 		} else {
939 			this.writeDataButton.type = "button";
940 			this.writeDataButton.value = this.options.writeDataButtonText;
941 		}
942 		this.writeDataButton.id = this.options.writeDataButtonId;
943 		this.writeDataButton.addClassName(this.options.actionButtonClassName);
944 		this.writeDataButton.disabled = true;		
945         this.writeDataButton.onclick = function() {
946             var isSupportedDevice = true;
947         	if( this.options.restrictByDevice.length > 0){
948 				isSupportedDevice = this._restrictByDevice();
949 			}						
950 			if( isSupportedDevice) {
951             	this.writeDataButton.disabled = true;
952             	this.cancelWriteDataButton.disabled = false;
953             	if( this.options.autoHideUnusedElements ) {
954             	    if(this.findDevicesElement) {
955             	        this.findDevicesElement.hide();
956             	    }
957             	    if(this.writeDataElement) {
958             	        this.writeDataElement.hide();
959             	    }
960             	}
961             	this.showProgressBar();
962             	this.writeToDevice();
963 			}
964         }.bind(this);
965 		this.writeDataElement.appendChild(this.writeDataButton);
966 		
967 		this.cancelWriteDataButton = document.createElement( this.options.useLinks ? "div" : "input" );
968 		Element.extend(this.cancelWriteDataButton);
969 		if (this.options.useLinks) {
970 			this.cancelWriteDataButton.innerHTML = '<a href="#">'+this.options.cancelWriteDataButtonText+'</a>';
971 		} else {
972 			this.cancelWriteDataButton.type = "button";
973 			this.cancelWriteDataButton.value = this.options.cancelWriteDataButtonText;
974 		}
975 		this.cancelWriteDataButton.id = this.options.cancelWriteDataButtonId;
976 		this.cancelWriteDataButton.addClassName(this.options.actionButtonClassName);
977 		this.cancelWriteDataButton.disabled = false;
978 		this.cancelWriteDataButton.onclick = function() {
979 			this.resetUI();
980 			this.hideProgressBar();
981 			this.getController().cancelWriteToDevice();
982 		}.bind(this);
983 		this.writeDataElement.appendChild(this.cancelWriteDataButton);
984 		
985 		if(!this.options.showCancelWriteDataButton) {
986 			Element.hide(this.cancelWriteDataButton);
987 		}
988 
989 		if(this.options.showWriteDataElementOnDeviceFound) {
990 			Element.hide(this.writeDataElement);
991 		}
992 	},
993 
994     /* Build "Powered by" UI components.
995      * @private
996      */
997 	_generateAboutElement: function() {
998 		this.aboutElement = document.createElement("div");
999 		Element.extend(this.aboutElement);
1000 		this.aboutElement.id = "aboutElement";
1001 		this.aboutElement.addClassName(this.options.elementClassName);
1002 		this.mainElement.appendChild(this.aboutElement);
1003 
1004 		this.copyrightText = document.createElement("span");
1005 		this.copyrightText.innerHTML = this.options.poweredByGarmin;
1006 		this.aboutElement.appendChild(this.copyrightText);
1007 	},
1008 	
1009 	/* Checks the connected device against those listed in this.options.restrictByDevice
1010 	 * and throws an error if the connected device is not supported by
1011 	 * the application.  
1012 	 * 
1013 	 * @return true if the connected device is supported, false otherwise.
1014 	 */
1015 	_restrictByDevice: function() {
1016 		
1017 		// Get connected device
1018 		var device = this.getController().getDevices()[this.deviceSelectInput.value];
1019 		var devicePartNumber = device.getPartNumber();
1020 		
1021 		var isSupportedDevice = false;
1022 		// Compare to restricted list
1023 		for(var i=0; i < this.options.restrictByDevice.length; i++) {
1024 		    var supportedPartNumber = this.options.restrictByDevice[i];
1025 			if(devicePartNumber == supportedPartNumber) {
1026 				isSupportedDevice = true;
1027 			}
1028 		}
1029 		
1030 		// Hide everything but status
1031 		if( !isSupportedDevice ) {
1032 			error = new Error(this.options.unsupportedDevice);
1033     	    error.name = "UnsupportedDeviceException";
1034 			this.handleException(error);
1035 		}
1036 		
1037 		return isSupportedDevice;
1038 	},
1039 
1040     ////////////////////////// FIND DEVICES METHODS ////////////////////////// 
1041     
1042     /** Entry point for searching for connected devices.
1043      * Will attempt to unlock the plugin if necessary.
1044      * @see Garmin.DeviceDisplay.cancelFindDevices
1045      * @see Garmin.DeviceDisplay.onStartFindDevices
1046      */
1047 	startFindDevices: function() {
1048 		this.getController(true); //try to unlock plugin
1049 		if(this.options.autoHideUnusedElements){
1050 		    if( this.findDevicesButton) {
1051 		      this.findDevicesButton.hide();
1052 		    }
1053 		    if( this.browseComputerButton ) { 
1054 		      this.browseComputerButton.hide();
1055 		    }
1056 		}
1057 		if(this.findDevicesButton) 
1058 	       	this.findDevicesButton.disabled = true;
1059 	    if (this.cancelFindDevicesButton)
1060     		this.cancelFindDevicesButton.disabled = !this.isUnlocked();
1061 		if (this.isUnlocked()) {
1062        		this.getController().findDevices();
1063 		}
1064 	},
1065 
1066     /** Entry point for cancelling search for connected devices.
1067      * @see Garmin.DeviceDisplay.onCancelFindDevices
1068      */
1069 	cancelFindDevices: function() {
1070 		this.resetUI();
1071        	this.getController().cancelFindDevices();
1072 	},
1073 
1074     /** Call-back triggered before plugin searches for connected devices.
1075      * @event 
1076      * @param {JSON} json
1077      * @see Garmin.DeviceControl 
1078      * @see Garmin.DeviceDisplay.startFindDevices
1079      */
1080     onStartFindDevices: function(json) {
1081         this.setStatus(this.options.lookingForDevices);
1082     },
1083 
1084     /** Call-back triggered after plugin has completed its search for devices.
1085      * @event
1086      * @param {JSON} json
1087      * @see Garmin.DeviceControl
1088      * @see Garmin.DeviceDisplay.startFindDevices
1089      */
1090     onFinishFindDevices: function(json) {
1091 		this.resetUI();
1092         if(json.controller.numDevices > 0) {
1093             this.devices = json.controller.getDevices();               
1094             
1095             var template = (this.devices.length == 1) ? this.options.foundDevice : this.options.foundDevices;
1096             var values = {deviceName: this.getShortDeviceName(this.devices[0]), deviceCount: json.controller.numDevices};
1097             this.setStatus( this.evaluateTemplate(template, values) );
1098  
1099 			if(this.options.showFindDevicesElement ) {
1100 				Element.show(this.findDevicesElement);
1101 				if (this.options.showDeviceButtonsOnFound) {
1102 					if (this.findDevicesButton && this.options.showFindDevicesButton)
1103 						Element.show(this.findDevicesButton);
1104 					if (this.cancelFindDevicesButton)
1105 						Element.show(this.cancelFindDevicesButton);	
1106 				} else {
1107 					if (this.findDevicesButton)
1108 						Element.hide(this.findDevicesButton);
1109 					if (this.cancelFindDevicesButton)
1110 						Element.hide(this.cancelFindDevicesButton);
1111 				}
1112 				// Hide device select on single device
1113 				if ((this.devices.length < 2 && !this.options.showDeviceSelectOnSingle) || this.options.autoSelectFirstDevice) {
1114 					Element.hide(this.deviceSelectElement);
1115 				} else {
1116 					Element.show(this.deviceSelectElement);
1117 				}
1118 				// Populate the devices list based on UI option
1119 				this.options.useDeviceSelectList ? this._generateDeviceListView() : this._populateDeviceSelectDropDown();
1120 			}
1121 			
1122 			if(this.options.showReadDataElementOnDeviceFound) {
1123 				Element.show(this.readDataElement);
1124 			}
1125 			
1126 			if(this.options.showSendDataElementOnDeviceFound) {
1127 				Element.show(this.sendDataElement);
1128 			}
1129 			
1130 			if(this.options.showWriteDataElementOnDeviceFound) {
1131 				Element.show(this.writeDataElement);
1132 			}
1133 			
1134 			if(this.options.autoHideUnusedElements) {
1135 				if(this.activityDirectoryElement) {
1136 				    Element.hide(this.activityDirectoryElement);
1137 				}
1138 				if(this.readDataElement) {
1139 				    Element.show(this.readDataElement);
1140 				}
1141 				if(this.options.showBrowseComputer) {
1142         		    if( this.browseComputerButton) {
1143                         this.browseComputerButton.show();
1144         		    }
1145         		}
1146 			}
1147 			
1148 			if (this.options.autoReadData) {
1149 	        	this.showProgressBar();
1150 	        	if (this.options.showReadDataTypesSelect) {
1151 	        		this.readSpecificTypeFromDevice(this.readDataTypesSelect.value);
1152 	        	} else if (this.options.readDataType != null) {
1153 	        		this.readSpecificTypeFromDevice(this.options.readDataType);
1154 	        	} else {
1155 					this.readFromDevice();
1156 	        	}
1157 			}
1158 			
1159 			if (this.options.autoWriteData) {
1160 	        	this.showProgressBar();
1161         		this.writeToDevice();
1162 			}
1163         } else { // No devices found!
1164         	if ((this.options.autoReadData || this.options.autoWriteData) && !this.options.showStatusElement) {
1165         		alert(this.options.noDeviceDetectedStatusText);
1166         	}
1167         	
1168 			this.setStatus(this.options.noDeviceDetectedStatusText);
1169             if(this.findDevicesButton) {
1170                 this.findDevicesButton.show();  // allow user to retry
1171             }
1172             
1173             //allow user to browse computer in activity directory
1174             if(this.options.uploadSelectedActivities) {
1175                 this.browseComputerButton.show();
1176             }
1177 			
1178 			if(this.options.showFindDevicesElement) {
1179 				if (this.options.showCancelFindDevicesButton) {
1180 					Element.show(this.cancelFindDevicesButton);	
1181 				}
1182 				if (this.options.showDeviceSelectNoDevice && !this.options.autoSelectFirstDevice) {
1183 					Element.show(this.deviceSelectElement);
1184 				}
1185 			}					
1186         }
1187         if (this.options.afterFinishFindDevices) {
1188     		this.options.afterFinishFindDevices.call(this, this.devices);
1189         }
1190     },
1191     
1192     /** Call-back for find device cancelled.
1193      * @event
1194      * @param {JSON} json
1195      * @see Garmin.DeviceControl 
1196      * @see Garmin.DeviceDisplay.cancelFindDevices
1197      */
1198 	onCancelFindDevices: function(json) {
1199 		this.setStatus(this.options.findCancelled);
1200     	this.resetUI();
1201     },
1202 
1203     /** Load device list into select UI component.
1204      * @private
1205      */
1206 	_populateDeviceSelectDropDown: function() {
1207 	    this.deviceSelectElement.appendChild(this.deviceSelectInput);
1208 		this._clearHtmlSelect(this.deviceSelectInput);
1209 		if(this.options.showFindDevicesElement) {
1210 			for( var i=0; i < this.devices.length; i++ ) {
1211            	    this.deviceSelectInput.options[i] = new Option(this.getShortDeviceName(this.devices[i]),this.devices[i].getNumber());
1212 			    
1213 	           	if(this.devices[i].getNumber() == this.getController().deviceNumber) {
1214 	           		this.deviceSelectInput.selectedIndex = i;
1215 	           		// Adding afterSelectDevice functionality to the old select UI
1216 	                if (this.options.afterSelectDevice != null) {
1217 	                    this.options.afterSelectDevice.call(this, this.getController().deviceNumber, this.devices, this.garminController.getCurrentDeviceXml());
1218 	                }
1219 	           	}
1220 			}
1221 			this.deviceSelectInput.onchange = function() {
1222 				var device = this.getController().getDevices()[this.deviceSelectInput.value];
1223 				this.setStatus(this.evaluateTemplate(this.options.usingDevice, {deviceName:this.getShortDeviceName(device)}));
1224 				this.getController().setDeviceNumber(this.deviceSelectInput.value);
1225 				// Adding afterSelectDevice functionality to the old select UI
1226 				if (this.options.afterSelectDevice != null) {
1227 					this.options.afterSelectDevice.call(this, this.getController().deviceNumber, this.devices, this.garminController.getCurrentDeviceXml());
1228 				}				
1229 			}.bind(this);
1230 			this.deviceSelectInput.disabled = false;
1231 		}
1232 	},
1233 	
1234 	/* Load device browser content.
1235      * @private
1236      */
1237 	_generateAndDisplayDeviceBrowser: function() {
1238 	   
1239         // Create device browser
1240 		if( this.options.useDeviceBrowser) {
1241             if( this.deviceBrowserElement == null ) {
1242                 this.generateDeviceBrowserElement(this.devices); 
1243             }
1244             
1245             if( !this.advancedUploadMode) {
1246                 // Simple upload
1247                 this.devicePreviewElement.show();
1248                 this.progressBar.hide();
1249             } else {
1250                 // Advanced upload
1251                 this.deviceBrowserElement.show();
1252     		    this.activityDirectoryElement.show();
1253             }
1254     		    this.statusText.hide();
1255     		    this.showProgressBar();
1256 
1257 		    // Show loading screen while reading device
1258 		    if( this.loadingContentElement == null) {
1259                 this._generateLoadingContent(this.statusElement);
1260 		    } else {
1261 		        // Update the device name displayed
1262 		        this._updateLoadingContent(this.evaluateTemplate(this.options.loadingContentText, {deviceName:this.getShortDeviceName(this.getCurrentDevice())}));
1263 		    }
1264 		}
1265 	},
1266 	
1267 	/* Load device list into select UI component.
1268      * @private
1269      */
1270 	_generateDeviceListView: function() {
1271 	    var deviceSelectContainer;
1272 	    
1273 		this._clearHtmlSelect(this.deviceSelectInput);
1274 		
1275 		deviceSelectContainer = document.createElement("div");
1276 		Element.extend(deviceSelectContainer);
1277 		deviceSelectContainer.className = this.options.deviceSelectClass;
1278 		
1279 		// Display change only if there are multiple devices
1280 		if( this.devices.length > 1) {
1281     		// Change device link
1282     		this.changeDeviceElement = document.createElement("div");
1283     		Element.extend(this.changeDeviceElement);
1284     		this.changeDeviceElement.id = this.options.changeDeviceElementId;
1285     		this.changeDeviceElement.className = this.options.changeDeviceClass;
1286     		this.changeDeviceElement.innerHTML = '<a href="#">'+this.options.changeDeviceButtonText+'</a>';
1287     		this.changeDeviceElement.onclick = function() {
1288     		    this.devicePreviewElement.toggle();
1289     		    this.connectedDevices.toggle();
1290     		    this.deviceSelectInput.toggle();
1291     		}.bind(this);
1292     		this.deviceSelectElement.appendChild(this.changeDeviceElement);
1293 		}
1294 		
1295 		// Display pre-selected device (first device)
1296 		this.devicePreviewElement = document.createElement("div");
1297 		Element.extend(this.devicePreviewElement);
1298 		this.devicePreviewElement.id = this.options.previewDeviceElementId;
1299 		this.devicePreviewElement.innerHTML = '<p>'+this.getShortDeviceName(this.getCurrentDevice())+'</p>';
1300 		deviceSelectContainer.appendChild(this.devicePreviewElement);
1301 		this.deviceSelectElement.appendChild(deviceSelectContainer);
1302 		
1303 		if(this.options.showFindDevicesElement) {
1304 		    
1305 		    // Connected devices label
1306 		    this.connectedDevices = new Element("div", {className: this.options.connectedDevicesClass});
1307 		    this.connectedDevices.update('<img src="'+ this.options.connectedDevicesImg +'" />' + this.options.connectedDevicesLabel);
1308 		    this.connectedDevices.hide();
1309 		    deviceSelectContainer.appendChild(this.connectedDevices);
1310 		    
1311             this._populateDeviceList(this.deviceSelectInput, this._updateDevicePreview);
1312 		   
1313 			deviceSelectContainer.appendChild(this.deviceSelectInput);
1314 			this.deviceSelectElement.appendChild(deviceSelectContainer);
1315 			this.deviceSelectInput.hide();
1316 			this.deviceSelectInput.disabled = false;
1317 		}
1318 	},
1319 	
1320 	/* Returns the current selected device according to the control object.
1321 	 * @private
1322 	 * @return {Device} the Device object belonging to the selected device
1323 	 * @see Garmin.Device
1324 	 */
1325 	getCurrentDevice: function() {
1326         return this.devices[this.getController().deviceNumber];
1327 	},
1328 	
1329 	/* Populates the device list.  'List' is emphasized because we are counting
1330 	 * on the fact that it is not a select drop down. See _populateDeviceSelectDropDown
1331 	 * for that.
1332 	 * 
1333 	 * Sets the onclick event for each device in the list.  When a device is selected,
1334 	 * the device number in control is set to that device.  The class names of the
1335 	 * entire list are also updated in order to indicate visually which device is selected.
1336 	 * 
1337 	 * After the above is finished, the callback method is executed.
1338 	 *   
1339 	 * @private
1340 	 * @param {Element} deviceListElement the list element for the device listing
1341 	 * @param {function} callback(deviceIndex) - the callback function to use when a device is selected.
1342 	 *     deviceIndex is the device number selected by the user.   
1343 	 */
1344 	_populateDeviceList: function(deviceListElement, callback) {
1345 	    var itemLink;
1346 	    var listItem;
1347 	    
1348 	     // Insert detected devices into the display list
1349 		for( var i=0; i < this.devices.length; i++ ) {
1350             listItem = document.createElement("li");
1351             Element.extend(listItem);
1352 	        listItem.className = (i == this.getController().deviceNumber) ? "selected" : "unselected";
1353 	        itemLink = document.createElement("a");
1354 	        Element.extend(itemLink);
1355 	        itemLink.href = "#";
1356 			itemLink.innerHTML = this.getShortDeviceName(this.devices[i]);
1357 	        itemLink.onclick = function(deviceListElement, deviceIndex, devices, callback){
1358 	            
1359 	            // Stop any existing reads and hide stuffs
1360                 this.getController().cancelReadFromDevice();
1361 	            
1362 	            // Hide and unselect My Computer if selected
1363 	            if( this.browseComputerElement != null) {
1364 	               this.browseComputerElement.hide();
1365 	               deviceListElement.childNodes[this.devices.length].className = "unselected";
1366 	            }
1367 	            this.statusElement.show();
1368 	            
1369 	            // Set the new device to talk to
1370 	            this.getController().setDeviceNumber(deviceIndex);
1371 	            // Update the class names in the entire list
1372 	            for(var j=0; j< this.devices.length; j++) {
1373 	               var listItem = deviceListElement.childNodes[j];
1374 	               listItem.className = (j == this.getController().deviceNumber) ? "selected" : "unselected";
1375 	            }
1376 	            // The callback function has to take these two parameters!  Even if it ignores em.
1377 	            callback.call(this, deviceIndex, this.devices, this.getController().getCurrentDeviceXml());
1378 	        }.bind(this,deviceListElement,i,this.devices,callback); //bind with parameter
1379 	        listItem.appendChild(itemLink);
1380 	        deviceListElement.appendChild(listItem);
1381 		}
1382 		
1383 		// Select first device
1384 		if(this.options.autoSelectFirstDevice) {
1385 		    this.options.afterSelectDevice.call(this, this.getController().deviceNumber, this.devices, this.garminController.getCurrentDeviceXml());
1386 		}
1387 	},
1388 	
1389 	/* Process the filename, trim it if its too long
1390 	 * @param {Garmin.Device} the device whose name is to be processed
1391 	 * @return {String} the truncated device name, according to the max size set in the display options
1392 	 * @see Garmin.DeviceDisplayDefaultOptions.deviceLabelMaxSize
1393 	 * @see Garmin.Device
1394 	 */
1395 	getShortDeviceName : function(device) {
1396 		var deviceName = device.getDisplayName();
1397 		if (deviceName.length > this.options.deviceLabelMaxSize) {
1398 			deviceName = deviceName.substring(0, this.options.deviceLabelMaxSize) + "...";
1399 		}
1400 		return deviceName;
1401 	},
1402 	
1403 	/* Update the device preview display to show the device selected by the user.
1404 	 * Hides the input list and shows the preview element.
1405 	 * @param int deviceNumber - the device number (index) selected by the user
1406 	 * @param String deviceXml - describes the device (unused right now)  
1407 	 */
1408 	_updateDevicePreview : function(deviceNumber, deviceXml){
1409         this.devicePreviewElement.innerHTML = '<p>'+this.getShortDeviceName(this.devices[deviceNumber])+'</p>';
1410         this.devicePreviewElement.show();
1411         if(this.deviceSelectInput) this.deviceSelectInput.hide();
1412         if(this.connectedDevices) this.connectedDevices.hide();
1413 	},
1414 	
1415     ////////////////////////////// READ METHODS ////////////////////////////// 
1416     
1417     /** Initiation call for reading from a device.  If a fitness device is detected reads TCX
1418      * otherwise reads GPX.
1419      * Upon completion if the afterFinishReadFromDevice method is defined
1420      * it will be called.  At this time you may also obtain location data using the 
1421      * getTracks and getWaypoints methods.
1422      * @param {Array} readDataTypes list of read data types
1423      * @see Garmin.DeviceControl.FILE_TYPES
1424      */
1425 	readFromDevice: function(readDataTypes) {
1426 		var deviceNumber = this.getController().deviceNumber;
1427 		var device = this.getController().getDevices()[deviceNumber];
1428 		
1429 		// TODO remove this later 
1430 		// Backwards compatability for deprecated method
1431 		if( this.options.readDataType != null ) {
1432 		    readDataTypes = new Array();
1433 		    readDataTypes[0] = this.options.readDataType;
1434 		}
1435 		
1436 		// Read the first supported type in the list
1437 		var supported = null;
1438 		for(var i=0; i < readDataTypes.length; i++) {
1439 		    var datatype = readDataTypes[i];
1440 		    if(supported == null && this.getController().checkDeviceReadSupport(datatype)) {
1441 		        supported = datatype;
1442 		        
1443 		        if(this.options.uploadSelectedActivities) {
1444     		        // Handle directory types
1445     		        switch(datatype) {
1446     		        	case Garmin.DeviceControl.FILE_TYPES.gpxDir:
1447                 		case Garmin.DeviceControl.FILE_TYPES.tcxDir:
1448                 		case Garmin.DeviceControl.FILE_TYPES.fitDir:
1449                             if( this.activityTable == null) {
1450                                 this._generateActivityTableElement();
1451                             } else {
1452                                 this._clearActivityTable();
1453                             }     		
1454                             this._generateAndDisplayDeviceBrowser();
1455     		        }
1456 		        }
1457 		        this.getController().readDataFromDevice(datatype);
1458 		    }
1459 		}
1460 		
1461 		// No supported types found, throw error
1462 		if( supported == null) {
1463     	    var error = new Error(this.options.unsupportedDevice);
1464     	    error.name = "UnsupportedDataTypeException";
1465     	    this.handleException(error);
1466 		}
1467 	},
1468 	
1469     /** Read the filtered activities.  Filtered activities are those picked
1470      * by the API as well as any user-selected activities, if applicable.  
1471 	 * 
1472 	 * Activities may be uploaded after being read.
1473 	 * 
1474 	 * Filtered activities are detected before
1475 	 * the activities themselves are read, and data filters filter the data
1476 	 * after the activities are read.
1477 	 * 
1478 	 * @see Garmin.DeviceDisplay.onFinishUploads
1479 	 */
1480 	readFilteredActivities: function() {
1481 	    // Make sure there are selected activities to read
1482         if( this._directoryHasSelected() == false) {
1483             if( this.advancedUploadMode) {
1484                 // Alert user to select
1485                 alert(this.options.errorActivitySelect);
1486             } else {
1487                 // No new activities
1488                 this.numQueuedActivities = 0;
1489                 this.setStatus(this.options.noFilteredActivities);
1490                 if( this.options.uploadSelectedActivities ) {
1491                     this.getController()._broadcaster.dispatch("onFinishUploads", { display: this });
1492                 }            
1493             }
1494         } else {
1495         	this.activities = null;
1496         	this.readTracksSelect.length = 0;	
1497         	this.readSelectedButton.disabled = true;
1498         	if(this.options.useLinks) {
1499         	    this.readSelectedButton.hide();
1500         	}
1501         	if(this.checkAllBox != null) {
1502         	  this.checkAllBox.disabled = false;
1503         	}
1504     	
1505     	    if(this.options.useDeviceBrowser && this.advancedUploadMode){
1506     	       this.statusElement.hide();  
1507     	    } else {
1508         	   this.showProgressBar();
1509     	    }
1510     	    
1511     	    this._populateActivityQueue();
1512     
1513             if(this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.tcxDir) {
1514                 this.fileTypeRead = Garmin.DeviceControl.FILE_TYPES.tcxDetail;
1515                 //setTimeout(function(){this._readNextSelected();}.bind(this), 0);
1516                 this._readNextSelected();
1517             } else if(this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.fitDir) {
1518                 this.fileTypeRead = Garmin.DeviceControl.FILE_TYPES.fit;
1519                 //setTimeout(function(){this._readNextSelected();}.bind(this), 0);
1520                 this._readNextSelected();
1521             } else if (this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.gpxDir) {
1522             	this.fileTypeRead = Garmin.DeviceControl.FILE_TYPES.gpxDetail;
1523             	
1524             	while (this.activityQueue.size() != 0) {
1525             		// Display "Uploading..."
1526             		this._displayProcessingForCurrentActivity(this.activityQueue.last());
1527             		this.activityQueue.pop();
1528             	}
1529 
1530             	this.garminController.readDataFromDevice(this.fileTypeRead);
1531             }    		     		    		    	
1532         }
1533 	},
1534 	
1535     /** Generic read method, supporting GPX, TCX, Courses, Workouts, User Profiles, 
1536 	 * TCX activity directory, and TCX course directory reads. <br/> 
1537      * <br/>
1538      * Upon completion if the afterFinishReadFromDevice method is defined
1539      * it will be called.  At this time you may also obtain location data using the 
1540      * getTracks and getWaypoints methods.<br/>
1541 	 * <br/>
1542 	 * Fitness detail reading (one specific activity) is not supported by this read method, refer to 
1543 	 * readDetailFromDevice for that. <br/>  
1544 	 * 
1545      * @param String readDataType - type of data to read. 
1546      * @see Garmin.DeviceControl.FILE_TYPES
1547      * @see Garmin.DeviceDisplayDefaultOptions.afterFinishReadFromDevice
1548      */
1549 	readSpecificTypeFromDevice: function(readDataType) {
1550 	    // Check to make sure device supports reading this type. Must do this at display layer otherwise exception will not
1551 	    // bubble up to the user.
1552     	if( this.getController().checkDeviceReadSupport(readDataType) == false) {
1553     	    var error = new Error(this.evaluateTemplate(this.options.unsupportedReadDataType, {dataType: readDataType}));
1554     	    error.name = "UnsupportedDataTypeException";
1555     	    this.handleException(error);
1556     	} else {
1557     		var deviceNumber = this.getController().deviceNumber;
1558     		var device = this.getController().getDevices()[deviceNumber];
1559     		
1560     		switch(readDataType) {
1561         		case Garmin.DeviceControl.FILE_TYPES.tcxDir:
1562         		case Garmin.DeviceControl.FILE_TYPES.fitDir:   
1563                     if( this.activityTable == null) {
1564                         this._generateActivityTableElement();
1565                     } else {
1566                         this._clearActivityTable();
1567                     }     		
1568                     this._generateAndDisplayDeviceBrowser();
1569         			// no break!  keep on goin'
1570         		case Garmin.DeviceControl.FILE_TYPES.gpx:
1571         		case Garmin.DeviceControl.FILE_TYPES.gpxDir:
1572         		case Garmin.DeviceControl.FILE_TYPES.tcx:
1573         		case Garmin.DeviceControl.FILE_TYPES.crs:
1574         		case Garmin.DeviceControl.FILE_TYPES.wkt:
1575         		case Garmin.DeviceControl.FILE_TYPES.tcxProfile:        		
1576         		case Garmin.DeviceControl.FILE_TYPES.crsDir:
1577         			this.getController().readDataFromDevice(readDataType);
1578         			break;
1579         		case Garmin.DeviceControl.FILE_TYPES.deviceXml:
1580         			this.getController().readDataFromDevice(readDataType);
1581         			break;
1582         		default:
1583         			var error = new Error(Garmin.DeviceControl.MESSAGES.invalidFileType + readDataType);
1584     				error.name = "InvalidTypeException";			
1585     				this.handleException(error);
1586         	} 
1587     	}
1588 	},
1589 
1590     /** Call-back for device read progress.
1591      * @param {JSON} json the progress report in JSON format
1592      * @see Garmin.DeviceDisplay.onFinishReadFromDevice
1593      * @event
1594      */
1595     onProgressReadFromDevice: function(json) {
1596 		if(this.options.showProgressBar) {
1597 	    	this.updateProgressBar(this.progressBarDisplay, json.progress.getPercentage());
1598 	    	this.updateProgressBarText(this.progressBarText, this.options.showDetailedStatus ? json.progress.text[0] + json.progress.text[1] : json.progress.text[1]);
1599 	    } else {
1600     	   this.setStatus(json.progress);
1601 	    }
1602     },
1603     
1604     /** Call-back for device read cancelled.
1605      * @see Garmin.DeviceControl
1606      * @see Garmin.DeviceDisplay.onStartReadFromDevice
1607      * @param {JSON} json the progress report in JSON format
1608      */
1609 	onCancelReadFromDevice: function(json) {
1610     	this.setStatus(this.options.cancelReadStatusText);
1611     	this.resetUI();
1612     },
1613 
1614     /** Call-back for device read.
1615      * @see Garmin.DeviceControl
1616      * @param {JSON} json the progress report in JSON format
1617      */
1618     onFinishReadFromDevice: function(json) {
1619     	
1620     	this.fileTypeRead = json.controller.gpsDataType;
1621     	this.readDataDoc = json.controller.gpsData;
1622 	    this.readDataString = json.controller.gpsDataString;
1623     	
1624 		this.setStatus(this.options.dataReadProcessing);
1625 	    this.resetUI();
1626 	    
1627 	    this.clearMapDisplay();
1628 
1629     	// select the correct factory for the parsing job, except for binary, which just passes through
1630     	switch(this.fileTypeRead) {
1631     		case Garmin.DeviceControl.FILE_TYPES.tcx:
1632     		case Garmin.DeviceControl.FILE_TYPES.tcxDir:
1633     		case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
1634     			this.factory = Garmin.TcxActivityFactory;
1635     			break;
1636     		case Garmin.DeviceControl.FILE_TYPES.gpx:
1637     		case Garmin.DeviceControl.FILE_TYPES.gpxDir:
1638     		case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
1639     			this.factory = Garmin.GpxActivityFactory;
1640     			break;
1641     		case Garmin.DeviceControl.FILE_TYPES.fitDir:
1642     			this.factory = Garmin.DirectoryFactory;
1643     			break;
1644     		case Garmin.DeviceControl.FILE_TYPES.fit:
1645     		case Garmin.DeviceControl.FILE_TYPES.binary:
1646                 // Post to server immediately (and finishes reading activities on the queue)
1647 		    	if(this.options.uploadSelectedActivities){
1648 	    		    // Compressed data
1649 	    		    if(this.options.uploadCompressedData) {
1650                 	   this.readDataString = json.controller.gpsDataStringCompressed;
1651                 	} 
1652                 	this._postDataUpdateDisplay(this.readDataString);
1653 	    		}
1654 	    		this._finishReadProcessing(json);
1655     		    break;
1656     		    
1657     		default:
1658 	    		var error = new Error( + this.fileTypeRead);
1659 				error.name = "InvalidTypeException";
1660 				this.handleException(error);
1661     	}
1662 
1663 		// parse the data into activities if possible
1664 		if (this.factory != null) {
1665 			// Convert the data obtained from the device into activities.
1666 			// If we're starting a new read session (as opposed to individual 
1667 			// activity reads from the activity directory), start a new activities array
1668 			if( this.activities == null) {
1669 				this.activities = new Array();
1670 			}
1671 			
1672 			// Populate this.activities
1673 			switch(this.fileTypeRead) {
1674 				case Garmin.DeviceControl.FILE_TYPES.gpxDir:
1675 				case Garmin.DeviceControl.FILE_TYPES.tcxDir:
1676 				case Garmin.DeviceControl.FILE_TYPES.fitDir:
1677 				    
1678 				    // TODO should merge tcx and fit directory types at some point so we can share code
1679 				    if(this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.tcxDir) {
1680                         this.activities = this.factory.parseDocument(this.readDataDoc);
1681                         this._createActivityDirectory(Garmin.DeviceControl.FILE_TYPES.tcxDir, this.activities);
1682 				    } else if(this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.fitDir) {
1683 	    			    var files = this.factory.parseDocument(this.readDataDoc);
1684                         var activityFiles = Garmin.DirectoryFactory.getActivityFiles(files);
1685     	    			// Only use activity files for the activity directory
1686     	    			this._createActivityDirectory(Garmin.DeviceControl.FILE_TYPES.fitDir, activityFiles);
1687 				    } else if(this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.gpxDir) {
1688 				    	this.activities = this.factory.parseDocumentByType(this.readDataDoc, Garmin.GpxActivityFactory.GPX_TYPE.tracks);
1689 				    	if(this.options.uploadSelectedActivities) {
1690 				    	   this._createActivityDirectory(Garmin.DeviceControl.FILE_TYPES.gpxDir, this.activities);
1691 				    	}				    	
1692 				    }
1693 	    			
1694 	    			if( this.options.detectNewActivities && this.options.uploadSelectedActivities) {
1695         	            // No activities on device  
1696                 		if( this.activityDirectory.size() == 0) {
1697         	                if(this.advancedUploadMode) {
1698                     		    this._updateLoadingContent(this.options.noActivitiesOnDevice);
1699                     		} else {
1700                                 this.getController()._broadcaster.dispatch("onFinishUploads", { display: this });
1701         	                }
1702                 		}
1703         	            else {
1704     	    			    // There are activities to compare
1705     	    			    this.activityMatcher = new Garmin.ActivityMatcher(this.garminController.getCurrentDeviceXml(), 
1706                                 this.activityDirectory.getIds(), this.options.syncDataUrl, this.options.syncDataOptions, 
1707                                 function(){this._finishReadProcessing(json)}.bind(this));
1708     	    			    this.activityMatcher.run();
1709         	            }
1710 	    			} else {
1711 	    			    // Finished reading activities in queue, if any, so list them.
1712 	    			    this._finishReadProcessing(json);
1713 	    			}
1714 					break;
1715 				case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
1716 				    if(this.options.uploadSelectedActivities){      
1717                         this._postDataUpdateDisplay(this.readDataString);
1718                     }
1719                     break;          
1720 				case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
1721 	    			
1722 	    			// Store this read activity
1723 	    			// TODO: May not need this line, merge logic with binary type
1724 	    			this.activities = this.activities.concat( this.factory.parseDocument(this.readDataDoc) );
1725 	    			
1726 		    		// Post to server (and finishes reading activities on the queue)
1727 		    		if(this.options.uploadSelectedActivities){
1728 		    		    // Compressed data
1729 		    		    if(this.options.uploadCompressedData) {
1730                     	   this.readDataString = json.controller.gpsDataStringCompressed;
1731                     	} 
1732                     	this._postDataUpdateDisplay(this.readDataString);
1733 		    		}
1734 		    		// Finished reading activities in queue, if any, so list them.
1735 		    		this._finishReadProcessing(json);
1736 					break;			
1737 	    		default:
1738 	    			this.activities = this.factory.parseDocument(this.readDataDoc);
1739 	    			// filter the activities
1740     				this._applyDataFilters();
1741             		// Finished reading activities in queue, if any, so list them.
1742     				this._finishReadProcessing(json);
1743 	    			break;
1744 			}
1745 	
1746 		}
1747     },
1748     
1749     _postDataUpdateDisplay: function(data) {
1750         // Post to server (and finishes reading activities on the queue)
1751         if( this.loadingContentElement != null) {
1752         	this.loadingContentElement.innerHTML = this.options.uploadingStatusText;
1753         	this.loadingContentElement.show();
1754         }
1755 		this._postActivityToServer(data);
1756     },
1757 
1758 	_applyDataFilters: function() {
1759 		var dataFilters = this.options.dataFilters;
1760 		if (dataFilters != null) {
1761 			for (var i = 0; i < dataFilters.length; i++) {
1762 				if (dataFilters[i].run != null) {
1763 					dataFilters[i].run(this.activities, garminFilterQueue);
1764 				}
1765 			}
1766 		}
1767 	},
1768 
1769     /** Process the read data.  Calls afterFinishReadFromDevice when finished.
1770      * @see Garmin.DeviceDisplay.afterFinishReadFromDevice
1771      * @param {JSON} json the progress report in JSON format
1772      */
1773 	_finishReadProcessing: function(json) {
1774 		if (garminFilterQueue != null && garminFilterQueue.length > 0) {
1775 			//console.debug("waiting for filters to finish...");
1776 			setTimeout(function(){this._finishReadProcessing(json);}.bind(this), 500);
1777 		} else {
1778 			
1779 			// list activities and set status to indicate how many were found
1780 			if( this.activityQueue == null || this.activityQueue.length == 0 ) {
1781 		    	
1782 	    		// List the activities
1783 		    	if(this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.fitDir){
1784 		    	    
1785 		    	    var summary = this._listDirectory(this.activityDirectory);
1786 		    	    // Display # of activities found
1787 	    		    this.setStatus( this.evaluateTemplate(this.options.dataFound, summary) );
1788 		    	}
1789 				if( this.activities != null && this.activities.length > 0) {
1790 					if( this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.tcxDir ||
1791 					    this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.gpxDir) {
1792 						var summary = this._listDirectory(this.activityDirectory);
1793 					} else {
1794 		    			var summary = this._listActivities(this.activities);
1795 					}
1796 					
1797 				    // Display # of activities found
1798 	    		    this.setStatus( this.evaluateTemplate(this.options.dataFound, summary) );
1799 				}
1800 		    	
1801 		    	// Disable appropriate buttons after read is finished
1802 		    	if(this.options.uploadSelectedActivities) {
1803     		    	switch(this.fileTypeRead) {
1804     		    		case Garmin.DeviceControl.FILE_TYPES.gpx:
1805     		    		case Garmin.DeviceControl.FILE_TYPES.gpxDir:
1806     		    		case Garmin.DeviceControl.FILE_TYPES.tcx:
1807     		    		case Garmin.DeviceControl.FILE_TYPES.crs:
1808     		    		case Garmin.DeviceControl.FILE_TYPES.tcxDir:
1809     		    		case Garmin.DeviceControl.FILE_TYPES.crsDir:
1810     		    		case Garmin.DeviceControl.FILE_TYPES.fitDir:
1811     		    			this.deviceSelectInput.disabled = true;
1812     		    			if( this.advancedUploadMode) {
1813     		    			    // Advanced upload
1814     		    			    if(this.loadingContentElement != null) {
1815                                     this.loadingContentElement.hide();
1816     		    			    }
1817     		    			} else { 
1818         		    			// Simple upload
1819         		    			this.progressBar.hide();
1820     	        	            this.uploadProgressBar.show();
1821     		    			    this.readFilteredActivities();		    			    
1822     		    			}
1823     		    			break;
1824     		    		case Garmin.DeviceControl.FILE_TYPES.gpxDetail:
1825     		    		case Garmin.DeviceControl.FILE_TYPES.tcxDetail:
1826     		    		case Garmin.DeviceControl.FILE_TYPES.crsDetail:
1827     		    		case Garmin.DeviceControl.FILE_TYPES.fit:
1828     		    		case Garmin.DeviceControl.FILE_TYPES.binary:
1829     		    			this.readSelectedButton.disabled = false;
1830     		    			if( this.options.useLinks) {
1831     		    			    this.readSelectedButton.show();
1832     		    			}
1833     		    			this.readSelectedButton.disabled = false;
1834     		    			if(this.checkAllBox != null) {
1835     		    			    this.checkAllBox.disabled = false;
1836     		    			}
1837     		    			break;
1838     		    	}
1839 		    	}
1840 			}
1841 			
1842 			// pass data to the user if they want it			
1843 			if (this.options.afterFinishReadFromDevice) {
1844 				var dataString = this.factory != null ? this.factory.produceString(this.activities) : json.controller.gpsDataString;
1845 				var dataDoc = this.factory != null ? Garmin.XmlConverter.toDocument(dataString): json.controller.gpsData;
1846 	    		this.options.afterFinishReadFromDevice(dataString, dataDoc, json.controller.gpsDataType, this.activities, this);
1847 			}
1848 		}
1849 	},
1850 
1851     /** As uploads continue processing, this method will be called.  This is called once
1852      * per upload item.  This does not track byte-progress of a single upload.
1853      * @event
1854      * @param {JSON} json the progress report in JSON format
1855      * @see Garmin.DeviceDisplay.onFinishUploads
1856      */
1857     onProgressUpload: function(json) {
1858         if(this.options.showProgressBar) {
1859 	    	this.updateProgressBar(this.uploadProgressBarDisplay, json.progress.percentage);
1860 	    	this.updateProgressBarText(this.uploadProgressBarText, json.progress.text);
1861 	    } else {
1862     	   this.setStatus(json.progress);
1863 	    }
1864     },
1865     
1866     /** Returns the current status of the upload progress based on the activity queue.
1867      * If there is no upload in progress, all values will be 0. 
1868      * @returns {JSON} json object with report values and current DeviceDisplay instance
1869      * @returns json.progress
1870      * @returns {String} json.progress.current the current upload index from the activity queue
1871      * @returns {String} json.progress.total whole number of uploads finished
1872      * @returns {String} json.progress.percentage percentage value of uploads finished 
1873      * @returns {String} json.progress.text upload progress text to display to user
1874      * @returns {Garmin.DeviceDisplay} json.display the current DeviceDisplay instance for UI purposes
1875      */
1876     getUploadProgressJson: function() {
1877         
1878         var currentVal;
1879         var totalVal;
1880         var percentageVal;
1881         
1882         if( this.numQueuedActivities == null || this.activityQueue == null || this.activityQueue.length == 0) {
1883             currentVal = 0;
1884             totalVal = 0;
1885             percentageVal = 0;
1886         } else {
1887             currentVal = this.numQueuedActivities - this.activityQueue.length;
1888             totalVal = this.numQueuedActivities;
1889             percentageVal = currentVal / totalVal * 100;
1890         }
1891         
1892         return {
1893                     progress: {
1894                         current: currentVal, 
1895                         total: totalVal,
1896                         percentage: percentageVal,
1897                         text: this.evaluateTemplate(this.options.uploadProgressStatusText, {currentUpload: currentVal, totalUploads: totalVal})
1898                     },
1899                     display: this
1900                 };
1901     },
1902     
1903 	/* Reads the user-selected activities from the device by using the activity queue. 
1904      */
1905     _readNextSelected: function() {    	
1906     	// Look at the next selected activity on the queue.  (The queue only holds selected activities)    	
1907 		this._displayProcessingForCurrentActivity(this.activityQueue.last());
1908     	this.setStatus(this.options.uploadingActivities);
1909     	
1910     	var currentActivityId = $(this.currentActivity).value;
1911     	
1912     	if( this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.tcxDetail ) {
1913     	   this.garminController.readDetailFromDevice(this.fileTypeRead, currentActivityId);
1914     	} else if( this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.fit ) {
1915     	   var deviceNumber = this.getCurrentDevice().getNumber();
1916     	   this.garminController.getBinaryFile(deviceNumber, this.activityDirectory.getEntry(currentActivityId).path); 
1917     	}
1918     },
1919     
1920     /**
1921      * Displays the processing icon for a given activity
1922      * 
1923      * @param activity - the activity that will have the processing icon
1924      */
1925     _displayProcessingForCurrentActivity: function(activity) {
1926     	this.currentActivity = activity;
1927     	
1928     	// Display 'processing' image next to corresponding activity in table 
1929         var statusCellIdElement = $(this.currentActivity.replace(/Checkbox/, "Status"));
1930         statusCellIdElement.innerHTML = this.options.statusCellProcessingImg;
1931     },
1932     
1933     /** Stop uploading activities in the queue, and go on to finished screen.
1934      * Useful for certain error cases.
1935      * @see Garmin.DeviceDisplay.onFinishUploads
1936      */
1937     stopQueuedUploads: function() {
1938         this.clearActivityQueue();
1939     	// Broadcast all uploads finished
1940         this.getController()._broadcaster.dispatch("onFinishUploads", { display: this });  
1941     },
1942     
1943     /* Posts the last read activity data from the activityQueue.  See {@link this.options.sendDataUrl}, 
1944      * {@link this.options.sendDataOptions} for designating the server and options for the AJAX request. 
1945      * 
1946      * A custom handler is also possible by defining {@link this.options.postActivityHandler}.  Defining 
1947      * this method will override the default Send Data implementation provided by this API.
1948      * 
1949      * @param String dataString - the data string to post to server  
1950      * @see Garmin.DeviceDisplayDefaultOptions.postActivityHandler
1951      */
1952     _postActivityToServer: function(dataString) {
1953     	if( this.options.sendDataUrl == null && this.options.postActivityHandler == null ) {
1954             throw new Error("Need to define either sendDataUrl or the postActivityHandler in display" +
1955                     " options, depending on desired behavior.");
1956     	}
1957     	else {
1958     	    // nested function
1959     	    var finishPostProcessing = function() {
1960                 // Exceptions are handled in postToServer. Even if errors occur, doesn't necessarily mean
1961                 // that the rest of the uploads should be stopped.  The uploadQueue needs to be cleared 
1962                 // if that is the desired behavior.
1963             	this.activityQueue.pop();
1964             	
1965                 // Broadcast upload progress
1966                 this.getController()._broadcaster.dispatch("onProgressUpload", this.getUploadProgressJson());
1967                     
1968             	// TODO: This doesn't quite belong here, but it's the only way to ensure synchronization.
1969             	if(this.activityQueue.length > 0) {
1970         	    	// Read what's left in the queue
1971         			this._readNextSelected();
1972             	} else { 
1973             	    // Broadcast all uploads finished
1974             	    this.getController()._broadcaster.dispatch("onFinishUploads", { display: this });
1975             	}
1976     	    }.bind(this);
1977     	    
1978     	    if( this.options.sendDataUrl != null ) {
1979     	           // post the activity and then read the next one
1980     	           this.postToServer(finishPostProcessing);
1981     	    }
1982     	    else if( this.options.postActivityHandler != null) {
1983                 this.options.postActivityHandler(dataString, this);
1984                 finishPostProcessing();
1985     	    }
1986     	}
1987     },
1988     
1989     /** Callback when all uploads are finished. The display is passed in as the single param.
1990      * @event
1991      * @param {Garmin.DeviceDisplay} the current DeviceDisplay instance
1992      * @see Garmin.DeviceDisplayDefaultOptions.afterFinishUploads
1993      */
1994     onFinishUploads: function(display) {
1995         
1996         // Activities were uploaded
1997         if( this.numQueuedActivities > 0) {
1998             this.loadingContentElement.hide();
1999         }
2000         // Nothing to upload, so show it 
2001         else if( !this.advancedUploadMode ) {
2002             this.loadingContentElement.className = 'shortStatus';
2003             this.activityTable.hide();
2004             this._updateLoadingContent('No new activities to upload.');            
2005         }
2006         // Show the directory for results 
2007         this.activityDirectoryElement.show();
2008         this.uploadProgressBar.hide();
2009         this.findDevicesElement.hide();
2010         this.readSelectedButton.hide();
2011         this.deviceBrowserElement.hide();
2012     
2013         if( this.options.afterFinishUploads ) {
2014             this.options.afterFinishUploads.call(this, this);
2015         } 
2016     },
2017     
2018     _clearActivityTable: function() {
2019     	//clear previous data, if any, including the header
2020     	while(this.activityTableHeader.rows.length > 0) {
2021     	    this.activityTableHeader.deleteRow(0);
2022     	}
2023 		while(this.activityTable.rows.length > 0) {
2024 			this.activityTable.deleteRow(0);
2025 		}
2026     },
2027     
2028     /** Creates the activity directory of all activities (activity IDs) on the device
2029      * of the user-selected type.  Most recent entries are first.
2030      * @param listType String type of directory described by the list
2031      * @param list Array list of directory entries, of any type. Currently expects activities (tcx) or files (fit)
2032      * @private 
2033      */
2034     _createActivityDirectory: function(listType, list) {
2035         
2036         if( this.advancedUploadMode ) {
2037             this.activityDirectoryElement.show();
2038         }
2039     	this.activityQueue = new Array(); // Initialized here so that we can detect activity selection read status    	
2040     	
2041     	this.activityDirectory = new Garmin.ActivityDirectory();
2042     	
2043     	for( var jj = 0; jj < list.length; jj++) {
2044             var id;
2045             var name;
2046             var duration;
2047             var entry;            
2048             
2049     	    if( listType == Garmin.DeviceControl.FILE_TYPES.tcxDir) {
2050     	        // list of Garmin.Activity
2051     	        var activity = list[jj];
2052     	        id = activity.getAttribute(Garmin.Activity.ATTRIBUTE_KEYS.activityName);
2053     	        name = activity.getSummaryValue(Garmin.Activity.SUMMARY_KEYS.startTime).getValue().getTimeString();
2054     	        duration = activity.getStartTime().getDurationTo(activity.getEndTime()); // Correct time zone
2055     	        
2056                 entry = this.activityDirectory.addEntry(id, name, duration, null);
2057                 
2058     	    } else if( listType == Garmin.DeviceControl.FILE_TYPES.fitDir) {
2059     	        // list of Garmin.File
2060     	        var file = list[jj];
2061     	        id = file.getIdValue(Garmin.FileId.KEYS.id);
2062     	        name = file.getAttribute(Garmin.File.ATTRIBUTE_KEYS.creationTime).getTimeString();
2063                 this.activityDirectory.addEntry(id, name, null, null);
2064                 this.activityDirectory.getEntry(id).path = file.getAttribute(Garmin.File.ATTRIBUTE_KEYS.path);                
2065     	    } else if (listType == Garmin.DeviceControl.FILE_TYPES.gpxDir) {
2066     	    	// list of Garmin.Activity
2067     	    	var activity = list[jj];
2068     	    	var summaryValue = activity.getSummaryValue(Garmin.Activity.SUMMARY_KEYS.startTime);
2069     	    	var attribute = activity.getAttribute(Garmin.Activity.ATTRIBUTE_KEYS.activityName);
2070     	    	
2071     	    	// Make sure these are not null or else we will skip
2072     	    	if (summaryValue != null && attribute != null)
2073     	    	{
2074 	    	    	id = summaryValue.getValue().getXsdString();
2075 	    	    	name = attribute;
2076 	    	    	
2077 	    	    	this.activityDirectory.addEntry(id, name, null, null);
2078     	    	} 	    	    	    
2079     	    }
2080     	}
2081     },
2082     
2083     /* Creates the activity queue of selected activities to read in detail from device.
2084      * Called after the user has finished selecting activities and also after the API 
2085      * does its synchronization thing).  The queue is an Array that is constructed and 
2086      * then reversed to simulate a queue.
2087      */
2088     _populateActivityQueue: function() {
2089     	var checkBoxName = "activityItemCheckbox";
2090         // TODO Create a class for the activity queue
2091     	for( var jj = 0; jj < this.activityDirectory.size(); jj++) {
2092     		var checkBoxElementId = checkBoxName + jj; 
2093     				
2094     		if($(checkBoxElementId).checked == true){
2095     			this.activityQueue.push(checkBoxElementId);
2096     		}
2097     		
2098     		var activityId = this.activityDirectory.getIds()[jj];
2099     		this.activityDirectory.getEntry(activityId).displayElementId = checkBoxElementId;
2100     	}
2101     	// Reverse the array to turn it into a queue
2102     	this.activityQueue.reverse(); 
2103     	
2104     	// Save the original size for status reporting
2105     	this.numQueuedActivities = this.activityQueue.length;
2106     },
2107     
2108     /** Empties the activity queue if it has any entries.
2109      */
2110     clearActivityQueue: function() {
2111         for( var i=0; i < this.activityQueue.length; i++) {
2112             this.activityQueue.pop();
2113         }
2114     },
2115     
2116 	/* The activityTable object is the HTML table element on the demo page.  This function
2117 	 * adds the necessary row to the table with the activity data.
2118 	 * @param int index - the internal index assigned to the activity value in order
2119 	 * to update the table status
2120 	 * @param {Garmin.ActivityDirectory.Entry} entry - entry to add to the table
2121 	 * @see afterTableInsert
2122 	 */
2123 	_addToActivityTable: function(index, entry) {
2124 		
2125 		var tableIndex = 0;
2126 		
2127 		var activityId = entry.id;
2128 		
2129 		var row = this.activityTable.insertRow(this.activityTable.rows.length); // append a new row to the table
2130 		// Color odd rows
2131 		if( (index+2) % 2 != 0) {
2132             row.setAttribute('bgcolor', '#f3f3f3');
2133 		}
2134 		
2135 		var selectCell = row.insertCell(tableIndex++);
2136 		selectCell.width = '40'; // Set widths to match header 
2137 		selectCell.align = 'right';
2138 
2139 		var checkbox = document.createElement("input");
2140 		Element.extend(checkbox);
2141 		checkbox.id = "activityItemCheckbox" + index;
2142 		checkbox.type = "checkbox";
2143 		checkbox.value = activityId;
2144 		
2145 		// When checkbox is clicked, pass last 2 args to callback method, which is bounded to the display object
2146 		// TODO pass the entire directory object and handle appropriately
2147 		checkbox.observe('click', this.onActivitySelect.bind(this, checkbox.id, this.activityDirectory.getIds())); 
2148 		selectCell.appendChild(checkbox);
2149 
2150 		var nameCell = row.insertCell(tableIndex++);
2151 		nameCell.width = '220';
2152 
2153         if( entry.duration != null) {
2154     		var durationCell = row.insertCell(tableIndex++);
2155 		    durationCell.width = '210';
2156 		}
2157 		
2158 		var statusCell = row.insertCell(tableIndex++);
2159 		statusCell.id = "activityItemStatus" + index;
2160 		
2161 		// Name and duration cells 
2162 		if( this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.tcxDir ||
2163 		      this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.fitDir || 
2164 		      this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.gpxDir) {
2165 			nameCell.innerHTML = entry.name;
2166 			
2167 			if( durationCell != null) {
2168 			    durationCell.innerHTML = entry.duration;
2169 			}
2170 		}
2171 		else if( this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.crsDir ) {
2172 			nameCell.innerHTML = activityId;
2173 		}
2174 		
2175 		if( this.options.afterTableInsert ) {
2176 		    this.options.afterTableInsert.call(this, index, entry, statusCell, checkbox, row, this.activityMatcher);
2177 		}
2178 	},
2179 	
2180 	/**
2181 	 * Adds the single row to the activity table header.  The columns in the table
2182 	 * are determined by the data available in the directory, using an all or nothing 
2183 	 * check.
2184 	 * @param directory {Garmin.ActivityDirectory} the activity directory to build the table header off of
2185 	 */
2186 	_addToActivityTableHeader: function(directory) {
2187 	    
2188 	    var tableIndex = 0;
2189 		
2190 	    var row = this.activityTableHeader.insertRow(0); // append a new row to the table
2191 		
2192 		var selectCell = row.insertCell(tableIndex++);
2193 		selectCell.id = 'selectAllHeader';
2194 		selectCell.width = '40';
2195 		selectCell.align = 'left';
2196 
2197 		var nameCell = row.insertCell(tableIndex++);
2198 		nameCell.id = 'nameHeader';
2199 		nameCell.width = '220';
2200 		nameCell.align = 'left';
2201 		nameCell.innerHTML = this.options.getActivityDirectoryHeaderIdLabel.call(this); 
2202         
2203 		if( directory.getFirstEntry().duration != null) {
2204             var durationCell = row.insertCell(tableIndex++);
2205     		durationCell.id = 'durationHeader';
2206     		durationCell.width = '210';
2207     		durationCell.align = 'left';
2208     		durationCell.innerHTML = this.options.activityDirectoryHeaderDuration;
2209         }
2210 
2211 		var statusCell = row.insertCell(tableIndex++);
2212 		statusCell.innerHTML = this.options.activityDirectoryHeaderStatus;
2213 
2214 		// Only display 'check all' box if there's no upload limit 
2215 		if( this.options.uploadMaximum < 1) {
2216     		this.checkAllBox = document.createElement("input");
2217     		Element.extend(this.checkAllBox);
2218     		this.checkAllBox.id = "checkAllBox";
2219     		this.checkAllBox.type = "checkbox";
2220     		
2221     		if( this.options.uploadMaximum == 0) {
2222     		    this.checkAllBox.hide();
2223     		}
2224     		selectCell.appendChild(this.checkAllBox);
2225     		
2226     		this.checkAllBox.onclick = function() { this._checkAllDirectory(); }.bind(this);
2227 		}
2228 	},
2229 	
2230 	/** Callback for enforcing upload selection limit.  Called each time the user modifies selection.
2231 	 * @param String elementId - the ID of the input element selected to trigger this callback
2232 	 * @param Array activityDirectory - array of all activity IDs listed in the activity directory  
2233 	 * @see uploadSelectionLimit
2234 	 */
2235 	onActivitySelect: function(elementId, activityDirectory) {
2236 	    var selectedCount = 0;
2237 	    for( var jj = 0; jj < activityDirectory.length; jj++) {
2238     		if( $("activityItemCheckbox" + jj).checked == true){
2239     			selectedCount++;
2240     		}
2241     	}
2242     	if( this.options.uploadMaximum > 0 ) {
2243             if( selectedCount > this.options.uploadMaximum ) {
2244                 // Cancel the selection
2245                 $(elementId).checked = false;
2246                 alert( this.evaluateTemplate(this.options.uploadMaximumReached, {activities:this.options.uploadMaximum}) );
2247             }
2248     	}
2249 	},
2250 	
2251 	/* Selects all checkboxes in the activity directory, which selects all activities to be read from the device.
2252 	 * uploadMaximum must be -1 or 0 (no limit) for this method to be called.
2253 	 */
2254 	_checkAllDirectory: function() {
2255 		for( var boxIndex=0; boxIndex < this.activityDirectory.size(); boxIndex++ ) {
2256 			$("activityItemCheckbox" + boxIndex).checked = this.checkAllBox.checked;
2257 		}
2258 	},
2259 	
2260 	/* Checks if any activities in directory listing are selected.  Returns true if so, false otherwise.
2261 	 */
2262 	_directoryHasSelected: function() {
2263 		for( var boxIndex=0; boxIndex < this.activityDirectory.size(); boxIndex++ ) {
2264 			if ( $("activityItemCheckbox" + boxIndex).checked == true) {
2265 				return true;
2266 			}
2267 		}
2268 		
2269 		return false;
2270 	},
2271 	
2272 	/* Lists the directory and returns summary data (# of entries).
2273 	 * @param entries {Garmin.ActivityDirectory} 
2274 	 */
2275 	_listDirectory: function(activityDirectory) {
2276 		// clear existing entries
2277 		this._clearHtmlSelect(this.readTracksSelect);
2278 		
2279 		this._addToActivityTableHeader(activityDirectory);
2280 		
2281 		// loop through each entry
2282 	    var entries = activityDirectory.getEntries();
2283 		for (var i = 0; i < activityDirectory.size(); i++) {
2284 			var entry = entries[i];
2285 			
2286 			// Directory entry
2287 			if(this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.tcxDir 
2288     			|| this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.crsDir
2289     			|| this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.fitDir
2290     			|| this.fileTypeRead == Garmin.DeviceControl.FILE_TYPES.gpxDir ){				
2291 				this._addToActivityTable(i, entry);
2292 			}
2293 		}
2294 		
2295 		return {tracks: activityDirectory.size()};
2296 	},
2297 	
2298 	_listActivities: function(activities) {
2299 		var numOfRoutes = 0;
2300 		var numOfTracks = 0;
2301 		var numOfWaypoints = 0;
2302 		
2303 		// clear existing entries
2304 		this._clearHtmlSelect(this.readRoutesSelect);
2305 		this._clearHtmlSelect(this.readTracksSelect);
2306     	this._clearHtmlSelect(this.readWaypointsSelect);
2307 		
2308 		// loop through each activity
2309 		for (var i = 0; i < activities.length; i++) {
2310 			var activity = activities[i];
2311 			var series = activity.getSeries();
2312 			// loop through each series in the activity
2313 			for (var j = 0; j < series.length; j++) {
2314 				var curSeries = series[j];								
2315 				if (curSeries.getSeriesType() == Garmin.Series.TYPES.history) {
2316 					// activity contains a series of type history, list the track
2317 					this._listTrack(activity, curSeries, i, j);
2318 					numOfTracks++;
2319 				} else if (curSeries.getSeriesType() == Garmin.Series.TYPES.route) {
2320 					// activity contains a series of type route, list the route
2321 					this._listRoute(activity, curSeries, i, j);
2322 					numOfRoutes++;
2323 				} else if (curSeries.getSeriesType() == Garmin.Series.TYPES.waypoint) {
2324 					// activity contains a series of type waypoint, list the waypoint
2325 					this._listWaypoint(activity, curSeries, i, j);				
2326 					numOfWaypoints++;
2327 				}
2328 			}
2329 		}
2330 		if (this.options.showReadRoutesSelect) {
2331 			if(numOfRoutes > 0) {
2332 				Element.show(this.readRoutesElement);
2333 				this.readRoutesSelect.disabled = false;
2334 				this.displayTrack( this._seriesFromSelect(this.readRoutesSelect) );
2335 			} else {
2336 				Element.hide(this.readRoutesElement);
2337 				this.readRoutesSelect.disabled = true;
2338 			}
2339 		}
2340 		if (this.options.showReadTracksSelect) {		
2341 			if(numOfTracks > 0) {
2342 				Element.show(this.readTracksElement);
2343 				this.readTracksSelect.disabled = false;
2344 				this.displayTrack( this._seriesFromSelect(this.readTracksSelect) );
2345 			} else {
2346 				Element.hide(this.readTracksElement);
2347 				this.readTracksSelect.disabled = true;
2348 			}
2349 		}
2350 		if (this.options.showReadWaypointsSelect) {		
2351 			if(numOfWaypoints > 0) {
2352 				Element.show(this.readWaypointsElement);
2353 				this.readWaypointsSelect.disabled = false;
2354 				this.displayWaypoint( this._seriesFromSelect(this.readWaypointsSelect) );
2355 			} else {
2356 				Element.hide(this.readWaypointsElement);
2357 				this.readWaypointsSelect.disabled = true;			
2358 			}
2359 		}	
2360 		return {routes: numOfRoutes, tracks: numOfTracks, waypoints: numOfWaypoints};
2361 	},
2362 
2363     /* Load route names into select UI component.
2364      * 
2365      */    
2366 	_listRoute: function(activity, series, activityIndex, seriesIndex) {
2367 		// make sure the select component exists
2368 		if (this.readRoutesSelect) {
2369 			var routeName = activity.getAttribute(Garmin.Activity.ATTRIBUTE_KEYS.activityName);
2370 			this.readRoutesSelect.options[this.readRoutesSelect.length] = new Option(routeName, activityIndex + "," + seriesIndex);
2371 		}		
2372 	},
2373 
2374     /* Load track name into select UI component.
2375      * 
2376      */    
2377 	_listTrack: function(activity, series, activityIndex, seriesIndex) {
2378 		// make sure the select component exists
2379 		if (this.readTracksSelect) {
2380 			var startDate = activity.getSummaryValue(Garmin.Activity.SUMMARY_KEYS.startTime).getValue();
2381 			var endDate = activity.getSummaryValue(Garmin.Activity.SUMMARY_KEYS.endTime).getValue();
2382 			var values = {date:startDate.getDateString(), duration:startDate.getDurationTo(endDate)}
2383 			var trackName = this.evaluateTemplate(this.options.trackListing, values)
2384 			this.readTracksSelect.options[this.readTracksSelect.length] = new Option(trackName, activityIndex + "," + seriesIndex);
2385 		}
2386 	},
2387 
2388     /* Load waypoint name into select UI component.
2389      * 
2390      */
2391 	_listWaypoint: function(activity, series, activityIndex, seriesIndex) {
2392 		// make sure the select component exists
2393 		if (this.readWaypointsSelect) {
2394 			var wptName = activity.getAttribute(Garmin.Activity.ATTRIBUTE_KEYS.activityName);
2395 			this.readWaypointsSelect.options[this.readWaypointsSelect.length] = new Option(wptName, activityIndex + "," + seriesIndex);
2396 		}
2397 	},
2398 
2399     
2400     /* Retreive the two index string value from the selected index.
2401      * Activities are stored in Select objects as strings with 2 
2402      * numbers: "(index of array), (index of series)", for example:  "1,1".
2403      * @param Select select - the Select DOM instance
2404      * @type Garmin.Series
2405      * @return a Series instance
2406      */
2407     _seriesFromSelect: function(select) {
2408     	var indexesStr = select.options[select.selectedIndex].value;
2409     	var indexes = indexesStr.split(",", 2);
2410     	var activity = this.activities[parseInt(indexes[0])];
2411     	var series = activity.getSeries()[parseInt(indexes[1])];
2412     	return series;
2413     },
2414 
2415     
2416     /** Draws a simple line on the map using the Garmin.MapController.
2417      * 
2418      * @param Garmin.Series series - that contains a track. 
2419      */
2420     displayTrack: function(series) {
2421 		if(this.options.showReadGoogleMap) {
2422 			this.readMapController.map.clearOverlays();
2423 			this.readMapController.drawTrack(series);
2424 	    }
2425     },
2426 
2427     /** Draws a point (usualy as a thumb tack) on the map using the Garmin.MapController.
2428      * @param Garmin.Series series - that contains the lat/lon position of the point. 
2429      */
2430     displayWaypoint: function(series) {
2431 		if(this.options.showReadGoogleMap) {
2432 			this.readMapController.map.clearOverlays();
2433 	        this.readMapController.drawWaypoint(series);
2434 		}
2435     },
2436 
2437     /** Clears overlays from map.
2438      * 
2439      */
2440 	clearMapDisplay: function() {
2441 		if(this.options.showReadGoogleMap) {
2442 			this.readMapController.map.clearOverlays();
2443 		}
2444 	},
2445 
2446 
2447     ////////////////////////////// WRITE METHODS ////////////////////////////// 
2448     
2449     /** Writes any supported data to the device.
2450      * 
2451      * Requires that the option writeDataType field be set correctly to any of the following values 
2452      * located in Garmin.DeviceControl.FILE_TYPES:
2453      * 
2454      * 		gpx, crs, binary, goals
2455      * 
2456      * @throws InvalidTypeException
2457      * @see Garmin.DeviceDisplayDefaultOptions.writeDataType
2458      * @see Garmin.DeviceControl.FILE_TYPES
2459      */
2460     writeToDevice: function() {
2461 		var dataType = null;
2462 		var supported = null;
2463 		
2464 		// TODO remove this later 
2465 		// Backwards compatability for deprecated method
2466 		if( this.options.writeDataType != null ) {
2467 		    this.options.writeDataTypes = new Array();
2468 		    this.options.writeDataTypes[0] = this.options.writeDataType;
2469 		}
2470 		
2471 		for (var i = 0; i < this.options.writeDataTypes.length; i++) {
2472 			dataType = this.options.writeDataTypes[i];
2473 			var deviceWriteSupport = this.getController().checkDeviceWriteSupport(dataType);
2474 			//var deviceWriteSupport = this.getController().checkDeviceWriteSupport(this.options.writeDataType);
2475 			
2476 			if (supported == null && deviceWriteSupport == true) {
2477 				supported = dataType;
2478 				
2479 				switch (dataType) {
2480                     case Garmin.DeviceControl.FILE_TYPES.goals:
2481 					case Garmin.DeviceControl.FILE_TYPES.gpx:
2482 					case Garmin.DeviceControl.FILE_TYPES.crs:					
2483 					case Garmin.DeviceControl.FILE_TYPES.wkt:
2484 					case Garmin.DeviceControl.FILE_TYPES.tcxProfile:
2485 					case Garmin.DeviceControl.FILE_TYPES.nlf:                    
2486                         var writeData = this.options.getWriteData();
2487                         var writeDataFileName = this.options.getWriteDataFileName();                        
2488                         this.getController().writeDataToDevice(dataType, writeData, writeDataFileName);
2489                         break;
2490 					// TODO Deprecated.  Delete this fella.
2491 					case Garmin.DeviceControl.FILE_TYPES.gpi:
2492 						var xmlDescription = Garmin.GpiUtil.buildMultipleDeviceDownloadsXML(this.options.getGpiWriteDescription());
2493 						this.getController().downloadToDevice(xmlDescription);
2494 						break;					
2495 					case Garmin.DeviceControl.FILE_TYPES.fitSettings:
2496 					case Garmin.DeviceControl.FILE_TYPES.fitSport:
2497 					case Garmin.DeviceControl.FILE_TYPES.fitCourse:
2498 					case Garmin.DeviceControl.FILE_TYPES.binary:
2499 						var xmlDescription = Garmin.GpiUtil.buildMultipleDeviceDownloadsXML(this.options.getBinaryWriteDescription());
2500 						this.getController().downloadToDevice(xmlDescription);
2501 						break;					
2502 					default:
2503 						var error = new Error(Garmin.DeviceControl.MESSAGES.invalidFileType + dataType);
2504 						error.name = "InvalidTypeException";
2505 						this.handleException(error); 
2506 				}
2507 			}
2508 		}
2509 		
2510 		// No supported types found, throw error
2511 		if (deviceWriteSupport == false) {
2512 			var error = new Error(this.evaluateTemplate(this.options.unsupportedWriteDataType, {dataType: dataType}));
2513 			error.name = "UnsupportedDataTypeException";
2514 			this.handleException(error);
2515 		}
2516     },
2517 
2518 	/** Call-back triggered before writing to a device.
2519      * @see Garmin.DeviceControl
2520      * @event
2521 	 */
2522     onStartWriteToDevice: function(json) { 
2523      	this.setStatus(this.options.writingToDevice);
2524     },
2525 
2526 	/** Call-back triggered when write has been cancelled.
2527      * @see Garmin.DeviceControl
2528      * @event
2529 	 */
2530     onCancelWriteToDevice: function(json) { 
2531     	this.setStatus(this.options.writingCancelled);
2532     },
2533 
2534     /** Call-back when the device already has a file with this name on it.  Do we want to override?  1 is yes, 2 is no
2535      * @see Garmin.DeviceControl
2536      * @event
2537      */ 
2538     onWaitingWriteToDevice: function(json) { 
2539         if(confirm(json.message.getText())) {
2540             this.setStatus(this.options.overwritingFile);
2541             json.controller.respondToMessageBox(true);
2542         } else {
2543             this.setStatus(this.options.notOverwritingFile);
2544             json.controller.respondToMessageBox(false);
2545         }
2546     },
2547 
2548     /**
2549      * @event
2550      */
2551     onProgressWriteToDevice: function(json) {
2552 	  	if(this.options.showProgressBar) {
2553 	  		this.updateProgressBar(this.progressBarDisplay, json.progress.getPercentage());
2554 	  	}
2555   		this.setStatus( json.progress.percentage==100 ? this.options.dataDownloadProcessing : json.progress );
2556     },
2557 
2558     /**
2559      * @event
2560      */
2561     onFinishWriteToDevice: function(json) {
2562     	this.setStatus(this.options.writtenToDevice);
2563 	    this.resetUI();
2564 		if (this.options.afterFinishWriteToDevice) {
2565     		this.options.afterFinishWriteToDevice.call(this, json.success);
2566 		}
2567     },
2568     
2569     ////////////////////////////// UTILITY METHODS ////////////////////////////// 
2570     
2571     /** Sets up the device control which handles most of the work that isn't user
2572      * interface related.  The controller is lazy loaded the first time it is called.
2573      * Early calls must specify the unlock parameter, but read and write methods should
2574      * not because they should follow a call to findDevice.
2575      * 
2576      * Also initializes the RemoteTransfer object used to transfer data to remote servers.
2577      *   
2578      * @param Boolean optional request to unlock the plugin if not already done.
2579      */
2580 	getController: function(unlock) {
2581 		if (!this.garminController) {
2582 			try {
2583 				this.garminController = new Garmin.DeviceControl();
2584 				this.garminController.register(this);
2585 				this.garminController.setPluginRequiredVersion(this.options.pluginRequiredVersion);
2586 				this.garminController.setPluginLatestVersion(this.options.pluginLatestVersion);
2587 				this.garminController.validatePlugin();
2588 	        } catch (e) {
2589 	            this.handleException(e);
2590 	            return null;
2591 	        }
2592 		}
2593 		if (!this.error && unlock && !this.isUnlocked()) {
2594 			if(this.garminController.unlock(this.options.pathKeyPairsArray)) {
2595 	        	this.setStatus(this.options.pluginUnlocked);
2596 			} else {
2597 	        	this.setStatus(this.options.pluginNotUnlocked);
2598 			}
2599 			this.garminRemoteTransfer = new Garmin.RemoteTransfer();
2600 		}
2601 		return this.garminController;
2602 	},
2603 
2604 	/** Plugin unlock status
2605 	 * @returns Boolean
2606 	 */
2607 	isUnlocked: function() {
2608 		return (this.garminController && this.garminController.isUnlocked());
2609 	},
2610 	
2611 	/** Sets options for this display object.  Any options that are set will override
2612 	 * the default values.
2613 	 *
2614 	 * @see Garmin.DeviceDisplayDefaultOptions for possible options and default values.
2615 	 * @throws InvalidOptionException
2616 	 * @param Object options - Object with options.
2617 	 */
2618 	setOptions: function(options) {
2619 		for(key in options || {}) {
2620 			if ( ! (key in Garmin.DeviceDisplayDefaultOptions) ) {
2621 				var err = new Error(key+" is not a valid option name, see Garmin.DeviceDisplayDefaultOptions");
2622 				err.name = "InvalidOptionException";
2623 				throw err;
2624 			}
2625 		}
2626 		this.options = Object.extend(Garmin.DeviceDisplayDefaultOptions, options || {});
2627 	},
2628 
2629 	/*Sets the size of the select to zero which essentially clears it from 
2630 	 * any values.
2631 	 * 
2632 	 * @param {HTMLElement} select DOM element
2633 	 */
2634     _clearHtmlSelect: function(select) {
2635 		if(select) {
2636 			select.length = 0;
2637 		}
2638     },
2639 
2640     /** Set status text if showStatusElement is visible.
2641      * @param String text to display.
2642      */
2643 	setStatus: function(statusText) {
2644 	    if(statusText == null) {
2645 	        statusText = '';
2646 	    }
2647 		if(this.options.showStatusElement) {
2648 			
2649 			var statusDisplayString;
2650 			
2651 			if( this.options.showDetailedStatus) {
2652 				statusDisplayString = this._buildDescriptiveStatusString(statusText);
2653 			} else {
2654 				if( statusText.getText ) {
2655 					
2656 					var text = statusText.getText();
2657 					if( text instanceof Array) {
2658 						if( text.length == 0) { 
2659 							statusDisplayString = '';
2660 						} else { 
2661 							statusDisplayString = statusText.getText()[0];
2662 						}
2663 					}
2664 				} else {
2665 					statusDisplayString = statusText;
2666 				}
2667 			} 
2668 		   	this.statusText.innerHTML = statusDisplayString;
2669 		}
2670 	},
2671 	
2672 	/** Builds a descriptive string from the status response (typically JSON, but also can be a plain ol' string).
2673 	 */
2674 	_buildDescriptiveStatusString: function(statusText) {
2675 		if(statusText == null) {
2676 	        statusText = '';
2677 	    }
2678 		var resultString = statusText;
2679 		
2680 		if( statusText.getTitle ) {
2681 			resultString = statusText.getTitle() + "<br />";
2682 		}
2683 		if( statusText.getText ) {
2684 			resultString += statusText.getText();
2685 		}
2686 		
2687 		return resultString;
2688 	},
2689 	
2690 	/** Helper method. Evaluates Prototype's Template object, and returns the evaluated string.
2691 	 * 
2692 	 * Templates should contain fields as in the following format: 'My cow has #{numSpots}' spots!'
2693 	 * Fields are in the following format: {numSpots: 22}
2694 	 * 
2695 	 * @param String template - the template to evaluate     
2696 	 * @param Hash fields - the fields referenced in template
2697 	 * @return the evaluated template string   
2698 	 */
2699 	// TODO Move out to js-util
2700 	evaluateTemplate: function(template, fields) {
2701 	    return new Template(template).evaluate(fields);
2702 	},
2703 
2704     /** Makes progress bar visible.
2705      * @deprecated
2706      */
2707 	showProgressBar: function() {
2708 		if(this.options.showStatusElement && this.options.showProgressBar) {
2709 		    Element.show(this.progressBar);
2710 		}
2711 	},
2712 
2713     /** Hides progress bar.
2714      */
2715 	hideProgressBar: function() {
2716 		if(this.options.showStatusElement && this.options.showProgressBar) {
2717 		    Element.hide(this.progressBar);
2718 		}
2719 	},
2720 
2721     /** Update percentage representation of progress bar.
2722      * The progress bar starts out as full and then resets to empty.  This is to
2723      * take care of extremely short transfers.
2724      * @param {Element} progressBarDisplay - the progress bar display DOM element to update. 
2725      * @param int percentage completion: 0-100 
2726      */
2727 	updateProgressBar: function(progressBarDisplay, value) {
2728 		if(this.options.showStatusElement && this.options.showProgressBar && value) {
2729 			var percent = (0 < value && value <= 100) ? value : 100;
2730 		    progressBarDisplay.style.width = percent + "%";
2731 		}
2732 	},
2733 	
2734 	/** Update the progress text of the progress bar.
2735 	 * @param {Element} progressBarText - the progress bar text DOM element to update.
2736 	 * @param String the progress text to display near the progress bar
2737 	 */
2738 	updateProgressBarText: function(progressBarText, text) {
2739 	    if(this.options.showStatusElement && this.options.showProgressBar && text) {
2740 	        progressBarText.innerHTML = text;
2741 	    }
2742 	},
2743 	
2744     /** Call-back for asynchronous method exceptions.
2745      * @see Garmin.DeviceControl
2746      * @event
2747      */
2748 	onException: function(json) {
2749 		this.handleException(json.msg);
2750 	},
2751 	
2752     /** Central exception dispatch method will delegate to options.customExceptionHandler
2753      * if defined or call defaultExceptionHandler otherwise.
2754      * @param {Error} error to process.
2755      */	
2756 	handleException: function(error) {
2757 		this.error = true;
2758    	    //console.debug("Display.handleException error="+error)
2759 		if (this.options.customExceptionHandler) {
2760 			this.options.customExceptionHandler.call(this, error);
2761 		} else {
2762 			this.defaultExceptionHandler(error);
2763 		}
2764 	},
2765     /** Default exception handler method handles plug-in support/downloads/upgrades. 
2766      * If options.showStatusElement is true then put error messages in the status div otherwise
2767      * put it in a alert popup.
2768      * @param {Error} error - error to process.
2769      */	
2770 	defaultExceptionHandler: function(error) {
2771 		var errorStatus;
2772 		var hideFromBrowser = false;
2773 		if(error.name == "BrowserNotSupportedException") {
2774 			errorStatus = error.message;
2775 			if (this.options.hideIfBrowserNotSupported) {
2776 				hideFromBrowser = true;
2777 			}
2778 		} else if (error.name == "PluginNotInstalledException" || error.name == "OutOfDatePluginException") {
2779 			errorStatus = error.message;
2780 			errorStatus += " <a href=\""+Garmin.DeviceDisplay.LINKS.pluginDownload+"\" target=\"_blank\">"  + this.options.downloadAndInstall + "</a>";
2781 		} else if (Garmin.PluginUtils.isDeviceErrorXml(error)) {
2782 			errorStatus = Garmin.PluginUtils.getDeviceErrorMessage(error);	
2783 		} else if (error.name == "UnsupportedDeviceException" || error.name == "UnsupportedDataTypeException" || error.name == "RemoteTransferException") {
2784 		    errorStatus = error.message;
2785 		} else {
2786 			errorStatus = error.name + ": " + error.message;	
2787 		}						
2788 
2789 		this.setStatus(errorStatus);
2790 		this.resetUI();
2791 		//if no status UI div is defined, make sure the user sees the error
2792 		if (!this.options.showStatusElement && !hideFromBrowser) {
2793 			if (error.name == "PluginNotInstalledException" || error.name == "OutOfDatePluginException") {
2794 				if (window.confirm(error.message+"\n" + this.options.installNow)) {
2795 					window.open(Garmin.DeviceDisplay.LINKS.pluginDownload, "_blank");
2796 				}
2797 			} else {
2798 				alert(errorStatus);
2799 			}
2800 		}
2801 	}
2802     
2803 };
2804 
2805 /** Constant defining links referenced in the DeviceDisplay
2806  * 
2807  * @struct {public} Garmin.DeviceDisplay.LINKS
2808  */
2809 Garmin.DeviceDisplay.LINKS = {
2810 	pluginDownload:	"http://www.garmin.com/products/communicator/",
2811 	/** Function that returns a direct download link based on the user's OS.
2812 	 */
2813 	 // TODO Use this when we have a way of getting the latest version all the time.
2814 	pluginDownloadDetectOs: function() { 
2815             if( BrowserDetect.OS == "Windows") return "http://www.garmin.com/products/communicator/";
2816             else if( BrowserDetect.OS == "Mac") return "http://www.garmin.com/products/communicator/";
2817             else return "http://www.garmin.com/products/communicator/"
2818 	      }
2819 };
2820 
2821 /** A queue of filters to be applied to activities after data is
2822  * obtained from the device.  Also used by display to determine
2823  * if the filtering process is finished; 
2824  */
2825 var garminFilterQueue = new Array();
2826 
2827 /** The default display options for the generated plug-in elements including
2828  * booleans for which sub-items to show.  Override specific option values by 
2829  * calling setOptions(optionsHash) on your instance of Garmin.DeviceDisplay
2830  * to customize your display options.
2831  *
2832  * @class Garmin.DeviceDisplayDefaultOptions  
2833  * @name Garmin.DeviceDisplayDefaultOptions 
2834  */
2835 Garmin.DeviceDisplayDefaultOptions =
2836     /** @lends Garmin.DeviceDisplayDefaultOptions */ 
2837     {
2838 	// ================== Plugin unlock ======================
2839 
2840 	/**Unlock plugin when user lands on containing page which may result in security or
2841 	 * plugin-not-installed messages.  Set to false to supress plugin acivity
2842 	 * until user initiates an action.
2843 	 * 
2844 	 * @default true
2845 	 * @type Boolean
2846 	 */
2847 	unlockOnPageLoad:			true,
2848 
2849 	/**The array of strings that contain the unlock codes for the plugin.
2850 	* @example [URL1,KEY1,URL2,KEY2] 
2851 	* Add as many url/key pairs as you'd like.
2852 	* @type String[]
2853 	*/
2854 	pathKeyPairsArray:			["file:///C:/dev/", "bd04dc1f5e97a6ff1ea76c564d133b7e"],
2855 
2856 
2857 	// ================== Global Options ======================
2858 	/**The class name used by various parts of the display to make
2859 	 * CSS styling easier.
2860 	 * 
2861 	 * @see Garmin.DeviceDisplayDefaultOptions.statusElementId
2862 	 * @see Garmin.DeviceDisplayDefaultOptions.readDataElementId
2863 	 * @see Garmin.DeviceDisplayDefaultOptions.findDevicesElementId
2864 	 * @default "pluginElement"
2865 	 * @type String
2866 	 */
2867 	elementClassName:			"pluginElement",
2868 	
2869 	/**Display link instead of buttons.  Currently this only applies to the 'Find Devices' button.
2870 	 * @default false
2871 	 * @type Boolean
2872 	 */
2873 	useLinks:					false,
2874 	
2875 	/**Action to take if browser is not supported:
2876 	 * if true on't display the application,
2877 	 * else if status bar is visible, display message, otherwise popup an alert dialog
2878 	 * @default false
2879 	 * @type Boolean
2880 	 */
2881 	hideIfBrowserNotSupported:		false,
2882 
2883 	/**The function called when an error occurs.  This is here to allow
2884 	 * custom error handling logic.  <br/>
2885 	 * <br/>
2886 	 * The function should accept an arguement of type Error (Javascript 
2887 	 * Error Object) and a second arguement for the DeviceDisplay instance.<br/>
2888 	 * <br/>
2889 	 * <br/>Error.name - the type of the error in a String format.
2890 	 * <br/>Error.message - the detailed message of the error.<br/>
2891 	 *<br/>
2892 	 * Some Errors:<br/>
2893 	 * 	PluginNotInstalledException - the plugin is not installed<br/>
2894 	 *  OutOfDatePluginException - the plugin is out of date<br/>
2895 	 *  BrowserNotSupportedException - the browser is not support by the site<br/>
2896 	 * @example
2897 	 * function(error, display){ display.defaultExceptionHandler(error); }
2898 	 * @type function 
2899 	 * @function
2900 	 */				
2901 	customExceptionHandler:		null, 
2902 
2903 	/**Class name to add for all buttons/links that perform an action
2904 	 * @default "actionButton"
2905 	 * @type String 
2906 	 */
2907 	actionButtonClassName:		"actionButton",
2908 	
2909 	/**DEPRECATED - Use autoHideUnusedElements instead!  
2910 	 * Auto-hides read elements when they are not in use.  This is currently used for the Upload Activities use case.
2911 	 * @default false
2912 	 * @type String 
2913 	 */
2914 	autoHideUnusedReadElements: false,
2915 	
2916 	/**Auto-hides elements when they are not in use.  This is used to simulate a UI with different screens, until we design a better way to do this. 
2917 	 * @default false
2918 	 * @type String
2919 	 */
2920 	autoHideUnusedElements:		false,
2921 	
2922 	/**The choice to display the about ('Powered by...') element
2923 	 * @default true
2924 	 * @type Boolean
2925 	 */
2926 	showAboutElement:          true,
2927 	
2928 	/** Optional - Restricts transfer of data to specifically listed devices ONLY.  This is an array list of 
2929 	 * device part numbers, which can be found in the GarminDevice.XML.  
2930 	 * @example ["006-B1018-00"] // Forerunner 310 only
2931 	 * @type Array
2932 	 */
2933 	restrictByDevice:			[],
2934 	
2935 	/** Sets the required version of the Communicator Plugin that is compatible with the given application.
2936 	 * This value is set using an array of the major and minor build numbers.	 * 
2937 	 * @example version 2.2.0.1 is given by [2,2,0,1].
2938 	 * @default [2,2,0,1] (for version 2.2.0.1)
2939 	 * @type Array
2940 	 */
2941 	pluginRequiredVersion:			[2,2,0,1],
2942 
2943 	/** Sets the latest plugin version number.  This represents the latest version available for download at Garmin.
2944 	 * We will attempt to keep the default value of this up to date with each API release, but this is not guaranteed,
2945 	 * so set this to be safe or if you don't want to upgrade to the latest API.
2946 	 * 
2947 	 * @param reqVersionArray Array  The latest version to set to.  In the format [versionMajor, versionMinor, buildMajor, buildMinor]
2948 	 * 			i.e. [2,2,0,1]
2949 	 * @default [2,5,1,0] (for version 2.5.1.0)
2950 	 * @type Array
2951 	 */	
2952 	pluginLatestVersion:			[2,5,2,0],
2953 
2954 	// ================== Status Element Options ======================
2955 	/**The choice to display the feedback regarding the communications 
2956 	 * with the device.
2957 	 *
2958 	 * @default true 
2959 	 * @type Boolean
2960 	 */
2961 	showStatusElement:			true,
2962 	
2963 	/** The choice to display more detailed status when transferring data to and from device.
2964 	 * @default false
2965 	 * @type Boolean
2966 	 */
2967 	showDetailedStatus:		false,
2968 	
2969 	/**The id of the HTML element where the statusBox is to be rendered.
2970 	 * @type String
2971 	 * @default "statusBox"
2972 	 */
2973 	statusElementId:			"statusBox",
2974 	
2975 	/**The id of the HTML element where the status text messages are to be displayed.
2976 	 * @default "statusText"
2977 	 * @type String
2978 	 */
2979 	statusTextId:				"statusText",
2980 	
2981 	/**The progress bar is a graphical percentage bar used to display 
2982 	 * the amount of reading/writing is complete.
2983 	 * @default true
2984 	 * @type Boolean 
2985 	 */
2986 	showProgressBar:			true,
2987 	
2988 	/** The id of the HTML element where the progress status text is to be displayed.  
2989 	 * This element will be a child of the statusText element.
2990 	 * @default "progressText"
2991 	 * @type String 
2992 	 */
2993 	progressTextId:             "progressText",
2994 	
2995 	/** The class name for the progress status text.  
2996 	 * This element will be a child of the statusText element.
2997 	 * @type String  
2998 	 */
2999 	progressTextClass:          "progressTextClass",
3000 	
3001 	/**The class name for the progress bar container element.
3002 	 * @default 'progressBarClass'
3003 	 * @type String 
3004 	 */
3005 	progressBarClass:           "progressBarClass",
3006 	
3007 	/** The background of the progress bar.  This stays static during transfer.
3008 	 * @default 'progressBarBackClass'
3009 	 * @type String
3010 	 */
3011 	progressBarBackClass:       "progressBarBackClass",
3012 	
3013 	/** The class name for the dynamic progress bar that is overlaid
3014 	 * on top of the progressBar element.  This controls
3015 	 * the part that 'moves'.
3016 	 * @default 'progressBarDisplayClass'
3017 	 * @type String 
3018 	 */
3019 	progressBarDisplayClass:    "progressBarDisplayClass",
3020 	
3021 	/**The container for the progress bar.
3022 	 * @default 'progressBar'
3023 	 * @type String 
3024 	 */
3025 	progressBarId:				"progressBar",
3026 	
3027 	/** The background of the progress bar.  This stays static during transfer.
3028 	 * @default 'progressBarBack'
3029 	 * @type String 
3030 	 */
3031 	progressBarBackId:          "progressBarBack",
3032 	
3033 	/**The id of the displayed progress element.  This is dynamic during transfer.
3034 	 * @default 'progressBarDisplay'
3035 	 * @type String 
3036 	 */
3037 	progressBarDisplayId:		"progressBarDisplay",
3038 	
3039 	/** The class name for the progress bar text.  
3040 	 * @default progressBarTextClass 
3041 	 * @type String 
3042 	 */
3043 	progressBarTextClass:      "progressBarTextClass",
3044 	
3045 	/** The id of the progress bar text.  This is generally the percentage
3046 	 * value text displayed, but could also be in the context of what is being transferred
3047 	 * (i.e. 1 of 5 activities).
3048 	 * @default progressBarText
3049 	 * @type String 
3050 	 */
3051 	progressBarTextId:          "progressBarText",
3052 	
3053 	// Upload Progress bar
3054 	
3055 	/** The id of the HTML element where the upload progress status text is to be displayed.  
3056 	 * This element will be a child of the statusText element.
3057 	 * @type String
3058 	 * @default "uploadProgressText"
3059 	 */
3060 	uploadProgressTextId:             "uploadProgressText",
3061 	
3062 	/** The class name for the uploadProgress status text.  
3063 	 * This element will be a child of the statusText element.
3064 	 * @type String
3065 	 * @default "uploadProgressTextClass"
3066 	 */
3067 	uploadProgressTextClass:          "uploadProgressTextClass",
3068 	
3069 	/**The class name for the uploadProgress bar container element.
3070 	 * @default 'uploadProgressBarClass'
3071 	 * @type String 
3072 	 */
3073 	uploadProgressBarClass:           "uploadProgressBarClass",
3074 	
3075 	/** The background of the uploadProgress bar.  This stays static during transfer.
3076 	 * @default 'uploadProgressBarBackClass'
3077 	 * @type String
3078 	 */
3079 	uploadProgressBarBackClass:       "uploadProgressBarBackClass",
3080 	
3081 	/** The class name for the dynamic uploadProgress bar that is overlaid
3082 	 * on top of the uploadProgressBar element.  This controls
3083 	 * the part that 'moves'.
3084 	 * @default 'uploadProgressBarDisplayClass'
3085 	 * @type String 
3086 	 */
3087 	uploadProgressBarDisplayClass:    "uploadProgressBarDisplayClass",
3088 	
3089 	/**The container for the uploadProgress bar.
3090 	 * @default 'uploadProgressBar'
3091 	 * @type String 
3092 	 */
3093 	uploadProgressBarId:				"uploadProgressBar",
3094 	
3095 	/** The background of the uploadProgress bar.  This stays static during transfer.
3096 	 * @default 'uploadProgressBarBack'
3097 	 * @type String 
3098 	 */
3099 	uploadProgressBarBackId:          "uploadProgressBarBack",
3100 	
3101 	/**The id of the displayed uploadProgress element.  This is dynamic during transfer.
3102 	 * @default 'uploadProgressBarDisplay'
3103 	 * @type String 
3104 	 */
3105 	uploadProgressBarDisplayId:		"uploadProgressBarDisplay",
3106 	
3107 	/** The class name for the uploadProgress bar text.  
3108 	 * @default uploadProgressBarTextClass 
3109 	 * @type String 
3110 	 */
3111 	uploadProgressBarTextClass:      "uploadProgressBarTextClass",
3112 	
3113 	/** The id of the uploadProgress bar text.  This is generally the percentage
3114 	 * value text displayed, but could also be in the context of what is being transferred
3115 	 * (i.e. 1 of 5 activities).
3116 	 * @default uploadProgressBarText
3117 	 * @type String
3118 	 */
3119 	uploadProgressBarTextId:          "uploadProgressBarText",
3120 	
3121 	/** The text to display above the upload progress bar.  This is a static string.
3122 	 * @default "Uploading activities..."
3123 	 * @type String
3124 	 */
3125 	uploadingStatusText:                 "Uploading activities...",
3126 	
3127 	/** Templated string used to display the upload progress status.
3128 	 * The template parameters currentUpload and totalUploads must be included
3129 	 * to work properly, as the default value does.
3130      * 
3131      * @default #{currentUpload} of #{totalUploads} completed.
3132      * @type String
3133      */
3134     uploadProgressStatusText: '#{currentUpload} of #{totalUploads} completed.',
3135 	
3136 	//===================  Find Devices Element Options ===============
3137 	
3138 	/**Choice to display the find devices area that will search for connected devices.
3139 	 * @type Boolean
3140 	 * @default true
3141 	 */
3142 	showFindDevicesElement:		true,
3143 	
3144 	/**Choice to display the find devices area that will search for connected devices when the page loads.
3145 	 * @type Boolean
3146 	 * @default true
3147 	 */
3148 	showFindDevicesElementOnLoad:	true,
3149 	
3150     /**Looks for devices as soon as the page is loaded and the plugin unlocked.
3151      * This might be particularly annoying in many situations since the plugin 
3152      * requires the user to authorize access to device information via a 
3153      * dialog box.
3154 	 * @type Boolean
3155 	 * @default false
3156      */
3157 	autoFindDevices:			false,
3158 	
3159 	/**Controls the view of the buttons related to find devices (find & cancel) if 
3160 	 * based on if the plugin finds one or more devices.  
3161 	 * When set to <b>false</b> and  
3162 	 * used with {@link showDeviceButtonsOnLoad} =false 
3163 	 * and {@link autoFindDevices} =true these buttons will only
3164 	 * show up if a device is not found (minimizing confusion for the user).
3165 	 * <p>
3166 	 * More granular control is provided on each of the device buttons 
3167 	 * {@link showFindDevicesButton}  and {@link showCancelFindDevicesButton} .
3168 	 * </p>
3169 	 * @see Garmin.DeviceDisplayDefaultOptions.showFindDevicesElement
3170 	 * @type Boolean
3171 	 * @default true
3172 	 */
3173 	showDeviceButtonsOnFound:	true,
3174 	
3175 	/**If true the buttons will show when the page is rendered. 
3176 	 * If false, the buttons will not be displayed until the plugin detects that a device is not found. 
3177 	 * If you choose not to see the buttons at all (regardless if device is found or not) then 
3178 	 * {@link showFindDevicesElement}  should be set to false.
3179 	 * 
3180 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceButtonsOnFound
3181 	 * @see Garmin.DeviceDisplayDefaultOptions.showFindDevicesElement
3182 	 * @type Boolean
3183 	 * @default true 
3184 	 */
3185 	showDeviceButtonsOnLoad:	true,
3186 	
3187 	/**Allows granular control to hide the find devices button independent
3188 	 * of the {@link showCancelFindDevicesButton}  cancel button contol.
3189 	 * @type Boolean 
3190 	 * @default true
3191 	 */
3192 	showFindDevicesButton:		true,
3193 	
3194 	/**The id referencing the HTML container around the find devices buttons.
3195 	 * This is useful for CSS customizations. 
3196 	 * <p>
3197 	 * @default deviceBox
3198 	 * </p>
3199 	 * @type String 
3200 	 * @default "deviceBox"
3201 	 */
3202 	findDevicesElementId:		"deviceBox",
3203 	
3204 	/**The id referencing the find devices button.  This is useful for
3205 	 * CSS customizations.
3206 	 * 
3207 	 * @type String 
3208 	 * @default findDevicesButton
3209 	 */
3210 	findDevicesButtonId:		"findDevicesButton",
3211 	
3212 	/**The text for the find device button.
3213 	 * @type String
3214 	 * @default "Find Devices" 
3215 	 */		
3216 	findDevicesButtonText:			"Find Devices",	
3217 	
3218 	/**Controls the view of the cancel find devices button. When
3219 	 * set to <b>false</b> the button will never show.  When
3220 	 * set to <b>true</b> the button's behavior will depend on other
3221 	 * settings such as {@link showFindDevicesButton} , 
3222 	 * {@link showDeviceButtonsOnFound} , {@link showDeviceButtonsOnLoad} ,
3223 	 * and {@link showFindDevicesElement} .
3224 	 * @default false
3225 	 * @type Boolean 
3226 	 */	
3227 	showCancelFindDevicesButton:		false,
3228 	
3229 	/**The id referencing the cancel find devices button.  This is useful for
3230 	 * CSS customizations.
3231 	 * @default cancelFindDevicesButton
3232 	 * @type String 
3233 	 */	
3234 	cancelFindDevicesButtonId:	"cancelFindDevicesButton",
3235 	
3236 	/**The text for the cancel find device button.
3237 	 * @type String 
3238 	 * @default "Cancel Find Devices"
3239 	 */		
3240 	cancelFindDevicesButtonText:		"Cancel Find Devices",
3241 
3242 	/**Controls the view of the device select box.
3243 	 * When set to <b>true</b> the select device box will show even when only
3244 	 * one device is found.
3245 	 * When set to <b>false</b> the select device box will hide when only
3246 	 * one device is found.
3247 	 * When {@link showFindDevicesElement}  is set to false, the device select
3248 	 * box will never show.
3249 	 * @default false
3250 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceSelectNoDevice
3251 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceSelectOnLoad	  	 
3252 	 * @see Garmin.DeviceDisplayDefaultOptions.showFindDevicesElement	 
3253 	 * @type Boolean 
3254 	 */
3255 	showDeviceSelectOnSingle:	false,
3256 	
3257 	/**Controls the view of the device select box.
3258 	 * When set to <b>true</b> the select device box will show even when
3259 	 * no device is found.
3260 	 * When set to <b>false</b> the select device box will hide when
3261 	 * no device is found.
3262 	 * When {@link showFindDevicesElement}  is set to false, the device select
3263 	 * box will never show.
3264 	 * 
3265 	 * @default true
3266 	 * 
3267 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceSelectOnSingle
3268 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceSelectOnLoad	  
3269 	 * @see Garmin.DeviceDisplayDefaultOptions.showFindDevicesElement	 
3270 	 * @type Boolean 
3271 	 */	
3272 	showDeviceSelectNoDevice:	false,
3273 	
3274 	/**Controls the view of the device select box.
3275 	 * When set to <b>true</b> the select device box will show when
3276 	 * the display loads.
3277 	 * When set to <b>false</b> the select device box will never be visible.
3278 	 * When {@link showFindDevicesElement}  is set to false, the device select
3279 	 * box will never show.
3280 	 * 
3281 	 * @default true
3282 	 * 
3283 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceSelectOnSingle
3284 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceSelectNoDevice	  
3285 	 * @see Garmin.DeviceDisplayDefaultOptions.showFindDevicesElement	 
3286 	 * @type Boolean 
3287 	 */		
3288 	showDeviceSelectOnLoad:		true,
3289 	
3290 	/**When more than one device is detected automaticly pick the first device.
3291 	 * This allows single button interfaces to avoid having to ask the user to 
3292 	 * choose the device and keeps the deviceSelect hidden.
3293 	 * 
3294 	 * @default false
3295 	 * 
3296 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceSelectOnSingle
3297 	 * @see Garmin.DeviceDisplayDefaultOptions.showDeviceSelectNoDevice	  
3298 	 * @see Garmin.DeviceDisplayDefaultOptions.showFindDevicesElement	 
3299 	 * @type Boolean
3300 	 */		
3301 	autoSelectFirstDevice:		false,
3302 	
3303 	//===================  Upload UI Options ===============
3304 	
3305 	/**The id referencing the device select box.  This is useful for
3306 	 * CSS customizations.
3307 	 * 
3308 	 * @default deviceSelectBox
3309 	 * @type String 
3310 	 */		
3311 	deviceSelectElementId:		"deviceSelectBox",
3312 	
3313 	/**The label for the device select box.  Shows up next to the
3314 	 * device select box.
3315 	 * @type String 
3316 	 * @default "Devices: "
3317 	 */		
3318 	deviceSelectLabel:			"Devices: ",	
3319 
3320 	/**The id referencing the device select box label.  This is useful for
3321 	 * CSS customizations.
3322 	 * 
3323 	 * @default deviceSelectLabel
3324 	 * @type String 
3325 	 */			
3326 	deviceSelectLabelId:		"deviceSelectLabel",	
3327 	
3328 	/** The class name referencing the device select element.  This is useful for CSS customizations.
3329 	 * 
3330 	 * @default deviceSelectClass
3331 	 * @type String
3332 	 */
3333 	deviceSelectClass:          "deviceSelectClass",
3334 	
3335 	/**The id referencing the device select element.  This is useful for
3336 	 * CSS customizations.
3337 	 * 
3338 	 * @default deviceSelect
3339 	 * @type String 
3340 	 */			
3341 	deviceSelectId:				"deviceSelect",
3342 	
3343 	/**The id referencing the element that displays what device was selected.  This is useful for CSS customizations.
3344 	 *
3345 	 * @default deviceSelected
3346 	 * @type String 
3347 	 */
3348 	deviceSelectedElementId:	"deviceSelected",
3349 	
3350 	/**The label for the device selected element.  This label preceeds the device selected element.  This is useful for CSS customizations.
3351 	 *
3352 	 * @default "Previewing "
3353 	 * @type String 
3354 	 */
3355 	deviceSelectedLabel:		"Previewing ",
3356 	
3357 	/**The id referencing the label for the element that displays what device was selected.  This is useful for CSS customizations.
3358 	 *
3359 	 * @default deviceSelectedLabel
3360 	 * @type String 
3361 	 */
3362 	deviceSelectedLabelId:		"deviceSelectedLabel",
3363 	
3364 	/**The status text that is displayed when no devices are found.  The Find Devices button is 
3365 	 * displayed to allow the user to try again.  To change the button text, set findDevicesButtonText.
3366 	 * 
3367 	 * @type String 
3368 	 * @default "No devices found."
3369 	 */			
3370 	noDeviceDetectedStatusText:	"No devices found.",
3371 	/**The status text that prepends itself to the device name when a single device is found.
3372 	 * 
3373 	 * @type String
3374 	 * @default "Found " 
3375 	 */
3376 	singleDeviceDetectedStatusText: "Found ",
3377 
3378     /**The function called when device search completes successfully or unsuccessfully.
3379      * The function should have two arguments:
3380      *  devices {Array<Garmin.Device>}  - an array of device descriptors or an empty array in none were found.
3381      *  display {Garmin.DeviceDisplay}  - the current instance of the DeveiceDisplay
3382 	 * @example function(devices){...}
3383 	 * @type function 
3384 	 * @function
3385 	 */				
3386 	afterFinishFindDevices:	null,
3387 
3388     /** The function called after all item uploads complete successfully or unsuccessfully.
3389      * The function will have one argument:
3390      * display {Garmin.DeviceDisplay - the current instance of the DeviceDisplay
3391      * @example
3392      * function(display) {...}
3393      * @type function
3394      * @function
3395      */
3396     afterFinishUploads: null,
3397 
3398 	// ================== Read Element Options ======================
3399 	/**Start reading data from the device when one or more device(s)
3400 	 * is found.
3401 	 * 
3402 	 * @default false
3403 	 * 
3404 	 * @see Garmin.DeviceDisplayDefaultOptions.autoFindDevices
3405 	 * @see Garmin.DeviceDisplayDefaultOptions.autoWriteData	  
3406 	 * @type Boolean 
3407 	 */					
3408 	autoReadData:				false,
3409 	
3410 	/**Display the user interface associated with reading from
3411 	 * a connected device.
3412 	 * 
3413 	 * @default true
3414 	 * @type Boolean 
3415 	 */
3416 	showReadDataElement:		true,
3417 	
3418 	/**Controls the view of the read data button. When
3419 	 * set to <b>false</b> the button will never show.  When
3420 	 * set to <b>true</b> the button's behavior will depend on other
3421 	 * settings such as {@link showReadDataElement} .
3422 	 * 
3423 	 * @default true
3424 	 * @type Boolean 
3425 	 */	
3426 	showReadDataButton:        true,
3427 	
3428 	/**Controls the view of the read data element. When
3429 	 * set to <b>true</b> the element will only show after a
3430 	 * device has been found.  When set to <b>false</b> the
3431 	 * element will show on page load.
3432 	 * Behavior will depend on other settings such as
3433 	 * and {@link showReadDataElement} .
3434 	 * 
3435 	 * @default false
3436 	 * @type Boolean 
3437 	 */
3438 	showReadDataElementOnDeviceFound:		false,
3439 	
3440 	/**The id referencing the box containing read elements.  This is 
3441 	 * useful for CSS customizations.
3442 	 * 
3443 	 * @default readBox
3444 	 * @type String 
3445 	 */		
3446 	readDataElementId:			"readBox",
3447 	
3448 	/**The id referencing the read data button.  This is useful for
3449 	 * CSS customizations.
3450 	 * 
3451 	 * @default readDataButton
3452 	 * @type String 
3453 	 */			
3454 	readDataButtonId:			"readDataButton",
3455 	
3456 	/**The text on the read button.
3457 	 * 
3458 	 * @type String
3459 	 */		
3460 	readDataButtonText:			"Get Data",
3461 	
3462 	/**Controls the view of the cancel read data button. When
3463 	 * set to <b>false</b> the button will never show.  When
3464 	 * set to <b>true</b> the button's behavior will depend on other
3465 	 * settings such as {@link showReadDataButton} , 
3466 	 * and {@link showReadDataElement} .
3467 	 * 
3468 	 * @default true
3469 	 * @type Boolean 
3470 	 */	
3471 	showCancelReadDataButton:		true,
3472 	
3473 	/**The id referencing the cancel read data button.  This is 
3474 	 * useful for CSS customizations.
3475 	 * 
3476 	 * @default cancelReadDataButton
3477 	 * @type String 
3478 	 */		
3479 	cancelReadDataButtonId:		"cancelReadDataButton",
3480 	
3481 	/**The text on the cancel read button.
3482 	 * 
3483 	 * @type String 
3484 	 */		
3485 	cancelReadDataButtonText:	"Cancel Get Data",
3486 	
3487 	/**The status text that is displayed when user cancels the
3488 	 * read progress.
3489 	 * 
3490 	 * @type String
3491 	 */		
3492 	cancelReadStatusText:		"Read cancelled",
3493 	
3494 	/**Controls the view of the device select box.
3495 	 * When set to <b>true</b> the select device box will show when
3496 	 * the display loads.
3497 	 * When set to <b>false</b> the select device box will hide when
3498 	 * the display loads.
3499 	 * When {@link showReadDataElement}  is set to false, the results select
3500 	 * box will never show.
3501 	 * 
3502 	 * @default false
3503 	 *  
3504 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement	 
3505 	 * @type Boolean 
3506 	 */		
3507 	showReadResultsSelectOnLoad:	false,
3508 
3509 	/**The class to set for select lists that are displaying results
3510 	 * from a read operation.  This is useful for CSS customizations.
3511 	 * 
3512 	 * @default readResultsSelect
3513 	 * @type String 
3514 	 */		
3515 	readResultsSelectClass:			"readResultsSelect",
3516 	
3517 	/**The class to set for results elements.  This is useful for CSS customizations.
3518 	 * 
3519 	 * @default readResultsElement
3520 	 * @type String 
3521 	 */		
3522 	readResultsElementClass:		"readResultsElement",
3523 
3524 	/**Display the route select dropdown.  When
3525 	 * <@link showReadDataElement> is set to false, the select
3526 	 * track dropdown will not show.
3527 	 * 
3528 	 * @default true
3529 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement
3530 	 * @type Boolean 
3531 	 */
3532 	showReadRoutesSelect:		true,
3533 
3534 	/**The id referencing the read routes element.  This is 
3535 	 * useful for CSS customizations.
3536 	 * 
3537 	 * @default readRoutesElement
3538 	 * @type String
3539 	 */		
3540 	readRoutesElementId	:		"readRoutesElement",
3541 		
3542 	/**The id referencing the route select dropdown.  This is 
3543 	 * useful for CSS customizations.
3544 	 * 
3545 	 * @default readRoutesSelect
3546 	 * @type String 
3547 	 */		
3548 	readRoutesSelectId:			"readRoutesSelect",
3549 	
3550 	/**The label for the read routes select box.  Shows up next to the
3551 	 * read routes select box.
3552 	 * 
3553 	 * @type String 
3554 	 */		
3555 	readRoutesSelectLabel:		"Routes: ",	
3556 		
3557 	/**The id referencing the read routes select box label.  This is useful for
3558 	 * CSS customizations.
3559 	 * 
3560 	 * @default readRoutesSelectLabel
3561 	 * @type String 
3562 	 */			
3563 	readRoutesSelectLabelId:	"readRoutesSelectLabel",
3564 	
3565 	/** The id referencing the button for uploading selected activities button.  This is useful for CSS customizations.
3566 	 * 
3567 	 * @default readSelectedButton
3568 	 * @type String 
3569 	 */
3570 	readSelectedButtonId: "readSelectedButton",
3571 	
3572 	/** The text label for the upload selected data button.  This is useful for CSS customizations.
3573 	 * 
3574 	 * @default Upload Selected
3575 	 * @type String 
3576 	 */
3577 	readSelectedButtonText: "Upload Selected",		
3578 		
3579 	/**Display the track select dropdown.  When
3580 	 * <@link showReadDataElement> is set to false, the select
3581 	 * track dropdown will not show.
3582 	 * 
3583 	 * @default true
3584 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement
3585 	 * @type Boolean 
3586 	 */
3587 	showReadTracksSelect:		true,		
3588 		
3589 	/**The id referencing the read tracks element.  This is 
3590 	 * useful for CSS customizations.
3591 	 * 
3592 	 * @default readTracksElement
3593 	 * @type String 
3594 	 */		
3595 	readTracksElementId:		"readTracksElement",
3596 	
3597 	/**The id referencing the track select dropdown.  This is 
3598 	 * useful for CSS customizations.
3599 	 * 
3600 	 * @default readTracksSelect
3601 	 * @type String 
3602 	 */		
3603 	readTracksSelectId:			"readTracksSelect",
3604 
3605 	/**The label for the read tracks select box.  Shows up next to the
3606 	 * read tracks select box.
3607 	 * 
3608 	 * @type String 
3609 	 */		
3610 	readTracksSelectLabel:		"Tracks: ",
3611 
3612 	/**The id referencing the read tracks select box label.  This is useful for
3613 	 * CSS customizations.
3614 	 * 
3615 	 * @default deviceSelectLabel
3616 	 * @type String 
3617 	 */			
3618 	readTracksSelectLabelId:	"readTracksSelectLabel",
3619 
3620 	/**The id referencing the read tracks element.  This is 
3621 	 * useful for CSS customizations.
3622 	 * 
3623 	 * @default readTracksElement
3624 	 * @type String 
3625 	 */		
3626 	readWaypointsElementId:		"readWaypointsElement",
3627 
3628 	/**Display the waypoint select dropdown.  When
3629 	 * <@link showReadDataElement> is set to false, the select
3630 	 * waypoint dropdown will not show.
3631 	 * 
3632 	 * @default true
3633 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement
3634 	 * @type Boolean 
3635 	 */	
3636 	showReadWaypointsSelect:	true,
3637 	
3638 	/**The id referencing the waypoint select dropdown.  This is 
3639 	 * useful for CSS customizations.
3640 	 * 
3641 	 * @default readWaypointsSelect
3642 	 * @type String 
3643 	 */		
3644 	readWaypointsSelectId:		"readWaypointsSelect",
3645 	
3646 	/**The label for the read waypoints select box.  Shows up next to the
3647 	 * read tracks select box.
3648 	 * 
3649 	 * @type String 
3650 	 */		
3651 	readWaypointsSelectLabel:	"Waypoints: ",
3652 
3653 	/**The id referencing the read waypoints select box label.  This is useful for
3654 	 * CSS customizations.
3655 	 * 
3656 	 * @default readWaypointsSelectLabel
3657 	 * @type String
3658 	 */			
3659 	readWaypointsSelectLabelId:	"readWaypointsSelectLabel",
3660 	
3661 	/**Display Google map to show tracks and laps that have been read.  When <@link showReadDataElement> is 
3662 	 * set to false, the Google map will not show.
3663 	 * 
3664 	 * @default false
3665 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement
3666 	 * @type Boolean 
3667 	 */		
3668 	showReadGoogleMap:			false,
3669 	
3670 	/**The id referencing the google map display.  This is 
3671 	 * useful for CSS customizations.
3672 	 * 
3673 	 * @default readMap
3674 	 * @type String 
3675 	 */		
3676 	readGoogleMapId:			"readMap",
3677 	
3678 	/**DEPRECATED - Use {@link Garmin.DeviceDisplayDefaultOptions.readDataTypes} Tells the plug-in what data type to read from the device.  Options for this
3679 	 * are currently constants listed in {@link Garmin.DeviceControl.FILE_TYPES} , 
3680 	 * and the values are: crs, gpx, gpi, or null to skip this option altogether and get the default data type from 
3681 	 * the device.
3682 	 * <p>
3683 	 * This property works in conjunction with the following functions, based on the datatype:
3684 	 * <p>
3685 	 * For CRS and GPX:	Define the getWriteData() and getWriteDataFileName() functions in your options section.
3686 	 * <p>
3687 	 * For GPI: Define the getWriteData() and getWriteDataFileName() functions in your options section.
3688 	 * 			The getGpiWriteDescription() function replaces getWriteData().
3689 	 * <p>
3690 	 * @default Garmin.DeviceControl.FILE_TYPES.gpx
3691 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement
3692 	 * @see Garmin.DeviceControl.FILE_TYPES
3693 	 * @see Garmin.DeviceDisplayDefaultOptions.readDataTypes
3694 	 * @type String 
3695 	 * @deprecated
3696 	 */		
3697 	readDataType:	null,
3698 	
3699 	/** OVERRIDES readDataType! 
3700 	 * 
3701 	 * Tells the plug-in what data types to read from the device, in order of preference.  Options for this
3702 	 * are currently constants listed in {@link Garmin.DeviceControl.FILE_TYPES}.
3703      *
3704 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement
3705 	 * @see Garmin.DeviceControl.FILE_TYPES
3706 	 * @default TCX, GPX
3707 	 * @example ["FitnessHistory", "GPSData"]
3708 	 * @type Array 
3709 	 */		
3710 	readDataTypes: ["GPSData"],
3711 	
3712 	/**Display the dropdown select box for selecting what type
3713 	 * of data to read from the device.  When 
3714 	 * <@link showReadDataElement> is set to false, 
3715 	 * this device type select will not show.
3716 	 * 
3717 	 * @default false
3718 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement
3719 	 * @type Boolean 
3720 	 */		
3721 	showReadDataTypesSelect:	false,
3722 	
3723 	/**The id referencing the data type select.  This is 
3724 	 * useful for CSS customizations.
3725 	 * 
3726 	 * @default readDataTypesSelect
3727 	 * @type String 
3728 	 */			
3729 	readDataTypesSelectId:		"readDataTypesSelect",
3730 	
3731 	/**The function called when data is successfully read from
3732 	 * the device.  The function should have three arguements:<br/>
3733 	 * <br/>
3734 	 * 	dataString - the xml received from the device in String format<br/>
3735 	 *  dataDoc - the xml received from the device in Document format<br/>
3736 	 *  extension - the file type extension of the data, used to determine
3737 	 * 				the type of data received.<br/>
3738 	 *  activities - list of <@link Garmin.Activity> parsed from the xml.<br/>
3739 	 *  display - the display object<br/>
3740 	 * @see Garmin.DeviceDisplayDefaultOptions.Garmin.Activity
3741 	 * @example function(dataString, dataDoc, extension, activities, display){...} 
3742 	 * @type function
3743 	 * @function 
3744 	 */				
3745 	afterFinishReadFromDevice:	null,
3746 
3747 	/**Load tracks even if they don't have a timestamp (technically these are
3748 	 * routes).  Set to false if you need to do synchronization with existing
3749 	 * track log database (like MotionBased does).
3750 	 * 
3751 	 * @default true
3752 	 * @see Garmin.DeviceDisplayDefaultOptions._listTracks
3753 	 * @type Boolean 
3754 	 */		
3755 	loadTracksWithoutATimestamp:	true,
3756 	
3757 	// ================== Write Element Options ======================
3758 	
3759 	/**Start writing data to the device when one or more device(s)
3760 	 * is found.
3761 	 * 
3762 	 * @default false
3763 	 * 
3764 	 * @see Garmin.DeviceDisplayDefaultOptions.autoFindDevices
3765 	 * @see Garmin.DeviceDisplayDefaultOptions.autoReadData
3766 	 * @type Boolean 
3767 	 */					
3768 	autoWriteData:				false,
3769 	
3770 	/**Display the user interface associated with writing to
3771 	 * a connected device.
3772 	 * 
3773 	 * @default false
3774 	 * @type Boolean 
3775 	 */	
3776 	showWriteDataElement:		false,
3777 
3778 	/**Controls the view of the write data element. When
3779 	 * set to <b>true</b> the element will only show after a
3780 	 * device has been found.  When set to <b>false</b> the
3781 	 * element will show on page load.
3782 	 * Behavior will depend on other settings such as
3783 	 * and {@link showWriteDataElement} .
3784 	 * 
3785 	 * @default false
3786 	 * @type Boolean 
3787 	 */
3788 	showWriteDataElementOnDeviceFound:		false,
3789 	
3790 	/**The id referencing the box containing write elements.  This is 
3791 	 * useful for CSS customizations.
3792 	 * 
3793 	 * @default writeBox
3794 	 * @type String 
3795 	 */
3796 	writeDataElementId:			"writeBox",
3797 	
3798 	/**The id referencing the write data button.  This is 
3799 	 * useful for CSS customizations.
3800 	 * 
3801 	 * @default writeDataButton
3802 	 * @type String 
3803 	 */		
3804 	writeDataButtonId:			"writeDataButton",
3805 	
3806 	/**The text on the write button.
3807 	 * 
3808 	 * @type String 
3809 	 */		
3810 	writeDataButtonText:		"Write",
3811 	
3812 	/**Controls the view of the cancel write data button. When
3813 	 * set to <b>false</b> the button will never show.  When
3814 	 * set to <b>true</b> the button's behavior will depend on other
3815 	 * settings such as {@link showWriteDataButton} , 
3816 	 * and {@link showWriteDataElement} .
3817 	 * 
3818 	 * @default true
3819 	 * @type Boolean 
3820 	 */	
3821 	showCancelWriteDataButton:		true,
3822 	
3823 	/**The id referencing the cancel write data button.  This is 
3824 	 * useful for CSS customizations.
3825 	 * 
3826 	 * @default cancelWriteDataButton
3827 	 * @type String 
3828 	 */		
3829 	cancelWriteDataButtonId:	"cancelWriteDataButton",
3830 	
3831 	/**The text on the cancel write button.
3832 	 * 
3833 	 * @type String 
3834 	 */		
3835 	cancelWriteDataButtonText:  "Cancel Write",
3836 	
3837 	/**The function called when data is successfully written to
3838 	 * the device.  This method takes two parameters:
3839 	 *  success Boolean  - true if data was written
3840 	 *  display {Garmin.DeviceDisplay}  - the current instance of the DeviceDisplay
3841 	 * @type function 
3842 	 * @example function(success, display) {...}
3843 	 * @function
3844 	 */				
3845 	afterFinishWriteToDevice:	null,
3846 	
3847 	/**Array of filters to sequencialy apply to activities before being sent or displayed.
3848 	 * 
3849 	 * @see Garmin.FILTERS
3850 	 * @type Array dataFilters
3851 	 * @example [Garmin.FILTERS.historyOnly]
3852 	 */				
3853 	dataFilters:				[],	
3854 	
3855 	/**The function called by the display in order to acquire the data
3856 	 * that will be written to the device during the writing operation.
3857 	 * 
3858 	 * This function should return a String.
3859 	 * 
3860 	 * @see Garmin.DeviceDisplayDefaultOptions.getWriteDataFileName
3861 	 * @type function 
3862 	 * @example function() { return $("myTextAreaId").value; }
3863 	 * @function
3864 	 */
3865 	getWriteData:				null,
3866 	
3867 	/**The function called by the display in order to acquire the filename
3868 	 * of the data that will be written to the device during the writing 
3869 	 * operation.
3870 	 * 
3871 	 * This function should return a String.
3872 	 * @default function(){ return "myData.gpx"; }
3873 	 * @see Garmin.DeviceDisplayDefaultOptions.getWriteData
3874 	 * @type function
3875 	 */
3876 	getWriteDataFileName:		function(){ return "myData.gpx"; } ,
3877 	
3878 	/**DEPRECATED (see {@link getBinaryWriteDescription}) - The function called by the display in order to acquire the data
3879 	 * that will be written to the device during the writing operation.
3880 	 * 
3881 	 * This function should return an array of strings where adjacent items
3882 	 * indicate the source (URL) of the gpi to be written and the destination
3883 	 * (device path and filename) to write to the device.
3884 	 *
3885 	 * e.g.: [SOURCE,DESTINATION,SOURCE2,DESTINATION2] add as many source/destination 
3886 	 * pairs as you'd like.
3887 	 * 
3888 	 * @example function() { return ["http://connect.garmin.com/SampleGpi.gpi", "Garmin\\POI\\Test.gpi"] } 
3889 	 * @type function 
3890 	 * @function
3891 	 */
3892 	getGpiWriteDescription:		null,
3893 	
3894 	/**The function called by the display in order to acquire the data
3895 	 * that will be written to the device during the writing operation.
3896 	 * 
3897 	 * This function should return an array of strings where adjacent items
3898 	 * indicate the source (URL) of the binary data to be written and the destination
3899 	 * (device path and filename) to write to the device.
3900 	 *
3901 	 * e.g.: [SOURCE,DESTINATION,SOURCE2,DESTINATION2] add as many source/destination 
3902 	 * pairs as you'd like.
3903 	 * 
3904 	 * @example function() { return ["http://connect.garmin.com/SampleGpi.gpi", "Garmin\\POI\\Test.gpi"] }
3905 	 * @type function
3906 	 * @function 
3907 	 */
3908 	getBinaryWriteDescription:		null,
3909 	
3910 	/**DEPRECATED - Use {@link Garmin.DeviceDisplayDefaultOptions.writeDataTypes}
3911 	 * Tells the plug-in what data type to write to the device.  
3912 	 * Options are "gpx" which will use {@link getWriteData}  to get the data
3913 	 * or "gpi" which will use {@link getGpiWriteDescription}  to get the data to
3914 	 * save to the device.
3915 	 *
3916 	 * @default Garmin.DeviceControl.FILE_TYPES.gpx
3917 	 * @see Garmin.DeviceDisplayDefaultOptions.showWriteDataElement
3918 	 * @see Garmin.DeviceDisplayDefaultOptions.getWriteData
3919 	 * @see Garmin.DeviceDisplayDefaultOptions.getGpiWriteDescription
3920 	 * @see Garmin.DeviceDisplayDefaultOptions.writeDataTypes
3921 	 * @type String 
3922 	 * @deprecated
3923 	 */		
3924 	writeDataType:	null,
3925 	
3926 	/** OVERRIDES writeDataType!
3927 	 *
3928 	 * Tells the plug-in what data type to write to the device.  
3929 	 * Options are "gpx" which will use {@link getWriteData}  to get the data
3930 	 * or "gpi" which will use {@link getGpiWriteDescription}  to get the data to
3931 	 * save to the device.
3932 	 *
3933 	 * @default Garmin.DeviceControl.FILE_TYPES.gpx
3934 	 * @see Garmin.DeviceDisplayDefaultOptions.showWriteDataElement
3935 	 * @see Garmin.DeviceDisplayDefaultOptions.getWriteData
3936 	 * @see Garmin.DeviceDisplayDefaultOptions.getGpiWriteDescription
3937 	 * @type Array 
3938 	 */		
3939 	writeDataTypes:	["GPSData"],
3940 	
3941 	//===================  Activity Directory Element Options ===============
3942 	
3943 	/** Displays the activity directory table, which essentially 
3944 	 * allows users to select individual activities to read from 
3945 	 * the device.  showReadDataElement must be true for this 
3946 	 * to display.
3947 	 * 
3948 	 * @default true
3949 	 * 
3950 	 * @see Garmin.DeviceDisplayDefaultOptions.showReadDataElement
3951 	 * @type Boolean 
3952 	 */
3953 	showActivityDirectoryElement:	true,
3954 	
3955 	/**
3956 	 * The classname referencing the element that lists individual activities. This element includes the 
3957 	 * data table as well as the related buttons (such as for uploading data). This is useful for CSS customizations.
3958 	 * @default activityDirectoryClass
3959 	 * @type String 
3960 	 */
3961 	activityDirectoryClass:        "activityDirectoryClass",
3962 	
3963 	/** The id referencing the table that holds the activity directory data.  This is useful for CSS customizations.
3964 	 * 
3965 	 * @default activityDirectoryData
3966 	 * @type String 
3967 	 */
3968 	activityDirectoryDataId: "activityDirectoryData",
3969 	
3970 	/** The id referencing the element that lists individual activities. This element includes the 
3971 	 * data table as well as the related buttons (such as for uploading data). This is useful for CSS customizations.
3972 	 * 
3973 	 * @default activityDirectory
3974 	 * @type String 
3975 	 */
3976 	activityDirectoryElementId: "activityDirectory",
3977 	
3978 	/** The id referencing the header of the activity table that lists individual activities. 
3979 	 * This is useful for CSS customizations.
3980 	 * @default activityTableHeader
3981 	 * @type String 
3982 	 */
3983 	activityTableHeaderId:      "activityTableHeader",
3984 	
3985 	/** The id referencing the activity table that lists individual activities. This table contains
3986 	 * the activity data. This is useful for CSS customizations.
3987 	 * 
3988 	 * @default activityTable
3989 	 * @type String 
3990 	 */
3991 	activityTableId:           "activityTable",
3992 	
3993 	/** The function called after an activity entry is added to the activity listing table.
3994 	 * Useful for updating the status cell in a unique way.
3995 	 * @param Boolean index - the index in the table where the activity was added
3996 	 * @param {Garmin.Activity} activity
3997 	 * @param {Element} statusCell - the status td element associated with the activity
3998 	 * @param {Element} checkbox - the checkbox input element associated with the activity
3999 	 * @param Object row - the table row element associated with the activity
4000 	 * @param {Garmin.ActivityMatcher} activityMatcher - the activity matcher object, if available.
4001 	 * @param {Garmin.DeviceDisplay} display - the current instance of the display object
4002 	 * @type function
4003 	 * @function
4004 	 * @example function(index, activity, statusCell, checkbox, row, this.activityMatcher, this) {...}
4005 	 */
4006 	afterTableInsert:          null,
4007 	
4008 	//===================  Upload Options ===============
4009 
4010     /** Class name for the cancel upload button. 
4011      * Useful for CSS customizations.
4012      * @default cancelUploadButtonClass
4013      * @type String 
4014      */
4015 	cancelUploadButtonClass: 'cancelUploadButtonClass',
4016 	
4017 	/** Element ID for the cancel upload button.
4018 	 * @default 'cancelUploadButton'
4019 	 * @type String 
4020 	 */
4021 	cancelUploadButtonId: 'cancelUploadButton',
4022 	
4023 	/** Text to display on Cancel upload button.
4024 	 * @default '(Cancel)'
4025 	 * @type String 
4026 	 */
4027 	cancelUploadButtonText: '(Cancel)',
4028 	
4029 	/**Display the user interface associated with uploading new activities from a connected device.  
4030 	 * 
4031 	 * @default true
4032 	 * @type Boolean 
4033 	 */
4034 	showUploadNewButton: false,
4035 	
4036 	/** The id referencing the button for uploading data to a server. This is useful for CSS customizations.
4037 	 * 
4038 	 * @default uploadNewButton
4039 	 * @type String  
4040 	 */
4041 	uploadNewButtonId: "uploadNewButton",
4042 	
4043 	/** The text label for the upload data button.  This is useful for CSS customizations.
4044 	 * 
4045 	 * @default activityDirectory
4046 	 * @type String 
4047 	 */
4048 	uploadNewButtonText: "Upload new activities",
4049 	
4050 	/** Select activities to upload, rather than all activities read off the device.
4051 	 * 
4052 	 * @default false
4053 	 * @type Boolean 
4054 	 */
4055 	uploadSelectedActivities: false,
4056 	
4057 	/** Upload compressed data.  Compressed data is gzip base 64 encoded.  Compression is supported
4058 	 * for fitness history activities only.
4059 	 * 
4060 	 * @default false
4061 	 * @type Boolean
4062 	 */
4063 	uploadCompressedData: false,
4064 	
4065 	/** Maximum number of activities allowed for upload selection.  Users are notified during the selection
4066 	 * process if they try to exceed this value.  uploadSelectedActivities must be set to true 
4067 	 * for this to work.  Note that if this value is > 0, the 'select all activities' feature will not be available.  
4068 	 * 
4069 	 * Set this value to 0 for no limit with NO 'select all activities' checkbox.
4070 	 * 
4071 	 * @default -1 (no limit with 'select all' checkbox)
4072 	 * @type int
4073 	 * @see Garmin.DeviceDisplayDefaultOptions.uploadSelectedActivities
4074 	 */
4075 	uploadMaximum: -1, 
4076 	
4077 	// ================== Post to Server ======================
4078 	
4079 	/** The function called when a single activity is finished reading, and the data is ready to post.
4080 	 * Use this if you need a custom way of uploading data to your server (advanced users).<br/>  
4081 	 * <br/>
4082 	 * Otherwise, if you just need an AJAX call, use Send Data. (See {@link this.options.sendDataUrl} 
4083 	 * and {@link this.options.sendDataOptions}.)
4084 	 *  
4085      * Parameters to this function:<br/>
4086      *  <br/>datastring String  - The XML datastring of the activity read from the device.<br/>
4087      *  <br/>display {GarminDeviceDisplay} - the display object
4088      * @example function(datastring, display){...}
4089      * @function
4090      * @type function  
4091 	 */
4092 	postActivityHandler: 		null, 
4093 	
4094 	/** Show the element to send data to a remote server.
4095 	 * 
4096 	 * @type Boolean 
4097 	 */
4098 	showSendDataElement:		false,
4099 	
4100 	/**Controls the view of the send data element. When
4101 	 * set to <b>true</b> the element will only show after a
4102 	 * device has been found.  When set to <b>false</b> the
4103 	 * element will show on page load.
4104 	 * Behavior will depend on other settings such as
4105 	 * and {@link showSendDataElement} .
4106 	 * 
4107 	 * @default false
4108 	 * @type Boolean 
4109 	 */
4110 	showSendDataElementOnDeviceFound:		false,
4111 	
4112 	/** The callback function to set the request options when hitting the remote server.
4113 	 * This function is passed these parameters:
4114 	 * 
4115 	 * options - The options object.  Use this object to set the property values.  
4116 	 *           Some may already be set by sendDataOptions.  getSendOptions will overwrite any existing ones. 
4117 	 * deviceXml - the active device's XML
4118 	 * data - read data from the device, if any
4119 	 * 
4120 	 * Don't forget to return the options object!
4121 	 * 
4122 	 * @type function 
4123 	 * @example function(options, deviceXml, data) {} 
4124 	 * @function
4125 	 */
4126 	getSendOptions:					null,
4127 	
4128 	/** The URL to send the data to.
4129 	 * 
4130 	 * @type String 
4131 	 */
4132 	sendDataUrl:					null,
4133 	
4134 	/** The AJAX request options to use for sending data to a server.  To be used in conjunction with {@link sendDataUrl} .
4135 	 * 
4136 	 * See the <a href="http://www.prototypejs.org/api/ajax/options">AJAX options page</a> for configurable options and default values. 
4137 	 * 
4138 	 * @type Object 
4139 	 */
4140 	sendDataOptions:				null,
4141 	
4142 	/**The id referencing the box containing send elements.  This is 
4143 	 * useful for CSS customizations.
4144 	 * 
4145 	 * @default sendBox
4146 	 * @type String 
4147 	 */		
4148 	sendDataElementId:			"sendBox",
4149 	
4150 	/**The id referencing the send data button.  This is useful for
4151 	 * CSS customizations.
4152 	 * 
4153 	 * @default sendDataButton
4154 	 * @type String 
4155 	 */			
4156 	sendDataButtonId:			"sendDataButton",
4157 	
4158 	/**The text on the read button.
4159 	 * 
4160 	 * @type String 
4161 	 */		
4162 	sendDataButtonText:			"Send Data",
4163 	
4164 	/** The callback function that will be passed the AJAX response 
4165 	 * after making the request.  The display object is passed in, so you can 
4166 	 * make follow-up read or write calls if so desired.
4167 	 *
4168 	 * @see Garmin.DeviceDisplayDefaultOptions.sendDataUrl
4169 	 * @see Garmin.DeviceDisplayDefaultOptions.getSendOptions
4170 	 * @param response object (see <a href="http://www.prototypejs.org/api/ajax/response">Ajax.Response</a> for response attributes)
4171 	 * @default null
4172 	 * @type function
4173 	 * @function 
4174 	 * @example function(response){}
4175 	 */
4176 	afterFinishSendData:			null,
4177 
4178 	//===================  Device Browser Element Options ===============
4179 	
4180 	/** The callback function that will be called when the user selects a
4181 	 * device from the device browser. 
4182 	 *
4183 	 * @default null
4184 	 * @param deviceNum the device number of the selected device
4185 	 * @param devices an array of the detected devices 
4186 	 * @param deviceXml the device xml of the selected device
4187 	 * @type function 
4188 	 * @function
4189 	 * @example function(deviceXml){...}
4190 	 */
4191 	afterSelectDevice:       null,
4192 	
4193 	/** Show the device browser list.  Currently the browser list is only available
4194 	 * when activity directory reading is on (readDataType = Garmin.DeviceControl.FILE_TYPES.tcxDir). 
4195 	 * 
4196 	 * @default true
4197 	 * 
4198 	 * @see Garmin.DeviceDisplayDefaultOptions.uploadSelectedActivities
4199 	 * @type Boolean 
4200 	 */
4201 	useDeviceBrowser:   true,
4202 	
4203 	/**Display list instead of select drop down.  
4204 	 * @default false
4205 	 * @type Boolean 
4206 	 */
4207 	useDeviceSelectList:                 false,
4208 	
4209 	/** The classname for the device browser element.  This is useful for custom CSS.
4210 	 * @default deviceBrowserBoxClass
4211 	 * @type String 
4212 	 */
4213 	deviceBrowserElementClass:    "deviceBrowserBoxClass",
4214 	
4215 	/** The id referencing the device browser element.  This is useful for custom CSS.
4216 	 * @default deviceBrowserList
4217 	 * @type String 
4218 	 */
4219 	deviceBrowserElementId:    "deviceBrowserBox",
4220 	
4221 	/** The classname for the device browser text label.  This is useful for custom CSS.
4222 	 * @default deviceBrowserLabelClass
4223 	 * @type String  
4224 	 */
4225 	deviceBrowserLabelClass:      "deviceBrowserLabelClass",
4226 	
4227 	/** The id referencing the device browser text label.  This is useful for custom CSS.
4228 	 * @default deviceBrowserLabelId
4229 	 * @type String  
4230 	 */
4231 	deviceBrowserLabelId:      "deviceBrowserLabel",
4232 	
4233 	/** The text label to display above the device browser list.  This is useful for custom CSS.
4234 	 * @default 'Browse devices:'
4235 	 * @type String  
4236 	 */
4237 	deviceBrowserLabel:        "Browse devices:",
4238 	
4239 	/** The id referencing the device browser list.  This is useful for
4240 	 * CSS customizations.
4241 	 * 
4242 	 * @default deviceBrowserList
4243 	 * @type String 
4244 	 */
4245 	deviceBrowserListId:       "deviceBrowserList",
4246 	
4247 	/** The classname referencing the button that displays the UI for browsing the user's
4248 	 * file system.  
4249 	 * @default browseComputerButtonClass
4250 	 * @type String 
4251 	 */
4252 	browseComputerButtonClass:    "browseComputerButtonClass",
4253 	
4254 	/** The id referencing the button that displays the UI for browsing the user's
4255 	 * file system.  
4256 	 * @default browseComputerButton
4257 	 * @type String 
4258 	 */
4259 	browseComputerButtonId:    "browseComputerButton",
4260 	
4261 	/** The text for the button that displays the UI for browsing the user's file system.
4262 	 * @default "Browse Computer"
4263 	 * @type String 
4264 	 */
4265 	browseComputerButtonText:  "Browse Computer",
4266 	
4267 	/** The classname for the browse computer container.
4268 	 * @type String
4269 	 */
4270 	browseComputerElementClass:    "browseComputerElementClass",
4271 	
4272 	/** The id for the browse computer container. 
4273 	 * @type String
4274 	 */
4275 	browseComputerElementId:       "browseComputerElement",
4276 	
4277     /** The url of the iframe to load into the browse computer container.
4278      * The iframe object is generated by the API.  See browseComputerElementClass
4279      * to change the dimensions of the iframe.
4280      * @type String
4281      */	
4282 	browseComputerContentUrl:       "about:blank",
4283 	
4284 	/** The text label to display in the browser list for browsing the computer.
4285 	 * @default My Computer
4286 	 * @type String 
4287 	 */
4288 	browseComputerLabel:       "My Computer",
4289 	
4290 	/** The text to display on the loading content screen.  This is useful for internationalization.
4291 	 * @default 'Loading content...'
4292 	 * @type String 
4293 	 */
4294 	loadingContentText:        "Loading content from #{deviceName}, please wait...",
4295 	
4296 	/** The text to display to change the device.  This is useful for internationalization.
4297 	 * @default '(Change)'
4298 	 * @type String 
4299 	 */
4300 	changeDeviceButtonText: "(Change)",
4301 	
4302 	/** The classname referencing the change device element, which is a link that allows
4303 	 * the user to change device in list mode.
4304 	 * @default changeDevice
4305 	 * @type String 
4306 	 */
4307 	changeDeviceClass:            "change",
4308 	
4309 	/** The id referencing the change device element, which is a link that allows
4310 	 * the user to change device in list mode.
4311 	 * @default changeDevice
4312 	 * @type String 
4313 	 */
4314 	changeDeviceElementId:                "changeDevice",
4315 	
4316 	/** The class name referencing the connected devices label displayed when using
4317 	 * the device select list.
4318 	 * @default connectedDevicesClass
4319 	 * @type String 
4320 	 */
4321 	connectedDevicesClass:                 "connectedDevicesClass",
4322 	
4323 	/** The image to display in the connected devices label.
4324 	 * @type String 
4325 	 */
4326 	connectedDevicesImg:                   null,
4327 	
4328 	/** The label for connected devices, displayed when using the device select list.
4329 	 * @default 'Connected devices:'
4330 	 * @type String 
4331 	 */
4332 	connectedDevicesLabel:                 "Connected devices:",
4333 	
4334 	/** The classname referencing the preview device element, which displays an image
4335 	 * depending on the device selected.
4336 	 * 
4337 	 * @default previewDevice
4338 	 * @see Garmin.DeviceDisplayDefaultOptions.previewDeviceDefaultImg
4339 	 * @type String 
4340 	 */
4341 	previewDeviceClass:           "preview",
4342 	
4343 	/** The id referencing the change device element, which is a link that allows
4344 	 * the user to change device in list mode.
4345 	 * @default changeDevice
4346 	 * @type String 
4347 	 */
4348 	previewDeviceElementId:                 "previewDevice",
4349 	
4350 	/** The default image URL to display for any selected device. 
4351 	 * @see Garmin.DeviceDisplayDefaultOptions.previewDeviceId
4352 	 * @see Garmin.DeviceDisplayDefaultOptions.useDeviceSelectList
4353 	 * @type String 
4354 	 */
4355 	previewDeviceDefaultImg:   "../../../theme/upload/images/icon-edge.png",
4356 	
4357 	/**The maximum number of characters to display for a device name.
4358 	 *@type int 
4359 	 */
4360 	deviceLabelMaxSize: 20,
4361 	
4362 	/** Allows the user to browse their file system for upload. 
4363 	 * {@link uploadSelectedActivities} must be set to true for this
4364 	 * option to take effect.
4365 	 * @default false
4366 	 * @type Boolean 
4367 	 */
4368 	showBrowseComputer: false,
4369 	
4370 	// ================== Synchronization ======================
4371 	
4372 	/**Detect new activities in the activity listing by comparing the device list to a server list.
4373 	 * @default true
4374 	 * @type Boolean 
4375 	 */
4376 	detectNewActivities:                  false,
4377 	
4378 	/**
4379 	 * The URL to make the sync request to.  To be used in conjuntion with {@link syncDataOptions}.
4380 	 * {@link detectNewActivities} must be set to true.
4381 	 * @type String 
4382 	 */
4383 	syncDataUrl:                          null,
4384 	
4385 	/** The AJAX request options to use for posting the parameters to a server.  To be used in conjunction with {@link syncDataUrl} .
4386 	 * 
4387 	 * See the <a href="http://www.prototypejs.org/api/ajax/options">AJAX options page</a> for configurable options and default values. 
4388 	 * 
4389 	 * @type Object 
4390 	 */
4391 	syncDataOptions:                      null,
4392 	
4393 	// ================== Internationalization ======================
4394 	/** Status message exposed for internationalization. @type String  */
4395 	pluginUnlocked: "Plug-in initialized.  Find some devices to get started.",
4396 	/** Status message exposed for internationalization. @type String  */
4397 	pluginNotUnlocked: "The plug-in was not unlocked successfully",
4398 	/** Read data selection option exposed for internationalization. @type String  */
4399 	gpsData: "GPS Data",
4400 	/** Read data selection option exposed for internationalization. @type String  */
4401 	trainingData: "Training Data",
4402 	/** Status message exposed for internationalization. Prepended to the device name after user selects which device to use.  
4403 	 * i.e. "Using Diana's Forerunner 305"  
4404 	 * @default "Using "
4405 	 * @type String  
4406 	 */
4407 	usingDevice: "Using #{deviceName}",
4408 	/** Track list box item template exposed for internationalization. @type String  */
4409 	trackListing: "#{date}  (Duration: #{duration} )",
4410 	/** Status message template exposed for internationalization. @type String  */
4411 	dataFound: "#{routes}  routes, #{tracks}  tracks and #{waypoints}  waypoints found",
4412 	/** Status message exposed for internationalization. @type String  */
4413 	writingToDevice: "Writing data to the device",
4414 	/** Status message exposed for internationalization. @type String  */
4415 	writtenToDevice: "Data written to the device",
4416 	/** Status message exposed for internationalization. @type String  */
4417 	writingCancelled: "Writing cancelled",
4418 	/** Status message exposed for internationalization. @type String  */
4419 	overwritingFile: "Overwriting file",
4420 	/** Status message exposed for internationalization. @type String  */
4421 	notOverwritingFile: "Will not be overwriting file",
4422 	/** Status message exposed for internationalization. @type String  */
4423 	lookingForDevices: "Looking for connected devices...",
4424 	/** Status template exposed for internationalization. When single device is found. @type String  */
4425 	foundDevice: "Found #{deviceName} ",
4426 	/** Status template exposed for internationalization. When multiple devices are found. @type String  */
4427 	foundDevices: "Found #{deviceCount}  devices",
4428 	/** Status message exposed for internationalization. When user cancels Find Devices.@type String  */
4429 	findCancelled: "Find cancelled",
4430 	/** Status message exposed for internationalization. When reading data from the device. @type String  */
4431 	dataReadProcessing: "Data read from device. Processing...",
4432 	/** Status message exposed for internationalization. When large files are being written to device.  @type String  */
4433 	dataDownloadProcessing: "Processing data to write... ",
4434 	/** Status message exposed for internationalization. When uploads have completed.  @type String  */
4435 	uploadsFinished: "Transfer Complete!",
4436 	/** Error message exposed for internationalization. @type String  */
4437 	noParseSupportForType: "The plugin does not have parsing support for file type ",
4438 	/** Request message exposed for internationalization. @type String  */
4439 	installNow: "Install now?",
4440 	/** Request message exposed for internationalization. @type String  */
4441 	downloadAndInstall: "Download and install now",
4442 	/** Powered-by message. Required for plugin license agreement. @type String  */
4443 	poweredByGarmin: "Powered by <a href='http://www.garmin.com/products/communicator/' target='_new'>Garmin Communicator</a>",
4444 	/** Status message for devices that are not in the allowed devices list, or do not support any of the application's supported filetypes. @type String  */
4445 	unsupportedDevice:	"Your device is not supported by this application.",
4446 	/** Error message to display when user attempts to upload 0 activities. @type String  */
4447 	errorActivitySelect: "No selected or new activities to upload.",
4448 	/** DEPRECATED Column header for the Date fields in the activity directory.  Useful for CSS customizations and internationalization.  @type String @deprecated @see Garmin.DeviceDisplayDefaultOptions.getActivityDirectoryHeaderIdLabel */
4449 	activityDirectoryHeaderDate: "<b>Date</b>",
4450 	/** Column header for the Date/Name fields in the activity directory.  Useful for CSS customizations and internationalization. Default to Date if nothing is specified @type String  */
4451 	getActivityDirectoryHeaderIdLabel: function () { return "<b>Date</b>"; },
4452 	/** Column header for the Duration fields in the activity directory.  Useful for CSS customizations and internationalization.  @type String  */
4453 	activityDirectoryHeaderDuration: "<b>Duration</b>",
4454 	/** Column header for the Status fields in the activity directory.  Useful for CSS customizations and internationalization.  @type String  */
4455 	activityDirectoryHeaderStatus: "<b>Status</b>",
4456 	/** Error message to display when attempting to write to a device that does not support the provided datatype. Useful for CSS customizations and internationalization.  @type String  */
4457 	unsupportedReadDataType: "Your device does not support reading of the type #{dataType}.",
4458 	/** Error message to display when attempting to write to a device that does not support the provided datatype. Useful for CSS customizations and internationalization.  @type String  */
4459 	unsupportedWriteDataType: "Your device does not support writing of the type #{dataType}.",
4460 	/** Status message to display when activities are being uploaded.  @type String  */
4461 	uploadingActivities: "Uploading activities...",
4462 	/** Error message exposed for internationalization.  When maximum selection for upload is reached. @type String  */
4463 	uploadMaximumReached: "Maximum upload selection of #{activities} activities reached.",
4464 	/** The innerHTML to use for status while an activity is processing (for upload).  Define using the img tag if you wish to use an image.  
4465 	 * 
4466 	 * @default '<img src="style/ajax-loader.gif" />'
4467 	 * 
4468 	 * which is an animated loader image.  You can customize this to be text instead of an image by not using the image tags.   
4469 	 * @type String  */
4470 	statusCellProcessingImg: '<img src="style/processing-arrows.gif" width="15" height="15" />',
4471 	/** Status text to display when the plugin is sending data to a remote server.  @type String  */
4472 	sendingDataToServer: "Sending data from #{deviceName} to server...",
4473 	/** Error message to display when there is an error getting the HTTP response back from the HTTP request.  @type String  */
4474 	errorHttpResponse: "Unable to get valid response from HTTP request object.  Ensure that your options are set correctly and try again.",
4475 	/** Status text to display when none of the activities from the device meet the filter requirements. @type String  */
4476 	noFilteredActivities: "No new activities found on device.",
4477 	noActivitiesOnDevice: "No activities found on selected device."
4478 } ;
4479 
4480 
4481 /*
4482  * DisplayBootstrap - not sure what form this should take: class or global var 
4483  * It should probably be in the Garmin namesapce.
4484  * 
4485  * Dynamic include of required libraries and check for Prototype
4486  * Code taken from scriptaculous (http://script.aculo.us/) - thanks guys!
4487  */
4488 var GarminDeviceDisplay = {
4489 	require: function(libraryName) {
4490 		// inserting via DOM fails in Safari 2.0, so brute force approach
4491 		document.write('<script type="text/javascript" src="'+libraryName+'"></script>');
4492 	},
4493 
4494 	load: function() {
4495 		if((typeof Prototype=='undefined') || 
4496 			(typeof Element == 'undefined') || 
4497 			(typeof Element.Methods=='undefined') ||
4498 			parseFloat(Prototype.Version.split(".")[0] + "." +
4499 			Prototype.Version.split(".")[1]) < 1.5) {
4500 			throw("GarminDeviceDisplay requires the Prototype JavaScript framework >= 1.5.0");
4501 		}
4502 
4503 		$A(document.getElementsByTagName("script"))
4504 		.findAll(
4505 			function(s) {
4506 				return (s.src && s.src.match(/GarminDeviceDisplay\.js(\?.*)?$/))
4507 			}
4508 		)
4509 		.each(
4510 			function(s) {
4511 				var path = s.src.replace(/GarminDeviceDisplay\.js(\?.*)?$/,'../../');
4512 				var includes = s.src.match(/\?.*load=([a-z,]*)/);
4513 				var dependencies = 'garmin/device/GarminDeviceControl' +
4514 									',garmin/device/GarminDevicePlugin' +
4515 									',garmin/device/GarminGpsDataStructures' +
4516 									',garmin/device/GoogleMapController' +
4517 									',garmin/device/GarminDevice' +
4518 									',garmin/device/GarminPluginUtils' +
4519 									',garmin/api/GarminRemoteTransfer' +
4520 									',garmin/util/Util-XmlConverter' +
4521 									',garmin/util/Util-Broadcaster' +
4522 									',garmin/util/Util-DateTimeFormat' +
4523 									',garmin/util/Util-BrowserDetect' +
4524 									',garmin/util/Util-PluginDetect' +
4525 									',garmin/device/GarminObjectGenerator' +
4526 									',garmin/activity/GarminMeasurement' +
4527 									',garmin/activity/GarminSample' +
4528 									',garmin/activity/GarminSeries' +
4529 									',garmin/activity/GarminActivity' +
4530 									',garmin/activity/GarminActivityDirectory' +
4531 									',garmin/activity/GarminActivityFilter' +
4532 									',garmin/activity/GarminActivityMatcher' +
4533 									',garmin/activity/TcxActivityFactory' +									
4534 									',garmin/activity/GpxActivityFactory'+
4535 									',garmin/directory/GarminDirectoryFactory'+
4536 									',garmin/directory/GarminFile';
4537 			    (includes ? includes[1] : dependencies).split(',').each(
4538 					function(include) {
4539 						GarminDeviceDisplay.require(path+include+'.js') 
4540 					}
4541 				);
4542 			}
4543 		);
4544 	}	
4545 }
4546 
4547 GarminDeviceDisplay.load();
4548