blob: 848c4df2e29ef7f0b713cb9d7b55e60c34e0a1cc [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.search_engines', function() {
/** @const */ var ControlledSettingIndicator =
options.ControlledSettingIndicator;
/** @const */ var InlineEditableItemList = options.InlineEditableItemList;
/** @const */ var InlineEditableItem = options.InlineEditableItem;
/** @const */ var ListSelectionController = cr.ui.ListSelectionController;
/**
* Creates a new search engine list item.
* @param {Object} searchEnigne The search engine this represents.
* @constructor
* @extends {cr.ui.ListItem}
*/
function SearchEngineListItem(searchEngine) {
var el = cr.doc.createElement('div');
el.searchEngine_ = searchEngine;
SearchEngineListItem.decorate(el);
return el;
}
/**
* Decorates an element as a search engine list item.
* @param {!HTMLElement} el The element to decorate.
*/
SearchEngineListItem.decorate = function(el) {
el.__proto__ = SearchEngineListItem.prototype;
el.decorate();
};
SearchEngineListItem.prototype = {
__proto__: InlineEditableItem.prototype,
/**
* Input field for editing the engine name.
* @type {HTMLElement}
* @private
*/
nameField_: null,
/**
* Input field for editing the engine keyword.
* @type {HTMLElement}
* @private
*/
keywordField_: null,
/**
* Input field for editing the engine url.
* @type {HTMLElement}
* @private
*/
urlField_: null,
/**
* Whether or not an input validation request is currently outstanding.
* @type {boolean}
* @private
*/
waitingForValidation_: false,
/**
* Whether or not the current set of input is known to be valid.
* @type {boolean}
* @private
*/
currentlyValid_: false,
/** @override */
decorate: function() {
InlineEditableItem.prototype.decorate.call(this);
var engine = this.searchEngine_;
if (engine.modelIndex == '-1') {
this.isPlaceholder = true;
engine.name = '';
engine.keyword = '';
engine.url = '';
}
this.currentlyValid_ = !this.isPlaceholder;
if (engine.default)
this.classList.add('default');
this.deletable = engine.canBeRemoved;
// Construct the name column.
var nameColEl = this.ownerDocument.createElement('div');
nameColEl.className = 'name-column';
nameColEl.classList.add('weakrtl');
this.contentElement.appendChild(nameColEl);
// Add the favicon.
var faviconDivEl = this.ownerDocument.createElement('div');
faviconDivEl.className = 'favicon';
if (!this.isPlaceholder) {
faviconDivEl.style.backgroundImage = imageset(
'chrome://favicon/size/16@scalefactorx/iconurl/' + engine.iconURL);
}
nameColEl.appendChild(faviconDivEl);
var nameEl = this.createEditableTextCell(engine.displayName);
nameEl.classList.add('weakrtl');
nameColEl.appendChild(nameEl);
// Then the keyword column.
var keywordEl = this.createEditableTextCell(engine.keyword);
keywordEl.className = 'keyword-column';
keywordEl.classList.add('weakrtl');
this.contentElement.appendChild(keywordEl);
// And the URL column.
var urlEl = this.createEditableTextCell(engine.url);
// Extensions should not display a URL column.
if (!engine.isExtension) {
var urlWithButtonEl = this.ownerDocument.createElement('div');
urlWithButtonEl.appendChild(urlEl);
urlWithButtonEl.className = 'url-column';
urlWithButtonEl.classList.add('weakrtl');
this.contentElement.appendChild(urlWithButtonEl);
// Add the Make Default button. Temporary until drag-and-drop
// re-ordering is implemented. When this is removed, remove the extra
// div above.
if (engine.canBeDefault) {
var makeDefaultButtonEl = this.ownerDocument.createElement('button');
makeDefaultButtonEl.className =
'custom-appearance list-inline-button';
makeDefaultButtonEl.textContent =
loadTimeData.getString('makeDefaultSearchEngineButton');
makeDefaultButtonEl.onclick = function(e) {
chrome.send('managerSetDefaultSearchEngine', [engine.modelIndex]);
};
makeDefaultButtonEl.onmousedown = function(e) {
// Don't select the row when clicking the button.
e.stopPropagation();
// Don't focus on the button.
e.preventDefault();
};
urlWithButtonEl.appendChild(makeDefaultButtonEl);
}
}
// Do final adjustment to the input fields.
this.nameField_ = nameEl.querySelector('input');
// The editable field uses the raw name, not the display name.
this.nameField_.value = engine.name;
this.keywordField_ = keywordEl.querySelector('input');
this.urlField_ = urlEl.querySelector('input');
if (engine.urlLocked)
this.urlField_.disabled = true;
if (engine.isExtension)
this.nameField_.disabled = true;
if (this.isPlaceholder) {
this.nameField_.placeholder =
loadTimeData.getString('searchEngineTableNamePlaceholder');
this.keywordField_.placeholder =
loadTimeData.getString('searchEngineTableKeywordPlaceholder');
this.urlField_.placeholder =
loadTimeData.getString('searchEngineTableURLPlaceholder');
}
var fields = [this.nameField_, this.keywordField_, this.urlField_];
for (var i = 0; i < fields.length; i++) {
fields[i].oninput = this.startFieldValidation_.bind(this);
}
// Listen for edit events.
if (engine.canBeEdited) {
this.addEventListener('edit', this.onEditStarted_.bind(this));
this.addEventListener('canceledit', this.onEditCancelled_.bind(this));
this.addEventListener('commitedit', this.onEditCommitted_.bind(this));
} else {
this.editable = false;
this.querySelector('.row-delete-button').hidden = true;
var indicator = ControlledSettingIndicator();
indicator.setAttribute('setting', 'search-engine');
// Create a synthetic pref change event decorated as
// CoreOptionsHandler::CreateValueForPref() does.
var event = new cr.Event(this.contentType);
event.value = { controlledBy: 'policy' };
indicator.handlePrefChange(event);
this.appendChild(indicator);
}
},
/** @override */
get currentInputIsValid() {
return !this.waitingForValidation_ && this.currentlyValid_;
},
/** @override */
get hasBeenEdited() {
var engine = this.searchEngine_;
return this.nameField_.value != engine.name ||
this.keywordField_.value != engine.keyword ||
this.urlField_.value != engine.url;
},
/**
* Called when entering edit mode; starts an edit session in the model.
* @param {Event} e The edit event.
* @private
*/
onEditStarted_: function(e) {
var editIndex = this.searchEngine_.modelIndex;
chrome.send('editSearchEngine', [String(editIndex)]);
this.startFieldValidation_();
},
/**
* Called when committing an edit; updates the model.
* @param {Event} e The end event.
* @private
*/
onEditCommitted_: function(e) {
chrome.send('searchEngineEditCompleted', this.getInputFieldValues_());
},
/**
* Called when cancelling an edit; informs the model and resets the control
* states.
* @param {Event} e The cancel event.
* @private
*/
onEditCancelled_: function() {
chrome.send('searchEngineEditCancelled');
// The name field has been automatically set to match the display name,
// but it should use the raw name instead.
this.nameField_.value = this.searchEngine_.name;
this.currentlyValid_ = !this.isPlaceholder;
},
/**
* Returns the input field values as an array suitable for passing to
* chrome.send. The order of the array is important.
* @private
* @return {array} The current input field values.
*/
getInputFieldValues_: function() {
return [this.nameField_.value,
this.keywordField_.value,
this.urlField_.value];
},
/**
* Begins the process of asynchronously validing the input fields.
* @private
*/
startFieldValidation_: function() {
this.waitingForValidation_ = true;
var args = this.getInputFieldValues_();
args.push(this.searchEngine_.modelIndex);
chrome.send('checkSearchEngineInfoValidity', args);
},
/**
* Callback for the completion of an input validition check.
* @param {Object} validity A dictionary of validitation results.
*/
validationComplete: function(validity) {
this.waitingForValidation_ = false;
// TODO(stuartmorgan): Implement the full validation UI with
// checkmark/exclamation mark icons and tooltips showing the errors.
if (validity.name) {
this.nameField_.setCustomValidity('');
} else {
this.nameField_.setCustomValidity(
loadTimeData.getString('editSearchEngineInvalidTitleToolTip'));
}
if (validity.keyword) {
this.keywordField_.setCustomValidity('');
} else {
this.keywordField_.setCustomValidity(
loadTimeData.getString('editSearchEngineInvalidKeywordToolTip'));
}
if (validity.url) {
this.urlField_.setCustomValidity('');
} else {
this.urlField_.setCustomValidity(
loadTimeData.getString('editSearchEngineInvalidURLToolTip'));
}
this.currentlyValid_ = validity.name && validity.keyword && validity.url;
},
};
var SearchEngineList = cr.ui.define('list');
SearchEngineList.prototype = {
__proto__: InlineEditableItemList.prototype,
/** @override */
createItem: function(searchEngine) {
return new SearchEngineListItem(searchEngine);
},
/** @override */
deleteItemAtIndex: function(index) {
var modelIndex = this.dataModel.item(index).modelIndex;
chrome.send('removeSearchEngine', [String(modelIndex)]);
},
/**
* Passes the results of an input validation check to the requesting row
* if it's still being edited.
* @param {number} modelIndex The model index of the item that was checked.
* @param {Object} validity A dictionary of validitation results.
*/
validationComplete: function(validity, modelIndex) {
// If it's not still being edited, it no longer matters.
var currentSelection = this.selectedItem;
if (!currentSelection)
return;
var listItem = this.getListItem(currentSelection);
if (listItem.editing && currentSelection.modelIndex == modelIndex)
listItem.validationComplete(validity);
},
};
// Export
return {
SearchEngineList: SearchEngineList
};
});