Home | History | Annotate | Download | only in ntp4
      1 // Copyright (c) 2012 The Chromium Authors. All rights reserved.
      2 // Use of this source code is governed by a BSD-style license that can be
      3 // found in the LICENSE file.
      4 
      5 /**
      6  * @fileoverview Nav dot
      7  * This is the class for the navigation controls that appear along the bottom
      8  * of the NTP.
      9  */
     10 
     11 cr.define('ntp', function() {
     12   'use strict';
     13 
     14   /**
     15    * Creates a new navigation dot.
     16    * @param {TilePage} page The associated TilePage.
     17    * @param {string} title The title of the navigation dot.
     18    * @param {bool} titleIsEditable If true, the title can be changed.
     19    * @param {bool} animate If true, animates into existence.
     20    * @constructor
     21    * @extends {HTMLLIElement}
     22    */
     23   function NavDot(page, title, titleIsEditable, animate) {
     24     var dot = cr.doc.createElement('li');
     25     dot.__proto__ = NavDot.prototype;
     26     dot.initialize(page, title, titleIsEditable, animate);
     27 
     28     return dot;
     29   }
     30 
     31   NavDot.prototype = {
     32     __proto__: HTMLLIElement.prototype,
     33 
     34     initialize: function(page, title, titleIsEditable, animate) {
     35       this.className = 'dot';
     36       this.setAttribute('role', 'button');
     37 
     38       this.page_ = page;
     39 
     40       var selectionBar = this.ownerDocument.createElement('div');
     41       selectionBar.className = 'selection-bar';
     42       this.appendChild(selectionBar);
     43 
     44       // TODO(estade): should there be some limit to the number of characters?
     45       this.input_ = this.ownerDocument.createElement('input');
     46       this.input_.setAttribute('spellcheck', false);
     47       this.input_.value = title;
     48       // Take the input out of the tab-traversal focus order.
     49       this.input_.disabled = true;
     50       this.appendChild(this.input_);
     51 
     52       this.displayTitle = title;
     53       this.titleIsEditable_ = titleIsEditable;
     54 
     55       this.addEventListener('keydown', this.onKeyDown_);
     56       this.addEventListener('click', this.onClick_);
     57       this.addEventListener('dblclick', this.onDoubleClick_);
     58       this.dragWrapper_ = new cr.ui.DragWrapper(this, this);
     59       this.addEventListener('webkitTransitionEnd', this.onTransitionEnd_);
     60 
     61       this.input_.addEventListener('blur', this.onInputBlur_.bind(this));
     62       this.input_.addEventListener('mousedown',
     63                                    this.onInputMouseDown_.bind(this));
     64       this.input_.addEventListener('keydown', this.onInputKeyDown_.bind(this));
     65 
     66       if (animate) {
     67         this.classList.add('small');
     68         var self = this;
     69         window.setTimeout(function() {
     70           self.classList.remove('small');
     71         }, 0);
     72       }
     73     },
     74 
     75     /**
     76      * @return {TilePage} The associated TilePage.
     77      */
     78     get page() {
     79       return this.page_;
     80     },
     81 
     82     /**
     83      * Sets/gets the display title.
     84      * @type {string} title The display name for this nav dot.
     85      */
     86     get displayTitle() {
     87       return this.title;
     88     },
     89     set displayTitle(title) {
     90       this.title = this.input_.value = title;
     91     },
     92 
     93     /**
     94      * Removes the dot from the page. If |opt_animate| is truthy, we first
     95      * transition the element to 0 width.
     96      * @param {boolean=} opt_animate Whether to animate the removal or not.
     97      */
     98     remove: function(opt_animate) {
     99       if (opt_animate)
    100         this.classList.add('small');
    101       else
    102         this.parentNode.removeChild(this);
    103     },
    104 
    105     /**
    106      * Navigates the card slider to the page for this dot.
    107      */
    108     switchToPage: function() {
    109       ntp.getCardSlider().selectCardByValue(this.page_, true);
    110     },
    111 
    112     /**
    113      * Handler for keydown event on the dot.
    114      * @param {Event} e The KeyboardEvent.
    115      */
    116     onKeyDown_: function(e) {
    117       if (e.keyIdentifier == 'Enter') {
    118         this.onClick_(e);
    119         e.stopPropagation();
    120       }
    121     },
    122 
    123     /**
    124      * Clicking causes the associated page to show.
    125      * @param {Event} e The click event.
    126      * @private
    127      */
    128     onClick_: function(e) {
    129       this.switchToPage();
    130       // The explicit focus call is necessary because of overriding the default
    131       // handling in onInputMouseDown_.
    132       if (this.ownerDocument.activeElement != this.input_)
    133         this.focus();
    134 
    135       e.stopPropagation();
    136     },
    137 
    138     /**
    139      * Double clicks allow the user to edit the page title.
    140      * @param {Event} e The click event.
    141      * @private
    142      */
    143     onDoubleClick_: function(e) {
    144       if (this.titleIsEditable_) {
    145         this.input_.disabled = false;
    146         this.input_.focus();
    147         this.input_.select();
    148       }
    149     },
    150 
    151     /**
    152      * Prevent mouse down on the input from selecting it.
    153      * @param {Event} e The click event.
    154      * @private
    155      */
    156     onInputMouseDown_: function(e) {
    157       if (this.ownerDocument.activeElement != this.input_)
    158         e.preventDefault();
    159     },
    160 
    161     /**
    162      * Handle keypresses on the input.
    163      * @param {Event} e The click event.
    164      * @private
    165      */
    166     onInputKeyDown_: function(e) {
    167       switch (e.keyIdentifier) {
    168         case 'U+001B':  // Escape cancels edits.
    169           this.input_.value = this.displayTitle;
    170         case 'Enter':  // Fall through.
    171           this.input_.blur();
    172           break;
    173       }
    174     },
    175 
    176     /**
    177      * When the input blurs, commit the edited changes.
    178      * @param {Event} e The blur event.
    179      * @private
    180      */
    181     onInputBlur_: function(e) {
    182       window.getSelection().removeAllRanges();
    183       this.displayTitle = this.input_.value;
    184       ntp.saveAppPageName(this.page_, this.displayTitle);
    185       this.input_.disabled = true;
    186     },
    187 
    188     shouldAcceptDrag: function(e) {
    189       return this.page_.shouldAcceptDrag(e);
    190     },
    191 
    192     /**
    193      * A drag has entered the navigation dot. If the user hovers long enough,
    194      * we will navigate to the relevant page.
    195      * @param {Event} e The MouseOver event for the drag.
    196      * @private
    197      */
    198     doDragEnter: function(e) {
    199       var self = this;
    200       function navPageClearTimeout() {
    201         self.switchToPage();
    202         self.dragNavTimeout = null;
    203       }
    204       this.dragNavTimeout = window.setTimeout(navPageClearTimeout, 500);
    205 
    206       this.doDragOver(e);
    207     },
    208 
    209     /**
    210      * A dragged element has moved over the navigation dot. Show the correct
    211      * indicator and prevent default handling so the <input> won't act as a drag
    212      * target.
    213      * @param {Event} e The MouseOver event for the drag.
    214      * @private
    215      */
    216     doDragOver: function(e) {
    217       e.preventDefault();
    218 
    219       if (!this.dragWrapper_.isCurrentDragTarget)
    220         ntp.setCurrentDropEffect(e.dataTransfer, 'none');
    221       else
    222         this.page_.setDropEffect(e.dataTransfer);
    223     },
    224 
    225     /**
    226      * A dragged element has been dropped on the navigation dot. Tell the page
    227      * to append it.
    228      * @param {Event} e The MouseOver event for the drag.
    229      * @private
    230      */
    231     doDrop: function(e) {
    232       e.stopPropagation();
    233       var tile = ntp.getCurrentlyDraggingTile();
    234       if (tile && tile.tilePage != this.page_)
    235         this.page_.appendDraggingTile();
    236       // TODO(estade): handle non-tile drags.
    237 
    238       this.cancelDelayedSwitch_();
    239     },
    240 
    241     /**
    242      * The drag has left the navigation dot.
    243      * @param {Event} e The MouseOver event for the drag.
    244      * @private
    245      */
    246     doDragLeave: function(e) {
    247       this.cancelDelayedSwitch_();
    248     },
    249 
    250     /**
    251      * Cancels the timer for page switching.
    252      * @private
    253      */
    254     cancelDelayedSwitch_: function() {
    255       if (this.dragNavTimeout) {
    256         window.clearTimeout(this.dragNavTimeout);
    257         this.dragNavTimeout = null;
    258       }
    259     },
    260 
    261     /**
    262      * A transition has ended.
    263      * @param {Event} e The transition end event.
    264      * @private
    265      */
    266     onTransitionEnd_: function(e) {
    267       if (e.propertyName === 'max-width' && this.classList.contains('small'))
    268         this.parentNode.removeChild(this);
    269     },
    270   };
    271 
    272   return {
    273     NavDot: NavDot,
    274   };
    275 });
    276