upload tizen1.0 source
[framework/web/web-ui-fw.git] / src / widgets / optionheader / js / jquery.mobile.tizen.optionheader.js
1 /*
2  * jQuery Mobile Widget @VERSION
3  *
4  * This software is licensed under the MIT licence (as defined by the OSI at
5  * http://www.opensource.org/licenses/mit-license.php)
6  *
7  * ***************************************************************************
8  * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
9  * Copyright (c) 2011 by Intel Corporation Ltd.
10  *
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:
17  *
18  * The above copyright notice and this permission notice shall be included in
19  * all copies or substantial portions of the Software.
20  *
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  * ***************************************************************************
29  *
30  * Authors: Elliot Smith <elliot.smith@intel.com>
31  */
32
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,
37 // will collapse it.
38 //
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:
41 //
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>
49 //        </div>
50 //     </div>
51 // </div>
52 //
53 // The optionheader can also be used inline (e.g. in a content block or
54 // a widget).
55 //
56 // Alternatively, use $('...').optionheader() to apply programmatically.
57 //
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.)
61 //
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.
67 //
68 // The widget is configurable via a data-options attribute on the same
69 // div as the data-role="optionheader" attribute, e.g.
70 //
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>
78 //        </div>
79 //     </div>
80 // </div>
81 //
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).
86 //
87 // See below for the available options.
88 //
89 // Theme: by default, gets a 'b' swatch; override with data-theme="X"
90 // as per usual
91 //
92 // Options (can be set with a data-options attribute):
93 //
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
100 //   drawn.
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.
104 //
105 // Methods (see below for docs):
106 //
107 //   toggle(options)
108 //   expand(options)
109 //   collapse(options)
110 //
111 // Events:
112 //
113 //   expand: Triggered when the option header is expanded
114 //   collapse: Triggered when the option header is collapsed
115 //
116
117
118 (function ($, undefined) {
119         $.widget("tizen.optionheader", $.mobile.widget, {
120                 options: {
121                         initSelector: ":jqmData(role='optionheader')",
122                         showIndicator: true,
123                         theme: 's',
124                         startCollapsed: false,
125                         expandable: true,
126                         duration: 0.25,
127                         collapseOnInit : true,
128                         default_font_size : $('html').css('font-size')
129                 },
130                 collapsedHeight: '5px',
131
132                 _create: function () {
133                         var options,
134                                 theme,
135                                 self = this,
136                                 elementHeight = 106,
137                                 parentPage,
138                                 dataOptions = this.element.jqmData( 'options' ),
139                                 page = this.element.closest( ':jqmData(role="page")' );
140                         // parse data-options
141                         $.extend( this.options, dataOptions );
142
143                         this.isCollapsed = this.options.collapseOnInit;
144                         this.expandedHeight = null;
145
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;
149
150                         this.element.closest( ':jqmData(role="header")' ).addClass( "ui-option-header-resizing" );
151
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 () {
156                                 self.toggle();
157                         };
158
159                         /* Apply REM scaling */
160                         elementHeight = elementHeight / ( 36 / parseInt(this.option.default_font_size) );
161
162                         if ( this.element.height() < elementHeight ) {
163                                 this.element.css( "height", elementHeight );
164                         }
165
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)
169                         // or on pageshow
170
171                         if ( page.is(":visible") ) {
172                                 self.refresh();
173                                 self._realize();
174                         } else {
175                                 self.refresh();
176
177                                 page.bind( "pagebeforeshow", function () {
178                                         self._setArrowLeft();
179                                         self._realize();
180                                 });
181                         }
182                         self._setArrowLeft();
183         //        this.refresh();
184                 },
185
186                 _realize: function () {
187                         if ( !this.expandedHeight ) {
188                                 this.expandedHeight = this.element.height();
189                         }
190
191                         if ( this.isCollapsed ) {
192         //        if (this.options.startCollapsed) {
193                                 this.collapse( {duration: 0} );
194                         }
195                 },
196
197                 _setArrowLeft: function () {
198                         var matchingBtn = $( this.element ).jqmData( "for" ),
199                                 arrowCenter = 14,
200                                 btn2Position = 10,
201                                 btn3Position = 144,
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) );
206
207                         if ( $(this.element).parents(".ui-page").find( "#" + matchingBtn ).length != 0 ) {
208
209                                 if ( this.options.expandable ) {
210                                         matchBtn.bind( 'vclick', this.clickHandler );
211                                 } else {
212                                         matchBtn.unbind( 'vclick', this.clickHandler );
213                                 }
214
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" );
220                                 }
221                         } else {
222                                 $( ".ui-triangle-image" ).css( "left", document.documentElement.clientWidth / 2 - ( arrowCenter / scaleFactor ) + "px" );
223                         }
224                 },
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,
232                                 numRows,
233                                 rowsClass,
234                                 themeClass,
235                                 klass,
236                                 o = $.extend( {grid: null} ),
237                                 $kids = el.find( "div" ).eq( 0 ).children().children(),
238                                 letter,
239                                 gridCols = {solo: 1, a: 2, b: 3, c: 4, d: 5},
240                                 grid = o.grid;
241
242                         if ( !grid ) {
243                                 if ( $kids.length <= 5 ) {
244                                         for ( letter in gridCols ) {
245                                                 if ( gridCols[ letter ] === $kids.length ) {
246                                                         grid = letter;
247                                                 }
248                                         }
249                                         numRows = $kids.length / gridCols[grid];
250                                 } else {
251                                         numRows = 2;
252                                 }
253                         }
254
255                 // count ui-grid-* elements to get number of rows
256         //        numRows = el.find(gridRowSelector).length;
257
258                 // ...at least one row
259         //        numRows = Math.max(1, numRows);
260
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')
264                 //   ui-option-header
265                         rowsClass = 'ui-option-header-' + numRows + '-row';
266                         themeClass = 'ui-body-' + this.options.theme;
267
268                         el.removeClass( rowsClass ).addClass( rowsClass );
269                         el.removeClass( themeClass ).addClass( themeClass );
270                         el.removeClass( optionHeaderClass ).addClass( optionHeaderClass );
271
272                         // remove any arrow currently visible
273                         el.prev( '.ui-option-header-triangle-arrow' ).remove();
274         //        el.prev('.ui-triangle-container').remove();
275
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 ) {
281                                 el.before( arrow );
282                                 arrow.append("<div class='ui-triangle-image'></div>");
283         //            arrow.triangle({"color": el.css('background-color'), offset: "50%"});
284                         }
285
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 );
292
293                         } else {
294                                 el.unbind( 'vclick', this.clickHandler );
295                                 arrow.unbind( 'vclick', this.clickHandler );
296                         }
297
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);
303                 });*/
304                         klass = 'ui-option-header-row-' + ( numRows );
305                         el.find( "div" ).eq( 0 ).removeClass( klass ).addClass( klass );
306
307                         // redraw the buttons (now that the optionheader has the right
308                         // swatch)
309                         el.find( '.ui-btn' ).each(function () {
310                                 $( this ).attr( 'data-' + $.mobile.ns + 'theme', theme );
311
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 );
317                         });
318                 },
319
320                 _setHeight: function ( height, isCollapsed, options ) {
321                         var self = this,
322                                 elt = this.element.get( 0 ),
323                                 duration,
324                                 commonCallback,
325                                 callback,
326                                 handler;
327
328                         options = options || {};
329
330                         // set default duration if not specified
331                         duration = options.duration;
332                         if ( typeof duration == 'undefined' ) {
333                                 duration = this.options.duration;
334                         }
335
336                         // the callback to always call after expanding or collapsing
337                         commonCallback = function () {
338                                 self.isCollapsed = isCollapsed;
339
340                                 if ( isCollapsed ) {
341                                         self.element.trigger( 'collapse' );
342                                 } else {
343                                         self.element.trigger( 'expand' );
344                                 }
345                         };
346
347                         // combine commonCallback with any user-specified callback
348                         if ( options.callback ) {
349                                 callback = function () {
350                                         options.callback();
351                                         commonCallback();
352                                 };
353                         } else {
354                                 callback = function () {
355                                         commonCallback();
356                                 };
357                         }
358
359                         // apply the animation
360                         if ( duration > 0 && $.support.cssTransitions ) {
361                                 // add a handler to invoke a callback when the animation is done
362
363                                 handler = {
364                                         handleEvent: function ( e ) {
365                                                 elt.removeEventListener( 'webkitTransitionEnd', this );
366                                                 self.element.css( '-webkit-transition', null );
367                                                 callback();
368                                         }
369                                 };
370
371                                 elt.addEventListener( 'webkitTransitionEnd', handler, false );
372
373                                 // apply the transition
374                                 this.element.css( '-webkit-transition', 'height ' + duration + 's ease-out' );
375                                 this.element.css( 'height', height );
376                         } else {
377                         // make sure the callback gets called even when there's no
378                         // animation
379                                 this.element.css( 'height', height );
380                                 callback();
381                         }
382                 },
383
384                 /**
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
390                 */
391
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" ),
395                                 CollapsedTop = 110,
396                                 ExpandedTop = 206,
397                                 CalculateTime,
398                                 /* Apply REM scaling */
399                                 scaleFactor = ( 36 / parseInt($('html').css('font-size')));
400                         if ( toggle_header.children().is( ".input-search-bar" ) ) {
401                                 CollapsedTop = 218;
402                                 ExpandedTop = 314;
403                         }
404
405                         /* Scale Factor */
406                         CollapsedTop = ( CollapsedTop / scaleFactor );
407                         ExpandedTop = ( ExpandedTop / scaleFactor );
408
409                         if ( $( window ).scrollTop() <= CollapsedTop ) {
410                                 toggle_header.css( "position", "relative" );
411                                 toggle_content.css( "top", "0px" );
412                         }
413
414                         if ( this.isCollapsed ) {
415                                 this.expand( options );
416
417                                 if ( $( window ).scrollTop() <= ExpandedTop ) {
418                                         CalculateTime = setTimeout( function () {
419                                                 toggle_header.css( 'position', 'fixed' );
420                                                 toggle_content.css( 'top', ExpandedTop + "px" );
421                                         }, 500 );
422                                 } else {
423                                         //   Need to move scroll top
424                                         toggle_header.css( 'position', 'fixed' );
425                                         toggle_content.css( 'top', ExpandedTop + "px" );
426                                 }
427                                 this.options.collapseOnInit = false;
428                         } else {
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" );
434                                         }, 500 );
435                                 } else {
436                                         toggle_header.css( 'position', 'fixed' );
437                                         toggle_content.css( 'top', CollapsedTop + "px" );
438                                 }
439                         }
440                         this.options.collapseOnInit = true;
441                 },
442
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");
446                 },
447                 /**
448                 * Takes the same options as toggle()
449                 */
450                 collapse: function ( options ) {
451                         var collapsedBarHeight = 10,
452                         scaleFactor = ( 36 / parseInt($('html').css('font-size')));
453
454                         collapsedBarHeight = collapsedBarHeight / scaleFactor;
455
456         //        if (!this.isCollapsed) {
457                         this._setHeight( collapsedBarHeight + "px", true, options );
458         //        }
459                 },
460
461                 /**
462                 * Takes the same options as toggle()
463                 */
464                 expand: function ( options ) {
465         //        if (this.isCollapsed) {
466                         this._setHeight( this.expandedHeight, false, options );
467         //        }
468                 }
469         });
470
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')")
475                         .optionheader();
476         });
477
478 }(jQuery) );