blob: 23f7c1a43a5cabf4863f2c27c6fa1c6f370adb71 [file] [log] [blame]
// Copyright (c) 2012 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
cr.define('options.browser_options', function() {
/** @const */ var AutocompleteList = cr.ui.AutocompleteList;
/** @const */ var InlineEditableItem = options.InlineEditableItem;
/** @const */ var InlineEditableItemList = options.InlineEditableItemList;
/**
* Creates a new startup page list item.
* @param {Object} pageInfo The page this item represents.
* @constructor
* @extends {cr.ui.ListItem}
*/
function StartupPageListItem(pageInfo) {
var el = cr.doc.createElement('div');
el.pageInfo_ = pageInfo;
StartupPageListItem.decorate(el);
return el;
}
/**
* Decorates an element as a startup page list item.
* @param {!HTMLElement} el The element to decorate.
*/
StartupPageListItem.decorate = function(el) {
el.__proto__ = StartupPageListItem.prototype;
el.decorate();
};
StartupPageListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/**
* Input field for editing the page url.
* @type {HTMLElement}
* @private
*/
urlField_: null,
/** @override */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
var pageInfo = this.pageInfo_;
if (pageInfo.modelIndex == -1) {
this.isPlaceholder = true;
pageInfo.title = loadTimeData.getString('startupAddLabel');
pageInfo.url = '';
}
var titleEl = this.ownerDocument.createElement('div');
titleEl.className = 'title';
titleEl.classList.add('favicon-cell');
titleEl.classList.add('weakrtl');
titleEl.textContent = pageInfo.title;
if (!this.isPlaceholder) {
titleEl.style.backgroundImage = getFaviconImageSet(pageInfo.url);
titleEl.title = pageInfo.tooltip;
}
this.contentElement.appendChild(titleEl);
var urlEl = this.createEditableTextCell(pageInfo.url);
urlEl.className = 'url';
urlEl.classList.add('weakrtl');
this.contentElement.appendChild(urlEl);
var urlField = urlEl.querySelector('input');
urlField.className = 'weakrtl';
urlField.placeholder = loadTimeData.getString('startupPagesPlaceholder');
this.urlField_ = urlField;
this.addEventListener('commitedit', this.onEditCommitted_);
var self = this;
urlField.addEventListener('focus', function(event) {
self.parentNode.autocompleteList.attachToInput(urlField);
});
urlField.addEventListener('blur', function(event) {
self.parentNode.autocompleteList.detach();
});
if (!this.isPlaceholder)
this.draggable = true;
},
/** @override */
get currentInputIsValid() {
return this.urlField_.validity.valid;
},
/** @override */
get hasBeenEdited() {
return this.urlField_.value != this.pageInfo_.url;
},
/**
* Called when committing an edit; updates the model.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
var url = this.urlField_.value;
if (this.isPlaceholder)
chrome.send('addStartupPage', [url]);
else
chrome.send('editStartupPage', [this.pageInfo_.modelIndex, url]);
},
};
var StartupPageList = cr.ui.define('list');
StartupPageList.prototype = {
__proto__: InlineEditableItemList.prototype,
/**
* An autocomplete suggestion list for URL editing.
* @type {AutocompleteList}
*/
autocompleteList: null,
/**
* The drop position information: "below" or "above".
*/
dropPos: null,
/** @override */
decorate: function() {
InlineEditableItemList.prototype.decorate.call(this);
// Listen to drag and drop events.
this.addEventListener('dragstart', this.handleDragStart_.bind(this));
this.addEventListener('dragenter', this.handleDragEnter_.bind(this));
this.addEventListener('dragover', this.handleDragOver_.bind(this));
this.addEventListener('drop', this.handleDrop_.bind(this));
this.addEventListener('dragleave', this.handleDragLeave_.bind(this));
this.addEventListener('dragend', this.handleDragEnd_.bind(this));
},
/** @override */
createItem: function(pageInfo) {
var item = new StartupPageListItem(pageInfo);
item.urlField_.disabled = this.disabled;
return item;
},
/** @override */
deleteItemAtIndex: function(index) {
chrome.send('removeStartupPages', [index]);
},
/**
* Computes the target item of drop event.
* @param {Event} e The drop or dragover event.
* @private
*/
getTargetFromDropEvent_: function(e) {
var target = e.target;
// e.target may be an inner element of the list item
while (target != null && !(target instanceof StartupPageListItem)) {
target = target.parentNode;
}
return target;
},
/**
* Handles the dragstart event.
* @param {Event} e The dragstart event.
* @private
*/
handleDragStart_: function(e) {
// Prevent dragging if the list is disabled.
if (this.disabled) {
e.preventDefault();
return false;
}
var target = e.target;
// StartupPageListItem should be the only draggable element type in the
// page but let's make sure.
if (target instanceof StartupPageListItem) {
this.draggedItem = target;
this.draggedItem.editable = false;
e.dataTransfer.effectAllowed = 'move';
// We need to put some kind of data in the drag or it will be
// ignored. Use the URL in case the user drags to a text field or the
// desktop.
e.dataTransfer.setData('text/plain', target.urlField_.value);
}
},
/*
* Handles the dragenter event.
* @param {Event} e The dragenter event.
* @private
*/
handleDragEnter_: function(e) {
e.preventDefault();
},
/*
* Handles the dragover event.
* @param {Event} e The dragover event.
* @private
*/
handleDragOver_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
// Determines whether the drop target is to accept the drop.
// The drop is only successful on another StartupPageListItem.
if (!(dropTarget instanceof StartupPageListItem) ||
dropTarget == this.draggedItem || dropTarget.isPlaceholder) {
this.hideDropMarker_();
return;
}
// Compute the drop postion. Should we move the dragged item to
// below or above the drop target?
var rect = dropTarget.getBoundingClientRect();
var dy = e.clientY - rect.top;
var yRatio = dy / rect.height;
var dropPos = yRatio <= .5 ? 'above' : 'below';
this.dropPos = dropPos;
this.showDropMarker_(dropTarget, dropPos);
e.preventDefault();
},
/*
* Handles the drop event.
* @param {Event} e The drop event.
* @private
*/
handleDrop_: function(e) {
var dropTarget = this.getTargetFromDropEvent_(e);
if (!(dropTarget instanceof StartupPageListItem) ||
dropTarget.pageInfo_.modelIndex == -1) {
return;
}
this.hideDropMarker_();
// Insert the selection at the new position.
var newIndex = this.dataModel.indexOf(dropTarget.pageInfo_);
if (this.dropPos == 'below')
newIndex += 1;
chrome.send('dragDropStartupPage',
[newIndex, this.selectionModel.selectedIndexes]);
},
/**
* Handles the dragleave event.
* @param {Event} e The dragleave event.
* @private
*/
handleDragLeave_: function(e) {
this.hideDropMarker_();
},
/**
* Handles the dragend event.
* @param {Event} e The dragend event.
* @private
*/
handleDragEnd_: function(e) {
this.draggedItem.editable = true;
this.draggedItem.updateEditState();
},
/**
* Shows and positions the marker to indicate the drop target.
* @param {HTMLElement} target The current target list item of drop.
* @param {string} pos 'below' or 'above'.
* @private
*/
showDropMarker_: function(target, pos) {
window.clearTimeout(this.hideDropMarkerTimer_);
var marker = $('startupPagesListDropmarker');
var rect = target.getBoundingClientRect();
var markerHeight = 6;
if (pos == 'above') {
marker.style.top = (rect.top - markerHeight / 2) + 'px';
} else {
marker.style.top = (rect.bottom - markerHeight / 2) + 'px';
}
marker.style.width = rect.width + 'px';
marker.style.left = rect.left + 'px';
marker.style.display = 'block';
},
/**
* Hides the drop marker.
* @private
*/
hideDropMarker_: function() {
// Hide the marker in a timeout to reduce flickering as we move between
// valid drop targets.
window.clearTimeout(this.hideDropMarkerTimer_);
this.hideDropMarkerTimer_ = window.setTimeout(function() {
$('startupPagesListDropmarker').style.display = '';
}, 100);
},
};
return {
StartupPageList: StartupPageList
};
});