Merge Chromium + Blink git repositories
[chromium-blink-merge.git] / chrome / browser / resources / options / browser_options_startup_page_list.js
blob329c3e781ab97d5aa4ca9a0124f021d3aed28082
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.
5 cr.define('options.browser_options', function() {
6   /** @const */ var AutocompleteList = cr.ui.AutocompleteList;
7   /** @const */ var InlineEditableItem = options.InlineEditableItem;
8   /** @const */ var InlineEditableItemList = options.InlineEditableItemList;
10   /**
11    * Creates a new startup page list item.
12    * @param {Object} pageInfo The page this item represents.
13    * @constructor
14    * @extends {options.InlineEditableItem}
15    */
16   function StartupPageListItem(pageInfo) {
17     var el = cr.doc.createElement('div');
18     el.pageInfo_ = pageInfo;
19     StartupPageListItem.decorate(el);
20     return el;
21   }
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   };
32   StartupPageListItem.prototype = {
33     __proto__: InlineEditableItem.prototype,
35     /**
36      * Input field for editing the page url.
37      * @type {HTMLElement}
38      * @private
39      */
40     urlField_: null,
42     /** @override */
43     decorate: function() {
44       InlineEditableItem.prototype.decorate.call(this);
46       var pageInfo = this.pageInfo_;
48       if (pageInfo.modelIndex == -1) {
49         this.isPlaceholder = true;
50         pageInfo.title = loadTimeData.getString('startupAddLabel');
51         pageInfo.url = '';
52       }
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       }
64       this.contentElement.appendChild(titleEl);
66       var urlEl = this.createEditableTextCell(pageInfo.url);
67       urlEl.className = 'url';
68       urlEl.classList.add('weakrtl');
69       this.contentElement.appendChild(urlEl);
71       var urlField = /** @type {HTMLElement} */(urlEl.querySelector('input'));
72       urlField.className = 'weakrtl';
73       urlField.placeholder = loadTimeData.getString('startupPagesPlaceholder');
74       this.urlField_ = urlField;
76       this.addEventListener('commitedit', this.onEditCommitted_);
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       });
86       if (!this.isPlaceholder)
87         titleEl.draggable = true;
88     },
90     /** @override */
91     get currentInputIsValid() {
92       return this.urlField_.validity.valid;
93     },
95     /** @override */
96     get hasBeenEdited() {
97       return this.urlField_.value != this.pageInfo_.url;
98     },
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   };
114   var StartupPageList = cr.ui.define('list');
116   StartupPageList.prototype = {
117     __proto__: InlineEditableItemList.prototype,
119     /**
120      * An autocomplete suggestion list for URL editing.
121      * @type {AutocompleteList}
122      */
123     autocompleteList: null,
125     /**
126      * The drop position information: "below" or "above".
127      */
128     dropPos: null,
130     /** @override */
131     decorate: function() {
132       InlineEditableItemList.prototype.decorate.call(this);
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     },
143     /** @override */
144     createItem: function(pageInfo) {
145       var item = new StartupPageListItem(pageInfo);
146       item.urlField_.disabled = this.disabled;
147       return item;
148     },
150     /** @override */
151     deleteItemAtIndex: function(index) {
152       chrome.send('removeStartupPages', [index]);
153     },
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     },
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       }
181       var target = this.getTargetFromDropEvent_(e);
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     },
195     /**
196      * Handles the dragenter event.
197      * @param {Event} e The dragenter event.
198      * @private
199      */
200     handleDragEnter_: function(e) {
201       e.preventDefault();
202     },
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     },
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);
237       if (!(dropTarget instanceof StartupPageListItem) ||
238           dropTarget.pageInfo_.modelIndex == -1) {
239         return;
240       }
242       this.hideDropMarker_();
244       // Insert the selection at the new position.
245       var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
246       if (this.dropPos == 'below')
247         newIndex += 1;
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       }
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     },
264     /**
265      * Handles the dragleave event.
266      * @param {Event} e The dragleave event.
267      * @private
268      */
269     handleDragLeave_: function(e) {
270       this.hideDropMarker_();
271     },
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     },
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     },
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   };
318   return {
319     StartupPageList: StartupPageList
320   };