37323d9ae51b0265b3c8c329f5eec3ce96ed2eb9
[platform/framework/web/web-ui-fw.git] / src / widgets / multibuttonentry / js / jquery.mobile.tizen.multibuttonentry.js
1 /* ***************************************************************************
2  * Copyright (c) 2000 - 2011 Samsung Electronics Co., Ltd.
3  *
4  * Permission is hereby granted, free of charge, to any person obtaining a
5  * copy of this software and associated documentation files (the "Software"),
6  * to deal in the Software without restriction, including without limitation
7  * the rights to use, copy, modify, merge, publish, distribute, sublicense,
8  * and/or sell copies of the Software, and to permit persons to whom the
9  * Software is furnished to do so, subject to the following conditions:
10  *
11  * The above copyright notice and this permission notice shall be included in
12  * all copies or substantial portions of the Software.
13  *
14  * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15  * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16  * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17  * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18  * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
19  * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
20  * DEALINGS IN THE SOFTWARE.
21  * ***************************************************************************
22  *
23  *      Author: Kangsik Kim <kangsik81.kim@samsung.com>
24  *                              Minkyeong Kim <minkyeong.kim@samsung.com>
25 */
26
27 /**
28  *      The MultiButtonEntry widget changes a text item to a button. It can be comprised of a number of button widgets. 
29  *      When a user types text and the text gets a specific event to change from a text to a button, 
30  *      the input text is changed to a MultiButtonEntry widget.
31  *      A user can add the MultiButtonEntry widget to a contact list, email list, or another list.
32  *      The typical use of this widget is composing a number of contacts or phone numbers in a specific area of the screen.
33  *
34  *      HTML Attributes:
35  *
36  *              data-list-id : Represents the page id.
37  *                              The page contains data for the user, for example, an address book.
38  *                              If the value is null, anchor button doesn't work. (Default : null)
39  *              data-label:     Provide a label for a user-guide. (Default : 'To : ')
40  *              data-description : This attribute is managing message format.
41  *                              This message is displayed when widget status was changed to 'focusout'. (Default : '+ {0}')
42  *
43  *      APIs:
44  *
45  *              inputtext (  [string]  )
46  *                      : If argument is not exist, will get a string from inputbox.
47  *                      If argument is exist, will set a string to inputbox.
48  *              select (  [number]  )
49  *                      : If no argument exists, gets a string of the selected block.
50  *                      If any button isn't selected on a multibuttonentry widget, this method returns "null" value.
51  *                      When a user call this method with an argument which is a number type,
52  *                      this method selects the button which is matched with the argument.
53  *              add ( text, [number] )
54  *                      :  If second argument does not exist, will insert to a new button at last position.
55  *                      Insert a new button at indexed position. The position is decided by the second argument.
56  *                      "index of position" means that the position of inserting a new button is decided by the second argument on "add" method.
57  *                      For example, if a user call the method like this "add("Tizen", 2)",
58  *                      new button labed "Tizen" will be inserted on the third position.
59  *              remove ( [number] )
60  *                      : If no argument exists, all buttons are removed.
61  *                      Remove a button at indexed position.
62  *                      The position is decided by the second argument. (index: index of button)
63  *              length ( void )
64  *                      : Get a number of buttons.
65  *              foucsIn ( void )
66  *                      : This method change a status to 'focusin'.
67  *                      This status is able to manage a widget.
68  *              focusOut ( void )
69  *                      : Changes the focus status to 'focus out'.
70  *                      The status is not able to manage a widget.
71  *                      All buttons that contained in the widget are removed and
72  *                      summarized message is displayed.
73  *              destroy ( void )
74  *                      : Remove all of the new DOM elements for the current widget that you created.
75  *
76  *      Events:
77  *
78  *              create : Occur when create MultiButtonEntry widget.
79  *              select : Occur when a button is selected.
80  *              add : Occur when new button is inserted.
81  *              remove : Occur when a button is removed.
82  *
83  *      Examples:
84  *
85  *              <div data-role="multibuttonentry" data-label="To : " data-list-id:"#addressbook" data-description="+ {0}">
86  *              </div>
87  *
88  */
89
90 ( function ( $, window, document, undefined ) {
91         $.widget( "tizen.multibuttonentry", $.mobile.widget, {
92                 _focusStatus : null,
93                 _items : null,
94                 _viewWidth : 0,
95                 _reservedWidth : 0,
96                 _currentWidth : 0,
97                 _fontSize : 0,
98                 _anchorWidth : 0,
99                 _labelWidth : 0,
100                 _marginWidth : 0,
101                 options : {
102                         label : "To : ",
103                         listId : null,
104                         description : "+ {0}"
105                 },
106
107                 _create : function () {
108                         var self = this,
109                                 $view = this.element,
110                                 role = $view.jqmData( "role" ),
111                                 option = this.options,
112                                 className = "ui-multibuttonentry-link",
113                                 inputbox = $( document.createElement( "input" ) ),
114                                 labeltag = $( document.createElement( "label" ) ),
115                                 moreBlock = $( document.createElement( "a" ) );
116
117                         $view.hide().empty().addClass( "ui-" + role );
118
119                         // create a label tag.
120                         $( labeltag ).text( option.label ).addClass( "ui-multibuttonentry-label" );
121                         $view.append( labeltag );
122
123                         // create a input tag
124                         $( inputbox ).text( option.label ).addClass( "ui-multibuttonentry-input" );
125                         $view.append( inputbox );
126
127                         // create a anchor tag.
128                         if ( option.listId === null || $.trim(option.listId).length < 1  ) {
129                                 className += "-dim";
130                         }
131                         $( moreBlock ).text( "+" ).attr( "href", $.trim(option.listId) ).addClass( "ui-multibuttonentry-link-base" ).addClass( className );
132
133                         // append default htmlelements to main widget.
134                         $view.append( moreBlock );
135
136                         // bind a event
137                         this._bindEvents();
138                         self._focusStatus = "init";
139                         // display widget
140                         $view.show();
141                         $view.attr( "tabindex", -1 ).focusin( function ( e ) {
142                                 self.focusIn();
143                         });
144
145                         // assign global variables
146                         self._viewWidth = $view.innerWidth();
147                         self._reservedWidth += self._calcBlockWidth( moreBlock );
148                         self._reservedWidth += self._calcBlockWidth( labeltag );
149                         self._fontSize = parseInt( $( moreBlock ).css( "font-size" ), 10 );
150                         self._currentWidth = self._reservedWidth;
151                 },
152
153                 // bind events
154                 _bindEvents : function () {
155                         var self = this,
156                                 $view = self.element,
157                                 option = self.options,
158                                 inputbox = $view.find( ".ui-multibuttonentry-input" ),
159                                 moreBlock = $view.find( ".ui-multibuttonentry-link-base" ),
160                                 isSeparator = false;
161
162                         // delegate a event to HTMLDivElement(each block).
163                         $view.delegate( "div", "vclick", function ( event ) {
164                                 if ( $( this ).hasClass( "ui-multibuttonentry-sblock" ) ) {
165                                         // If block is selected, it will be removed.
166                                         self._removeTextBlock();
167                                 }
168
169                                 var lockBlock = $view.find( "div.ui-multibuttonentry-sblock" );
170                                 if ( typeof lockBlock !== "undefined" ) {
171                                         lockBlock.removeClass( "ui-multibuttonentry-sblock" ).addClass( "ui-multibuttonentry-block" );
172                                 }
173                                 $( this ).removeClass( "ui-multibuttonentry-block" ).addClass( "ui-multibuttonentry-sblock" );
174                                 self._trigger( "select" );
175                         });
176
177                         inputbox.bind( "keyup", function ( event ) {
178                                 // 8  : backspace
179                                 // 13 : Enter
180                                 // 186 : semi-colon
181                                 // 188 : comma
182                                 var keyValue = event.keyCode,
183                                         valueString = $( inputbox ).val(),
184                                         valueStrings = [],
185                                         index;
186
187                                 if ( keyValue === 8 ) {
188                                         if ( valueString.length === 0 ) {
189                                                 self._validateTargetBlock();
190                                         }
191                                 } else if ( keyValue === 13 || keyValue === 186 || keyValue === 188 ) {
192                                         if ( valueString.length !== 0 ) {
193                                                 // split content by separators(',', ';')
194                                                 valueStrings = valueString.split ( /[,;]/ );
195                                                 for ( index = 0; index < valueStrings.length; index++ ) {
196                                                         if ( valueStrings[index].length !== 0 && valueStrings[index].replace( /\s/g, "" ).length !== 0 ) {
197                                                                 self._addTextBlock( valueStrings[index] );
198                                                         }
199                                                 }
200                                         }
201                                         inputbox.val( "" );
202                                         isSeparator = true;
203                                 } else {
204                                         self._unlockTextBlock();
205                                 }
206
207                                 return !isSeparator;
208                         });
209
210                         moreBlock.click( function () {
211                                 if ( $( moreBlock ).hasClass( "ui-multibuttonentry-link-dim" ) ) {
212                                         return ;
213                                 }
214
215                                 $(inputbox).hide();
216
217                                 $.mobile.changePage( option.listId, {
218                                         transition: "slide",
219                                         reverse: false,
220                                         changeHash: false
221                                 });
222                         });
223
224                         $( document ).bind( "pagechange.mbe", function ( event ) {
225                                 if ( $view.innerWidth() === 0 ) {
226                                         return ;
227                                 }
228                                 var inputBox = $view.find( ".ui-multibuttonentry-input" );
229                                 if ( self._labelWidth === 0 ) {
230                                         self._labelWidth = $view.find( ".ui-multibuttonentry-label" ).outerWidth( true );
231                                         self._anchorWidth = $view.find( ".ui-multibuttonentry-link-base" ).outerWidth( true );
232                                         self._marginWidth = parseInt( ( $( inputBox ).css( "margin-left" ) ), 10 );
233                                         self._marginWidth += parseInt( ( $( inputBox ).css( "margin-right" ) ), 10 );
234                                         self._viewWidth = $view.innerWidth();
235                                 }
236                                 self._modifyInputBoxWidth();
237                                 $(inputbox).show();
238                         });
239
240                         $view.bind( "click", function ( event ) {
241                                 if ( self._focusStatus === "focusOut" ) {
242                                         self.focusIn();
243                                 }
244                         });
245                 },
246
247                 // create a textbutton and append this button to parent layer.
248                 // @param arg1 : string
249                 // @param arg2 : index
250                 _addTextBlock : function ( messages, blockIndex ) {
251                         if ( arguments.length === 0 ) {
252                                 return;
253                         }
254
255                         if ( !messages ) {
256                                 return ;
257                         }
258
259                         var self = this,
260                                 $view = self.element,
261                                 content = messages,
262                                 index = blockIndex,
263                                 blocks = null,
264                                 textBlock = null;
265
266                         if ( self._viewWidth === 0 ) {
267                                 self._viewWidth = $view.innerWidth();
268                         }
269
270                         // Create a new text HTMLDivElement.
271                         textBlock = $( document.createElement( 'div' ) );
272
273                         textBlock.text( content ).addClass( "ui-multibuttonentry-block" );
274                         textBlock.css( {'visibility': 'hidden'} );
275
276                         blocks = $view.find( "div" );
277                         if ( index !== null && index <= blocks.length ) {
278                                 $( blocks[index] ).before( textBlock );
279                         } else {
280                                 $view.find( ".ui-multibuttonentry-input" ).before( textBlock );
281                         }
282
283                         textBlock = self._ellipsisTextBlock( textBlock );
284                         textBlock.css( {'visibility': 'visible'} );
285
286                         self._currentWidth += self._calcBlockWidth( textBlock );
287                         self._modifyInputBoxWidth();
288                         self._trigger( "add" );
289                 },
290
291                 _removeTextBlock : function () {
292                         var self = this,
293                                 $view = this.element,
294                                 lockBlock = $view.find( "div.ui-multibuttonentry-sblock" );
295
296                         if ( lockBlock !== null && lockBlock.length > 0 ) {
297                                 self._currentWidth -= self._calcBlockWidth( lockBlock );
298                                 lockBlock.remove();
299                                 self._modifyInputBoxWidth();
300                                 this._trigger( "remove" );
301                         } else {
302                                 $view.find( "div:last" ).removeClass( "ui-multibuttonentry-block" ).addClass( "ui-multibuttonentry-sblock" );
303                         }
304                 },
305
306                 _calcBlockWidth : function ( block ) {
307                         return $( block ).outerWidth( true );
308                 },
309
310                 _unlockTextBlock : function () {
311                         var $view = this.element,
312                                 lockBlock = $view.find( "div.ui-multibuttonentry-sblock" );
313                         if ( !lockBlock ) {
314                                 lockBlock.removeClass( "ui-multibuttonentry-sblock" ).addClass( "ui-multibuttonentry-block" );
315                         }
316                 },
317
318                 // call when remove text block by backspace key.
319                 _validateTargetBlock : function () {
320                         var self = this,
321                                 $view = self.element,
322                                 lastBlock = $view.find( "div:last" ),
323                                 tmpBlock = null;
324
325                         if ( lastBlock.hasClass( "ui-multibuttonentry-sblock" ) ) {
326                                 self._removeTextBlock();
327                         } else {
328                                 tmpBlock = $view.find( "div.ui-multibuttonentry-sblock" );
329                                 tmpBlock.removeClass( "ui-multibuttonentry-sblock" ).addClass( "ui-multibuttonentry-block" );
330                                 lastBlock.removeClass( "ui-multibuttonentry-block" ).addClass( "ui-multibuttonentry-sblock" );
331                         }
332                 },
333
334                 _ellipsisTextBlock : function ( textBlock ) {
335                         var self = this,
336                                 $view = self.element,
337                                 maxWidth = $view.innerWidth() - ( self._labelWidth + self._anchorWidth ) * 2;
338
339                         if ( self._calcBlockWidth( textBlock ) > maxWidth ) {
340                                 $( textBlock ).width( maxWidth - self._marginWidth );
341                         }
342
343                         return textBlock;
344                 },
345
346                 _modifyInputBoxWidth : function () {
347                         var self = this,
348                                 $view = self.element,
349                                 margin = self._marginWidth,
350                                 labelWidth = self._labelWidth,
351                                 anchorWidth = self._anchorWidth,
352                                 inputBoxWidth = self._viewWidth - labelWidth,
353                                 blocks = $view.find( "div" ),
354                                 blockWidth = 0,
355                                 index = 0,
356                                 inputBox = $view.find( ".ui-multibuttonentry-input" );
357
358                         if ( $view.width() === 0 ) {
359                                 return ;
360                         }
361
362                         for ( index = 0; index < blocks.length; index += 1 ) {
363                                 blockWidth = self._calcBlockWidth( blocks[index] );
364
365                                 if ( blockWidth >= inputBoxWidth + anchorWidth ) {
366                                         if ( blockWidth >= inputBoxWidth ) {
367                                                 inputBoxWidth = self._viewWidth - blockWidth;
368                                         } else {
369                                                 inputBoxWidth = self._viewWidth ;
370                                         }
371                                 } else {
372                                         if ( blockWidth >= inputBoxWidth ) {
373                                                 inputBoxWidth = self._viewWidth - blockWidth;
374                                         } else {
375                                                 inputBoxWidth -= blockWidth;
376                                         }
377                                 }
378                         }
379
380                         inputBoxWidth -= margin;
381                         if ( inputBoxWidth < anchorWidth * 2 ) {
382                                 inputBoxWidth = self._viewWidth - margin;
383                         }
384                         $( inputBox ).width( inputBoxWidth - anchorWidth );
385                 },
386
387                 _stringFormat : function ( expression ) {
388                         var pattern = null,
389                                 message = expression,
390                                 i = 0;
391                         for ( i = 1; i < arguments.length; i += 1 ) {
392                                 pattern = "{" + ( i - 1 ) + "}";
393                                 message = message.replace( pattern, arguments[i] );
394                         }
395                         return message;
396                 },
397
398                 _resizeBlocks : function () {
399                         var self = this,
400                                 $view = self.element,
401                                 blocks = $view.find( "div" ),
402                                 index = 0;
403
404                         for ( index = 0 ; index < blocks.length ; index += 1 ) {
405                                 $( blocks[index] ).css( "width", "auto" );
406                                 blocks[index] = self._ellipsisTextBlock( blocks[index] );
407                         }
408                 },
409
410                 //---------------------------------------------------- //
411                 //                                      Public Method   //
412                 //----------------------------------------------------//
413                 //
414                 // Focus In Event
415                 //
416                 focusIn : function () {
417                         if ( this._focusStatus === "focusIn" ) {
418                                 return;
419                         }
420
421                         var $view = this.element;
422
423                         $view.find( "label" ).show();
424                         $view.find( ".ui-multibuttonentry-desclabel" ).remove();
425                         $view.find( "div.ui-multibuttonentry-sblock" ).removeClass( "ui-multibuttonentry-sblock" ).addClass( "ui-multibuttonentry-block" );
426                         $view.find( "div" ).show();
427                         $view.find( ".ui-multibuttonentry-input" ).show();
428                         $view.find( "a" ).show();
429
430                         // change focus state.
431                         this._modifyInputBoxWidth();
432                         this._focusStatus = "focusIn";
433                         $view.removeClass( "ui-multibuttonentry-focusout" ).addClass( "ui-multibuttonentry-focusin" );
434                 },
435
436                 focusOut : function () {
437                         if ( this._focusStatus === "focusOut" ) {
438                                 return;
439                         }
440
441                         var self = this,
442                                 $view = self.element,
443                                 tempBlock = null,
444                                 statement = "",
445                                 index = 0,
446                                 lastIndex = 10,
447                                 label = $view.find( "label" ),
448                                 more = $view.find( "span" ),
449                                 blocks = $view.find( "div" ),
450                                 currentWidth = $view.outerWidth( true ) - more.outerWidth( true ) - label.outerWidth( true ),
451                                 blockWidth = 0;
452
453                         $view.find( ".ui-multibuttonentry-input" ).hide();
454                         $view.find( "a" ).hide();
455                         blocks.hide();
456
457                         currentWidth = currentWidth - self._reservedWidth;
458
459                         for ( index = 0; index < blocks.length; index++ ) {
460                                 blockWidth = $( blocks[index] ).outerWidth( true );
461                                 if ( currentWidth - blockWidth <= 0 ) {
462                                         lastIndex = index - 1;
463                                         break;
464                                 }
465
466                                 $( blocks[index] ).show();
467                                 currentWidth -= blockWidth;
468                         }
469
470                         if ( lastIndex !== blocks.length ) {
471                                 statement = self._stringFormat( self.options.description, blocks.length - lastIndex - 1 );
472                                 tempBlock = $( document.createElement( 'label' ));
473                                 tempBlock.text( statement );
474                                 tempBlock.addClass( "ui-multibuttonentry-desclabel" ).addClass( "ui-multibuttonentry-desclabel" );
475                                 $( blocks[lastIndex] ).after( tempBlock );
476                         }
477
478                         // update foucs state
479                         this._focusStatus = "focusOut";
480                         $view.removeClass( "ui-multibuttonentry-focusin" ).addClass( "ui-multibuttonentry-focusout" );
481                 },
482
483                 inputText : function ( message ) {
484                         var $view = this.element;
485
486                         if ( arguments.length === 0 ) {
487                                 return $view.find( ".ui-multibuttonentry-input" ).val();
488                         }
489                         $view.find( ".ui-multibuttonentry-input" ).val( message );
490                         return message;
491                 },
492
493                 select : function ( index ) {
494                         var $view = this.element,
495                                 lockBlock = null,
496                                 blocks = null;
497
498                         if ( this._focusStatus === "focusOut" ) {
499                                 return;
500                         }
501
502                         if ( arguments.length === 0 ) {
503                                 // return a selected block.
504                                 lockBlock = $view.find( "div.ui-multibuttonentry-sblock" );
505                                 if ( lockBlock) {
506                                         return lockBlock.text();
507                                 }
508                                 return null;
509                         }
510                         // 1. unlock all blocks.
511                         this._unlockTextBlock();
512                         // 2. select pointed block.
513                         blocks = $view.find( "div" );
514                         if ( blocks.length > index ) {
515                                 $( blocks[index] ).removeClass( "ui-multibuttonentry-block" ).addClass( "ui-multibuttonentry-sblock" );
516                                 this._trigger( "select" );
517                         }
518                         return null;
519                 },
520
521                 add : function ( message, position ) {
522                         if ( this._focusStatus === "focusOut" ) {
523                                 return;
524                         }
525
526                         this._addTextBlock( message, position );
527                 },
528
529                 remove : function ( position ) {
530                         var self = this,
531                                 $view = this.element,
532                                 blocks = $view.find( "div" ),
533                                 index = 0;
534                         if ( this._focusStatus === "focusOut" ) {
535                                 return;
536                         }
537
538                         if ( arguments.length === 0 ) {
539                                 blocks.remove();
540                                 this._trigger( "clear" );
541                         } else if ( typeof position == "number" ) {
542                                 // remove selected button
543                                 index = ( ( position < blocks.length ) ? position : ( blocks.length - 1 ) );
544                                 $( blocks[index] ).remove();
545                                 this._trigger( "remove" );
546                         }
547                         self._modifyInputBoxWidth();
548                 },
549
550                 length : function () {
551                         return this.element.find( "div" ).length;
552                 },
553
554                 refresh : function () {
555                         var self = this,
556                                 $view = this.element;
557
558                         self._viewWidth = $view.innerWidth();
559                         self._resizeBlocks();
560                         self._modifyInputBoxWidth();
561                 },
562
563                 destroy : function () {
564                         var $view = this.element;
565
566                         $view.find( "label" ).remove();
567                         $view.find( "div" ).undelegate( "vclick" ).remove();
568                         $view.find( "a" ).remove();
569                         $view.find( ".ui-multibuttonentry-input" ).unbind( "keyup" ).remove();
570
571                         this._trigger( "destroy" );
572                 }
573         });
574
575         $( document ).bind( "pagecreate create", function () {
576                 $( ":jqmData(role='multibuttonentry')" ).multibuttonentry();
577         });
578
579         $( window ).bind( "resize", function () {
580                 $( ":jqmData(role='multibuttonentry')" ).multibuttonentry( "refresh" );
581         });
582 } ( jQuery, window, document ) );