tizen beta release
[platform/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) 2011 by Intel Corporation Ltd.
9  *
10  * Permission is hereby granted, free of charge, to any person obtaining a
11  * copy of this software and associated documentation files (the "Software"),
12  * to deal in the Software without restriction, including without limitation
13  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
14  * and/or sell copies of the Software, and to permit persons to whom the
15  * Software is furnished to do so, subject to the following conditions:
16  *
17  * The above copyright notice and this permission notice shall be included in
18  * all copies or substantial portions of the Software.
19  *
20  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
21  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
23  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
24  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
25  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
26  * DEALINGS IN THE SOFTWARE.
27  * ***************************************************************************
28  *
29  * Authors: Elliot Smith <elliot.smith@intel.com>
30  */
31
32 // optionheader provides a collapsible toolbar for buttons and
33 // segmented controls directly under the title bar. It
34 // wraps a jQuery Mobile grid in a collapsible container; clicking
35 // on the container, or on one of the buttons inside the container,
36 // will collapse it.
37 //
38 // To add an option header to a page, mark up the header with a
39 // data-role="optionheader" attribute, as shown in this example:
40 //
41 // <div data-role="header">
42 //     <h1>Option header - 3 buttons example</h1>
43 //     <div data-role="optionheader">
44 //        <div class="ui-grid-b">
45 //             <div class="ui-block-a"><a data-role="button">Previous</a></div>
46 //             <div class="ui-block-b"><a data-role="button">Cancel</a></div>
47 //             <div class="ui-block-c"><a data-role="button">Next</a></div>
48 //        </div>
49 //     </div>
50 // </div>
51 //
52 // The optionheader can also be used inline (e.g. in a content block or
53 // a widget).
54 //
55 // Alternatively, use $('...').optionheader() to apply programmatically.
56 //
57 // The grid inside the optionheader should be marked up as for
58 // a standard jQuery Mobile grid. (The widget has been tested with one
59 // or two rows of 2-4 columns each.)
60 //
61 // Note that if you use this with fixed headers, you may find that
62 // expanding the option header increases the page size so that scrollbars
63 // appear (jQuery Mobile's own collapsible content areas cause the
64 // same issue). You can alleviate this somewhat by calling the show() method
65 // on the page toolbars each time the size of the header changes.
66 //
67 // The widget is configurable via a data-options attribute on the same
68 // div as the data-role="optionheader" attribute, e.g.
69 //
70 // <div data-role="header">
71 //     <h1>Option header - configured</h1>
72 //     <div data-role="optionheader" data-options='{"collapsed":true, "duration":1.5}'>
73 //        <div class="ui-grid-b">
74 //             <div class="ui-block-a"><a data-role="button">Previous</a></div>
75 //             <div class="ui-block-b"><a data-role="button">Cancel</a></div>
76 //             <div class="ui-block-c"><a data-role="button">Next</a></div>
77 //        </div>
78 //     </div>
79 // </div>
80 //
81 // Options can also be set with $(...).optionheader('option', 'name', value).
82 // However, if you do this, you'll need to call $(...).optionheader('refresh')
83 // afterwards for the new values to take effect (note that optionheader()
84 // can be applied multiple times to an element without side effects).
85 //
86 // See below for the available options.
87 //
88 // Theme: by default, gets a 'b' swatch; override with data-theme="X"
89 // as per usual
90 //
91 // Options (can be set with a data-options attribute):
92 //
93 //   {Boolean} [showIndicator=true] Set to true (the default) to show
94 //   the upward-pointing arrow indicator on top of the title bar.
95 //   {Boolean} [startCollapsed=false] Sets the appearance when the option
96 //   header is first displayed; defaults to false (i.e. show the header
97 //   expanded on first draw). NB setting this option later has no
98 //   effect: use collapse() to collapse a widget which is already
99 //   drawn.
100 //   {Boolean} [expandable=true] Sets whether the header will expand
101 //   in response to clicks; default = true.
102 //   {Float} [duration=0.25] Duration of the expand/collapse animation.
103 //
104 // Methods (see below for docs):
105 //
106 //   toggle(options)
107 //   expand(options)
108 //   collapse(options)
109 //
110 // Events:
111 //
112 //   expand: Triggered when the option header is expanded
113 //   collapse: Triggered when the option header is collapsed
114 //
115
116
117 (function($, undefined) {
118 $.widget("tizen.optionheader", $.mobile.widget, {
119 //$.widget("todons.optionheader", $.todons.widgetex, {
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         },
129         collapsedHeight: '5px',
130
131         _create: function () {
132                 var options,
133                         theme,
134                         self = this,
135                         elementHeight = 106,
136                         parentPage,
137                         dataOptions = this.element.jqmData( 'options' );
138
139                 // parse data-options
140                 $.extend( this.options, dataOptions );
141
142                 this.isCollapsed = this.options.collapseOnInit;
143                 this.expandedHeight = null;
144
145                 // parse data-theme and reset options.theme if it's present
146                 theme = this.element.jqmData( 'theme' ) || this.options.theme;
147                 this.options.theme = theme;
148
149                 this.element.closest( ':jqmData(role="header")' ).addClass( "ui-option-header-resizing" );
150
151                 // set up the click handler; it's done here so it can
152                 // easily be removed, as there should only be one instance
153                 // of the handler function for each class instance
154                 this.clickHandler = function () {
155                         self.toggle();
156                 };
157
158                 if( this.element.height() < elementHeight ){
159                         this.element.css( "height", elementHeight );
160                 }
161
162                 // get the element's dimensions
163                 // and to set its initial collapse state;
164                 // either do it now (if the page is visible already)
165                 // or on pageshow
166                 page = this.element.closest( ':jqmData(role="page")' );
167
168                 if ( page.is(":visible") ){
169                         self.refresh();
170                         self._realize();
171                 } else {
172                         self.refresh();
173
174                 page.bind( "pagebeforeshow", function() {
175                         self._setArrowLeft();
176                         self._realize();
177                         });
178                 }
179                 self._setArrowLeft();
180 //        this.refresh();
181         },
182
183         _realize: function () {
184                 if ( !this.expandedHeight ) {
185                         this.expandedHeight = this.element.height();
186                 }
187
188                 if ( this.isCollapsed ) {
189 //        if (this.options.startCollapsed) {
190                         this.collapse( {duration: 0} );
191                 }
192         },
193
194         _setArrowLeft: function () {
195                 var matchingBtn = $( this.element ).jqmData( "for" ),
196                         arrowCenter = 14,
197                         btn2Position = 10,
198                         btn3Position = 144;
199
200                 if( $(this.element).parents(".ui-page").find("#"+matchingBtn).length != 0 ){
201                         matchBtn = $( this.element ).parents( ".ui-page" ).find( "#" + matchingBtn );
202
203
204                         if ( this.options.expandable ) {
205                                 matchBtn.bind( 'vclick', this.clickHandler );
206                         } else {
207                                 matchBtn.unbind( 'vclick', this.clickHandler );
208                         }
209
210                         // decide arrow Button position
211                         if( matchBtn.css( "left" ) && matchBtn.css( "left" ) != "auto" ){
212                                 $( ".ui-triangle-image" ).css( "left", matchBtn.width()/2 + parseInt(matchBtn.css("left")) - arrowCenter + "px" );
213                         } else if( matchBtn.css("right") ){
214                                 buttonRight = matchBtn.nextAll().is( "a" ) ? btn3Position : btn2Position;
215                                 $( ".ui-triangle-image" ).css( "left", document.documentElement.clientWidth - matchBtn.width()/2 - buttonRight - arrowCenter + "px" );
216                         }
217                 } else {
218                         $( ".ui-triangle-image" ).css( "left", document.documentElement.clientWidth/2 - arrowCenter + "px" );
219                 }
220         },
221         // Draw the option header, according to current options
222         refresh: function () {
223                 var el = this.element,
224                         arrow = $( '<div class="ui-option-header-triangle-arrow"></div>' ),
225                         optionHeaderClass = 'ui-option-header',
226                         self = this,
227                         gridRowSelector = '.ui-grid-a,.ui-grid-b,.ui-grid-c,.ui-grid-d,.ui-grid-e',
228                         theme = this.options.theme,
229                         numRows,
230                         rowsClass,
231                         themeClass;
232
233                 var $this = $( this ),
234                         o = $.extend({
235                                 grid: null
236                         }),
237                         $kids = el.find( "div" ).eq( 0 ).children().children(),
238                         gridCols = {solo:1, a:2, b:3, c:4, d:5},
239                         grid = o.grid,
240                         iterator;
241
242                 if ( !grid ) {
243                         if ( $kids.length <= 5 ) {
244                                 for ( var 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                 var 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                         duration,
323                         commonCallback,
324                         callback;
325
326                 options = options || {};
327
328                 // set default duration if not specified
329                 duration = options.duration;
330                 if ( typeof duration == 'undefined' ) {
331                         duration = this.options.duration;
332                 }
333
334                 // the callback to always call after expanding or collapsing
335                 commonCallback = function () {
336                         self.isCollapsed = isCollapsed;
337
338                         if ( isCollapsed ) {
339                                 self.element.trigger( 'collapse' );
340                         } else {
341                                 self.element.trigger( 'expand' );
342                         }
343                 };
344
345                 // combine commonCallback with any user-specified callback
346                 if ( options.callback ) {
347                         callback = function () {
348                                 options.callback();
349                                 commonCallback();
350                         };
351                 } else {
352                         callback = function () {
353                                 commonCallback();
354                         }
355                 }
356
357                 // apply the animation
358                 if( duration > 0 && $.support.cssTransitions ) {
359                         // add a handler to invoke a callback when the animation is done
360                         var elt = this.element.get( 0 );
361
362                         var handler = {
363                                 handleEvent: function ( e ) {
364                                         elt.removeEventListener( 'webkitTransitionEnd', this );
365                                         self.element.css( '-webkit-transition', null );
366                                         callback();
367                                 }
368                         };
369
370                         elt.addEventListener( 'webkitTransitionEnd', handler, false );
371
372                         // apply the transition
373                         this.element.css( '-webkit-transition', 'height ' + duration + 's ease-out' );
374                         this.element.css( 'height', height );
375                 }
376                 // make sure the callback gets called even when there's no
377                 // animation
378                 else {
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                 var toggle_content = this.element.parents( ":jqmData(role='page')" ).find( ".ui-content" );
395                 var CollapsedTop = 110,
396                         ExpandedTop = 206; 
397
398                 if( toggle_header.children().is(".input-search-bar") ){
399                         CollapsedTop = 218;
400                         ExpandedTop = 314;
401                 }
402
403                 if( $( window ).scrollTop() <= CollapsedTop ){
404                         toggle_header.css( "position", "relative" );
405                         toggle_content.css( "top", "0px" );
406         }
407
408                 if( this.isCollapsed ){
409                         this.expand( options );
410
411                         if( $( window ).scrollTop() <= ExpandedTop ){
412                                 var t = setTimeout( function(){  
413                                         toggle_header.css( 'position', 'fixed' );
414                                         toggle_content.css( 'top', ExpandedTop + "px" );
415                                 }, 500 );
416                         } else {
417                                 //   Need to move scroll top                    
418                                 toggle_header.css( 'position', 'fixed' );
419                                 toggle_content.css( 'top', ExpandedTop + "px" );
420                         }
421                         this.options.collapseOnInit = false;
422                 } else {
423                         this.collapse( options );
424                         if( $(window).scrollTop() <= ExpandedTop ){
425                                 var t = setTimeout( function(){
426                                 toggle_header.css( 'position', 'fixed' );
427                                 toggle_content.css( 'top', CollapsedTop + "px" );
428                         }, 500 );
429                 } else{
430                         toggle_header.css( 'position', 'fixed' );
431                         toggle_content.css( 'top', CollapsedTop + "px" );
432                         }
433                 }
434                 this.options.collapseOnInit = true;
435         },
436         _setDisabled: function( value ) {
437                 $.Widget.prototype._setOption.call( this, "disabled", value );
438                 this.element.add( this.element.prev(".ui-triangle-container") )[value ? "addClass" : "removeClass"]("ui-disabled");
439         },
440         /**
441         * Takes the same options as toggle()
442         */
443         collapse: function ( options ) {
444 //        if (!this.isCollapsed) {
445                 this._setHeight( '10px', true, options );
446 //        }
447         },
448
449         /**
450         * Takes the same options as toggle()
451         */
452         expand: function ( options ) {
453 //        if (this.isCollapsed) {
454                 this._setHeight( this.expandedHeight, false, options );
455 //        }
456         }
457 });
458
459 // auto self-init widgets
460 $(document).bind("pagecreate create", function (e) {
461     $($.tizen.optionheader.prototype.options.initSelector, e.target)
462     .not(":jqmData(role='none'), :jqmData(role='nojs')")
463     .optionheader();
464 });
465
466 })(jQuery);