2 * jQuery Mobile Widget @VERSION
4 * This software is licensed under the MIT licence (as defined by the OSI at
5 * http://www.opensource.org/licenses/mit-license.php)
7 * ***************************************************************************
8 * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
9 * Copyright (c) 2011 by Intel Corporation Ltd.
11 * Permission is hereby granted, free of charge, to any person obtaining a
12 * copy of this software and associated documentation files (the "Software"),
13 * to deal in the Software without restriction, including without limitation
14 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
15 * and/or sell copies of the Software, and to permit persons to whom the
16 * Software is furnished to do so, subject to the following conditions:
18 * The above copyright notice and this permission notice shall be included in
19 * all copies or substantial portions of the Software.
21 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
27 * DEALINGS IN THE SOFTWARE.
28 * ***************************************************************************
30 * Authors: Elliot Smith <elliot.smith@intel.com>
33 // optionheader provides a collapsible toolbar for buttons and
34 // segmented controls directly under the title bar. It
35 // wraps a jQuery Mobile grid in a collapsible container; clicking
36 // on the container, or on one of the buttons inside the container,
39 // To add an option header to a page, mark up the header with a
40 // data-role="optionheader" attribute, as shown in this example:
42 // <div data-role="header">
43 // <h1>Option header - 3 buttons example</h1>
44 // <div data-role="optionheader">
45 // <div class="ui-grid-b">
46 // <div class="ui-block-a"><a data-role="button">Previous</a></div>
47 // <div class="ui-block-b"><a data-role="button">Cancel</a></div>
48 // <div class="ui-block-c"><a data-role="button">Next</a></div>
53 // The optionheader can also be used inline (e.g. in a content block or
56 // Alternatively, use $('...').optionheader() to apply programmatically.
58 // The grid inside the optionheader should be marked up as for
59 // a standard jQuery Mobile grid. (The widget has been tested with one
60 // or two rows of 2-4 columns each.)
62 // Note that if you use this with fixed headers, you may find that
63 // expanding the option header increases the page size so that scrollbars
64 // appear (jQuery Mobile's own collapsible content areas cause the
65 // same issue). You can alleviate this somewhat by calling the show() method
66 // on the page toolbars each time the size of the header changes.
68 // The widget is configurable via a data-options attribute on the same
69 // div as the data-role="optionheader" attribute, e.g.
71 // <div data-role="header">
72 // <h1>Option header - configured</h1>
73 // <div data-role="optionheader" data-options='{"collapsed":true, "duration":1.5}'>
74 // <div class="ui-grid-b">
75 // <div class="ui-block-a"><a data-role="button">Previous</a></div>
76 // <div class="ui-block-b"><a data-role="button">Cancel</a></div>
77 // <div class="ui-block-c"><a data-role="button">Next</a></div>
82 // Options can also be set with $(...).optionheader('option', 'name', value).
83 // However, if you do this, you'll need to call $(...).optionheader('refresh')
84 // afterwards for the new values to take effect (note that optionheader()
85 // can be applied multiple times to an element without side effects).
87 // See below for the available options.
89 // Theme: by default, gets a 'b' swatch; override with data-theme="X"
92 // Options (can be set with a data-options attribute):
94 // {Boolean} [showIndicator=true] Set to true (the default) to show
95 // the upward-pointing arrow indicator on top of the title bar.
96 // {Boolean} [startCollapsed=false] Sets the appearance when the option
97 // header is first displayed; defaults to false (i.e. show the header
98 // expanded on first draw). NB setting this option later has no
99 // effect: use collapse() to collapse a widget which is already
101 // {Boolean} [expandable=true] Sets whether the header will expand
102 // in response to clicks; default = true.
103 // {Float} [duration=0.25] Duration of the expand/collapse animation.
105 // Methods (see below for docs):
113 // expand: Triggered when the option header is expanded
114 // collapse: Triggered when the option header is collapsed
118 (function ($, undefined) {
119 $.widget("tizen.optionheader", $.mobile.widget, {
121 initSelector: ":jqmData(role='optionheader')",
124 startCollapsed: false,
127 collapseOnInit : true,
128 default_font_size : $('html').css('font-size')
130 collapsedHeight: '5px',
132 _create: function () {
138 dataOptions = this.element.jqmData( 'options' ),
139 page = this.element.closest( ':jqmData(role="page")' );
140 // parse data-options
141 $.extend( this.options, dataOptions );
143 this.isCollapsed = this.options.collapseOnInit;
144 this.expandedHeight = null;
146 // parse data-theme and reset options.theme if it's present
147 theme = this.element.jqmData( 'theme' ) || this.options.theme;
148 this.options.theme = theme;
150 this.element.closest( ':jqmData(role="header")' ).addClass( "ui-option-header-resizing" );
152 // set up the click handler; it's done here so it can
153 // easily be removed, as there should only be one instance
154 // of the handler function for each class instance
155 this.clickHandler = function () {
159 /* Apply REM scaling */
160 elementHeight = elementHeight / ( 36 / parseInt(this.option.default_font_size) );
162 if ( this.element.height() < elementHeight ) {
163 this.element.css( "height", elementHeight );
166 // get the element's dimensions
167 // and to set its initial collapse state;
168 // either do it now (if the page is visible already)
171 if ( page.is(":visible") ) {
177 page.bind( "pagebeforeshow", function () {
178 self._setArrowLeft();
182 self._setArrowLeft();
186 _realize: function () {
187 if ( !this.expandedHeight ) {
188 this.expandedHeight = this.element.height();
191 if ( this.isCollapsed ) {
192 // if (this.options.startCollapsed) {
193 this.collapse( {duration: 0} );
197 _setArrowLeft: function () {
198 var matchingBtn = $( this.element ).jqmData( "for" ),
202 matchBtn = $( this.element ).parents( ".ui-page" ).find( "#" + matchingBtn ),
203 buttonRight = matchBtn.nextAll().is( "a" ) ? btn3Position : btn2Position;
204 /* Apply REM scaling */
205 scaleFactor = ( 36 / parseInt(this.option.default_font_size) );
207 if ( $(this.element).parents(".ui-page").find( "#" + matchingBtn ).length != 0 ) {
209 if ( this.options.expandable ) {
210 matchBtn.bind( 'vclick', this.clickHandler );
212 matchBtn.unbind( 'vclick', this.clickHandler );
215 // decide arrow Button position
216 if ( matchBtn.css( "left" ) && matchBtn.css( "left" ) != "auto" ) {
217 $( ".ui-triangle-image" ).css( "left", matchBtn.width() / 2 + parseInt(matchBtn.css( "left" ), 10) - ( arrowCenter / scaleFactor ) + "px" );
218 } else if ( matchBtn.css("right") ) {
219 $( ".ui-triangle-image" ).css( "left", document.documentElement.clientWidth - matchBtn.width() / 2 - ( ( buttonRight - arrowCenter ) / scaleFactor ) + "px" );
222 $( ".ui-triangle-image" ).css( "left", document.documentElement.clientWidth / 2 - ( arrowCenter / scaleFactor ) + "px" );
225 // Draw the option header, according to current options
226 refresh: function () {
227 var el = this.element,
228 arrow = $( '<div class="ui-option-header-triangle-arrow"></div>' ),
229 optionHeaderClass = 'ui-option-header',
230 gridRowSelector = '.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d,.ui-grid-e',
231 theme = this.options.theme,
236 o = $.extend( {grid: null} ),
237 $kids = el.find( "div" ).eq( 0 ).children().children(),
239 gridCols = {solo: 1, a: 2, b: 3, c: 4, d: 5},
243 if ( $kids.length <= 5 ) {
244 for ( letter in gridCols ) {
245 if ( gridCols[ letter ] === $kids.length ) {
249 numRows = $kids.length / gridCols[grid];
255 // count ui-grid-* elements to get number of rows
256 // numRows = el.find(gridRowSelector).length;
258 // ...at least one row
259 // numRows = Math.max(1, numRows);
261 // add classes to outer div:
262 // ui-option-header-N-row, where N = options.rows
263 // ui-bar-X, where X = options.theme (defaults to 'c')
265 rowsClass = 'ui-option-header-' + numRows + '-row';
266 themeClass = 'ui-body-' + this.options.theme;
268 el.removeClass( rowsClass ).addClass( rowsClass );
269 el.removeClass( themeClass ).addClass( themeClass );
270 el.removeClass( optionHeaderClass ).addClass( optionHeaderClass );
272 // remove any arrow currently visible
273 el.prev( '.ui-option-header-triangle-arrow' ).remove();
274 // el.prev('.ui-triangle-container').remove();
276 // if there are elements inside the option header
277 // and this.options.showIndicator,
278 // insert a triangle arrow as the first element inside the
279 // optionheader div to show the header has hidden content
280 if ( this.options.showIndicator ) {
282 arrow.append("<div class='ui-triangle-image'></div>");
283 // arrow.triangle({"color": el.css('background-color'), offset: "50%"});
286 // if expandable, bind clicks to the toggle() method
287 if ( this.options.expandable ) {
288 // el.unbind('vclick', this.clickHandler).bind('vclick', this.clickHandler);
289 // arrow.unbind('vclick', this.clickHandler).bind('vclick', this.clickHandler);
290 el.bind( 'vclick', this.clickHandler );
291 arrow.bind( 'vclick', this.clickHandler );
294 el.unbind( 'vclick', this.clickHandler );
295 arrow.unbind( 'vclick', this.clickHandler );
298 // for each ui-grid-a element, add a class ui-option-header-row-M
299 // to it, where M is the xpath position() of the div
300 /* el.find(gridRowSelector).each(function (index) {
301 var klass = 'ui-option-header-row-' + (index + 1);
302 $(this).removeClass(klass).addClass(klass);
304 klass = 'ui-option-header-row-' + ( numRows );
305 el.find( "div" ).eq( 0 ).removeClass( klass ).addClass( klass );
307 // redraw the buttons (now that the optionheader has the right
309 el.find( '.ui-btn' ).each(function () {
310 $( this ).attr( 'data-' + $.mobile.ns + 'theme', theme );
312 // hack the class of the button to remove the old swatch
313 var klass = $( this ).attr( 'class' );
314 klass = klass.replace(/ui-btn-up-\w{1}\s*/, '');
315 klass = klass + ' ui-btn-up-' + theme;
316 $( this ).attr( 'class', klass );
320 _setHeight: function ( height, isCollapsed, options ) {
322 elt = this.element.get( 0 ),
328 options = options || {};
330 // set default duration if not specified
331 duration = options.duration;
332 if ( typeof duration == 'undefined' ) {
333 duration = this.options.duration;
336 // the callback to always call after expanding or collapsing
337 commonCallback = function () {
338 self.isCollapsed = isCollapsed;
341 self.element.trigger( 'collapse' );
343 self.element.trigger( 'expand' );
347 // combine commonCallback with any user-specified callback
348 if ( options.callback ) {
349 callback = function () {
354 callback = function () {
359 // apply the animation
360 if ( duration > 0 && $.support.cssTransitions ) {
361 // add a handler to invoke a callback when the animation is done
364 handleEvent: function ( e ) {
365 elt.removeEventListener( 'webkitTransitionEnd', this );
366 self.element.css( '-webkit-transition', null );
371 elt.addEventListener( 'webkitTransitionEnd', handler, false );
373 // apply the transition
374 this.element.css( '-webkit-transition', 'height ' + duration + 's ease-out' );
375 this.element.css( 'height', height );
377 // make sure the callback gets called even when there's no
379 this.element.css( 'height', height );
385 * Toggle the expanded/collapsed state of the widget.
386 * {Object} [options] Configuration for the expand/collapse
387 * {Integer} [options.duration] Duration of the expand/collapse;
388 * defaults to this.options.duration
389 * {Function} options.callback Function to call after toggle completes
392 toggle: function ( options ) {
393 var toggle_header = this.element.parents( ":jqmData(role='header')" ),
394 toggle_content = this.element.parents( ":jqmData(role='page')" ).find( ".ui-content" ),
398 /* Apply REM scaling */
399 scaleFactor = ( 36 / parseInt($('html').css('font-size')));
400 if ( toggle_header.children().is( ".input-search-bar" ) ) {
406 CollapsedTop = ( CollapsedTop / scaleFactor );
407 ExpandedTop = ( ExpandedTop / scaleFactor );
409 if ( $( window ).scrollTop() <= CollapsedTop ) {
410 toggle_header.css( "position", "relative" );
411 toggle_content.css( "top", "0px" );
414 if ( this.isCollapsed ) {
415 this.expand( options );
417 if ( $( window ).scrollTop() <= ExpandedTop ) {
418 CalculateTime = setTimeout( function () {
419 toggle_header.css( 'position', 'fixed' );
420 toggle_content.css( 'top', ExpandedTop + "px" );
423 // Need to move scroll top
424 toggle_header.css( 'position', 'fixed' );
425 toggle_content.css( 'top', ExpandedTop + "px" );
427 this.options.collapseOnInit = false;
429 this.collapse( options );
430 if ( $(window).scrollTop() <= ExpandedTop ) {
431 CalculateTime = setTimeout( function () {
432 toggle_header.css( 'position', 'fixed' );
433 toggle_content.css( 'top', CollapsedTop + "px" );
436 toggle_header.css( 'position', 'fixed' );
437 toggle_content.css( 'top', CollapsedTop + "px" );
440 this.options.collapseOnInit = true;
443 _setDisabled: function ( value ) {
444 $.Widget.prototype._setOption.call( this, "disabled", value );
445 this.element.add( this.element.prev( ".ui-triangle-container" ) )[value ? "addClass" : "removeClass"]("ui-disabled");
448 * Takes the same options as toggle()
450 collapse: function ( options ) {
451 var collapsedBarHeight = 10,
452 scaleFactor = ( 36 / parseInt($('html').css('font-size')));
454 collapsedBarHeight = collapsedBarHeight / scaleFactor;
456 // if (!this.isCollapsed) {
457 this._setHeight( collapsedBarHeight + "px", true, options );
462 * Takes the same options as toggle()
464 expand: function ( options ) {
465 // if (this.isCollapsed) {
466 this._setHeight( this.expandedHeight, false, options );
471 // auto self-init widgets
472 $(document).bind("pagecreate create", function ( e ) {
473 $($.tizen.optionheader.prototype.options.initSelector, e.target)
474 .not(":jqmData(role='none'), :jqmData(role='nojs')")