1 /*! 2 * jQuery Mobile v1.0a3 3 * http://jquerymobile.com/ 4 * 5 * Copyright 2010, jQuery Project 6 * Dual licensed under the MIT or GPL Version 2 licenses. 7 * http://jquery.org/license 8 */ 9 /*! 10 * jQuery UI Widget @VERSION 11 * 12 * Copyright 2010, AUTHORS.txt (http://jqueryui.com/about) 13 * Dual licensed under the MIT or GPL Version 2 licenses. 14 * http://jquery.org/license 15 * 16 * http://docs.jquery.com/UI/Widget 17 */ 18 (function( $, undefined ) { 19 20 // jQuery 1.4+ 21 if ( $.cleanData ) { 22 var _cleanData = $.cleanData; 23 $.cleanData = function( elems ) { 24 for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { 25 $( elem ).triggerHandler( "remove" ); 26 } 27 _cleanData( elems ); 28 }; 29 } else { 30 var _remove = $.fn.remove; 31 $.fn.remove = function( selector, keepData ) { 32 return this.each(function() { 33 if ( !keepData ) { 34 if ( !selector || $.filter( selector, [ this ] ).length ) { 35 $( "*", this ).add( [ this ] ).each(function() { 36 $( this ).triggerHandler( "remove" ); 37 }); 38 } 39 } 40 return _remove.call( $(this), selector, keepData ); 41 }); 42 }; 43 } 44 45 $.widget = function( name, base, prototype ) { 46 var namespace = name.split( "." )[ 0 ], 47 fullName; 48 name = name.split( "." )[ 1 ]; 49 fullName = namespace + "-" + name; 50 51 if ( !prototype ) { 52 prototype = base; 53 base = $.Widget; 54 } 55 56 // create selector for plugin 57 $.expr[ ":" ][ fullName ] = function( elem ) { 58 return !!$.data( elem, name ); 59 }; 60 61 $[ namespace ] = $[ namespace ] || {}; 62 $[ namespace ][ name ] = function( options, element ) { 63 // allow instantiation without initializing for simple inheritance 64 if ( arguments.length ) { 65 this._createWidget( options, element ); 66 } 67 }; 68 69 var basePrototype = new base(); 70 // we need to make the options hash a property directly on the new instance 71 // otherwise we'll modify the options hash on the prototype that we're 72 // inheriting from 73 // $.each( basePrototype, function( key, val ) { 74 // if ( $.isPlainObject(val) ) { 75 // basePrototype[ key ] = $.extend( {}, val ); 76 // } 77 // }); 78 basePrototype.options = $.extend( true, {}, basePrototype.options ); 79 $[ namespace ][ name ].prototype = $.extend( true, basePrototype, { 80 namespace: namespace, 81 widgetName: name, 82 widgetEventPrefix: $[ namespace ][ name ].prototype.widgetEventPrefix || name, 83 widgetBaseClass: fullName 84 }, prototype ); 85 86 $.widget.bridge( name, $[ namespace ][ name ] ); 87 }; 88 89 $.widget.bridge = function( name, object ) { 90 $.fn[ name ] = function( options ) { 91 var isMethodCall = typeof options === "string", 92 args = Array.prototype.slice.call( arguments, 1 ), 93 returnValue = this; 94 95 // allow multiple hashes to be passed on init 96 options = !isMethodCall && args.length ? 97 $.extend.apply( null, [ true, options ].concat(args) ) : 98 options; 99 100 // prevent calls to internal methods 101 if ( isMethodCall && options.charAt( 0 ) === "_" ) { 102 return returnValue; 103 } 104 105 if ( isMethodCall ) { 106 this.each(function() { 107 var instance = $.data( this, name ); 108 if ( !instance ) { 109 throw "cannot call methods on " + name + " prior to initialization; " + 110 "attempted to call method '" + options + "'"; 111 } 112 if ( !$.isFunction( instance[options] ) ) { 113 throw "no such method '" + options + "' for " + name + " widget instance"; 114 } 115 var methodValue = instance[ options ].apply( instance, args ); 116 if ( methodValue !== instance && methodValue !== undefined ) { 117 returnValue = methodValue; 118 return false; 119 } 120 }); 121 } else { 122 this.each(function() { 123 var instance = $.data( this, name ); 124 if ( instance ) { 125 instance.option( options || {} )._init(); 126 } else { 127 $.data( this, name, new object( options, this ) ); 128 } 129 }); 130 } 131 132 return returnValue; 133 }; 134 }; 135 136 $.Widget = function( options, element ) { 137 // allow instantiation without initializing for simple inheritance 138 if ( arguments.length ) { 139 this._createWidget( options, element ); 140 } 141 }; 142 143 $.Widget.prototype = { 144 widgetName: "widget", 145 widgetEventPrefix: "", 146 options: { 147 disabled: false 148 }, 149 _createWidget: function( options, element ) { 150 // $.widget.bridge stores the plugin instance, but we do it anyway 151 // so that it's stored even before the _create function runs 152 $.data( element, this.widgetName, this ); 153 this.element = $( element ); 154 this.options = $.extend( true, {}, 155 this.options, 156 this._getCreateOptions(), 157 options ); 158 159 var self = this; 160 this.element.bind( "remove." + this.widgetName, function() { 161 self.destroy(); 162 }); 163 164 this._create(); 165 this._trigger( "create" ); 166 this._init(); 167 }, 168 _getCreateOptions: function() { 169 var options = {}; 170 if ( $.metadata ) { 171 options = $.metadata.get( element )[ this.widgetName ]; 172 } 173 return options; 174 }, 175 _create: function() {}, 176 _init: function() {}, 177 178 destroy: function() { 179 this.element 180 .unbind( "." + this.widgetName ) 181 .removeData( this.widgetName ); 182 this.widget() 183 .unbind( "." + this.widgetName ) 184 .removeAttr( "aria-disabled" ) 185 .removeClass( 186 this.widgetBaseClass + "-disabled " + 187 "ui-state-disabled" ); 188 }, 189 190 widget: function() { 191 return this.element; 192 }, 193 194 option: function( key, value ) { 195 var options = key; 196 197 if ( arguments.length === 0 ) { 198 // don't return a reference to the internal hash 199 return $.extend( {}, this.options ); 200 } 201 202 if (typeof key === "string" ) { 203 if ( value === undefined ) { 204 return this.options[ key ]; 205 } 206 options = {}; 207 options[ key ] = value; 208 } 209 210 this._setOptions( options ); 211 212 return this; 213 }, 214 _setOptions: function( options ) { 215 var self = this; 216 $.each( options, function( key, value ) { 217 self._setOption( key, value ); 218 }); 219 220 return this; 221 }, 222 _setOption: function( key, value ) { 223 this.options[ key ] = value; 224 225 if ( key === "disabled" ) { 226 this.widget() 227 [ value ? "addClass" : "removeClass"]( 228 this.widgetBaseClass + "-disabled" + " " + 229 "ui-state-disabled" ) 230 .attr( "aria-disabled", value ); 231 } 232 233 return this; 234 }, 235 236 enable: function() { 237 return this._setOption( "disabled", false ); 238 }, 239 disable: function() { 240 return this._setOption( "disabled", true ); 241 }, 242 243 _trigger: function( type, event, data ) { 244 var callback = this.options[ type ]; 245 246 event = $.Event( event ); 247 event.type = ( type === this.widgetEventPrefix ? 248 type : 249 this.widgetEventPrefix + type ).toLowerCase(); 250 data = data || {}; 251 252 // copy original event properties over to the new event 253 // this would happen if we could call $.event.fix instead of $.Event 254 // but we don't have a way to force an event to be fixed multiple times 255 if ( event.originalEvent ) { 256 for ( var i = $.event.props.length, prop; i; ) { 257 prop = $.event.props[ --i ]; 258 event[ prop ] = event.originalEvent[ prop ]; 259 } 260 } 261 262 this.element.trigger( event, data ); 263 264 return !( $.isFunction(callback) && 265 callback.call( this.element[0], event, data ) === false || 266 event.isDefaultPrevented() ); 267 } 268 }; 269 270 })( jQuery ); 271 /* 272 * jQuery Mobile Framework : widget factory extentions for mobile 273 * Copyright (c) jQuery Project 274 * Dual licensed under the MIT or GPL Version 2 licenses. 275 * http://jquery.org/license 276 */ 277 (function($, undefined ) { 278 279 $.widget( "mobile.widget", { 280 _getCreateOptions: function() { 281 var elem = this.element, 282 options = {}; 283 $.each( this.options, function( option ) { 284 var value = elem.data( option.replace( /[A-Z]/g, function( c ) { 285 return "-" + c.toLowerCase(); 286 } ) ); 287 if ( value !== undefined ) { 288 options[ option ] = value; 289 } 290 }); 291 return options; 292 } 293 }); 294 295 })( jQuery ); 296 /* 297 * jQuery Mobile Framework : resolution and CSS media query related helpers and behavior 298 * Copyright (c) jQuery Project 299 * Dual licensed under the MIT or GPL Version 2 licenses. 300 * http://jquery.org/license 301 */ 302 (function($, undefined ) { 303 304 var $window = $(window), 305 $html = $( "html" ), 306 307 //media-query-like width breakpoints, which are translated to classes on the html element 308 resolutionBreakpoints = [320,480,768,1024]; 309 310 311 /* $.mobile.media method: pass a CSS media type or query and get a bool return 312 note: this feature relies on actual media query support for media queries, though types will work most anywhere 313 examples: 314 $.mobile.media('screen') //>> tests for screen media type 315 $.mobile.media('screen and (min-width: 480px)') //>> tests for screen media type with window width > 480px 316 $.mobile.media('@media screen and (-webkit-min-device-pixel-ratio: 2)') //>> tests for webkit 2x pixel ratio (iPhone 4) 317 */ 318 $.mobile.media = (function() { 319 // TODO: use window.matchMedia once at least one UA implements it 320 var cache = {}, 321 testDiv = $( "<div id='jquery-mediatest'>" ), 322 fakeBody = $( "<body>" ).append( testDiv ); 323 324 return function( query ) { 325 if ( !( query in cache ) ) { 326 var styleBlock = document.createElement('style'), 327 cssrule = "@media " + query + " { #jquery-mediatest { position:absolute; } }"; 328 //must set type for IE! 329 styleBlock.type = "text/css"; 330 if (styleBlock.styleSheet){ 331 styleBlock.styleSheet.cssText = cssrule; 332 } 333 else { 334 styleBlock.appendChild(document.createTextNode(cssrule)); 335 } 336 337 $html.prepend( fakeBody ).prepend( styleBlock ); 338 cache[ query ] = testDiv.css( "position" ) === "absolute"; 339 fakeBody.add( styleBlock ).remove(); 340 } 341 return cache[ query ]; 342 }; 343 })(); 344 345 /* 346 private function for adding/removing breakpoint classes to HTML element for faux media-query support 347 It does not require media query support, instead using JS to detect screen width > cross-browser support 348 This function is called on orientationchange, resize, and mobileinit, and is bound via the 'htmlclass' event namespace 349 */ 350 function detectResolutionBreakpoints(){ 351 var currWidth = $window.width(), 352 minPrefix = "min-width-", 353 maxPrefix = "max-width-", 354 minBreakpoints = [], 355 maxBreakpoints = [], 356 unit = "px", 357 breakpointClasses; 358 359 $html.removeClass( minPrefix + resolutionBreakpoints.join(unit + " " + minPrefix) + unit + " " + 360 maxPrefix + resolutionBreakpoints.join( unit + " " + maxPrefix) + unit ); 361 362 $.each(resolutionBreakpoints,function( i, breakPoint ){ 363 if( currWidth >= breakPoint ){ 364 minBreakpoints.push( minPrefix + breakPoint + unit ); 365 } 366 if( currWidth <= breakPoint ){ 367 maxBreakpoints.push( maxPrefix + breakPoint + unit ); 368 } 369 }); 370 371 if( minBreakpoints.length ){ breakpointClasses = minBreakpoints.join(" "); } 372 if( maxBreakpoints.length ){ breakpointClasses += " " + maxBreakpoints.join(" "); } 373 374 $html.addClass( breakpointClasses ); 375 }; 376 377 /* $.mobile.addResolutionBreakpoints method: 378 pass either a number or an array of numbers and they'll be added to the min/max breakpoint classes 379 Examples: 380 $.mobile.addResolutionBreakpoints( 500 ); 381 $.mobile.addResolutionBreakpoints( [500, 1200] ); 382 */ 383 $.mobile.addResolutionBreakpoints = function( newbps ){ 384 if( $.type( newbps ) === "array" ){ 385 resolutionBreakpoints = resolutionBreakpoints.concat( newbps ); 386 } 387 else { 388 resolutionBreakpoints.push( newbps ); 389 } 390 resolutionBreakpoints.sort(function(a,b){ return a-b; }); 391 detectResolutionBreakpoints(); 392 }; 393 394 /* on mobileinit, add classes to HTML element 395 and set handlers to update those on orientationchange and resize*/ 396 $(document).bind("mobileinit.htmlclass", function(){ 397 /* bind to orientationchange and resize 398 to add classes to HTML element for min/max breakpoints and orientation */ 399 $window.bind("orientationchange.htmlclass resize.htmlclass", function(event){ 400 //add orientation class to HTML element on flip/resize. 401 if(event.orientation){ 402 $html.removeClass( "portrait landscape" ).addClass( event.orientation ); 403 } 404 //add classes to HTML element for min/max breakpoints 405 detectResolutionBreakpoints(); 406 }); 407 }); 408 409 /* Manually trigger an orientationchange event when the dom ready event fires. 410 This will ensure that any viewport meta tag that may have been injected 411 has taken effect already, allowing us to properly calculate the width of the 412 document. 413 */ 414 $(function(){ 415 //trigger event manually 416 $window.trigger( "orientationchange.htmlclass" ); 417 }); 418 419 })(jQuery);/* 420 * jQuery Mobile Framework : support tests 421 * Copyright (c) jQuery Project 422 * Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. 423 * Note: Code is in draft form and is subject to change 424 */ 425 (function($, undefined ) { 426 427 428 429 var fakeBody = $( "<body>" ).prependTo( "html" ), 430 fbCSS = fakeBody[0].style, 431 vendors = ['webkit','moz','o'], 432 webos = window.palmGetResource || window.PalmServiceBridge, //only used to rule out scrollTop 433 bb = window.blackberry; //only used to rule out box shadow, as it's filled opaque on BB 434 435 //thx Modernizr 436 function propExists( prop ){ 437 var uc_prop = prop.charAt(0).toUpperCase() + prop.substr(1), 438 props = (prop + ' ' + vendors.join(uc_prop + ' ') + uc_prop).split(' '); 439 for(var v in props){ 440 if( fbCSS[ v ] !== undefined ){ 441 return true; 442 } 443 } 444 }; 445 446 //test for dynamic-updating base tag support (allows us to avoid href,src attr rewriting) 447 function baseTagTest(){ 448 var fauxBase = location.protocol + '//' + location.host + location.pathname + "ui-dir/", 449 base = $("head base"), 450 fauxEle = null, 451 href = ''; 452 if (!base.length) { 453 base = fauxEle = $("<base>", {"href": fauxBase}).appendTo("head"); 454 } 455 else { 456 href = base.attr("href"); 457 } 458 var link = $( "<a href='testurl'></a>" ).prependTo( fakeBody ), 459 rebase = link[0].href; 460 base[0].href = href ? href : location.pathname; 461 if (fauxEle) { 462 fauxEle.remove(); 463 } 464 return rebase.indexOf(fauxBase) === 0; 465 }; 466 467 $.extend( $.support, { 468 orientation: "orientation" in window, 469 touch: "ontouchend" in document, 470 cssTransitions: "WebKitTransitionEvent" in window, 471 pushState: !!history.pushState, 472 mediaquery: $.mobile.media('only all'), 473 cssPseudoElement: !!propExists('content'), 474 boxShadow: !!propExists('boxShadow') && !bb, 475 scrollTop: ("pageXOffset" in window || "scrollTop" in document.documentElement || "scrollTop" in fakeBody[0]) && !webos, 476 dynamicBaseTag: baseTagTest() 477 }); 478 479 fakeBody.remove(); 480 481 //for ruling out shadows via css 482 if( !$.support.boxShadow ){ $('html').addClass('ui-mobile-nosupport-boxshadow'); } 483 484 })( jQuery );/* 485 * jQuery Mobile Framework : events 486 * Copyright (c) jQuery Project 487 * Dual licensed under the MIT or GPL Version 2 licenses. 488 * http://jquery.org/license 489 */ 490 (function($, undefined ) { 491 492 // add new event shortcuts 493 $.each( "touchstart touchmove touchend orientationchange tap taphold swipe swipeleft swiperight scrollstart scrollstop".split( " " ), function( i, name ) { 494 $.fn[ name ] = function( fn ) { 495 return fn ? this.bind( name, fn ) : this.trigger( name ); 496 }; 497 $.attrFn[ name ] = true; 498 }); 499 500 var supportTouch = $.support.touch, 501 scrollEvent = "touchmove scroll", 502 touchStartEvent = supportTouch ? "touchstart" : "mousedown", 503 touchStopEvent = supportTouch ? "touchend" : "mouseup", 504 touchMoveEvent = supportTouch ? "touchmove" : "mousemove"; 505 506 // also handles scrollstop 507 $.event.special.scrollstart = { 508 enabled: true, 509 510 setup: function() { 511 var thisObject = this, 512 $this = $( thisObject ), 513 scrolling, 514 timer; 515 516 function trigger( event, state ) { 517 scrolling = state; 518 var originalType = event.type; 519 event.type = scrolling ? "scrollstart" : "scrollstop"; 520 $.event.handle.call( thisObject, event ); 521 event.type = originalType; 522 } 523 524 // iPhone triggers scroll after a small delay; use touchmove instead 525 $this.bind( scrollEvent, function( event ) { 526 if ( !$.event.special.scrollstart.enabled ) { 527 return; 528 } 529 530 if ( !scrolling ) { 531 trigger( event, true ); 532 } 533 534 clearTimeout( timer ); 535 timer = setTimeout(function() { 536 trigger( event, false ); 537 }, 50 ); 538 }); 539 } 540 }; 541 542 // also handles taphold 543 $.event.special.tap = { 544 setup: function() { 545 var thisObject = this, 546 $this = $( thisObject ); 547 548 $this 549 .bind( "mousedown touchstart", function( event ) { 550 if ( event.which && event.which !== 1 || 551 //check if event fired once already by a device that fires both mousedown and touchstart (while supporting both events) 552 $this.data( "prevEvent") && $this.data( "prevEvent") !== event.type ) { 553 return false; 554 } 555 556 //save event type so only this type is let through for a temp duration, 557 //allowing quick repetitive taps but not duplicative events 558 $this.data( "prevEvent", event.type ); 559 setTimeout(function(){ 560 $this.removeData( "prevEvent" ); 561 }, 800); 562 563 var moved = false, 564 touching = true, 565 origTarget = event.target, 566 origEvent = event.originalEvent, 567 origPos = event.type == "touchstart" ? [origEvent.touches[0].pageX, origEvent.touches[0].pageY] : [ event.pageX, event.pageY ], 568 originalType, 569 timer; 570 571 572 function moveHandler( event ) { 573 if( event.type == "scroll" ){ 574 moved = true; 575 return; 576 } 577 var newPageXY = event.type == "touchmove" ? event.originalEvent.touches[0] : event; 578 if ((Math.abs(origPos[0] - newPageXY.pageX) > 10) || 579 (Math.abs(origPos[1] - newPageXY.pageY) > 10)) { 580 moved = true; 581 } 582 } 583 584 timer = setTimeout(function() { 585 if ( touching && !moved ) { 586 originalType = event.type; 587 event.type = "taphold"; 588 $.event.handle.call( thisObject, event ); 589 event.type = originalType; 590 } 591 }, 750 ); 592 593 //scroll now cancels tap 594 $(window).one("scroll", moveHandler); 595 596 $this 597 .bind( "mousemove touchmove", moveHandler ) 598 .one( "mouseup touchend", function( event ) { 599 $this.unbind( "mousemove touchmove", moveHandler ); 600 $(window).unbind("scroll", moveHandler); 601 clearTimeout( timer ); 602 touching = false; 603 604 /* ONLY trigger a 'tap' event if the start target is 605 * the same as the stop target. 606 */ 607 if ( !moved && ( origTarget == event.target ) ) { 608 originalType = event.type; 609 event.type = "tap"; 610 $.event.handle.call( thisObject, event ); 611 event.type = originalType; 612 } 613 }); 614 }); 615 } 616 }; 617 618 // also handles swipeleft, swiperight 619 $.event.special.swipe = { 620 setup: function() { 621 var thisObject = this, 622 $this = $( thisObject ); 623 624 $this 625 .bind( touchStartEvent, function( event ) { 626 var data = event.originalEvent.touches ? 627 event.originalEvent.touches[ 0 ] : 628 event, 629 start = { 630 time: (new Date).getTime(), 631 coords: [ data.pageX, data.pageY ], 632 origin: $( event.target ) 633 }, 634 stop; 635 636 function moveHandler( event ) { 637 if ( !start ) { 638 return; 639 } 640 641 var data = event.originalEvent.touches ? 642 event.originalEvent.touches[ 0 ] : 643 event; 644 stop = { 645 time: (new Date).getTime(), 646 coords: [ data.pageX, data.pageY ] 647 }; 648 649 // prevent scrolling 650 if ( Math.abs( start.coords[0] - stop.coords[0] ) > 10 ) { 651 event.preventDefault(); 652 } 653 } 654 655 $this 656 .bind( touchMoveEvent, moveHandler ) 657 .one( touchStopEvent, function( event ) { 658 $this.unbind( touchMoveEvent, moveHandler ); 659 if ( start && stop ) { 660 if ( stop.time - start.time < 1000 && 661 Math.abs( start.coords[0] - stop.coords[0]) > 30 && 662 Math.abs( start.coords[1] - stop.coords[1]) < 75 ) { 663 start.origin 664 .trigger( "swipe" ) 665 .trigger( start.coords[0] > stop.coords[0] ? "swipeleft" : "swiperight" ); 666 } 667 } 668 start = stop = undefined; 669 }); 670 }); 671 } 672 }; 673 674 (function($){ 675 // "Cowboy" Ben Alman 676 677 var win = $(window), 678 special_event, 679 get_orientation, 680 last_orientation; 681 682 $.event.special.orientationchange = special_event = { 683 setup: function(){ 684 // If the event is supported natively, return false so that jQuery 685 // will bind to the event using DOM methods. 686 if ( $.support.orientation ) { return false; } 687 688 // Get the current orientation to avoid initial double-triggering. 689 last_orientation = get_orientation(); 690 691 // Because the orientationchange event doesn't exist, simulate the 692 // event by testing window dimensions on resize. 693 win.bind( "resize", handler ); 694 }, 695 teardown: function(){ 696 // If the event is not supported natively, return false so that 697 // jQuery will unbind the event using DOM methods. 698 if ( $.support.orientation ) { return false; } 699 700 // Because the orientationchange event doesn't exist, unbind the 701 // resize event handler. 702 win.unbind( "resize", handler ); 703 }, 704 add: function( handleObj ) { 705 // Save a reference to the bound event handler. 706 var old_handler = handleObj.handler; 707 708 handleObj.handler = function( event ) { 709 // Modify event object, adding the .orientation property. 710 event.orientation = get_orientation(); 711 712 // Call the originally-bound event handler and return its result. 713 return old_handler.apply( this, arguments ); 714 }; 715 } 716 }; 717 718 // If the event is not supported natively, this handler will be bound to 719 // the window resize event to simulate the orientationchange event. 720 function handler() { 721 // Get the current orientation. 722 var orientation = get_orientation(); 723 724 if ( orientation !== last_orientation ) { 725 // The orientation has changed, so trigger the orientationchange event. 726 last_orientation = orientation; 727 win.trigger( "orientationchange" ); 728 } 729 }; 730 731 // Get the current page orientation. This method is exposed publicly, should it 732 // be needed, as jQuery.event.special.orientationchange.orientation() 733 special_event.orientation = get_orientation = function() { 734 var elem = document.documentElement; 735 return elem && elem.clientWidth / elem.clientHeight < 1.1 ? "portrait" : "landscape"; 736 }; 737 738 })(jQuery); 739 740 $.each({ 741 scrollstop: "scrollstart", 742 taphold: "tap", 743 swipeleft: "swipe", 744 swiperight: "swipe" 745 }, function( event, sourceEvent ) { 746 $.event.special[ event ] = { 747 setup: function() { 748 $( this ).bind( sourceEvent, $.noop ); 749 } 750 }; 751 }); 752 753 })( jQuery ); 754 /*! 755 * jQuery hashchange event - v1.3 - 7/21/2010 756 * http://benalman.com/projects/jquery-hashchange-plugin/ 757 * 758 * Copyright (c) 2010 "Cowboy" Ben Alman 759 * Dual licensed under the MIT and GPL licenses. 760 * http://benalman.com/about/license/ 761 */ 762 763 // Script: jQuery hashchange event 764 // 765 // *Version: 1.3, Last updated: 7/21/2010* 766 // 767 // Project Home - http://benalman.com/projects/jquery-hashchange-plugin/ 768 // GitHub - http://github.com/cowboy/jquery-hashchange/ 769 // Source - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.js 770 // (Minified) - http://github.com/cowboy/jquery-hashchange/raw/master/jquery.ba-hashchange.min.js (0.8kb gzipped) 771 // 772 // About: License 773 // 774 // Copyright (c) 2010 "Cowboy" Ben Alman, 775 // Dual licensed under the MIT and GPL licenses. 776 // http://benalman.com/about/license/ 777 // 778 // About: Examples 779 // 780 // These working examples, complete with fully commented code, illustrate a few 781 // ways in which this plugin can be used. 782 // 783 // hashchange event - http://benalman.com/code/projects/jquery-hashchange/examples/hashchange/ 784 // document.domain - http://benalman.com/code/projects/jquery-hashchange/examples/document_domain/ 785 // 786 // About: Support and Testing 787 // 788 // Information about what version or versions of jQuery this plugin has been 789 // tested with, what browsers it has been tested in, and where the unit tests 790 // reside (so you can test it yourself). 791 // 792 // jQuery Versions - 1.2.6, 1.3.2, 1.4.1, 1.4.2 793 // Browsers Tested - Internet Explorer 6-8, Firefox 2-4, Chrome 5-6, Safari 3.2-5, 794 // Opera 9.6-10.60, iPhone 3.1, Android 1.6-2.2, BlackBerry 4.6-5. 795 // Unit Tests - http://benalman.com/code/projects/jquery-hashchange/unit/ 796 // 797 // About: Known issues 798 // 799 // While this jQuery hashchange event implementation is quite stable and 800 // robust, there are a few unfortunate browser bugs surrounding expected 801 // hashchange event-based behaviors, independent of any JavaScript 802 // window.onhashchange abstraction. See the following examples for more 803 // information: 804 // 805 // Chrome: Back Button - http://benalman.com/code/projects/jquery-hashchange/examples/bug-chrome-back-button/ 806 // Firefox: Remote XMLHttpRequest - http://benalman.com/code/projects/jquery-hashchange/examples/bug-firefox-remote-xhr/ 807 // WebKit: Back Button in an Iframe - http://benalman.com/code/projects/jquery-hashchange/examples/bug-webkit-hash-iframe/ 808 // Safari: Back Button from a different domain - http://benalman.com/code/projects/jquery-hashchange/examples/bug-safari-back-from-diff-domain/ 809 // 810 // Also note that should a browser natively support the window.onhashchange 811 // event, but not report that it does, the fallback polling loop will be used. 812 // 813 // About: Release History 814 // 815 // 1.3 - (7/21/2010) Reorganized IE6/7 Iframe code to make it more 816 // "removable" for mobile-only development. Added IE6/7 document.title 817 // support. Attempted to make Iframe as hidden as possible by using 818 // techniques from http://www.paciellogroup.com/blog/?p=604. Added 819 // support for the "shortcut" format $(window).hashchange( fn ) and 820 // $(window).hashchange() like jQuery provides for built-in events. 821 // Renamed jQuery.hashchangeDelay to <jQuery.fn.hashchange.delay> and 822 // lowered its default value to 50. Added <jQuery.fn.hashchange.domain> 823 // and <jQuery.fn.hashchange.src> properties plus document-domain.html 824 // file to address access denied issues when setting document.domain in 825 // IE6/7. 826 // 1.2 - (2/11/2010) Fixed a bug where coming back to a page using this plugin 827 // from a page on another domain would cause an error in Safari 4. Also, 828 // IE6/7 Iframe is now inserted after the body (this actually works), 829 // which prevents the page from scrolling when the event is first bound. 830 // Event can also now be bound before DOM ready, but it won't be usable 831 // before then in IE6/7. 832 // 1.1 - (1/21/2010) Incorporated document.documentMode test to fix IE8 bug 833 // where browser version is incorrectly reported as 8.0, despite 834 // inclusion of the X-UA-Compatible IE=EmulateIE7 meta tag. 835 // 1.0 - (1/9/2010) Initial Release. Broke out the jQuery BBQ event.special 836 // window.onhashchange functionality into a separate plugin for users 837 // who want just the basic event & back button support, without all the 838 // extra awesomeness that BBQ provides. This plugin will be included as 839 // part of jQuery BBQ, but also be available separately. 840 841 (function($,window,undefined){ 842 '$:nomunge'; // Used by YUI compressor. 843 844 // Reused string. 845 var str_hashchange = 'hashchange', 846 847 // Method / object references. 848 doc = document, 849 fake_onhashchange, 850 special = $.event.special, 851 852 // Does the browser support window.onhashchange? Note that IE8 running in 853 // IE7 compatibility mode reports true for 'onhashchange' in window, even 854 // though the event isn't supported, so also test document.documentMode. 855 doc_mode = doc.documentMode, 856 supports_onhashchange = 'on' + str_hashchange in window && ( doc_mode === undefined || doc_mode > 7 ); 857 858 // Get location.hash (or what you'd expect location.hash to be) sans any 859 // leading #. Thanks for making this necessary, Firefox! 860 function get_fragment( url ) { 861 url = url || location.href; 862 return '#' + url.replace( /^[^#]*#?(.*)$/, '$1' ); 863 }; 864 865 // Method: jQuery.fn.hashchange 866 // 867 // Bind a handler to the window.onhashchange event or trigger all bound 868 // window.onhashchange event handlers. This behavior is consistent with 869 // jQuery's built-in event handlers. 870 // 871 // Usage: 872 // 873 // > jQuery(window).hashchange( [ handler ] ); 874 // 875 // Arguments: 876 // 877 // handler - (Function) Optional handler to be bound to the hashchange 878 // event. This is a "shortcut" for the more verbose form: 879 // jQuery(window).bind( 'hashchange', handler ). If handler is omitted, 880 // all bound window.onhashchange event handlers will be triggered. This 881 // is a shortcut for the more verbose 882 // jQuery(window).trigger( 'hashchange' ). These forms are described in 883 // the <hashchange event> section. 884 // 885 // Returns: 886 // 887 // (jQuery) The initial jQuery collection of elements. 888 889 // Allow the "shortcut" format $(elem).hashchange( fn ) for binding and 890 // $(elem).hashchange() for triggering, like jQuery does for built-in events. 891 $.fn[ str_hashchange ] = function( fn ) { 892 return fn ? this.bind( str_hashchange, fn ) : this.trigger( str_hashchange ); 893 }; 894 895 // Property: jQuery.fn.hashchange.delay 896 // 897 // The numeric interval (in milliseconds) at which the <hashchange event> 898 // polling loop executes. Defaults to 50. 899 900 // Property: jQuery.fn.hashchange.domain 901 // 902 // If you're setting document.domain in your JavaScript, and you want hash 903 // history to work in IE6/7, not only must this property be set, but you must 904 // also set document.domain BEFORE jQuery is loaded into the page. This 905 // property is only applicable if you are supporting IE6/7 (or IE8 operating 906 // in "IE7 compatibility" mode). 907 // 908 // In addition, the <jQuery.fn.hashchange.src> property must be set to the 909 // path of the included "document-domain.html" file, which can be renamed or 910 // modified if necessary (note that the document.domain specified must be the 911 // same in both your main JavaScript as well as in this file). 912 // 913 // Usage: 914 // 915 // jQuery.fn.hashchange.domain = document.domain; 916 917 // Property: jQuery.fn.hashchange.src 918 // 919 // If, for some reason, you need to specify an Iframe src file (for example, 920 // when setting document.domain as in <jQuery.fn.hashchange.domain>), you can 921 // do so using this property. Note that when using this property, history 922 // won't be recorded in IE6/7 until the Iframe src file loads. This property 923 // is only applicable if you are supporting IE6/7 (or IE8 operating in "IE7 924 // compatibility" mode). 925 // 926 // Usage: 927 // 928 // jQuery.fn.hashchange.src = 'path/to/file.html'; 929 930 $.fn[ str_hashchange ].delay = 50; 931 /* 932 $.fn[ str_hashchange ].domain = null; 933 $.fn[ str_hashchange ].src = null; 934 */ 935 936 // Event: hashchange event 937 // 938 // Fired when location.hash changes. In browsers that support it, the native 939 // HTML5 window.onhashchange event is used, otherwise a polling loop is 940 // initialized, running every <jQuery.fn.hashchange.delay> milliseconds to 941 // see if the hash has changed. In IE6/7 (and IE8 operating in "IE7 942 // compatibility" mode), a hidden Iframe is created to allow the back button 943 // and hash-based history to work. 944 // 945 // Usage as described in <jQuery.fn.hashchange>: 946 // 947 // > // Bind an event handler. 948 // > jQuery(window).hashchange( function(e) { 949 // > var hash = location.hash; 950 // > ... 951 // > }); 952 // > 953 // > // Manually trigger the event handler. 954 // > jQuery(window).hashchange(); 955 // 956 // A more verbose usage that allows for event namespacing: 957 // 958 // > // Bind an event handler. 959 // > jQuery(window).bind( 'hashchange', function(e) { 960 // > var hash = location.hash; 961 // > ... 962 // > }); 963 // > 964 // > // Manually trigger the event handler. 965 // > jQuery(window).trigger( 'hashchange' ); 966 // 967 // Additional Notes: 968 // 969 // * The polling loop and Iframe are not created until at least one handler 970 // is actually bound to the 'hashchange' event. 971 // * If you need the bound handler(s) to execute immediately, in cases where 972 // a location.hash exists on page load, via bookmark or page refresh for 973 // example, use jQuery(window).hashchange() or the more verbose 974 // jQuery(window).trigger( 'hashchange' ). 975 // * The event can be bound before DOM ready, but since it won't be usable 976 // before then in IE6/7 (due to the necessary Iframe), recommended usage is 977 // to bind it inside a DOM ready handler. 978 979 // Override existing $.event.special.hashchange methods (allowing this plugin 980 // to be defined after jQuery BBQ in BBQ's source code). 981 special[ str_hashchange ] = $.extend( special[ str_hashchange ], { 982 983 // Called only when the first 'hashchange' event is bound to window. 984 setup: function() { 985 // If window.onhashchange is supported natively, there's nothing to do.. 986 if ( supports_onhashchange ) { return false; } 987 988 // Otherwise, we need to create our own. And we don't want to call this 989 // until the user binds to the event, just in case they never do, since it 990 // will create a polling loop and possibly even a hidden Iframe. 991 $( fake_onhashchange.start ); 992 }, 993 994 // Called only when the last 'hashchange' event is unbound from window. 995 teardown: function() { 996 // If window.onhashchange is supported natively, there's nothing to do.. 997 if ( supports_onhashchange ) { return false; } 998 999 // Otherwise, we need to stop ours (if possible). 1000 $( fake_onhashchange.stop ); 1001 } 1002 1003 }); 1004 1005 // fake_onhashchange does all the work of triggering the window.onhashchange 1006 // event for browsers that don't natively support it, including creating a 1007 // polling loop to watch for hash changes and in IE 6/7 creating a hidden 1008 // Iframe to enable back and forward. 1009 fake_onhashchange = (function(){ 1010 var self = {}, 1011 timeout_id, 1012 1013 // Remember the initial hash so it doesn't get triggered immediately. 1014 last_hash = get_fragment(), 1015 1016 fn_retval = function(val){ return val; }, 1017 history_set = fn_retval, 1018 history_get = fn_retval; 1019 1020 // Start the polling loop. 1021 self.start = function() { 1022 timeout_id || poll(); 1023 }; 1024 1025 // Stop the polling loop. 1026 self.stop = function() { 1027 timeout_id && clearTimeout( timeout_id ); 1028 timeout_id = undefined; 1029 }; 1030 1031 // This polling loop checks every $.fn.hashchange.delay milliseconds to see 1032 // if location.hash has changed, and triggers the 'hashchange' event on 1033 // window when necessary. 1034 function poll() { 1035 var hash = get_fragment(), 1036 history_hash = history_get( last_hash ); 1037 1038 if ( hash !== last_hash ) { 1039 history_set( last_hash = hash, history_hash ); 1040 1041 $(window).trigger( str_hashchange ); 1042 1043 } else if ( history_hash !== last_hash ) { 1044 location.href = location.href.replace( /#.*/, '' ) + history_hash; 1045 } 1046 1047 timeout_id = setTimeout( poll, $.fn[ str_hashchange ].delay ); 1048 }; 1049 1050 // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 1051 // vvvvvvvvvvvvvvvvvvv REMOVE IF NOT SUPPORTING IE6/7/8 vvvvvvvvvvvvvvvvvvv 1052 // vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv 1053 $.browser.msie && !supports_onhashchange && (function(){ 1054 // Not only do IE6/7 need the "magical" Iframe treatment, but so does IE8 1055 // when running in "IE7 compatibility" mode. 1056 1057 var iframe, 1058 iframe_src; 1059 1060 // When the event is bound and polling starts in IE 6/7, create a hidden 1061 // Iframe for history handling. 1062 self.start = function(){ 1063 if ( !iframe ) { 1064 iframe_src = $.fn[ str_hashchange ].src; 1065 iframe_src = iframe_src && iframe_src + get_fragment(); 1066 1067 // Create hidden Iframe. Attempt to make Iframe as hidden as possible 1068 // by using techniques from http://www.paciellogroup.com/blog/?p=604. 1069 iframe = $('<iframe tabindex="-1" title="empty"/>').hide() 1070 1071 // When Iframe has completely loaded, initialize the history and 1072 // start polling. 1073 .one( 'load', function(){ 1074 iframe_src || history_set( get_fragment() ); 1075 poll(); 1076 }) 1077 1078 // Load Iframe src if specified, otherwise nothing. 1079 .attr( 'src', iframe_src || 'javascript:0' ) 1080 1081 // Append Iframe after the end of the body to prevent unnecessary 1082 // initial page scrolling (yes, this works). 1083 .insertAfter( 'body' )[0].contentWindow; 1084 1085 // Whenever `document.title` changes, update the Iframe's title to 1086 // prettify the back/next history menu entries. Since IE sometimes 1087 // errors with "Unspecified error" the very first time this is set 1088 // (yes, very useful) wrap this with a try/catch block. 1089 doc.onpropertychange = function(){ 1090 try { 1091 if ( event.propertyName === 'title' ) { 1092 iframe.document.title = doc.title; 1093 } 1094 } catch(e) {} 1095 }; 1096 1097 } 1098 }; 1099 1100 // Override the "stop" method since an IE6/7 Iframe was created. Even 1101 // if there are no longer any bound event handlers, the polling loop 1102 // is still necessary for back/next to work at all! 1103 self.stop = fn_retval; 1104 1105 // Get history by looking at the hidden Iframe's location.hash. 1106 history_get = function() { 1107 return get_fragment( iframe.location.href ); 1108 }; 1109 1110 // Set a new history item by opening and then closing the Iframe 1111 // document, *then* setting its location.hash. If document.domain has 1112 // been set, update that as well. 1113 history_set = function( hash, history_hash ) { 1114 var iframe_doc = iframe.document, 1115 domain = $.fn[ str_hashchange ].domain; 1116 1117 if ( hash !== history_hash ) { 1118 // Update Iframe with any initial `document.title` that might be set. 1119 iframe_doc.title = doc.title; 1120 1121 // Opening the Iframe's document after it has been closed is what 1122 // actually adds a history entry. 1123 iframe_doc.open(); 1124 1125 // Set document.domain for the Iframe document as well, if necessary. 1126 domain && iframe_doc.write( '<script>document.domain="' + domain + '"</script>' ); 1127 1128 iframe_doc.close(); 1129 1130 // Update the Iframe's hash, for great justice. 1131 iframe.location.hash = hash; 1132 } 1133 }; 1134 1135 })(); 1136 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1137 // ^^^^^^^^^^^^^^^^^^^ REMOVE IF NOT SUPPORTING IE6/7/8 ^^^^^^^^^^^^^^^^^^^ 1138 // ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1139 1140 return self; 1141 })(); 1142 1143 })(jQuery,this); 1144 /* 1145 * jQuery Mobile Framework : "page" plugin 1146 * Copyright (c) jQuery Project 1147 * Dual licensed under the MIT or GPL Version 2 licenses. 1148 * http://jquery.org/license 1149 */ 1150 (function($, undefined ) { 1151 1152 $.widget( "mobile.page", $.mobile.widget, { 1153 options: { 1154 backBtnText: "Back", 1155 addBackBtn: true, 1156 degradeInputs: { 1157 color: false, 1158 date: false, 1159 datetime: false, 1160 "datetime-local": false, 1161 email: false, 1162 month: false, 1163 number: false, 1164 range: "number", 1165 search: true, 1166 tel: false, 1167 time: false, 1168 url: false, 1169 week: false 1170 }, 1171 keepNative: null 1172 }, 1173 1174 _create: function() { 1175 var $elem = this.element, 1176 o = this.options; 1177 1178 this.keepNative = "[data-role='none'], [data-role='nojs']" + (o.keepNative ? ", " + o.keepNative : ""); 1179 1180 if ( this._trigger( "beforeCreate" ) === false ) { 1181 return; 1182 } 1183 1184 //some of the form elements currently rely on the presence of ui-page and ui-content 1185 // classes so we'll handle page and content roles outside of the main role processing 1186 // loop below. 1187 $elem.find( "[data-role='page'], [data-role='content']" ).andSelf().each(function() { 1188 $(this).addClass( "ui-" + $(this).data( "role" ) ); 1189 }); 1190 1191 $elem.find( "[data-role='nojs']" ).addClass( "ui-nojs" ); 1192 1193 // pre-find data els 1194 var $dataEls = $elem.find( "[data-role]" ).andSelf().each(function() { 1195 var $this = $( this ), 1196 role = $this.data( "role" ), 1197 theme = $this.data( "theme" ); 1198 1199 //apply theming and markup modifications to page,header,content,footer 1200 if ( role === "header" || role === "footer" ) { 1201 $this.addClass( "ui-bar-" + (theme || $this.parent('[data-role=page]').data( "theme" ) || "a") ); 1202 1203 // add ARIA role 1204 $this.attr( "role", role === "header" ? "banner" : "contentinfo" ); 1205 1206 //right,left buttons 1207 var $headeranchors = $this.children( "a" ), 1208 leftbtn = $headeranchors.hasClass( "ui-btn-left" ), 1209 rightbtn = $headeranchors.hasClass( "ui-btn-right" ); 1210 1211 if ( !leftbtn ) { 1212 leftbtn = $headeranchors.eq( 0 ).not( ".ui-btn-right" ).addClass( "ui-btn-left" ).length; 1213 } 1214 1215 if ( !rightbtn ) { 1216 rightbtn = $headeranchors.eq( 1 ).addClass( "ui-btn-right" ).length; 1217 } 1218 1219 // auto-add back btn on pages beyond first view 1220 if ( o.addBackBtn && role === "header" && 1221 $( ".ui-page" ).length > 1 && 1222 $elem.data( "url" ) !== $.mobile.path.stripHash( location.hash ) && 1223 !leftbtn && $this.data( "backbtn" ) !== false ) { 1224 1225 $( "<a href='#' class='ui-btn-left' data-rel='back' data-icon='arrow-l'>"+ o.backBtnText +"</a>" ).prependTo( $this ); 1226 } 1227 1228 //page title 1229 $this.children( "h1, h2, h3, h4, h5, h6" ) 1230 .addClass( "ui-title" ) 1231 //regardless of h element number in src, it becomes h1 for the enhanced page 1232 .attr({ "tabindex": "0", "role": "heading", "aria-level": "1" }); 1233 1234 } else if ( role === "content" ) { 1235 if ( theme ) { 1236 $this.addClass( "ui-body-" + theme ); 1237 } 1238 1239 // add ARIA role 1240 $this.attr( "role", "main" ); 1241 1242 } else if ( role === "page" ) { 1243 $this.addClass( "ui-body-" + (theme || "c") ); 1244 } 1245 1246 switch(role) { 1247 case "header": 1248 case "footer": 1249 case "page": 1250 case "content": 1251 $this.addClass( "ui-" + role ); 1252 break; 1253 case "collapsible": 1254 case "fieldcontain": 1255 case "navbar": 1256 case "listview": 1257 case "dialog": 1258 $this[ role ](); 1259 break; 1260 } 1261 }); 1262 1263 //enhance form controls 1264 this._enhanceControls(); 1265 1266 //links in bars, or those with data-role become buttons 1267 $elem.find( "[data-role='button'], .ui-bar > a, .ui-header > a, .ui-footer > a" ) 1268 .not( ".ui-btn" ) 1269 .not(this.keepNative) 1270 .buttonMarkup(); 1271 1272 $elem 1273 .find("[data-role='controlgroup']") 1274 .controlgroup(); 1275 1276 //links within content areas 1277 $elem.find( "a:not(.ui-btn):not(.ui-link-inherit)" ) 1278 .not(this.keepNative) 1279 .addClass( "ui-link" ); 1280 1281 //fix toolbars 1282 $elem.fixHeaderFooter(); 1283 }, 1284 1285 _enhanceControls: function() { 1286 var o = this.options; 1287 1288 // degrade inputs to avoid poorly implemented native functionality 1289 this.element.find( "input" ).not(this.keepNative).each(function() { 1290 var type = this.getAttribute( "type" ), 1291 optType = o.degradeInputs[ type ] || "text"; 1292 1293 if ( o.degradeInputs[ type ] ) { 1294 $( this ).replaceWith( 1295 $( "<div>" ).html( $(this).clone() ).html() 1296 .replace( /type="([a-zA-Z]+)"/, "type="+ optType +" data-type='$1'" ) ); 1297 } 1298 }); 1299 1300 // We re-find form elements since the degredation code above 1301 // may have injected new elements. We cache the non-native control 1302 // query to reduce the number of times we search through the entire page. 1303 1304 var allControls = this.element.find("input, textarea, select, button"), 1305 nonNativeControls = allControls.not(this.keepNative); 1306 1307 // XXX: Temporary workaround for issue 785. Turn off autocorrect and 1308 // autocomplete since the popup they use can't be dismissed by 1309 // the user. Note that we test for the presence of the feature 1310 // by looking for the autocorrect property on the input element. 1311 1312 var textInputs = allControls.filter( "input[type=text]" ); 1313 if (textInputs.length && typeof textInputs[0].autocorrect !== "undefined") { 1314 textInputs.each(function(){ 1315 // Set the attribute instead of the property just in case there 1316 // is code that attempts to make modifications via HTML. 1317 this.setAttribute("autocorrect", "off"); 1318 this.setAttribute("autocomplete", "off"); 1319 }); 1320 } 1321 1322 // enchance form controls 1323 nonNativeControls 1324 .filter( "[type='radio'], [type='checkbox']" ) 1325 .checkboxradio(); 1326 1327 nonNativeControls 1328 .filter( "button, [type='button'], [type='submit'], [type='reset'], [type='image']" ) 1329 .button(); 1330 1331 nonNativeControls 1332 .filter( "input, textarea" ) 1333 .not( "[type='radio'], [type='checkbox'], [type='button'], [type='submit'], [type='reset'], [type='image'], [type='hidden']" ) 1334 .textinput(); 1335 1336 nonNativeControls 1337 .filter( "input, select" ) 1338 .filter( "[data-role='slider'], [data-type='range']" ) 1339 .slider(); 1340 1341 nonNativeControls 1342 .filter( "select:not([data-role='slider'])" ) 1343 .selectmenu(); 1344 } 1345 }); 1346 1347 })( jQuery ); 1348 /*! 1349 * jQuery Mobile v@VERSION 1350 * http://jquerymobile.com/ 1351 * 1352 * Copyright 2010, jQuery Project 1353 * Dual licensed under the MIT or GPL Version 2 licenses. 1354 * http://jquery.org/license 1355 */ 1356 1357 (function( $, window, undefined ) { 1358 1359 //jQuery.mobile configurable options 1360 $.extend( $.mobile, { 1361 1362 //define the url parameter used for referencing widget-generated sub-pages. 1363 //Translates to to example.html&ui-page=subpageIdentifier 1364 //hash segment before &ui-page= is used to make Ajax request 1365 subPageUrlKey: "ui-page", 1366 1367 //anchor links with a data-rel, or pages with a data-role, that match these selectors will be untrackable in history 1368 //(no change in URL, not bookmarkable) 1369 nonHistorySelectors: "dialog", 1370 1371 //class assigned to page currently in view, and during transitions 1372 activePageClass: "ui-page-active", 1373 1374 //class used for "active" button state, from CSS framework 1375 activeBtnClass: "ui-btn-active", 1376 1377 //automatically handle clicks and form submissions through Ajax, when same-domain 1378 ajaxEnabled: true, 1379 1380 //automatically load and show pages based on location.hash 1381 hashListeningEnabled: true, 1382 1383 // TODO: deprecated - remove at 1.0 1384 //automatically handle link clicks through Ajax, when possible 1385 ajaxLinksEnabled: true, 1386 1387 // TODO: deprecated - remove at 1.0 1388 //automatically handle form submissions through Ajax, when possible 1389 ajaxFormsEnabled: true, 1390 1391 //set default transition - 'none' for no transitions 1392 defaultTransition: "slide", 1393 1394 //show loading message during Ajax requests 1395 //if false, message will not appear, but loading classes will still be toggled on html el 1396 loadingMessage: "loading", 1397 1398 //configure meta viewport tag's content attr: 1399 metaViewportContent: "width=device-width, minimum-scale=1, maximum-scale=1", 1400 1401 //support conditions that must be met in order to proceed 1402 gradeA: function(){ 1403 return $.support.mediaquery; 1404 }, 1405 1406 //automatically initialize first pages or not. 1407 autoInitialize: true, 1408 1409 //TODO might be useful upstream in jquery itself ? 1410 keyCode: { 1411 ALT: 18, 1412 BACKSPACE: 8, 1413 CAPS_LOCK: 20, 1414 COMMA: 188, 1415 COMMAND: 91, 1416 COMMAND_LEFT: 91, // COMMAND 1417 COMMAND_RIGHT: 93, 1418 CONTROL: 17, 1419 DELETE: 46, 1420 DOWN: 40, 1421 END: 35, 1422 ENTER: 13, 1423 ESCAPE: 27, 1424 HOME: 36, 1425 INSERT: 45, 1426 LEFT: 37, 1427 MENU: 93, // COMMAND_RIGHT 1428 NUMPAD_ADD: 107, 1429 NUMPAD_DECIMAL: 110, 1430 NUMPAD_DIVIDE: 111, 1431 NUMPAD_ENTER: 108, 1432 NUMPAD_MULTIPLY: 106, 1433 NUMPAD_SUBTRACT: 109, 1434 PAGE_DOWN: 34, 1435 PAGE_UP: 33, 1436 PERIOD: 190, 1437 RIGHT: 39, 1438 SHIFT: 16, 1439 SPACE: 32, 1440 TAB: 9, 1441 UP: 38, 1442 WINDOWS: 91 // COMMAND 1443 } 1444 }); 1445 1446 1447 1448 //trigger mobileinit event - useful hook for configuring $.mobile settings before they're used 1449 $( window.document ).trigger( "mobileinit" ); 1450 1451 //support conditions 1452 //if device support condition(s) aren't met, leave things as they are -> a basic, usable experience, 1453 //otherwise, proceed with the enhancements 1454 if ( !$.mobile.gradeA() ) { 1455 return; 1456 } 1457 1458 1459 //define vars for interal use 1460 var $window = $(window), 1461 $html = $( "html" ), 1462 $head = $( "head" ), 1463 1464 //loading div which appears during Ajax requests 1465 //will not appear if $.mobile.loadingMessage is false 1466 $loader = $.mobile.loadingMessage ? 1467 $( "<div class='ui-loader ui-body-a ui-corner-all'>" + 1468 "<span class='ui-icon ui-icon-loading spin'></span>" + 1469 "<h1>" + $.mobile.loadingMessage + "</h1>" + 1470 "</div>" ) 1471 : undefined; 1472 1473 1474 //add mobile, initial load "rendering" classes to docEl 1475 $html.addClass( "ui-mobile ui-mobile-rendering" ); 1476 1477 1478 //define & prepend meta viewport tag, if content is defined 1479 $.mobile.metaViewportContent ? $( "<meta>", { name: "viewport", content: $.mobile.metaViewportContent}).prependTo( $head ) : undefined; 1480 1481 1482 //expose some core utilities 1483 $.extend($.mobile, { 1484 1485 // turn on/off page loading message. 1486 pageLoading: function ( done ) { 1487 if ( done ) { 1488 $html.removeClass( "ui-loading" ); 1489 } else { 1490 if( $.mobile.loadingMessage ){ 1491 var activeBtn =$( "." + $.mobile.activeBtnClass ).first(); 1492 1493 $loader 1494 .appendTo( $.mobile.pageContainer ) 1495 //position at y center (if scrollTop supported), above the activeBtn (if defined), or just 100px from top 1496 .css( { 1497 top: $.support.scrollTop && $(window).scrollTop() + $(window).height() / 2 || 1498 activeBtn.length && activeBtn.offset().top || 100 1499 } ); 1500 } 1501 1502 $html.addClass( "ui-loading" ); 1503 } 1504 }, 1505 1506 //scroll page vertically: scroll to 0 to hide iOS address bar, or pass a Y value 1507 silentScroll: function( ypos ) { 1508 ypos = ypos || 0; 1509 // prevent scrollstart and scrollstop events 1510 $.event.special.scrollstart.enabled = false; 1511 1512 setTimeout(function() { 1513 window.scrollTo( 0, ypos ); 1514 $(document).trigger( "silentscroll", { x: 0, y: ypos }); 1515 },20); 1516 1517 setTimeout(function() { 1518 $.event.special.scrollstart.enabled = true; 1519 }, 150 ); 1520 }, 1521 1522 // find and enhance the pages in the dom and transition to the first page. 1523 initializePage: function(){ 1524 //find present pages 1525 var $pages = $( "[data-role='page']" ); 1526 1527 //add dialogs, set data-url attrs 1528 $pages.add( "[data-role='dialog']" ).each(function(){ 1529 $(this).attr( "data-url", $(this).attr( "id" )); 1530 }); 1531 1532 //define first page in dom case one backs out to the directory root (not always the first page visited, but defined as fallback) 1533 $.mobile.firstPage = $pages.first(); 1534 1535 //define page container 1536 $.mobile.pageContainer = $pages.first().parent().addClass( "ui-mobile-viewport" ); 1537 1538 //cue page loading message 1539 $.mobile.pageLoading(); 1540 1541 // if hashchange listening is disabled or there's no hash deeplink, change to the first page in the DOM 1542 if( !$.mobile.hashListeningEnabled || !$.mobile.path.stripHash( location.hash ) ){ 1543 $.mobile.changePage( $.mobile.firstPage, false, true, false, true ); 1544 } 1545 // otherwise, trigger a hashchange to load a deeplink 1546 else { 1547 $window.trigger( "hashchange", [ true ] ); 1548 } 1549 } 1550 }); 1551 1552 //dom-ready inits 1553 if($.mobile.autoInitialize){ 1554 $($.mobile.initializePage); 1555 } 1556 1557 1558 //window load event 1559 //hide iOS browser chrome on load 1560 $window.load( $.mobile.silentScroll ); 1561 1562 })( jQuery, this ); 1563 /* 1564 * jQuery Mobile Framework : core utilities for auto ajax navigation, base tag mgmt, 1565 * Copyright (c) jQuery Project 1566 * Dual licensed under the MIT or GPL Version 2 licenses. 1567 * http://jquery.org/license 1568 */ 1569 (function($, undefined ) { 1570 1571 //define vars for interal use 1572 var $window = $(window), 1573 $html = $('html'), 1574 $head = $('head'), 1575 1576 //url path helpers for use in relative url management 1577 path = { 1578 1579 //get path from current hash, or from a file path 1580 get: function( newPath ){ 1581 if( newPath == undefined ){ 1582 newPath = location.hash; 1583 } 1584 return path.stripHash( newPath ).replace(/[^\/]*\.[^\/*]+$/, ''); 1585 }, 1586 1587 //return the substring of a filepath before the sub-page key, for making a server request 1588 getFilePath: function( path ){ 1589 var splitkey = '&' + $.mobile.subPageUrlKey; 1590 return path && path.split( splitkey )[0].split( dialogHashKey )[0]; 1591 }, 1592 1593 //set location hash to path 1594 set: function( path ){ 1595 location.hash = path; 1596 }, 1597 1598 //location pathname from intial directory request 1599 origin: '', 1600 1601 setOrigin: function(){ 1602 path.origin = path.get( location.protocol + '//' + location.host + location.pathname ); 1603 }, 1604 1605 //prefix a relative url with the current path 1606 makeAbsolute: function( url ){ 1607 return path.get() + url; 1608 }, 1609 1610 //return a url path with the window's location protocol/hostname removed 1611 clean: function( url ){ 1612 return url.replace( location.protocol + "//" + location.host, ""); 1613 }, 1614 1615 //just return the url without an initial # 1616 stripHash: function( url ){ 1617 return url.replace( /^#/, "" ); 1618 }, 1619 1620 //check whether a url is referencing the same domain, or an external domain or different protocol 1621 //could be mailto, etc 1622 isExternal: function( url ){ 1623 return path.hasProtocol( path.clean( url ) ); 1624 }, 1625 1626 hasProtocol: function( url ){ 1627 return /^(:?\w+:)/.test( url ); 1628 }, 1629 1630 //check if the url is relative 1631 isRelative: function( url ){ 1632 return /^[^\/|#]/.test( url ) && !path.hasProtocol( url ); 1633 }, 1634 1635 isEmbeddedPage: function( url ){ 1636 return /^#/.test( url ); 1637 } 1638 }, 1639 1640 //will be defined when a link is clicked and given an active class 1641 $activeClickedLink = null, 1642 1643 //urlHistory is purely here to make guesses at whether the back or forward button was clicked 1644 //and provide an appropriate transition 1645 urlHistory = { 1646 //array of pages that are visited during a single page load. each has a url and optional transition 1647 stack: [], 1648 1649 //maintain an index number for the active page in the stack 1650 activeIndex: 0, 1651 1652 //get active 1653 getActive: function(){ 1654 return urlHistory.stack[ urlHistory.activeIndex ]; 1655 }, 1656 1657 getPrev: function(){ 1658 return urlHistory.stack[ urlHistory.activeIndex - 1 ]; 1659 }, 1660 1661 getNext: function(){ 1662 return urlHistory.stack[ urlHistory.activeIndex + 1 ]; 1663 }, 1664 1665 // addNew is used whenever a new page is added 1666 addNew: function( url, transition ){ 1667 //if there's forward history, wipe it 1668 if( urlHistory.getNext() ){ 1669 urlHistory.clearForward(); 1670 } 1671 1672 urlHistory.stack.push( {url : url, transition: transition } ); 1673 1674 urlHistory.activeIndex = urlHistory.stack.length - 1; 1675 }, 1676 1677 //wipe urls ahead of active index 1678 clearForward: function(){ 1679 urlHistory.stack = urlHistory.stack.slice( 0, urlHistory.activeIndex + 1 ); 1680 }, 1681 1682 //disable hashchange event listener internally to ignore one change 1683 //toggled internally when location.hash is updated to match the url of a successful page load 1684 ignoreNextHashChange: true 1685 }, 1686 1687 //define first selector to receive focus when a page is shown 1688 focusable = "[tabindex],a,button:visible,select:visible,input", 1689 1690 //contains role for next page, if defined on clicked link via data-rel 1691 nextPageRole = null, 1692 1693 //queue to hold simultanious page transitions 1694 pageTransitionQueue = [], 1695 1696 // indicates whether or not page is in process of transitioning 1697 isPageTransitioning = false, 1698 1699 //nonsense hash change key for dialogs, so they create a history entry 1700 dialogHashKey = "&ui-state=dialog"; 1701 1702 //existing base tag? 1703 var $base = $head.children("base"), 1704 hostURL = location.protocol + '//' + location.host, 1705 docLocation = path.get( hostURL + location.pathname ), 1706 docBase = docLocation; 1707 1708 if ($base.length){ 1709 var href = $base.attr("href"); 1710 if (href){ 1711 if (href.search(/^[^:/]+:\/\/[^/]+\/?/) == -1){ 1712 //the href is not absolute, we need to turn it into one 1713 //so that we can turn paths stored in our location hash into 1714 //relative paths. 1715 if (href.charAt(0) == '/'){ 1716 //site relative url 1717 docBase = hostURL + href; 1718 } 1719 else { 1720 //the href is a document relative url 1721 docBase = docLocation + href; 1722 //XXX: we need some code here to calculate the final path 1723 // just in case the docBase contains up-level (../) references. 1724 } 1725 } 1726 else { 1727 //the href is an absolute url 1728 docBase = href; 1729 } 1730 } 1731 //make sure docBase ends with a slash 1732 docBase = docBase + (docBase.charAt(docBase.length - 1) == '/' ? ' ' : '/'); 1733 } 1734 1735 //base element management, defined depending on dynamic base tag support 1736 base = $.support.dynamicBaseTag ? { 1737 1738 //define base element, for use in routing asset urls that are referenced in Ajax-requested markup 1739 element: ($base.length ? $base : $("<base>", { href: docBase }).prependTo( $head )), 1740 1741 //set the generated BASE element's href attribute to a new page's base path 1742 set: function( href ){ 1743 base.element.attr('href', docBase + path.get( href )); 1744 }, 1745 1746 //set the generated BASE element's href attribute to a new page's base path 1747 reset: function(){ 1748 base.element.attr('href', docBase ); 1749 } 1750 1751 } : undefined; 1752 1753 1754 1755 //set location pathname from intial directory request 1756 path.setOrigin(); 1757 1758 /* 1759 internal utility functions 1760 --------------------------------------*/ 1761 1762 1763 //direct focus to the page title, or otherwise first focusable element 1764 function reFocus( page ){ 1765 var pageTitle = page.find( ".ui-title:eq(0)" ); 1766 if( pageTitle.length ){ 1767 pageTitle.focus(); 1768 } 1769 else{ 1770 page.find( focusable ).eq(0).focus(); 1771 } 1772 }; 1773 1774 //remove active classes after page transition or error 1775 function removeActiveLinkClass( forceRemoval ){ 1776 if( !!$activeClickedLink && (!$activeClickedLink.closest( '.ui-page-active' ).length || forceRemoval )){ 1777 $activeClickedLink.removeClass( $.mobile.activeBtnClass ); 1778 } 1779 $activeClickedLink = null; 1780 }; 1781 1782 //animation complete callback 1783 $.fn.animationComplete = function( callback ){ 1784 if($.support.cssTransitions){ 1785 return $(this).one('webkitAnimationEnd', callback); 1786 } 1787 else{ 1788 // defer execution for consistency between webkit/non webkit 1789 setTimeout(callback, 0); 1790 } 1791 }; 1792 1793 1794 1795 /* exposed $.mobile methods */ 1796 1797 //update location.hash, with or without triggering hashchange event 1798 //TODO - deprecate this one at 1.0 1799 $.mobile.updateHash = path.set; 1800 1801 //expose path object on $.mobile 1802 $.mobile.path = path; 1803 1804 //expose base object on $.mobile 1805 $.mobile.base = base; 1806 1807 //url stack, useful when plugins need to be aware of previous pages viewed 1808 //TODO: deprecate this one at 1.0 1809 $.mobile.urlstack = urlHistory.stack; 1810 1811 //history stack 1812 $.mobile.urlHistory = urlHistory; 1813 1814 // changepage function 1815 // TODO : consider moving args to an object hash 1816 $.mobile.changePage = function( to, transition, reverse, changeHash, fromHashChange ){ 1817 //from is always the currently viewed page 1818 var toIsArray = $.type(to) === "array", 1819 toIsObject = $.type(to) === "object", 1820 from = toIsArray ? to[0] : $.mobile.activePage, 1821 to = toIsArray ? to[1] : to, 1822 url = fileUrl = $.type(to) === "string" ? path.stripHash( to ) : "", 1823 data = undefined, 1824 type = 'get', 1825 isFormRequest = false, 1826 duplicateCachedPage = null, 1827 currPage = urlHistory.getActive(), 1828 back = false, 1829 forward = false; 1830 1831 1832 // If we are trying to transition to the same page that we are currently on ignore the request. 1833 // an illegal same page request is defined by the current page being the same as the url, as long as there's history 1834 // and to is not an array or object (those are allowed to be "same") 1835 if( currPage && urlHistory.stack.length > 1 && currPage.url === url && !toIsArray && !toIsObject ) { 1836 return; 1837 } 1838 else if(isPageTransitioning) { 1839 pageTransitionQueue.unshift(arguments); 1840 return; 1841 } 1842 1843 isPageTransitioning = true; 1844 1845 // if the changePage was sent from a hashChange event 1846 // guess if it came from the history menu 1847 if( fromHashChange ){ 1848 1849 // check if url is in history and if it's ahead or behind current page 1850 $.each( urlHistory.stack, function( i ){ 1851 //if the url is in the stack, it's a forward or a back 1852 if( this.url == url ){ 1853 urlIndex = i; 1854 //define back and forward by whether url is older or newer than current page 1855 back = i < urlHistory.activeIndex; 1856 //forward set to opposite of back 1857 forward = !back; 1858 //reset activeIndex to this one 1859 urlHistory.activeIndex = i; 1860 } 1861 }); 1862 1863 //if it's a back, use reverse animation 1864 if( back ){ 1865 reverse = true; 1866 transition = transition || currPage.transition; 1867 } 1868 else if ( forward ){ 1869 transition = transition || urlHistory.getActive().transition; 1870 } 1871 } 1872 1873 1874 if( toIsObject && to.url ){ 1875 url = to.url, 1876 data = to.data, 1877 type = to.type, 1878 isFormRequest = true; 1879 //make get requests bookmarkable 1880 if( data && type == 'get' ){ 1881 if($.type( data ) == "object" ){ 1882 data = $.param(data); 1883 } 1884 1885 url += "?" + data; 1886 data = undefined; 1887 } 1888 } 1889 1890 //reset base to pathname for new request 1891 if(base){ base.reset(); } 1892 1893 //kill the keyboard 1894 $( window.document.activeElement ).add( "input:focus, textarea:focus, select:focus" ).blur(); 1895 1896 function defaultTransition(){ 1897 if(transition === undefined){ 1898 transition = $.mobile.defaultTransition; 1899 } 1900 } 1901 1902 //function for transitioning between two existing pages 1903 function transitionPages() { 1904 $.mobile.silentScroll(); 1905 1906 //get current scroll distance 1907 var currScroll = $window.scrollTop(), 1908 perspectiveTransitions = [ "flip" ], 1909 pageContainerClasses = []; 1910 1911 //support deep-links to generated sub-pages 1912 if( url.indexOf( "&" + $.mobile.subPageUrlKey ) > -1 ){ 1913 to = $( "[data-url='" + url + "']" ); 1914 } 1915 1916 if( from ){ 1917 //set as data for returning to that spot 1918 from.data( "lastScroll", currScroll); 1919 //trigger before show/hide events 1920 from.data( "page" )._trigger( "beforehide", { nextPage: to } ); 1921 } 1922 to.data( "page" )._trigger( "beforeshow", { prevPage: from || $("") } ); 1923 1924 function loadComplete(){ 1925 1926 if( changeHash !== false && url ){ 1927 //disable hash listening temporarily 1928 urlHistory.ignoreNextHashChange = false; 1929 //update hash and history 1930 path.set( url ); 1931 } 1932 1933 //add page to history stack if it's not back or forward 1934 if( !back && !forward ){ 1935 urlHistory.addNew( url, transition ); 1936 } 1937 1938 removeActiveLinkClass(); 1939 1940 //jump to top or prev scroll, sometimes on iOS the page has not rendered yet. I could only get by this with a setTimeout, but would like to avoid that. 1941 $.mobile.silentScroll( to.data( "lastScroll" ) ); 1942 1943 reFocus( to ); 1944 1945 //trigger show/hide events 1946 if( from ){ 1947 from.data( "page" )._trigger( "hide", null, { nextPage: to } ); 1948 } 1949 //trigger pageshow, define prevPage as either from or empty jQuery obj 1950 to.data( "page" )._trigger( "show", null, { prevPage: from || $("") } ); 1951 1952 //set "to" as activePage 1953 $.mobile.activePage = to; 1954 1955 //if there's a duplicateCachedPage, remove it from the DOM now that it's hidden 1956 if (duplicateCachedPage != null) { 1957 duplicateCachedPage.remove(); 1958 } 1959 1960 //remove initial build class (only present on first pageshow) 1961 $html.removeClass( "ui-mobile-rendering" ); 1962 1963 isPageTransitioning = false 1964 if(pageTransitionQueue.length>0) { 1965 $.mobile.changePage.apply($.mobile, pageTransitionQueue.pop()); 1966 } 1967 }; 1968 1969 function addContainerClass(className){ 1970 $.mobile.pageContainer.addClass(className); 1971 pageContainerClasses.push(className); 1972 }; 1973 1974 function removeContainerClasses(){ 1975 $.mobile 1976 .pageContainer 1977 .removeClass(pageContainerClasses.join(" ")); 1978 1979 pageContainerClasses = []; 1980 }; 1981 1982 1983 1984 if(transition && (transition !== 'none')){ 1985 $.mobile.pageLoading( true ); 1986 if( $.inArray(transition, perspectiveTransitions) >= 0 ){ 1987 addContainerClass('ui-mobile-viewport-perspective'); 1988 } 1989 1990 addContainerClass('ui-mobile-viewport-transitioning'); 1991 1992 if( from ){ 1993 from.addClass( transition + " out " + ( reverse ? "reverse" : "" ) ); 1994 } 1995 to.addClass( $.mobile.activePageClass + " " + transition + 1996 " in " + ( reverse ? "reverse" : "" ) ); 1997 1998 // callback - remove classes, etc 1999 to.animationComplete(function() { 2000 from.add( to ).removeClass("out in reverse " + transition ); 2001 if( from ){ 2002 from.removeClass( $.mobile.activePageClass ); 2003 } 2004 loadComplete(); 2005 removeContainerClasses(); 2006 }); 2007 } 2008 else{ 2009 $.mobile.pageLoading( true ); 2010 if( from ){ 2011 from.removeClass( $.mobile.activePageClass ); 2012 } 2013 to.addClass( $.mobile.activePageClass ); 2014 loadComplete(); 2015 } 2016 }; 2017 2018 //shared page enhancements 2019 function enhancePage(){ 2020 2021 //set next page role, if defined 2022 if ( nextPageRole || to.data('role') == 'dialog' ) { 2023 url = urlHistory.getActive().url + dialogHashKey; 2024 if(nextPageRole){ 2025 to.attr( "data-role", nextPageRole ); 2026 nextPageRole = null; 2027 } 2028 } 2029 2030 //run page plugin 2031 to.page(); 2032 }; 2033 2034 //if url is a string 2035 if( url ){ 2036 to = $( "[data-url='" + url + "']" ); 2037 fileUrl = path.getFilePath(url); 2038 } 2039 else{ //find base url of element, if avail 2040 var toID = to.attr('data-url'), 2041 toIDfileurl = path.getFilePath(toID); 2042 2043 if(toID != toIDfileurl){ 2044 fileUrl = toIDfileurl; 2045 } 2046 } 2047 2048 // ensure a transition has been set where pop is undefined 2049 defaultTransition(); 2050 2051 // find the "to" page, either locally existing in the dom or by creating it through ajax 2052 if ( to.length && !isFormRequest ) { 2053 if( fileUrl && base ){ 2054 base.set( fileUrl ); 2055 } 2056 enhancePage(); 2057 transitionPages(); 2058 } else { 2059 2060 //if to exists in DOM, save a reference to it in duplicateCachedPage for removal after page change 2061 if( to.length ){ 2062 duplicateCachedPage = to; 2063 } 2064 2065 $.mobile.pageLoading(); 2066 2067 $.ajax({ 2068 url: fileUrl, 2069 type: type, 2070 data: data, 2071 success: function( html ) { 2072 2073 //pre-parse html to check for a data-url, 2074 //use it as the new fileUrl, base path, etc 2075 var redirectLoc = / data-url="(.*)"/.test( html ) && RegExp.$1; 2076 2077 if( redirectLoc ){ 2078 if(base){ 2079 base.set( redirectLoc ); 2080 } 2081 url = fileUrl = path.getFilePath( redirectLoc ); 2082 } 2083 else { 2084 if(base){ 2085 base.set(fileUrl); 2086 } 2087 } 2088 2089 var all = $("<div></div>"); 2090 //workaround to allow scripts to execute when included in page divs 2091 all.get(0).innerHTML = html; 2092 to = all.find('[data-role="page"], [data-role="dialog"]').first(); 2093 2094 //rewrite src and href attrs to use a base url 2095 if( !$.support.dynamicBaseTag ){ 2096 var newPath = path.get( fileUrl ); 2097 to.find('[src],link[href]').each(function(){ 2098 var thisAttr = $(this).is('[href]') ? 'href' : 'src', 2099 thisUrl = $(this).attr(thisAttr); 2100 2101 //if full path exists and is same, chop it - helps IE out 2102 thisUrl.replace( location.protocol + '//' + location.host + location.pathname, '' ); 2103 2104 if( !/^(\w+:|#|\/)/.test(thisUrl) ){ 2105 $(this).attr(thisAttr, newPath + thisUrl); 2106 } 2107 }); 2108 } 2109 2110 //append to page and enhance 2111 to 2112 .attr( "data-url", fileUrl ) 2113 .appendTo( $.mobile.pageContainer ); 2114 2115 enhancePage(); 2116 setTimeout(function() { transitionPages() }, 0); 2117 }, 2118 error: function() { 2119 $.mobile.pageLoading( true ); 2120 removeActiveLinkClass(true); 2121 if(base){ 2122 base.set(path.get()); 2123 } 2124 $("<div class='ui-loader ui-overlay-shadow ui-body-e ui-corner-all'><h1>Error Loading Page</h1></div>") 2125 .css({ "display": "block", "opacity": 0.96, "top": $(window).scrollTop() + 100 }) 2126 .appendTo( $.mobile.pageContainer ) 2127 .delay( 800 ) 2128 .fadeOut( 400, function(){ 2129 $(this).remove(); 2130 }); 2131 } 2132 }); 2133 } 2134 2135 }; 2136 2137 2138 /* Event Bindings - hashchange, submit, and click */ 2139 2140 //bind to form submit events, handle with Ajax 2141 $( "form[data-ajax!='false']" ).live('submit', function(event){ 2142 if( !$.mobile.ajaxEnabled || 2143 //TODO: deprecated - remove at 1.0 2144 !$.mobile.ajaxFormsEnabled ){ return; } 2145 2146 var type = $(this).attr("method"), 2147 url = path.clean( $(this).attr( "action" ) ); 2148 2149 //external submits use regular HTTP 2150 if( path.isExternal( url ) ){ 2151 return; 2152 } 2153 2154 //if it's a relative href, prefix href with base url 2155 if( path.isRelative( url ) ){ 2156 url = path.makeAbsolute( url ); 2157 } 2158 2159 $.mobile.changePage({ 2160 url: url, 2161 type: type, 2162 data: $(this).serialize() 2163 }, 2164 undefined, 2165 undefined, 2166 true 2167 ); 2168 event.preventDefault(); 2169 }); 2170 2171 2172 //click routing - direct to HTTP or Ajax, accordingly 2173 $( "a" ).live( "click", function(event) { 2174 2175 var $this = $(this), 2176 2177 //get href, if defined, otherwise fall to null # 2178 href = $this.attr( "href" ) || "#", 2179 2180 //get href, remove same-domain protocol and host 2181 url = path.clean( href ), 2182 2183 //rel set to external 2184 isRelExternal = $this.is( "[rel='external']" ), 2185 2186 //rel set to external 2187 isEmbeddedPage = path.isEmbeddedPage( url ), 2188 2189 //check for protocol or rel and its not an embedded page 2190 //TODO overlap in logic from isExternal, rel=external check should be 2191 // moved into more comprehensive isExternalLink 2192 isExternal = path.isExternal( url ) || isRelExternal && !isEmbeddedPage, 2193 2194 //if target attr is specified we mimic _blank... for now 2195 hasTarget = $this.is( "[target]" ), 2196 2197 //if data-ajax attr is set to false, use the default behavior of a link 2198 hasAjaxDisabled = $this.is( "[data-ajax='false']" ); 2199 2200 //if there's a data-rel=back attr, go back in history 2201 if( $this.is( "[data-rel='back']" ) ){ 2202 window.history.back(); 2203 return false; 2204 } 2205 2206 if( url === "#" ){ 2207 //for links created purely for interaction - ignore 2208 return false; 2209 } 2210 2211 $activeClickedLink = $this.closest( ".ui-btn" ).addClass( $.mobile.activeBtnClass ); 2212 2213 if( isExternal || hasAjaxDisabled || hasTarget || !$.mobile.ajaxEnabled || 2214 // TODO: deprecated - remove at 1.0 2215 !$.mobile.ajaxLinksEnabled ){ 2216 //remove active link class if external (then it won't be there if you come back) 2217 removeActiveLinkClass(true); 2218 2219 //deliberately redirect, in case click was triggered 2220 if( hasTarget ){ 2221 window.open( url ); 2222 } 2223 else if( hasAjaxDisabled ){ 2224 return; 2225 } 2226 else{ 2227 location.href = url; 2228 } 2229 } 2230 else { 2231 //use ajax 2232 var transition = $this.data( "transition" ), 2233 direction = $this.data("direction"), 2234 reverse = direction && direction == "reverse" || 2235 // deprecated - remove by 1.0 2236 $this.data( "back" ); 2237 2238 //this may need to be more specific as we use data-rel more 2239 nextPageRole = $this.attr( "data-rel" ); 2240 2241 //if it's a relative href, prefix href with base url 2242 if( path.isRelative( url ) ){ 2243 url = path.makeAbsolute( url ); 2244 } 2245 2246 url = path.stripHash( url ); 2247 2248 $.mobile.changePage( url, transition, reverse); 2249 } 2250 event.preventDefault(); 2251 }); 2252 2253 2254 //hashchange event handler 2255 $window.bind( "hashchange", function( e, triggered ) { 2256 //find first page via hash 2257 var to = path.stripHash( location.hash ), 2258 //transition is false if it's the first page, undefined otherwise (and may be overridden by default) 2259 transition = $.mobile.urlHistory.stack.length === 0 ? false : undefined; 2260 2261 //if listening is disabled (either globally or temporarily), or it's a dialog hash 2262 if( !$.mobile.hashListeningEnabled || !urlHistory.ignoreNextHashChange || 2263 urlHistory.stack.length > 1 && to.indexOf( dialogHashKey ) > -1 && !$.mobile.activePage.is( ".ui-dialog" ) 2264 ){ 2265 if( !urlHistory.ignoreNextHashChange ){ 2266 urlHistory.ignoreNextHashChange = true; 2267 } 2268 return; 2269 } 2270 2271 //if to is defined, load it 2272 if ( to ){ 2273 $.mobile.changePage( to, transition, undefined, false, true ); 2274 } 2275 //there's no hash, go to the first page in the dom 2276 else { 2277 $.mobile.changePage( $.mobile.firstPage, transition, true, false, true ); 2278 } 2279 }); 2280 })( jQuery ); 2281 /* 2282 * jQuery Mobile Framework : "fixHeaderFooter" plugin - on-demand positioning for headers,footers 2283 * Copyright (c) jQuery Project 2284 * Dual licensed under the MIT or GPL Version 2 licenses. 2285 * http://jquery.org/license 2286 */ 2287 (function($, undefined ) { 2288 $.fn.fixHeaderFooter = function(options){ 2289 if( !$.support.scrollTop ){ return this; } 2290 2291 return this.each(function(){ 2292 var $this = $(this); 2293 2294 if( $this.data('fullscreen') ){ $this.addClass('ui-page-fullscreen'); } 2295 $this.find('.ui-header[data-position="fixed"]').addClass('ui-header-fixed ui-fixed-inline fade'); //should be slidedown 2296 $this.find('.ui-footer[data-position="fixed"]').addClass('ui-footer-fixed ui-fixed-inline fade'); //should be slideup 2297 }); 2298 }; 2299 2300 //single controller for all showing,hiding,toggling 2301 $.fixedToolbars = (function(){ 2302 if( !$.support.scrollTop ){ return; } 2303 var currentstate = 'inline', 2304 autoHideMode = false, 2305 showDelay = 100, 2306 delayTimer, 2307 ignoreTargets = 'a,input,textarea,select,button,label,.ui-header-fixed,.ui-footer-fixed', 2308 toolbarSelector = '.ui-header-fixed:first, .ui-footer-fixed:not(.ui-footer-duplicate):last', 2309 stickyFooter, //for storing quick references to duplicate footers 2310 supportTouch = $.support.touch, 2311 touchStartEvent = supportTouch ? "touchstart" : "mousedown", 2312 touchStopEvent = supportTouch ? "touchend" : "mouseup", 2313 stateBefore = null, 2314 scrollTriggered = false, 2315 touchToggleEnabled = true; 2316 2317 function showEventCallback(event) 2318 { 2319 // An event that affects the dimensions of the visual viewport has 2320 // been triggered. If the header and/or footer for the current page are in overlay 2321 // mode, we want to hide them, and then fire off a timer to show them at a later 2322 // point. Events like a resize can be triggered continuously during a scroll, on 2323 // some platforms, so the timer is used to delay the actual positioning until the 2324 // flood of events have subsided. 2325 // 2326 // If we are in autoHideMode, we don't do anything because we know the scroll 2327 // callbacks for the plugin will fire off a show when the scrolling has stopped. 2328 if (!autoHideMode && currentstate == 'overlay') { 2329 if (!delayTimer) 2330 $.fixedToolbars.hide(true); 2331 $.fixedToolbars.startShowTimer(); 2332 } 2333 } 2334 2335 $(function() { 2336 $(document) 2337 .bind(touchStartEvent,function(event){ 2338 if( touchToggleEnabled ) { 2339 if( $(event.target).closest(ignoreTargets).length ){ return; } 2340 stateBefore = currentstate; 2341 } 2342 }) 2343 .bind(touchStopEvent,function(event){ 2344 if( touchToggleEnabled ) { 2345 if( $(event.target).closest(ignoreTargets).length ){ return; } 2346 if( !scrollTriggered ){ 2347 $.fixedToolbars.toggle(stateBefore); 2348 stateBefore = null; 2349 } 2350 } 2351 }) 2352 .bind('scrollstart',function(event){ 2353 if( $(event.target).closest(ignoreTargets).length ){ return; } //because it could be a touchmove... 2354 scrollTriggered = true; 2355 if(stateBefore == null){ stateBefore = currentstate; } 2356 2357 // We only enter autoHideMode if the headers/footers are in 2358 // an overlay state or the show timer was started. If the 2359 // show timer is set, clear it so the headers/footers don't 2360 // show up until after we're done scrolling. 2361 var isOverlayState = stateBefore == 'overlay'; 2362 autoHideMode = isOverlayState || !!delayTimer; 2363 if (autoHideMode){ 2364 $.fixedToolbars.clearShowTimer(); 2365 if (isOverlayState) { 2366 $.fixedToolbars.hide(true); 2367 } 2368 } 2369 }) 2370 .bind('scrollstop',function(event){ 2371 if( $(event.target).closest(ignoreTargets).length ){ return; } 2372 scrollTriggered = false; 2373 if (autoHideMode) { 2374 autoHideMode = false; 2375 $.fixedToolbars.startShowTimer(); 2376 } 2377 stateBefore = null; 2378 }) 2379 .bind('silentscroll', showEventCallback); 2380 2381 $(window).bind('resize', showEventCallback); 2382 }); 2383 2384 //before page is shown, check for duplicate footer 2385 $('.ui-page').live('pagebeforeshow', function(event, ui){ 2386 var page = $(event.target), 2387 footer = page.find('[data-role="footer"]:not(.ui-sticky-footer)'), 2388 id = footer.data('id'); 2389 stickyFooter = null; 2390 if (id) 2391 { 2392 stickyFooter = $('.ui-footer[data-id="' + id + '"].ui-sticky-footer'); 2393 if (stickyFooter.length == 0) { 2394 // No sticky footer exists for this data-id. We'll use this 2395 // footer as the sticky footer for the group and then create 2396 // a placeholder footer for the page. 2397 stickyFooter = footer; 2398 footer = stickyFooter.clone(); // footer placeholder 2399 stickyFooter.addClass('ui-sticky-footer').before(footer); 2400 } 2401 footer.addClass('ui-footer-duplicate'); 2402 stickyFooter.appendTo($.pageContainer).css('top',0); 2403 setTop(stickyFooter); 2404 } 2405 }); 2406 2407 //after page is shown, append footer to new page 2408 $('.ui-page').live('pageshow', function(event, ui){ 2409 if( stickyFooter && stickyFooter.length ){ 2410 stickyFooter.appendTo(event.target).css('top',0); 2411 } 2412 $.fixedToolbars.show(true, this); 2413 }); 2414 2415 2416 // element.getBoundingClientRect() is broken in iOS 3.2.1 on the iPad. The 2417 // coordinates inside of the rect it returns don't have the page scroll position 2418 // factored out of it like the other platforms do. To get around this, 2419 // we'll just calculate the top offset the old fashioned way until core has 2420 // a chance to figure out how to handle this situation. 2421 // 2422 // TODO: We'll need to get rid of getOffsetTop() once a fix gets folded into core. 2423 2424 function getOffsetTop(ele) 2425 { 2426 var top = 0; 2427 if (ele) 2428 { 2429 var op = ele.offsetParent, body = document.body; 2430 top = ele.offsetTop; 2431 while (ele && ele != body) 2432 { 2433 top += ele.scrollTop || 0; 2434 if (ele == op) 2435 { 2436 top += op.offsetTop; 2437 op = ele.offsetParent; 2438 } 2439 ele = ele.parentNode; 2440 } 2441 } 2442 return top; 2443 } 2444 2445 function setTop(el){ 2446 var fromTop = $(window).scrollTop(), 2447 thisTop = getOffsetTop(el[0]), // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead. 2448 thisCSStop = el.css('top') == 'auto' ? 0 : parseFloat(el.css('top')), 2449 screenHeight = window.innerHeight, 2450 thisHeight = el.outerHeight(), 2451 useRelative = el.parents('.ui-page:not(.ui-page-fullscreen)').length, 2452 relval; 2453 if( el.is('.ui-header-fixed') ){ 2454 relval = fromTop - thisTop + thisCSStop; 2455 if( relval < thisTop){ relval = 0; } 2456 return el.css('top', ( useRelative ) ? relval : fromTop); 2457 } 2458 else{ 2459 //relval = -1 * (thisTop - (fromTop + screenHeight) + thisCSStop + thisHeight); 2460 //if( relval > thisTop ){ relval = 0; } 2461 relval = fromTop + screenHeight - thisHeight - (thisTop - thisCSStop); 2462 return el.css('top', ( useRelative ) ? relval : fromTop + screenHeight - thisHeight ); 2463 } 2464 } 2465 2466 //exposed methods 2467 return { 2468 show: function(immediately, page){ 2469 $.fixedToolbars.clearShowTimer(); 2470 currentstate = 'overlay'; 2471 var $ap = page ? $(page) : ($.mobile.activePage ? $.mobile.activePage : $(".ui-page-active")); 2472 return $ap.children( toolbarSelector ).each(function(){ 2473 var el = $(this), 2474 fromTop = $(window).scrollTop(), 2475 thisTop = getOffsetTop(el[0]), // el.offset().top returns the wrong value on iPad iOS 3.2.1, call our workaround instead. 2476 screenHeight = window.innerHeight, 2477 thisHeight = el.outerHeight(), 2478 alreadyVisible = (el.is('.ui-header-fixed') && fromTop <= thisTop + thisHeight) || (el.is('.ui-footer-fixed') && thisTop <= fromTop + screenHeight); 2479 2480 //add state class 2481 el.addClass('ui-fixed-overlay').removeClass('ui-fixed-inline'); 2482 2483 if( !alreadyVisible && !immediately ){ 2484 el.animationComplete(function(){ 2485 el.removeClass('in'); 2486 }).addClass('in'); 2487 } 2488 setTop(el); 2489 }); 2490 }, 2491 hide: function(immediately){ 2492 currentstate = 'inline'; 2493 var $ap = $.mobile.activePage ? $.mobile.activePage : $(".ui-page-active"); 2494 return $ap.children( toolbarSelector ).each(function(){ 2495 var el = $(this); 2496 2497 var thisCSStop = el.css('top'); thisCSStop = thisCSStop == 'auto' ? 0 : parseFloat(thisCSStop); 2498 2499 //add state class 2500 el.addClass('ui-fixed-inline').removeClass('ui-fixed-overlay'); 2501 2502 if (thisCSStop < 0 || (el.is('.ui-header-fixed') && thisCSStop != 0)) 2503 { 2504 if(immediately){ 2505 el.css('top',0); 2506 } 2507 else{ 2508 if( el.css('top') !== 'auto' && parseFloat(el.css('top')) !== 0 ){ 2509 var classes = 'out reverse'; 2510 el.animationComplete(function(){ 2511 el.removeClass(classes); 2512 el.css('top',0); 2513 }).addClass(classes); 2514 } 2515 } 2516 } 2517 }); 2518 }, 2519 startShowTimer: function(){ 2520 $.fixedToolbars.clearShowTimer(); 2521 var args = $.makeArray(arguments); 2522 delayTimer = setTimeout(function(){ 2523 delayTimer = undefined; 2524 $.fixedToolbars.show.apply(null, args); 2525 }, showDelay); 2526 }, 2527 clearShowTimer: function() { 2528 if (delayTimer) { 2529 clearTimeout(delayTimer); 2530 } 2531 delayTimer = undefined; 2532 }, 2533 toggle: function(from){ 2534 if(from){ currentstate = from; } 2535 return (currentstate == 'overlay') ? $.fixedToolbars.hide() : $.fixedToolbars.show(); 2536 }, 2537 setTouchToggleEnabled: function(enabled) { 2538 touchToggleEnabled = enabled; 2539 } 2540 }; 2541 })(); 2542 2543 })(jQuery);/* 2544 * jQuery Mobile Framework : "checkboxradio" plugin 2545 * Copyright (c) jQuery Project 2546 * Dual licensed under the MIT or GPL Version 2 licenses. 2547 * http://jquery.org/license 2548 */ 2549 (function($, undefined ) { 2550 $.widget( "mobile.checkboxradio", $.mobile.widget, { 2551 options: { 2552 theme: null 2553 }, 2554 _create: function(){ 2555 var self = this, 2556 input = this.element, 2557 label = input.closest("form,fieldset,[data-role='page']").find("label[for='" + input.attr( "id" ) + "']"), 2558 inputtype = input.attr( "type" ), 2559 checkedicon = "ui-icon-" + inputtype + "-on", 2560 uncheckedicon = "ui-icon-" + inputtype + "-off"; 2561 2562 if ( inputtype != "checkbox" && inputtype != "radio" ) { return; } 2563 2564 // If there's no selected theme... 2565 if( !this.options.theme ) { 2566 this.options.theme = this.element.data( "theme" ); 2567 } 2568 2569 label 2570 .buttonMarkup({ 2571 theme: this.options.theme, 2572 icon: this.element.parents( "[data-type='horizontal']" ).length ? undefined : uncheckedicon, 2573 shadow: false 2574 }); 2575 2576 // wrap the input + label in a div 2577 input 2578 .add( label ) 2579 .wrapAll( "<div class='ui-" + inputtype +"'></div>" ); 2580 2581 label.bind({ 2582 mouseover: function() { 2583 if( $(this).parent().is('.ui-disabled') ){ return false; } 2584 }, 2585 2586 "touchmove": function( event ){ 2587 var oe = event.originalEvent.touches[0]; 2588 if( label.data("movestart") ){ 2589 if( Math.abs( label.data("movestart")[0] - oe.pageX ) > 10 || 2590 Math.abs( abel.data("movestart")[1] - oe.pageY ) > 10 ){ 2591 label.data("moved", true); 2592 } 2593 } 2594 else{ 2595 label.data("movestart", [ parseFloat( oe.pageX ), parseFloat( oe.pageY ) ]); 2596 } 2597 }, 2598 2599 "touchend mouseup": function( event ){ 2600 label.removeData("movestart"); 2601 if( label.data("etype") && label.data("etype") !== event.type || label.data("moved") ){ 2602 label.removeData("etype").removeData("moved"); 2603 if( label.data("moved") ){ 2604 label.removeData("moved"); 2605 } 2606 return false; 2607 } 2608 label.data( "etype", event.type ); 2609 self._cacheVals(); 2610 input.attr( "checked", inputtype === "radio" && true || !input.is( ":checked" ) ); 2611 self._updateAll(); 2612 event.preventDefault(); 2613 }, 2614 2615 click: false 2616 2617 }); 2618 2619 input 2620 .bind({ 2621 mousedown: function(){ 2622 this._cacheVals(); 2623 }, 2624 2625 click: function(){ 2626 self._updateAll(); 2627 }, 2628 2629 focus: function() { 2630 label.addClass( "ui-focus" ); 2631 }, 2632 2633 blur: function() { 2634 label.removeClass( "ui-focus" ); 2635 } 2636 }); 2637 2638 this.refresh(); 2639 2640 }, 2641 2642 _cacheVals: function(){ 2643 this._getInputSet().each(function(){ 2644 $(this).data("cacheVal", $(this).is(":checked") ); 2645 }); 2646 }, 2647 2648 //returns either a set of radios with the same name attribute, or a single checkbox 2649 _getInputSet: function(){ 2650 return this.element.closest( "form,fieldset,[data-role='page']" ) 2651 .find( "input[name='"+ this.element.attr( "name" ) +"'][type='"+ this.element.attr( "type" ) +"']" ); 2652 }, 2653 2654 _updateAll: function(){ 2655 this._getInputSet().each(function(){ 2656 var dVal = $(this).data("cacheVal"); 2657 if( dVal && dVal !== $(this).is(":checked") || $(this).is( "[type='checkbox']" ) ){ 2658 $(this).trigger("change"); 2659 } 2660 }) 2661 .checkboxradio( "refresh" ); 2662 }, 2663 2664 refresh: function( ){ 2665 var input = this.element, 2666 label = input.closest("form,fieldset,[data-role='page']").find("label[for='" + input.attr( "id" ) + "']"), 2667 inputtype = input.attr( "type" ), 2668 icon = label.find( ".ui-icon" ), 2669 checkedicon = "ui-icon-" + inputtype + "-on", 2670 uncheckedicon = "ui-icon-" + inputtype + "-off"; 2671 2672 if ( input[0].checked ) { 2673 label.addClass( "ui-btn-active" ); 2674 icon.addClass( checkedicon ); 2675 icon.removeClass( uncheckedicon ); 2676 2677 } else { 2678 label.removeClass( "ui-btn-active" ); 2679 icon.removeClass( checkedicon ); 2680 icon.addClass( uncheckedicon ); 2681 } 2682 2683 if( input.is( ":disabled" ) ){ 2684 this.disable(); 2685 } 2686 else { 2687 this.enable(); 2688 } 2689 }, 2690 2691 disable: function(){ 2692 this.element.attr("disabled",true).parent().addClass("ui-disabled"); 2693 }, 2694 2695 enable: function(){ 2696 this.element.attr("disabled",false).parent().removeClass("ui-disabled"); 2697 } 2698 }); 2699 })( jQuery ); 2700 /* 2701 * jQuery Mobile Framework : "textinput" plugin for text inputs, textareas 2702 * Copyright (c) jQuery Project 2703 * Dual licensed under the MIT or GPL Version 2 licenses. 2704 * http://jquery.org/license 2705 */ 2706 (function($, undefined ) { 2707 $.widget( "mobile.textinput", $.mobile.widget, { 2708 options: { 2709 theme: null 2710 }, 2711 _create: function(){ 2712 var input = this.element, 2713 o = this.options, 2714 theme = o.theme, 2715 themeclass; 2716 2717 if ( !theme ) { 2718 var themedParent = this.element.closest("[class*='ui-bar-'],[class*='ui-body-']"); 2719 theme = themedParent.length ? 2720 /ui-(bar|body)-([a-z])/.exec( themedParent.attr("class") )[2] : 2721 "c"; 2722 } 2723 2724 themeclass = " ui-body-" + theme; 2725 2726 $('label[for='+input.attr('id')+']').addClass('ui-input-text'); 2727 2728 input.addClass('ui-input-text ui-body-'+ o.theme); 2729 2730 var focusedEl = input; 2731 2732 //"search" input widget 2733 if( input.is('[type="search"],[data-type="search"]') ){ 2734 focusedEl = input.wrap('<div class="ui-input-search ui-shadow-inset ui-btn-corner-all ui-btn-shadow ui-icon-searchfield'+ themeclass +'"></div>').parent(); 2735 var clearbtn = $('<a href="#" class="ui-input-clear" title="clear text">clear text</a>') 2736 .tap(function( e ){ 2737 input.val('').focus(); 2738 input.trigger('change'); 2739 clearbtn.addClass('ui-input-clear-hidden'); 2740 e.preventDefault(); 2741 }) 2742 .appendTo(focusedEl) 2743 .buttonMarkup({icon: 'delete', iconpos: 'notext', corners:true, shadow:true}); 2744 2745 function toggleClear(){ 2746 if(input.val() == ''){ 2747 clearbtn.addClass('ui-input-clear-hidden'); 2748 } 2749 else{ 2750 clearbtn.removeClass('ui-input-clear-hidden'); 2751 } 2752 } 2753 2754 toggleClear(); 2755 input.keyup(toggleClear); 2756 } 2757 else{ 2758 input.addClass('ui-corner-all ui-shadow-inset' + themeclass); 2759 } 2760 2761 input 2762 .focus(function(){ 2763 focusedEl.addClass('ui-focus'); 2764 }) 2765 .blur(function(){ 2766 focusedEl.removeClass('ui-focus'); 2767 }); 2768 2769 //autogrow 2770 if ( input.is('textarea') ) { 2771 var extraLineHeight = 15, 2772 keyupTimeoutBuffer = 100, 2773 keyup = function() { 2774 var scrollHeight = input[0].scrollHeight, 2775 clientHeight = input[0].clientHeight; 2776 if ( clientHeight < scrollHeight ) { 2777 input.css({ height: (scrollHeight + extraLineHeight) }); 2778 } 2779 }, 2780 keyupTimeout; 2781 input.keyup(function() { 2782 clearTimeout( keyupTimeout ); 2783 keyupTimeout = setTimeout( keyup, keyupTimeoutBuffer ); 2784 }); 2785 } 2786 }, 2787 2788 disable: function(){ 2789 ( this.element.attr("disabled",true).is('[type="search"],[data-type="search"]') ? this.element.parent() : this.element ).addClass("ui-disabled"); 2790 }, 2791 2792 enable: function(){ 2793 ( this.element.attr("disabled", false).is('[type="search"],[data-type="search"]') ? this.element.parent() : this.element ).removeClass("ui-disabled"); 2794 } 2795 }); 2796 })( jQuery ); 2797 /* 2798 * jQuery Mobile Framework : "selectmenu" plugin 2799 * Copyright (c) jQuery Project 2800 * Dual licensed under the MIT or GPL Version 2 licenses. 2801 * http://jquery.org/license 2802 */ 2803 (function($, undefined ) { 2804 $.widget( "mobile.selectmenu", $.mobile.widget, { 2805 options: { 2806 theme: null, 2807 disabled: false, 2808 icon: 'arrow-d', 2809 iconpos: 'right', 2810 inline: null, 2811 corners: true, 2812 shadow: true, 2813 iconshadow: true, 2814 menuPageTheme: 'b', 2815 overlayTheme: 'a', 2816 hidePlaceholderMenuItems: true, 2817 closeText: 'Close', 2818 nativeMenu: false 2819 }, 2820 _create: function(){ 2821 2822 var self = this, 2823 2824 o = this.options, 2825 2826 select = this.element 2827 .wrap( "<div class='ui-select'>" ), 2828 2829 selectID = select.attr( "id" ), 2830 2831 label = $( "label[for="+ selectID +"]" ).addClass( "ui-select" ), 2832 2833 button = ( self.options.nativeMenu ? $( "<div/>" ) : $( "<a>", { 2834 "href": "#", 2835 "role": "button", 2836 "id": buttonId, 2837 "aria-haspopup": "true", 2838 "aria-owns": menuId 2839 }) ) 2840 .text( $( select[0].options.item(select[0].selectedIndex) ).text() ) 2841 .insertBefore( select ) 2842 .buttonMarkup({ 2843 theme: o.theme, 2844 icon: o.icon, 2845 iconpos: o.iconpos, 2846 inline: o.inline, 2847 corners: o.corners, 2848 shadow: o.shadow, 2849 iconshadow: o.iconshadow 2850 }), 2851 2852 //multi select or not 2853 isMultiple = self.isMultiple = select[0].multiple; 2854 2855 //Opera does not properly support opacity on select elements 2856 //In Mini, it hides the element, but not its text 2857 //On the desktop,it seems to do the opposite 2858 //for these reasons, using the nativeMenu option results in a full native select in Opera 2859 if( o.nativeMenu && window.opera && window.opera.version ){ 2860 select.addClass( "ui-select-nativeonly" ); 2861 } 2862 2863 //vars for non-native menus 2864 if( !o.nativeMenu ){ 2865 var options = select.find("option"), 2866 2867 buttonId = selectID + "-button", 2868 2869 menuId = selectID + "-menu", 2870 2871 thisPage = select.closest( ".ui-page" ), 2872 2873 //button theme 2874 theme = /ui-btn-up-([a-z])/.exec( button.attr("class") )[1], 2875 2876 menuPage = $( "<div data-role='dialog' data-theme='"+ o.menuPageTheme +"'>" + 2877 "<div data-role='header'>" + 2878 "<div class='ui-title'>" + label.text() + "</div>"+ 2879 "</div>"+ 2880 "<div data-role='content'></div>"+ 2881 "</div>" ) 2882 .appendTo( $.mobile.pageContainer ) 2883 .page(), 2884 2885 menuPageContent = menuPage.find( ".ui-content" ), 2886 2887 menuPageClose = menuPage.find( ".ui-header a" ), 2888 2889 screen = $( "<div>", {"class": "ui-selectmenu-screen ui-screen-hidden"}) 2890 .appendTo( thisPage ), 2891 2892 listbox = $( "<div>", { "class": "ui-selectmenu ui-selectmenu-hidden ui-overlay-shadow ui-corner-all pop ui-body-" + o.overlayTheme } ) 2893 .insertAfter(screen), 2894 2895 list = $( "<ul>", { 2896 "class": "ui-selectmenu-list", 2897 "id": menuId, 2898 "role": "listbox", 2899 "aria-labelledby": buttonId, 2900 "data-theme": theme 2901 }) 2902 .appendTo( listbox ), 2903 2904 header = $( "<div>", { 2905 "class": "ui-header ui-bar-" + theme 2906 }) 2907 .prependTo( listbox ), 2908 2909 headerTitle = $( "<h1>", { 2910 "class": "ui-title" 2911 }) 2912 .appendTo( header ), 2913 2914 headerClose = $( "<a>", { 2915 "data-iconpos": "notext", 2916 "data-icon": "delete", 2917 "text": o.closeText, 2918 "href": "#", 2919 "class": "ui-btn-left" 2920 }) 2921 .appendTo( header ) 2922 .buttonMarkup(), 2923 2924 menuType; 2925 } //end non native vars 2926 2927 // add counter for multi selects 2928 if( isMultiple ){ 2929 self.buttonCount = $('<span>') 2930 .addClass( 'ui-li-count ui-btn-up-c ui-btn-corner-all' ) 2931 .hide() 2932 .appendTo( button ); 2933 } 2934 2935 //disable if specified 2936 if( o.disabled ){ this.disable(); } 2937 2938 //events on native select 2939 select 2940 .change(function(){ 2941 self.refresh(); 2942 }); 2943 2944 //expose to other methods 2945 $.extend(self, { 2946 select: select, 2947 optionElems: options, 2948 selectID: selectID, 2949 label: label, 2950 buttonId:buttonId, 2951 menuId:menuId, 2952 thisPage:thisPage, 2953 button:button, 2954 menuPage:menuPage, 2955 menuPageContent:menuPageContent, 2956 screen:screen, 2957 listbox:listbox, 2958 list:list, 2959 menuType:menuType, 2960 header:header, 2961 headerClose:headerClose, 2962 headerTitle:headerTitle, 2963 placeholder: '' 2964 }); 2965 2966 //support for using the native select menu with a custom button 2967 if( o.nativeMenu ){ 2968 2969 select 2970 .appendTo(button) 2971 .bind( "touchstart mousedown", function( e ){ 2972 //add active class to button 2973 button.addClass( $.mobile.activeBtnClass ); 2974 }) 2975 .bind( "focus mouseover", function(){ 2976 button.trigger( "mouseover" ); 2977 }) 2978 .bind( "touchmove", function(){ 2979 //remove active class on scroll/touchmove 2980 button.removeClass( $.mobile.activeBtnClass ); 2981 }) 2982 .bind( "change blur mouseout", function(){ 2983 button 2984 .trigger( "mouseout" ) 2985 .removeClass( $.mobile.activeBtnClass ); 2986 }); 2987 2988 button.attr( "tabindex", "-1" ); 2989 } else { 2990 2991 //create list from select, update state 2992 self.refresh(); 2993 2994 select 2995 .attr( "tabindex", "-1" ) 2996 .focus(function(){ 2997 $(this).blur(); 2998 button.focus(); 2999 }); 3000 3001 //button events 3002 button 3003 .bind( "touchstart" , function( event ){ 3004 //set startTouches to cached copy of 3005 $( this ).data( "startTouches", $.extend({}, event.originalEvent.touches[ 0 ]) ); 3006 }) 3007 .bind( $.support.touch ? "touchend" : "mouseup" , function( event ){ 3008 //if it's a scroll, don't open 3009 if( $( this ).data( "moved" ) ){ 3010 $( this ).removeData( "moved" ); 3011 } else { 3012 self.open(); 3013 } 3014 event.preventDefault(); 3015 }) 3016 .bind( "touchmove", function( event ){ 3017 //if touch moved enough, set data moved and don't open menu 3018 var thisTouches = event.originalEvent.touches[ 0 ], 3019 startTouches = $( this ).data( "startTouches" ), 3020 deltaX = Math.abs(thisTouches.pageX - startTouches.pageX), 3021 deltaY = Math.abs(thisTouches.pageY - startTouches.pageY); 3022 3023 if( deltaX > 10 || deltaY > 10 ){ 3024 $( this ).data( "moved", true ); 3025 } 3026 }); 3027 3028 3029 //events for list items 3030 list.delegate("li:not(.ui-disabled, .ui-li-divider)", "click", function(event){ 3031 // clicking on the list item fires click on the link in listview.js. 3032 // to prevent this handler from firing twice if the link isn't clicked on, 3033 // short circuit unless the target is the link 3034 if( !$(event.target).is("a") ){ return; } 3035 3036 // index of option tag to be selected 3037 var newIndex = list.find( "li:not(.ui-li-divider)" ).index( this ), 3038 option = self.optionElems.eq( newIndex )[0]; 3039 3040 // toggle selected status on the tag for multi selects 3041 option.selected = isMultiple ? !option.selected : true; 3042 3043 // toggle checkbox class for multiple selects 3044 if( isMultiple ){ 3045 $(this) 3046 .find('.ui-icon') 3047 .toggleClass('ui-icon-checkbox-on', option.selected) 3048 .toggleClass('ui-icon-checkbox-off', !option.selected); 3049 } 3050 3051 // trigger change 3052 select.trigger( "change" ); 3053 3054 //hide custom select for single selects only 3055 if( !isMultiple ){ 3056 self.close(); 3057 } 3058 3059 event.preventDefault(); 3060 }); 3061 3062 //events on "screen" overlay + close button 3063 screen 3064 .add( headerClose ) 3065 .add( menuPageClose ) 3066 .bind("click", function(event){ 3067 self.close(); 3068 event.preventDefault(); 3069 3070 // if the dialog's close icon was clicked, prevent the dialog's close 3071 // handler from firing. selectmenu's should take precedence 3072 if( $.contains(menuPageClose[0], event.target) ){ 3073 event.stopImmediatePropagation(); 3074 } 3075 }); 3076 } 3077 3078 3079 }, 3080 3081 _buildList: function(){ 3082 var self = this, 3083 o = this.options, 3084 placeholder = this.placeholder, 3085 optgroups = [], 3086 lis = [], 3087 dataIcon = self.isMultiple ? "checkbox-off" : "false"; 3088 3089 self.list.empty().filter('.ui-listview').listview('destroy'); 3090 3091 //populate menu with options from select element 3092 self.select.find( "option" ).each(function( i ){ 3093 var $this = $(this), 3094 $parent = $this.parent(), 3095 text = $this.text(), 3096 anchor = "<a href='#'>"+ text +"</a>", 3097 classes = [], 3098 extraAttrs = []; 3099 3100 // are we inside an optgroup? 3101 if( $parent.is("optgroup") ){ 3102 var optLabel = $parent.attr("label"); 3103 3104 // has this optgroup already been built yet? 3105 if( $.inArray(optLabel, optgroups) === -1 ){ 3106 lis.push( "<li data-role='list-divider'>"+ optLabel +"</li>" ); 3107 optgroups.push( optLabel ); 3108 } 3109 } 3110 3111 //find placeholder text 3112 if( !this.getAttribute('value') || text.length == 0 || $this.data('placeholder') ){ 3113 if( o.hidePlaceholderMenuItems ){ 3114 classes.push( "ui-selectmenu-placeholder" ); 3115 } 3116 placeholder = self.placeholder = text; 3117 } 3118 3119 // support disabled option tags 3120 if( this.disabled ){ 3121 classes.push( "ui-disabled" ); 3122 extraAttrs.push( "aria-disabled='true'" ); 3123 } 3124 3125 lis.push( "<li data-icon='"+ dataIcon +"' class='"+ classes.join(" ") + "' " + extraAttrs.join(" ") +">"+ anchor +"</li>" ) 3126 }); 3127 3128 self.list.html( lis.join(" ") ); 3129 3130 // hide header close link for single selects 3131 if( !this.isMultiple ){ 3132 this.headerClose.hide(); 3133 } 3134 3135 // hide header if it's not a multiselect and there's no placeholder 3136 if( !this.isMultiple && !placeholder.length ){ 3137 this.header.hide(); 3138 } else { 3139 this.headerTitle.text( this.placeholder ); 3140 } 3141 3142 //now populated, create listview 3143 self.list.listview(); 3144 }, 3145 3146 refresh: function( forceRebuild ){ 3147 var self = this, 3148 select = this.element, 3149 isMultiple = this.isMultiple, 3150 options = this.optionElems = select.find("option"), 3151 selected = options.filter(":selected"), 3152 3153 // return an array of all selected index's 3154 indicies = selected.map(function(){ 3155 return options.index( this ); 3156 }).get(); 3157 3158 if( !self.options.nativeMenu && ( forceRebuild || select[0].options.length > self.list.find('li').length )){ 3159 self._buildList(); 3160 } 3161 3162 self.button 3163 .find( ".ui-btn-text" ) 3164 .text(function(){ 3165 if( !isMultiple ){ 3166 return selected.text(); 3167 } 3168 3169 return selected.length ? 3170 selected.map(function(){ return $(this).text(); }).get().join(', ') : 3171 self.placeholder; 3172 }); 3173 3174 // multiple count inside button 3175 if( isMultiple ){ 3176 self.buttonCount[ selected.length > 1 ? 'show' : 'hide' ]().text( selected.length ); 3177 } 3178 3179 if( !self.options.nativeMenu ){ 3180 self.list 3181 .find( 'li:not(.ui-li-divider)' ) 3182 .removeClass( $.mobile.activeBtnClass ) 3183 .attr( 'aria-selected', false ) 3184 .each(function( i ){ 3185 if( $.inArray(i, indicies) > -1 ){ 3186 var item = $(this).addClass( $.mobile.activeBtnClass ); 3187 3188 // aria selected attr 3189 item.find( 'a' ).attr( 'aria-selected', true ); 3190 3191 // multiple selects: add the "on" checkbox state to the icon 3192 if( isMultiple ){ 3193 item.find('.ui-icon').removeClass('ui-icon-checkbox-off').addClass('ui-icon-checkbox-on'); 3194 } 3195 } 3196 }); 3197 } 3198 }, 3199 3200 open: function(){ 3201 if( this.options.disabled || this.options.nativeMenu ){ return; } 3202 3203 var self = this, 3204 menuHeight = self.list.outerHeight(), 3205 menuWidth = self.list.outerWidth(), 3206 scrollTop = $(window).scrollTop(), 3207 btnOffset = self.button.offset().top, 3208 screenHeight = window.innerHeight, 3209 screenWidth = window.innerWidth, 3210 dialogUsed = self.list.parents('.ui-dialog').length; 3211 3212 //add active class to button 3213 self.button.addClass( $.mobile.activeBtnClass ); 3214 3215 function focusMenuItem(){ 3216 self.list.find( ".ui-btn-active" ).focus(); 3217 } 3218 3219 // NOTE addresses issue with firefox outerHeight when the parent dialog 3220 // is display: none. Upstream? 3221 if( dialogUsed || menuHeight > screenHeight - 80 || !$.support.scrollTop ){ 3222 3223 //for webos (set lastscroll using button offset) 3224 if( scrollTop == 0 && btnOffset > screenHeight ){ 3225 self.thisPage.one('pagehide',function(){ 3226 $(this).data('lastScroll', btnOffset); 3227 }); 3228 } 3229 3230 self.menuPage.one('pageshow', function() { 3231 // silentScroll() is called whenever a page is shown to restore 3232 // any previous scroll position the page may have had. We need to 3233 // wait for the "silentscroll" event before setting focus to avoid 3234 // the browser's "feature" which offsets rendering to make sure 3235 // whatever has focus is in view. 3236 $(window).one("silentscroll", function(){ focusMenuItem(); }); 3237 }); 3238 3239 self.menuType = "page"; 3240 self.menuPageContent.append( self.list ); 3241 $.mobile.changePage(self.menuPage, 'pop', false, true); 3242 } 3243 else { 3244 self.menuType = "overlay"; 3245 3246 self.screen 3247 .height( $(document).height() ) 3248 .removeClass('ui-screen-hidden'); 3249 3250 //try and center the overlay over the button 3251 var roomtop = btnOffset - scrollTop, 3252 roombot = scrollTop + screenHeight - btnOffset, 3253 halfheight = menuHeight / 2, 3254 newtop,newleft; 3255 3256 if( roomtop > menuHeight / 2 && roombot > menuHeight / 2 ){ 3257 newtop = btnOffset + ( self.button.outerHeight() / 2 ) - halfheight; 3258 } 3259 else{ 3260 //30px tolerance off the edges 3261 newtop = roomtop > roombot ? scrollTop + screenHeight - menuHeight - 30 : scrollTop + 30; 3262 } 3263 3264 newleft = self.button.offset().left + self.button.outerWidth() / 2 - menuWidth / 2; 3265 3266 3267 self.listbox 3268 .append( self.list ) 3269 .removeClass( "ui-selectmenu-hidden" ) 3270 .css({ 3271 top: newtop, 3272 left: newleft 3273 }) 3274 .addClass("in"); 3275 3276 focusMenuItem(); 3277 } 3278 3279 // wait before the dialog can be closed 3280 setTimeout(function(){ 3281 self.isOpen = true; 3282 }, 400); 3283 }, 3284 3285 close: function(){ 3286 if( this.options.disabled || !this.isOpen || this.options.nativeMenu ){ return; } 3287 var self = this; 3288 3289 function focusButton(){ 3290 setTimeout(function(){ 3291 self.button.focus(); 3292 3293 //remove active class from button 3294 self.button.removeClass( $.mobile.activeBtnClass ); 3295 }, 40); 3296 3297 self.listbox.removeAttr('style').append( self.list ); 3298 } 3299 3300 if(self.menuType == "page"){ 3301 $.mobile.changePage([self.menuPage,self.thisPage], 'pop', true, false); 3302 self.menuPage.one("pagehide", focusButton); 3303 } 3304 else{ 3305 self.screen.addClass( "ui-screen-hidden" ); 3306 self.listbox.addClass( "ui-selectmenu-hidden" ).removeAttr( "style" ).removeClass("in"); 3307 focusButton(); 3308 } 3309 3310 // allow the dialog to be closed again 3311 this.isOpen = false; 3312 }, 3313 3314 disable: function(){ 3315 this.element.attr("disabled",true); 3316 this.button.addClass('ui-disabled').attr("aria-disabled", true); 3317 return this._setOption( "disabled", true ); 3318 }, 3319 3320 enable: function(){ 3321 this.element.attr("disabled",false); 3322 this.button.removeClass('ui-disabled').attr("aria-disabled", false); 3323 return this._setOption( "disabled", false ); 3324 } 3325 }); 3326 })( jQuery ); 3327 3328 /* 3329 * jQuery Mobile Framework : plugin for making button-like links 3330 * Copyright (c) jQuery Project 3331 * Dual licensed under the MIT or GPL Version 2 licenses. 3332 * http://jquery.org/license 3333 */ 3334 (function($, undefined ) { 3335 3336 $.fn.buttonMarkup = function( options ){ 3337 return this.each( function() { 3338 var el = $( this ), 3339 o = $.extend( {}, $.fn.buttonMarkup.defaults, el.data(), options), 3340 3341 // Classes Defined 3342 buttonClass, 3343 innerClass = "ui-btn-inner", 3344 iconClass; 3345 3346 if ( attachEvents ) { 3347 attachEvents(); 3348 } 3349 3350 // if not, try to find closest theme container 3351 if ( !o.theme ) { 3352 var themedParent = el.closest("[class*='ui-bar-'],[class*='ui-body-']"); 3353 o.theme = themedParent.length ? 3354 /ui-(bar|body)-([a-z])/.exec( themedParent.attr("class") )[2] : 3355 "c"; 3356 } 3357 3358 buttonClass = "ui-btn ui-btn-up-" + o.theme; 3359 3360 if ( o.inline ) { 3361 buttonClass += " ui-btn-inline"; 3362 } 3363 3364 if ( o.icon ) { 3365 o.icon = "ui-icon-" + o.icon; 3366 o.iconpos = o.iconpos || "left"; 3367 3368 iconClass = "ui-icon " + o.icon; 3369 3370 if ( o.shadow ) { 3371 iconClass += " ui-icon-shadow"; 3372 } 3373 } 3374 3375 if ( o.iconpos ) { 3376 buttonClass += " ui-btn-icon-" + o.iconpos; 3377 3378 if ( o.iconpos == "notext" && !el.attr("title") ) { 3379 el.attr( "title", el.text() ); 3380 } 3381 } 3382 3383 if ( o.corners ) { 3384 buttonClass += " ui-btn-corner-all"; 3385 innerClass += " ui-btn-corner-all"; 3386 } 3387 3388 if ( o.shadow ) { 3389 buttonClass += " ui-shadow"; 3390 } 3391 3392 el 3393 .attr( "data-theme", o.theme ) 3394 .addClass( buttonClass ); 3395 3396 var wrap = ("<D class='" + innerClass + "'><D class='ui-btn-text'></D>" + 3397 ( o.icon ? "<span class='" + iconClass + "'></span>" : "" ) + 3398 "</D>").replace(/D/g, o.wrapperEls); 3399 3400 el.wrapInner( wrap ); 3401 }); 3402 }; 3403 3404 $.fn.buttonMarkup.defaults = { 3405 corners: true, 3406 shadow: true, 3407 iconshadow: true, 3408 wrapperEls: "span" 3409 }; 3410 3411 var attachEvents = function() { 3412 $(".ui-btn:not(.ui-disabled)").live({ 3413 "touchstart mousedown": function() { 3414 var theme = $(this).attr( "data-theme" ); 3415 $(this).removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-down-" + theme ); 3416 }, 3417 "touchmove touchend mouseup": function() { 3418 var theme = $(this).attr( "data-theme" ); 3419 $(this).removeClass( "ui-btn-down-" + theme ).addClass( "ui-btn-up-" + theme ); 3420 }, 3421 "mouseover focus": function() { 3422 var theme = $(this).attr( "data-theme" ); 3423 $(this).removeClass( "ui-btn-up-" + theme ).addClass( "ui-btn-hover-" + theme ); 3424 }, 3425 "mouseout blur": function() { 3426 var theme = $(this).attr( "data-theme" ); 3427 $(this).removeClass( "ui-btn-hover-" + theme ).addClass( "ui-btn-up-" + theme ); 3428 } 3429 }); 3430 3431 attachEvents = null; 3432 }; 3433 3434 })(jQuery); 3435 /* 3436 * jQuery Mobile Framework : "button" plugin - links that proxy to native input/buttons 3437 * Copyright (c) jQuery Project 3438 * Dual licensed under the MIT or GPL Version 2 licenses. 3439 * http://jquery.org/license 3440 */ 3441 (function($, undefined ) { 3442 $.widget( "mobile.button", $.mobile.widget, { 3443 options: { 3444 theme: null, 3445 icon: null, 3446 iconpos: null, 3447 inline: null, 3448 corners: true, 3449 shadow: true, 3450 iconshadow: true 3451 }, 3452 _create: function(){ 3453 var $el = this.element, 3454 o = this.options; 3455 3456 //add ARIA role 3457 this.button = $( "<div></div>" ) 3458 .text( $el.text() || $el.val() ) 3459 .buttonMarkup({ 3460 theme: o.theme, 3461 icon: o.icon, 3462 iconpos: o.iconpos, 3463 inline: o.inline, 3464 corners: o.corners, 3465 shadow: o.shadow, 3466 iconshadow: o.iconshadow 3467 }) 3468 .insertBefore( $el ) 3469 .append( $el.addClass('ui-btn-hidden') ); 3470 3471 //add hidden input during submit 3472 if( $el.attr('type') !== 'reset' ){ 3473 $el.click(function(){ 3474 var $buttonPlaceholder = $("<input>", 3475 {type: "hidden", name: $el.attr("name"), value: $el.attr("value")}) 3476 .insertBefore($el); 3477 3478 //bind to doc to remove after submit handling 3479 $(document).submit(function(){ 3480 $buttonPlaceholder.remove(); 3481 }); 3482 }); 3483 } 3484 3485 }, 3486 3487 enable: function(){ 3488 this.element.attr("disabled", false); 3489 this.button.removeClass("ui-disabled").attr("aria-disabled", false); 3490 return this._setOption("disabled", false); 3491 }, 3492 3493 disable: function(){ 3494 this.element.attr("disabled", true); 3495 this.button.addClass("ui-disabled").attr("aria-disabled", true); 3496 return this._setOption("disabled", true); 3497 } 3498 }); 3499 })( jQuery );/* 3500 * jQuery Mobile Framework : "slider" plugin 3501 * Copyright (c) jQuery Project 3502 * Dual licensed under the MIT or GPL Version 2 licenses. 3503 * http://jquery.org/license 3504 */ 3505 (function($, undefined ) { 3506 $.widget( "mobile.slider", $.mobile.widget, { 3507 options: { 3508 theme: null, 3509 trackTheme: null, 3510 disabled: false 3511 }, 3512 _create: function(){ 3513 var self = this, 3514 3515 control = this.element, 3516 3517 parentTheme = control.parents('[class*=ui-bar-],[class*=ui-body-]').eq(0), 3518 3519 parentTheme = parentTheme.length ? parentTheme.attr('class').match(/ui-(bar|body)-([a-z])/)[2] : 'c', 3520 3521 theme = this.options.theme ? this.options.theme : parentTheme, 3522 3523 trackTheme = this.options.trackTheme ? this.options.trackTheme : parentTheme, 3524 3525 cType = control[0].nodeName.toLowerCase(), 3526 selectClass = (cType == 'select') ? 'ui-slider-switch' : '', 3527 controlID = control.attr('id'), 3528 labelID = controlID + '-label', 3529 label = $('[for='+ controlID +']').attr('id',labelID), 3530 val = function(){ 3531 return (cType == 'input') ? parseFloat(control.val()) : control[0].selectedIndex; 3532 }, 3533 min = (cType == 'input') ? parseFloat(control.attr('min')) : 0, 3534 max = (cType == 'input') ? parseFloat(control.attr('max')) : control.find('option').length-1, 3535 step = window.parseFloat(control.attr('step') || 1), 3536 slider = $('<div class="ui-slider '+ selectClass +' ui-btn-down-'+ trackTheme+' ui-btn-corner-all" role="application"></div>'), 3537 handle = $('<a href="#" class="ui-slider-handle"></a>') 3538 .appendTo(slider) 3539 .buttonMarkup({corners: true, theme: theme, shadow: true}) 3540 .attr({ 3541 'role': 'slider', 3542 'aria-valuemin': min, 3543 'aria-valuemax': max, 3544 'aria-valuenow': val(), 3545 'aria-valuetext': val(), 3546 'title': val(), 3547 'aria-labelledby': labelID 3548 }); 3549 3550 $.extend(this, { 3551 slider: slider, 3552 handle: handle, 3553 dragging: false, 3554 beforeStart: null 3555 }); 3556 3557 if(cType == 'select'){ 3558 slider.wrapInner('<div class="ui-slider-inneroffset"></div>'); 3559 var options = control.find('option'); 3560 3561 control.find('option').each(function(i){ 3562 var side = (i==0) ?'b':'a', 3563 corners = (i==0) ? 'right' :'left', 3564 theme = (i==0) ? ' ui-btn-down-' + trackTheme :' ui-btn-active'; 3565 $('<div class="ui-slider-labelbg ui-slider-labelbg-'+ side + theme +' ui-btn-corner-'+ corners+'"></div>').prependTo(slider); 3566 $('<span class="ui-slider-label ui-slider-label-'+ side + theme +' ui-btn-corner-'+ corners+'" role="img">'+$(this).text()+'</span>').prependTo(handle); 3567 }); 3568 3569 } 3570 3571 label.addClass('ui-slider'); 3572 3573 // monitor the input for updated values 3574 control 3575 .addClass((cType == 'input') ? 'ui-slider-input' : 'ui-slider-switch') 3576 .change(function(){ 3577 self.refresh( val(), true ); 3578 }) 3579 .keyup(function(){ // necessary? 3580 self.refresh( val(), true, true ); 3581 }) 3582 .blur(function(){ 3583 self.refresh( val(), true ); 3584 }); 3585 3586 // prevent screen drag when slider activated 3587 $(document).bind( "touchmove mousemove", function(event){ 3588 if ( self.dragging ) { 3589 self.refresh( event ); 3590 return false; 3591 } 3592 }); 3593 3594 slider 3595 .bind( "touchstart mousedown", function(event){ 3596 self.dragging = true; 3597 if ( cType === "select" ) { 3598 self.beforeStart = control[0].selectedIndex; 3599 } 3600 self.refresh( event ); 3601 return false; 3602 }); 3603 3604 slider 3605 .add(document) 3606 .bind( "touchend mouseup", function(){ 3607 if ( self.dragging ) { 3608 self.dragging = false; 3609 if ( cType === "select" ) { 3610 if ( self.beforeStart === control[0].selectedIndex ) { 3611 //tap occurred, but value didn't change. flip it! 3612 self.refresh( self.beforeStart === 0 ? 1 : 0 ); 3613 } 3614 var curval = val(); 3615 var snapped = Math.round( curval / (max - min) * 100 ); 3616 handle 3617 .addClass("ui-slider-handle-snapping") 3618 .css("left", snapped + "%") 3619 .animationComplete(function(){ 3620 handle.removeClass("ui-slider-handle-snapping"); 3621 }); 3622 } 3623 return false; 3624 } 3625 }); 3626 3627 slider.insertAfter(control); 3628 3629 // NOTE force focus on handle 3630 this.handle 3631 .bind( "touchstart mousedown", function(){ 3632 $(this).focus(); 3633 }); 3634 3635 this.handle 3636 .bind( "keydown", function( event ) { 3637 var index = val(); 3638 3639 if ( self.options.disabled ) { 3640 return; 3641 } 3642 3643 // In all cases prevent the default and mark the handle as active 3644 switch ( event.keyCode ) { 3645 case $.mobile.keyCode.HOME: 3646 case $.mobile.keyCode.END: 3647 case $.mobile.keyCode.PAGE_UP: 3648 case $.mobile.keyCode.PAGE_DOWN: 3649 case $.mobile.keyCode.UP: 3650 case $.mobile.keyCode.RIGHT: 3651 case $.mobile.keyCode.DOWN: 3652 case $.mobile.keyCode.LEFT: 3653 event.preventDefault(); 3654 3655 if ( !self._keySliding ) { 3656 self._keySliding = true; 3657 $( this ).addClass( "ui-state-active" ); 3658 } 3659 break; 3660 } 3661 3662 // move the slider according to the keypress 3663 switch ( event.keyCode ) { 3664 case $.mobile.keyCode.HOME: 3665 self.refresh(min); 3666 break; 3667 case $.mobile.keyCode.END: 3668 self.refresh(max); 3669 break; 3670 case $.mobile.keyCode.PAGE_UP: 3671 case $.mobile.keyCode.UP: 3672 case $.mobile.keyCode.RIGHT: 3673 self.refresh(index + step); 3674 break; 3675 case $.mobile.keyCode.PAGE_DOWN: 3676 case $.mobile.keyCode.DOWN: 3677 case $.mobile.keyCode.LEFT: 3678 self.refresh(index - step); 3679 break; 3680 } 3681 }) // remove active mark 3682 .keyup(function( event ) { 3683 if ( self._keySliding ) { 3684 self._keySliding = false; 3685 $( this ).removeClass( "ui-state-active" ); 3686 } 3687 }); 3688 3689 this.refresh(); 3690 }, 3691 3692 refresh: function(val, isfromControl, preventInputUpdate){ 3693 if ( this.options.disabled ) { return; } 3694 3695 var control = this.element, percent, 3696 cType = control[0].nodeName.toLowerCase(), 3697 min = (cType === "input") ? parseFloat(control.attr("min")) : 0, 3698 max = (cType === "input") ? parseFloat(control.attr("max")) : control.find("option").length - 1; 3699 3700 if ( typeof val === "object" ) { 3701 var data = val.originalEvent.touches ? val.originalEvent.touches[ 0 ] : val, 3702 // a slight tolerance helped get to the ends of the slider 3703 tol = 8; 3704 if ( !this.dragging 3705 || data.pageX < this.slider.offset().left - tol 3706 || data.pageX > this.slider.offset().left + this.slider.width() + tol ) { 3707 return; 3708 } 3709 percent = Math.round( ((data.pageX - this.slider.offset().left) / this.slider.width() ) * 100 ); 3710 } else { 3711 if ( val == null ) { 3712 val = (cType === "input") ? parseFloat(control.val()) : control[0].selectedIndex; 3713 } 3714 percent = (parseFloat(val) - min) / (max - min) * 100; 3715 } 3716 3717 if ( isNaN(percent) ) { return; } 3718 if ( percent < 0 ) { percent = 0; } 3719 if ( percent > 100 ) { percent = 100; } 3720 3721 var newval = Math.round( (percent / 100) * (max - min) ) + min; 3722 if ( newval < min ) { newval = min; } 3723 if ( newval > max ) { newval = max; } 3724 3725 //flip the stack of the bg colors 3726 if ( percent > 60 && cType === "select" ) { 3727 3728 } 3729 this.handle.css("left", percent + "%"); 3730 this.handle.attr({ 3731 "aria-valuenow": (cType === "input") ? newval : control.find("option").eq(newval).attr("value"), 3732 "aria-valuetext": (cType === "input") ? newval : control.find("option").eq(newval).text(), 3733 title: newval 3734 }); 3735 3736 // add/remove classes for flip toggle switch 3737 if ( cType === "select" ) { 3738 if ( newval === 0 ) { 3739 this.slider.addClass("ui-slider-switch-a") 3740 .removeClass("ui-slider-switch-b"); 3741 } else { 3742 this.slider.addClass("ui-slider-switch-b") 3743 .removeClass("ui-slider-switch-a"); 3744 } 3745 } 3746 3747 if(!preventInputUpdate){ 3748 // update control's value 3749 if ( cType === "input" ) { 3750 control.val(newval); 3751 } else { 3752 control[ 0 ].selectedIndex = newval; 3753 } 3754 if (!isfromControl) { control.trigger("change"); } 3755 } 3756 }, 3757 3758 enable: function(){ 3759 this.element.attr("disabled", false); 3760 this.slider.removeClass("ui-disabled").attr("aria-disabled", false); 3761 return this._setOption("disabled", false); 3762 }, 3763 3764 disable: function(){ 3765 this.element.attr("disabled", true); 3766 this.slider.addClass("ui-disabled").attr("aria-disabled", true); 3767 return this._setOption("disabled", true); 3768 } 3769 }); 3770 })( jQuery ); 3771 3772 /* 3773 * jQuery Mobile Framework : "collapsible" plugin 3774 * Copyright (c) jQuery Project 3775 * Dual licensed under the MIT or GPL Version 2 licenses. 3776 * http://jquery.org/license 3777 */ 3778 (function($, undefined ) { 3779 $.widget( "mobile.collapsible", $.mobile.widget, { 3780 options: { 3781 expandCueText: ' click to expand contents', 3782 collapseCueText: ' click to collapse contents', 3783 collapsed: false, 3784 heading: '>:header,>legend', 3785 theme: null, 3786 iconTheme: 'd' 3787 }, 3788 _create: function(){ 3789 3790 var $el = this.element, 3791 o = this.options, 3792 collapsibleContain = $el.addClass('ui-collapsible-contain'), 3793 collapsibleHeading = $el.find(o.heading).eq(0), 3794 collapsibleContent = collapsibleContain.wrapInner('<div class="ui-collapsible-content"></div>').find('.ui-collapsible-content'), 3795 collapsibleParent = $el.closest('[data-role="collapsible-set"]').addClass('ui-collapsible-set'); 3796 3797 //replace collapsibleHeading if it's a legend 3798 if(collapsibleHeading.is('legend')){ 3799 collapsibleHeading = $('<div role="heading">'+ collapsibleHeading.html() +'</div>').insertBefore(collapsibleHeading); 3800 collapsibleHeading.next().remove(); 3801 } 3802 3803 //drop heading in before content 3804 collapsibleHeading.insertBefore(collapsibleContent); 3805 3806 //modify markup & attributes 3807 collapsibleHeading.addClass('ui-collapsible-heading') 3808 .append('<span class="ui-collapsible-heading-status"></span>') 3809 .wrapInner('<a href="#" class="ui-collapsible-heading-toggle"></a>') 3810 .find('a:eq(0)') 3811 .buttonMarkup({ 3812 shadow: !!!collapsibleParent.length, 3813 corners:false, 3814 iconPos: 'left', 3815 icon: 'plus', 3816 theme: o.theme 3817 }) 3818 .find('.ui-icon') 3819 .removeAttr('class') 3820 .buttonMarkup({ 3821 shadow: true, 3822 corners:true, 3823 iconPos: 'notext', 3824 icon: 'plus', 3825 theme: o.iconTheme 3826 }); 3827 3828 if( !collapsibleParent.length ){ 3829 collapsibleHeading 3830 .find('a:eq(0)') 3831 .addClass('ui-corner-all') 3832 .find('.ui-btn-inner') 3833 .addClass('ui-corner-all'); 3834 } 3835 else { 3836 if( collapsibleContain.data('collapsible-last') ){ 3837 collapsibleHeading 3838 .find('a:eq(0), .ui-btn-inner') 3839 .addClass('ui-corner-bottom'); 3840 } 3841 } 3842 3843 3844 //events 3845 collapsibleContain 3846 .bind('collapse', function(event){ 3847 if( !event.isDefaultPrevented() ){ 3848 event.preventDefault(); 3849 collapsibleHeading 3850 .addClass('ui-collapsible-heading-collapsed') 3851 .find('.ui-collapsible-heading-status').text(o.expandCueText); 3852 3853 collapsibleHeading.find('.ui-icon').removeClass('ui-icon-minus').addClass('ui-icon-plus'); 3854 collapsibleContent.addClass('ui-collapsible-content-collapsed').attr('aria-hidden',true); 3855 3856 if( collapsibleContain.data('collapsible-last') ){ 3857 collapsibleHeading 3858 .find('a:eq(0), .ui-btn-inner') 3859 .addClass('ui-corner-bottom'); 3860 } 3861 } 3862 3863 }) 3864 .bind('expand', function(event){ 3865 if( !event.isDefaultPrevented() ){ 3866 event.preventDefault(); 3867 collapsibleHeading 3868 .removeClass('ui-collapsible-heading-collapsed') 3869 .find('.ui-collapsible-heading-status').text(o.collapseCueText); 3870 3871 collapsibleHeading.find('.ui-icon').removeClass('ui-icon-plus').addClass('ui-icon-minus'); 3872 collapsibleContent.removeClass('ui-collapsible-content-collapsed').attr('aria-hidden',false); 3873 3874 if( collapsibleContain.data('collapsible-last') ){ 3875 collapsibleHeading 3876 .find('a:eq(0), .ui-btn-inner') 3877 .removeClass('ui-corner-bottom'); 3878 } 3879 3880 } 3881 }) 3882 .trigger(o.collapsed ? 'collapse' : 'expand'); 3883 3884 3885 //close others in a set 3886 if( collapsibleParent.length && !collapsibleParent.data("collapsiblebound") ){ 3887 collapsibleParent 3888 .data("collapsiblebound", true) 3889 .bind("expand", function( event ){ 3890 $(this).find( ".ui-collapsible-contain" ) 3891 .not( $(event.target).closest( ".ui-collapsible-contain" ) ) 3892 .not( "> .ui-collapsible-contain .ui-collapsible-contain" ) 3893 .trigger( "collapse" ); 3894 }) 3895 var set = collapsibleParent.find('[data-role=collapsible]') 3896 3897 set.first() 3898 .find('a:eq(0)') 3899 .addClass('ui-corner-top') 3900 .find('.ui-btn-inner') 3901 .addClass('ui-corner-top'); 3902 3903 set.last().data('collapsible-last', true) 3904 } 3905 3906 collapsibleHeading.bind( $.support.touch ? "touchstart" : "click", function(){ 3907 if( collapsibleHeading.is('.ui-collapsible-heading-collapsed') ){ 3908 collapsibleContain.trigger('expand'); 3909 } 3910 else { 3911 collapsibleContain.trigger('collapse'); 3912 } 3913 return false; 3914 }); 3915 3916 } 3917 }); 3918 })( jQuery );/* 3919 * jQuery Mobile Framework: "controlgroup" plugin - corner-rounding for groups of buttons, checks, radios, etc 3920 * Copyright (c) jQuery Project 3921 * Dual licensed under the MIT or GPL Version 2 licenses. 3922 * http://jquery.org/license 3923 */ 3924 (function($, undefined ) { 3925 $.fn.controlgroup = function(options){ 3926 3927 return this.each(function(){ 3928 var o = $.extend({ 3929 direction: $( this ).data( "type" ) || "vertical", 3930 shadow: false 3931 },options); 3932 var groupheading = $(this).find('>legend'), 3933 flCorners = o.direction == 'horizontal' ? ['ui-corner-left', 'ui-corner-right'] : ['ui-corner-top', 'ui-corner-bottom'], 3934 type = $(this).find('input:eq(0)').attr('type'); 3935 3936 //replace legend with more stylable replacement div 3937 if( groupheading.length ){ 3938 $(this).wrapInner('<div class="ui-controlgroup-controls"></div>'); 3939 $('<div role="heading" class="ui-controlgroup-label">'+ groupheading.html() +'</div>').insertBefore( $(this).children(0) ); 3940 groupheading.remove(); 3941 } 3942 3943 $(this).addClass('ui-corner-all ui-controlgroup ui-controlgroup-'+o.direction); 3944 3945 function flipClasses(els){ 3946 els 3947 .removeClass('ui-btn-corner-all ui-shadow') 3948 .eq(0).addClass(flCorners[0]) 3949 .end() 3950 .filter(':last').addClass(flCorners[1]).addClass('ui-controlgroup-last'); 3951 } 3952 flipClasses($(this).find('.ui-btn')); 3953 flipClasses($(this).find('.ui-btn-inner')); 3954 if(o.shadow){ 3955 $(this).addClass('ui-shadow'); 3956 } 3957 }); 3958 }; 3959 })(jQuery);/* 3960 * jQuery Mobile Framework : "fieldcontain" plugin - simple class additions to make form row separators 3961 * Copyright (c) jQuery Project 3962 * Dual licensed under the MIT or GPL Version 2 licenses. 3963 * http://jquery.org/license 3964 */ 3965 (function($, undefined ) { 3966 $.fn.fieldcontain = function(options){ 3967 return this.addClass('ui-field-contain ui-body ui-br'); 3968 }; 3969 })(jQuery);/* 3970 * jQuery Mobile Framework : "listview" plugin 3971 * Copyright (c) jQuery Project 3972 * Dual licensed under the MIT or GPL Version 2 licenses. 3973 * http://jquery.org/license 3974 */ 3975 (function($, undefined ) { 3976 3977 $.widget( "mobile.listview", $.mobile.widget, { 3978 options: { 3979 theme: "c", 3980 countTheme: "c", 3981 headerTheme: "b", 3982 dividerTheme: "b", 3983 splitIcon: "arrow-r", 3984 splitTheme: "b", 3985 inset: false 3986 }, 3987 3988 _create: function() { 3989 var $list = this.element, 3990 o = this.options; 3991 3992 // create listview markup 3993 $list 3994 .addClass( "ui-listview" ) 3995 .attr( "role", "listbox" ) 3996 3997 if ( o.inset ) { 3998 $list.addClass( "ui-listview-inset ui-corner-all ui-shadow" ); 3999 } 4000 4001 $list.delegate( ".ui-li", "focusin", function() { 4002 $( this ).attr( "tabindex", "0" ); 4003 }); 4004 4005 this._itemApply( $list, $list ); 4006 4007 this.refresh( true ); 4008 4009 //keyboard events for menu items 4010 $list.keydown(function( e ) { 4011 var target = $( e.target ), 4012 li = target.closest( "li" ); 4013 4014 // switch logic based on which key was pressed 4015 switch ( e.keyCode ) { 4016 // up or left arrow keys 4017 case 38: 4018 var prev = li.prev(); 4019 4020 // if there's a previous option, focus it 4021 if ( prev.length ) { 4022 target 4023 .blur() 4024 .attr( "tabindex", "-1" ); 4025 4026 prev.find( "a" ).first().focus(); 4027 } 4028 4029 return false; 4030 break; 4031 4032 // down or right arrow keys 4033 case 40: 4034 var next = li.next(); 4035 4036 // if there's a next option, focus it 4037 if ( next.length ) { 4038 target 4039 .blur() 4040 .attr( "tabindex", "-1" ); 4041 4042 next.find( "a" ).first().focus(); 4043 } 4044 4045 return false; 4046 break; 4047 4048 case 39: 4049 var a = li.find( "a.ui-li-link-alt" ); 4050 4051 if ( a.length ) { 4052 target.blur(); 4053 a.first().focus(); 4054 } 4055 4056 return false; 4057 break; 4058 4059 case 37: 4060 var a = li.find( "a.ui-link-inherit" ); 4061 4062 if ( a.length ) { 4063 target.blur(); 4064 a.first().focus(); 4065 } 4066 4067 return false; 4068 break; 4069 4070 // if enter or space is pressed, trigger click 4071 case 13: 4072 case 32: 4073 target.trigger( "click" ); 4074 4075 return false; 4076 break; 4077 } 4078 }); 4079 4080 // tapping the whole LI triggers click on the first link 4081 $list.delegate( "li", "click", function(event) { 4082 if ( !$( event.target ).closest( "a" ).length ) { 4083 $( this ).find( "a" ).first().trigger( "click" ); 4084 return false; 4085 } 4086 }); 4087 }, 4088 4089 _itemApply: function( $list, item ) { 4090 // TODO class has to be defined in markup 4091 item.find( ".ui-li-count" ) 4092 .addClass( "ui-btn-up-" + ($list.data( "counttheme" ) || this.options.countTheme) + " ui-btn-corner-all" ); 4093 4094 item.find( "h1, h2, h3, h4, h5, h6" ).addClass( "ui-li-heading" ); 4095 4096 item.find( "p, dl" ).addClass( "ui-li-desc" ); 4097 4098 item.find( "li" ).find( "img:eq(0)" ).addClass( "ui-li-thumb" ).each(function() { 4099 $( this ).closest( "li" ) 4100 .addClass( $(this).is( ".ui-li-icon" ) ? "ui-li-has-icon" : "ui-li-has-thumb" ); 4101 }); 4102 4103 var aside = item.find( ".ui-li-aside" ); 4104 4105 if ( aside.length ) { 4106 aside.each(function(i, el) { 4107 $(el).prependTo( $(el).parent() ); //shift aside to front for css float 4108 }); 4109 } 4110 4111 if ( $.support.cssPseudoElement || !$.nodeName( item[0], "ol" ) ) { 4112 return; 4113 } 4114 }, 4115 4116 _removeCorners: function(li){ 4117 li 4118 .add( li.find(".ui-btn-inner, .ui-li-link-alt, .ui-li-thumb") ) 4119 .removeClass( "ui-corner-top ui-corner-bottom ui-corner-br ui-corner-bl ui-corner-tr ui-corner-tl" ); 4120 }, 4121 4122 refresh: function( create ) { 4123 this._createSubPages(); 4124 4125 var o = this.options, 4126 $list = this.element, 4127 self = this, 4128 dividertheme = $list.data( "dividertheme" ) || o.dividerTheme, 4129 li = $list.children( "li" ), 4130 counter = $.support.cssPseudoElement || !$.nodeName( $list[0], "ol" ) ? 0 : 1; 4131 4132 if ( counter ) { 4133 $list.find( ".ui-li-dec" ).remove(); 4134 } 4135 4136 li.attr({ "role": "option", "tabindex": "-1" }); 4137 4138 li.first().attr( "tabindex", "0" ); 4139 4140 li.each(function( pos ) { 4141 var item = $( this ), 4142 itemClass = "ui-li"; 4143 4144 // If we're creating the element, we update it regardless 4145 if ( !create && item.hasClass( "ui-li" ) ) { 4146 return; 4147 } 4148 4149 var itemTheme = item.data("theme") || o.theme; 4150 4151 var a = item.find( "a" ); 4152 4153 if ( a.length ) { 4154 var icon = item.data("icon"); 4155 4156 item 4157 .buttonMarkup({ 4158 wrapperEls: "div", 4159 shadow: false, 4160 corners: false, 4161 iconpos: "right", 4162 icon: a.length > 1 || icon === false ? false : icon || "arrow-r", 4163 theme: itemTheme 4164 }); 4165 4166 a.first().addClass( "ui-link-inherit" ); 4167 4168 if ( a.length > 1 ) { 4169 itemClass += " ui-li-has-alt"; 4170 4171 var last = a.last(), 4172 splittheme = $list.data( "splittheme" ) || last.data( "theme" ) || o.splitTheme; 4173 4174 last 4175 .appendTo(item) 4176 .attr( "title", last.text() ) 4177 .addClass( "ui-li-link-alt" ) 4178 .empty() 4179 .buttonMarkup({ 4180 shadow: false, 4181 corners: false, 4182 theme: itemTheme, 4183 icon: false, 4184 iconpos: false 4185 }) 4186 .find( ".ui-btn-inner" ) 4187 .append( $( "<span>" ).buttonMarkup({ 4188 shadow: true, 4189 corners: true, 4190 theme: splittheme, 4191 iconpos: "notext", 4192 icon: $list.data( "spliticon" ) || last.data( "icon" ) || o.splitIcon 4193 } ) ); 4194 } 4195 4196 } else if ( item.data( "role" ) === "list-divider" ) { 4197 itemClass += " ui-li-divider ui-btn ui-bar-" + dividertheme; 4198 item.attr( "role", "heading" ); 4199 4200 //reset counter when a divider heading is encountered 4201 if ( counter ) { 4202 counter = 1; 4203 } 4204 4205 } else { 4206 itemClass += " ui-li-static ui-btn-up-" + itemTheme; 4207 } 4208 4209 4210 if( o.inset ){ 4211 if ( pos === 0 ) { 4212 itemClass += " ui-corner-top"; 4213 4214 item 4215 .add( item.find( ".ui-btn-inner" ) ) 4216 .find( ".ui-li-link-alt" ) 4217 .addClass( "ui-corner-tr" ) 4218 .end() 4219 .find( ".ui-li-thumb" ) 4220 .addClass( "ui-corner-tl" ); 4221 if(item.next().next().length){ 4222 self._removeCorners( item.next() ); 4223 } 4224 4225 } 4226 if ( pos === li.length - 1 ) { 4227 itemClass += " ui-corner-bottom"; 4228 4229 item 4230 .add( item.find( ".ui-btn-inner" ) ) 4231 .find( ".ui-li-link-alt" ) 4232 .addClass( "ui-corner-br" ) 4233 .end() 4234 .find( ".ui-li-thumb" ) 4235 .addClass( "ui-corner-bl" ); 4236 4237 if(item.prev().prev().length){ 4238 self._removeCorners( item.prev() ); 4239 } 4240 } 4241 } 4242 4243 4244 if ( counter && itemClass.indexOf( "ui-li-divider" ) < 0 ) { 4245 item 4246 .find( ".ui-link-inherit" ).first() 4247 .addClass( "ui-li-jsnumbering" ) 4248 .prepend( "<span class='ui-li-dec'>" + (counter++) + ". </span>" ); 4249 } 4250 4251 item.addClass( itemClass ); 4252 4253 if ( !create ) { 4254 self._itemApply( $list, item ); 4255 } 4256 }); 4257 }, 4258 4259 //create a string for ID/subpage url creation 4260 _idStringEscape: function( str ){ 4261 return str.replace(/[^a-zA-Z0-9]/g, '-'); 4262 }, 4263 4264 _createSubPages: function() { 4265 var parentList = this.element, 4266 parentPage = parentList.closest( ".ui-page" ), 4267 parentId = parentPage.data( "url" ), 4268 o = this.options, 4269 self = this, 4270 persistentFooterID = parentPage.find( "[data-role='footer']" ).data( "id" ); 4271 4272 $( parentList.find( "ul, ol" ).toArray().reverse() ).each(function( i ) { 4273 var list = $( this ), 4274 parent = list.parent(), 4275 title = $.trim(parent.contents()[ 0 ].nodeValue) || parent.find('a:first').text(), 4276 id = parentId + "&" + $.mobile.subPageUrlKey + "=" + self._idStringEscape(title + " " + i), 4277 theme = list.data( "theme" ) || o.theme, 4278 countTheme = list.data( "counttheme" ) || parentList.data( "counttheme" ) || o.countTheme, 4279 newPage = list.wrap( "<div data-role='page'><div data-role='content'></div></div>" ) 4280 .parent() 4281 .before( "<div data-role='header' data-theme='" + o.headerTheme + "'><div class='ui-title'>" + title + "</div></div>" ) 4282 .after( persistentFooterID ? $( "<div>", { "data-role": "footer", "data-id": persistentFooterID, "class": "ui-footer-duplicate" } ) : "" ) 4283 .parent() 4284 .attr({ 4285 "data-url": id, 4286 "data-theme": theme, 4287 "data-count-theme": countTheme 4288 }) 4289 .appendTo( $.mobile.pageContainer ); 4290 4291 4292 4293 newPage.page(); 4294 var anchor = parent.find('a:first'); 4295 if (!anchor.length) { 4296 anchor = $("<a></a>").html(title).prependTo(parent.empty()); 4297 } 4298 anchor.attr('href','#' + id); 4299 }).listview(); 4300 } 4301 }); 4302 4303 })( jQuery ); 4304 /* 4305 * jQuery Mobile Framework : "listview" filter extension 4306 * Copyright (c) jQuery Project 4307 * Dual licensed under the MIT or GPL Version 2 licenses. 4308 * http://jquery.org/license 4309 */ 4310 (function($, undefined ) { 4311 4312 $.mobile.listview.prototype.options.filter = false; 4313 4314 $( "[data-role='listview']" ).live( "listviewcreate", function() { 4315 var list = $( this ), 4316 listview = list.data( "listview" ); 4317 if ( !listview.options.filter ) { 4318 return; 4319 } 4320 4321 var wrapper = $( "<form>", { "class": "ui-listview-filter ui-bar-c", "role": "search" } ), 4322 4323 search = $( "<input>", { 4324 placeholder: "Filter results...", 4325 "data-type": "search" 4326 }) 4327 .bind( "keyup change", function() { 4328 var val = this.value.toLowerCase();; 4329 list.children().show(); 4330 if ( val ) { 4331 list.children().filter(function() { 4332 return $( this ).text().toLowerCase().indexOf( val ) === -1; 4333 }).hide(); 4334 } 4335 4336 //listview._numberItems(); 4337 }) 4338 .appendTo( wrapper ) 4339 .textinput(); 4340 4341 wrapper.insertBefore( list ); 4342 }); 4343 4344 })( jQuery ); 4345 /* 4346 * jQuery Mobile Framework : "dialog" plugin. 4347 * Copyright (c) jQuery Project 4348 * Dual licensed under the MIT (MIT-LICENSE.txt) and GPL (GPL-LICENSE.txt) licenses. 4349 * Note: Code is in draft form and is subject to change 4350 */ 4351 (function($, undefined ) { 4352 $.widget( "mobile.dialog", $.mobile.widget, { 4353 options: {}, 4354 _create: function(){ 4355 var self = this, 4356 $el = self.element; 4357 4358 /* class the markup for dialog styling */ 4359 this.element 4360 //add ARIA role 4361 .attr("role","dialog") 4362 .addClass('ui-page ui-dialog ui-body-a') 4363 .find('[data-role=header]') 4364 .addClass('ui-corner-top ui-overlay-shadow') 4365 .prepend( '<a href="#" data-icon="delete" data-rel="back" data-iconpos="notext">Close</a>' ) 4366 .end() 4367 .find('.ui-content:not([class*="ui-body-"])') 4368 .addClass('ui-body-c') 4369 .end() 4370 .find('.ui-content,[data-role=footer]') 4371 .last() 4372 .addClass('ui-corner-bottom ui-overlay-shadow'); 4373 4374 /* bind events 4375 - clicks and submits should use the closing transition that the dialog opened with 4376 unless a data-transition is specified on the link/form 4377 - if the click was on the close button, or the link has a data-rel="back" it'll go back in history naturally 4378 */ 4379 this.element 4380 .bind( "click submit", function(e){ 4381 var $targetel; 4382 if( e.type == "click" ){ 4383 $targetel = $(e.target).closest("a"); 4384 } 4385 else{ 4386 $targetel = $(e.target).closest("form"); 4387 } 4388 4389 if( $targetel.length && !$targetel.data("transition") ){ 4390 $targetel 4391 .attr("data-transition", $.mobile.urlHistory.getActive().transition ) 4392 .attr("data-direction", "reverse"); 4393 } 4394 }); 4395 4396 }, 4397 4398 //close method goes back in history 4399 close: function(){ 4400 window.history.back(); 4401 } 4402 }); 4403 })( jQuery );/* 4404 * jQuery Mobile Framework : "navbar" plugin 4405 * Copyright (c) jQuery Project 4406 * Dual licensed under the MIT or GPL Version 2 licenses. 4407 * http://jquery.org/license 4408 */ 4409 (function($, undefined ) { 4410 $.widget( "mobile.navbar", $.mobile.widget, { 4411 options: { 4412 iconpos: 'top', 4413 grid: null 4414 }, 4415 _create: function(){ 4416 var $navbar = this.element, 4417 $navbtns = $navbar.find("a"), 4418 iconpos = $navbtns.filter('[data-icon]').length ? this.options.iconpos : undefined; 4419 4420 $navbar 4421 .addClass('ui-navbar') 4422 .attr("role","navigation") 4423 .find("ul") 4424 .grid({grid: this.options.grid }); 4425 4426 if( !iconpos ){ 4427 $navbar.addClass("ui-navbar-noicons"); 4428 } 4429 4430 $navbtns 4431 .buttonMarkup({ 4432 corners: false, 4433 shadow: false, 4434 iconpos: iconpos 4435 }); 4436 4437 $navbar.delegate("a", "click",function(event){ 4438 $navbtns.removeClass( "ui-btn-active" ); 4439 $( this ).addClass( "ui-btn-active" ); 4440 }); 4441 } 4442 }); 4443 })( jQuery );/* 4444 * jQuery Mobile Framework : plugin for creating CSS grids 4445 * Copyright (c) jQuery Project 4446 * Dual licensed under the MIT or GPL Version 2 licenses. 4447 * http://jquery.org/license 4448 */ 4449 (function($, undefined ) { 4450 $.fn.grid = function(options){ 4451 return this.each(function(){ 4452 var o = $.extend({ 4453 grid: null 4454 },options); 4455 4456 4457 var $kids = $(this).children(), 4458 gridCols = {a: 2, b:3, c:4, d:5}, 4459 grid = o.grid, 4460 iterator; 4461 4462 if( !grid ){ 4463 if( $kids.length <= 5 ){ 4464 for(var letter in gridCols){ 4465 if(gridCols[letter] == $kids.length){ grid = letter; } 4466 } 4467 } 4468 else{ 4469 grid = 'a'; 4470 } 4471 } 4472 iterator = gridCols[grid]; 4473 4474 $(this).addClass('ui-grid-' + grid); 4475 4476 $kids.filter(':nth-child(' + iterator + 'n+1)').addClass('ui-block-a'); 4477 $kids.filter(':nth-child(' + iterator + 'n+2)').addClass('ui-block-b'); 4478 4479 if(iterator > 2){ 4480 $kids.filter(':nth-child(3n+3)').addClass('ui-block-c'); 4481 } 4482 if(iterator> 3){ 4483 $kids.filter(':nth-child(4n+4)').addClass('ui-block-d'); 4484 } 4485 if(iterator > 4){ 4486 $kids.filter(':nth-child(5n+5)').addClass('ui-block-e'); 4487 } 4488 4489 }); 4490 }; 4491 })(jQuery); 4492 4493 console.log("jQuery len:" + jQuery.toString().length); 4494