• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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
5cr.define('options.browser_options', function() {
6  /** @const */ var AutocompleteList = cr.ui.AutocompleteList;
7  /** @const */ var InlineEditableItem = options.InlineEditableItem;
8  /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
9
10  /**
11   * Creates a new startup page list item.
12   * @param {Object} pageInfo The page this item represents.
13   * @constructor
14   * @extends {cr.ui.ListItem}
15   */
16  function StartupPageListItem(pageInfo) {
17    var el = cr.doc.createElement('div');
18    el.pageInfo_ = pageInfo;
19    StartupPageListItem.decorate(el);
20    return el;
21  }
22
23  /**
24   * Decorates an element as a startup page list item.
25   * @param {!HTMLElement} el The element to decorate.
26   */
27  StartupPageListItem.decorate = function(el) {
28    el.__proto__ = StartupPageListItem.prototype;
29    el.decorate();
30  };
31
32  StartupPageListItem.prototype = {
33    __proto__: InlineEditableItem.prototype,
34
35    /**
36     * Input field for editing the page url.
37     * @type {HTMLElement}
38     * @private
39     */
40    urlField_: null,
41
42    /** @override */
43    decorate: function() {
44      InlineEditableItem.prototype.decorate.call(this);
45
46      var pageInfo = this.pageInfo_;
47
48      if (pageInfo.modelIndex == -1) {
49        this.isPlaceholder = true;
50        pageInfo.title = loadTimeData.getString('startupAddLabel');
51        pageInfo.url = '';
52      }
53
54      var titleEl = this.ownerDocument.createElement('div');
55      titleEl.className = 'title';
56      titleEl.classList.add('favicon-cell');
57      titleEl.classList.add('weakrtl');
58      titleEl.textContent = pageInfo.title;
59      if (!this.isPlaceholder) {
60        titleEl.style.backgroundImage = getFaviconImageSet(pageInfo.url);
61        titleEl.title = pageInfo.tooltip;
62      }
63
64      this.contentElement.appendChild(titleEl);
65
66      var urlEl = this.createEditableTextCell(pageInfo.url);
67      urlEl.className = 'url';
68      urlEl.classList.add('weakrtl');
69      this.contentElement.appendChild(urlEl);
70
71      var urlField = urlEl.querySelector('input');
72      urlField.className = 'weakrtl';
73      urlField.placeholder = loadTimeData.getString('startupPagesPlaceholder');
74      this.urlField_ = urlField;
75
76      this.addEventListener('commitedit', this.onEditCommitted_);
77
78      var self = this;
79      urlField.addEventListener('focus', function(event) {
80        self.parentNode.autocompleteList.attachToInput(urlField);
81      });
82      urlField.addEventListener('blur', function(event) {
83        self.parentNode.autocompleteList.detach();
84      });
85
86      if (!this.isPlaceholder)
87        this.draggable = true;
88    },
89
90    /** @override */
91    get currentInputIsValid() {
92      return this.urlField_.validity.valid;
93    },
94
95    /** @override */
96    get hasBeenEdited() {
97      return this.urlField_.value != this.pageInfo_.url;
98    },
99
100    /**
101     * Called when committing an edit; updates the model.
102     * @param {Event} e The end event.
103     * @private
104     */
105    onEditCommitted_: function(e) {
106      var url = this.urlField_.value;
107      if (this.isPlaceholder)
108        chrome.send('addStartupPage', [url]);
109      else
110        chrome.send('editStartupPage', [this.pageInfo_.modelIndex, url]);
111    },
112  };
113
114  var StartupPageList = cr.ui.define('list');
115
116  StartupPageList.prototype = {
117    __proto__: InlineEditableItemList.prototype,
118
119    /**
120     * An autocomplete suggestion list for URL editing.
121     * @type {AutocompleteList}
122     */
123    autocompleteList: null,
124
125    /**
126     * The drop position information: "below" or "above".
127     */
128    dropPos: null,
129
130    /** @override */
131    decorate: function() {
132      InlineEditableItemList.prototype.decorate.call(this);
133
134      // Listen to drag and drop events.
135      this.addEventListener('dragstart', this.handleDragStart_.bind(this));
136      this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
137      this.addEventListener('dragover', this.handleDragOver_.bind(this));
138      this.addEventListener('drop', this.handleDrop_.bind(this));
139      this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
140      this.addEventListener('dragend', this.handleDragEnd_.bind(this));
141    },
142
143    /** @override */
144    createItem: function(pageInfo) {
145      var item = new StartupPageListItem(pageInfo);
146      item.urlField_.disabled = this.disabled;
147      return item;
148    },
149
150    /** @override */
151    deleteItemAtIndex: function(index) {
152      chrome.send('removeStartupPages', [index]);
153    },
154
155    /**
156     * Computes the target item of drop event.
157     * @param {Event} e The drop or dragover event.
158     * @private
159     */
160    getTargetFromDropEvent_: function(e) {
161      var target = e.target;
162      // e.target may be an inner element of the list item
163      while (target != null && !(target instanceof StartupPageListItem)) {
164        target = target.parentNode;
165      }
166      return target;
167    },
168
169    /**
170     * Handles the dragstart event.
171     * @param {Event} e The dragstart event.
172     * @private
173     */
174    handleDragStart_: function(e) {
175      // Prevent dragging if the list is disabled.
176      if (this.disabled) {
177        e.preventDefault();
178        return false;
179      }
180
181      var target = e.target;
182      // StartupPageListItem should be the only draggable element type in the
183      // page but let's make sure.
184      if (target instanceof StartupPageListItem) {
185        this.draggedItem = target;
186        this.draggedItem.editable = false;
187        e.dataTransfer.effectAllowed = 'move';
188        // We need to put some kind of data in the drag or it will be
189        // ignored.  Use the URL in case the user drags to a text field or the
190        // desktop.
191        e.dataTransfer.setData('text/plain', target.urlField_.value);
192      }
193    },
194
195    /*
196     * Handles the dragenter event.
197     * @param {Event} e The dragenter event.
198     * @private
199     */
200    handleDragEnter_: function(e) {
201      e.preventDefault();
202    },
203
204    /*
205     * Handles the dragover event.
206     * @param {Event} e The dragover event.
207     * @private
208     */
209    handleDragOver_: function(e) {
210      var dropTarget = this.getTargetFromDropEvent_(e);
211      // Determines whether the drop target is to accept the drop.
212      // The drop is only successful on another StartupPageListItem.
213      if (!(dropTarget instanceof StartupPageListItem) ||
214          dropTarget == this.draggedItem || dropTarget.isPlaceholder) {
215        this.hideDropMarker_();
216        return;
217      }
218      // Compute the drop postion. Should we move the dragged item to
219      // below or above the drop target?
220      var rect = dropTarget.getBoundingClientRect();
221      var dy = e.clientY - rect.top;
222      var yRatio = dy / rect.height;
223      var dropPos = yRatio <= .5 ? 'above' : 'below';
224      this.dropPos = dropPos;
225      this.showDropMarker_(dropTarget, dropPos);
226      e.preventDefault();
227    },
228
229    /*
230     * Handles the drop event.
231     * @param {Event} e The drop event.
232     * @private
233     */
234    handleDrop_: function(e) {
235      var dropTarget = this.getTargetFromDropEvent_(e);
236
237      if (!(dropTarget instanceof StartupPageListItem) ||
238          dropTarget.pageInfo_.modelIndex == -1) {
239        return;
240      }
241
242      this.hideDropMarker_();
243
244      // Insert the selection at the new position.
245      var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
246      if (this.dropPos == 'below')
247        newIndex += 1;
248
249      // If there are selected indexes, it was a re-order.
250      if (this.selectionModel.selectedIndexes.length > 0) {
251        chrome.send('dragDropStartupPage',
252                    [newIndex, this.selectionModel.selectedIndexes]);
253        return;
254      }
255
256      // Otherwise it was potentially a drop of new data (e.g. a bookmark).
257      var url = e.dataTransfer.getData('url');
258      if (url) {
259        e.preventDefault();
260        chrome.send('addStartupPage', [url, newIndex]);
261      }
262    },
263
264    /**
265     * Handles the dragleave event.
266     * @param {Event} e The dragleave event.
267     * @private
268     */
269    handleDragLeave_: function(e) {
270      this.hideDropMarker_();
271    },
272
273    /**
274     * Handles the dragend event.
275     * @param {Event} e The dragend event.
276     * @private
277     */
278    handleDragEnd_: function(e) {
279      this.draggedItem.editable = true;
280      this.draggedItem.updateEditState();
281    },
282
283    /**
284     * Shows and positions the marker to indicate the drop target.
285     * @param {HTMLElement} target The current target list item of drop.
286     * @param {string} pos 'below' or 'above'.
287     * @private
288     */
289    showDropMarker_: function(target, pos) {
290      window.clearTimeout(this.hideDropMarkerTimer_);
291      var marker = $('startupPagesListDropmarker');
292      var rect = target.getBoundingClientRect();
293      var markerHeight = 6;
294      if (pos == 'above') {
295        marker.style.top = (rect.top - markerHeight / 2) + 'px';
296      } else {
297        marker.style.top = (rect.bottom - markerHeight / 2) + 'px';
298      }
299      marker.style.width = rect.width + 'px';
300      marker.style.left = rect.left + 'px';
301      marker.style.display = 'block';
302    },
303
304    /**
305     * Hides the drop marker.
306     * @private
307     */
308    hideDropMarker_: function() {
309      // Hide the marker in a timeout to reduce flickering as we move between
310      // valid drop targets.
311      window.clearTimeout(this.hideDropMarkerTimer_);
312      this.hideDropMarkerTimer_ = window.setTimeout(function() {
313        $('startupPagesListDropmarker').style.display = '';
314      }, 100);
315    },
316  };
317
318  return {
319    StartupPageList: StartupPageList
320  };
321});
322