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;
11 * Creates a new startup page list item.
12 * @param {Object} pageInfo The page this item represents.
14 * @extends {options.InlineEditableItem}
16 function StartupPageListItem(pageInfo) {
17 var el = cr.doc.createElement('div');
18 el.pageInfo_ = pageInfo;
19 StartupPageListItem.decorate(el);
24 * Decorates an element as a startup page list item.
25 * @param {!HTMLElement} el The element to decorate.
27 StartupPageListItem.decorate = function(el) {
28 el.__proto__ = StartupPageListItem.prototype;
32 StartupPageListItem.prototype = {
33 __proto__: InlineEditableItem.prototype,
36 * Input field for editing the page url.
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');
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;
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_);
79 urlField.addEventListener('focus', function(event) {
80 self.parentNode.autocompleteList.attachToInput(urlField);
82 urlField.addEventListener('blur', function(event) {
83 self.parentNode.autocompleteList.detach();
86 if (!this.isPlaceholder)
87 titleEl.draggable = true;
91 get currentInputIsValid() {
92 return this.urlField_.validity.valid;
97 return this.urlField_.value != this.pageInfo_.url;
101 * Called when committing an edit; updates the model.
102 * @param {Event} e The end event.
105 onEditCommitted_: function(e) {
106 var url = this.urlField_.value;
107 if (this.isPlaceholder)
108 chrome.send('addStartupPage', [url]);
110 chrome.send('editStartupPage', [this.pageInfo_.modelIndex, url]);
114 var StartupPageList = cr.ui.define('list');
116 StartupPageList.prototype = {
117 __proto__: InlineEditableItemList.prototype,
120 * An autocomplete suggestion list for URL editing.
121 * @type {AutocompleteList}
123 autocompleteList: null,
126 * The drop position information: "below" or "above".
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));
144 createItem: function(pageInfo) {
145 var item = new StartupPageListItem(pageInfo);
146 item.urlField_.disabled = this.disabled;
151 deleteItemAtIndex: function(index) {
152 chrome.send('removeStartupPages', [index]);
156 * Computes the target item of drop event.
157 * @param {Event} e The drop or dragover event.
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;
170 * Handles the dragstart event.
171 * @param {Event} e The dragstart event.
174 handleDragStart_: function(e) {
175 // Prevent dragging if the list is disabled.
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
191 e.dataTransfer.setData('text/plain', target.urlField_.value);
196 * Handles the dragenter event.
197 * @param {Event} e The dragenter event.
200 handleDragEnter_: function(e) {
205 * Handles the dragover event.
206 * @param {Event} e The dragover event.
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_();
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);
230 * Handles the drop event.
231 * @param {Event} e The drop event.
234 handleDrop_: function(e) {
235 var dropTarget = this.getTargetFromDropEvent_(e);
237 if (!(dropTarget instanceof StartupPageListItem) ||
238 dropTarget.pageInfo_.modelIndex == -1) {
242 this.hideDropMarker_();
244 // Insert the selection at the new position.
245 var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
246 if (this.dropPos == 'below')
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]);
256 // Otherwise it was potentially a drop of new data (e.g. a bookmark).
257 var url = e.dataTransfer.getData('url');
260 chrome.send('addStartupPage', [url, newIndex]);
265 * Handles the dragleave event.
266 * @param {Event} e The dragleave event.
269 handleDragLeave_: function(e) {
270 this.hideDropMarker_();
274 * Handles the dragend event.
275 * @param {Event} e The dragend event.
278 handleDragEnd_: function(e) {
279 this.draggedItem.editable = true;
280 this.draggedItem.updateEditState();
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'.
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';
297 marker.style.top = (rect.bottom - markerHeight / 2) + 'px';
299 marker.style.width = rect.width + 'px';
300 marker.style.left = rect.left + 'px';
301 marker.style.display = 'block';
305 * Hides the drop marker.
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 = '';
319 StartupPageList: StartupPageList