Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(598)

Unified Diff: chrome/browser/resources/options2/language_list.js

Issue 8895023: Options2: Pull the trigger. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: DIAF. Created 9 years ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
Index: chrome/browser/resources/options2/language_list.js
diff --git a/chrome/browser/resources/options2/language_list.js b/chrome/browser/resources/options2/language_list.js
new file mode 100644
index 0000000000000000000000000000000000000000..3cbcb752a91acfa93e8ea97d975b8732fbc0df99
--- /dev/null
+++ b/chrome/browser/resources/options2/language_list.js
@@ -0,0 +1,487 @@
+// Copyright (c) 2011 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', function() {
+ const ArrayDataModel = cr.ui.ArrayDataModel;
+ const DeletableItem = options.DeletableItem;
+ const DeletableItemList = options.DeletableItemList;
+ const List = cr.ui.List;
+ const ListItem = cr.ui.ListItem;
+ const ListSingleSelectionModel = cr.ui.ListSingleSelectionModel;
+
+ /**
+ * Creates a new Language list item.
+ * @param {String} languageCode the languageCode.
+ * @constructor
+ * @extends {DeletableItem.ListItem}
+ */
+ function LanguageListItem(languageCode) {
+ var el = cr.doc.createElement('li');
+ el.__proto__ = LanguageListItem.prototype;
+ el.languageCode_ = languageCode;
+ el.decorate();
+ return el;
+ };
+
+ LanguageListItem.prototype = {
+ __proto__: DeletableItem.prototype,
+
+ /**
+ * The language code of this language.
+ * @type {String}
+ * @private
+ */
+ languageCode_: null,
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItem.prototype.decorate.call(this);
+
+ var languageCode = this.languageCode_;
+ var languageOptions = options.LanguageOptions.getInstance();
+ this.deletable = languageOptions.languageIsDeletable(languageCode);
+ this.languageCode = languageCode;
+ this.languageName = cr.doc.createElement('div');
+ this.languageName.className = 'language-name';
+ this.languageName.textContent =
+ LanguageList.getDisplayNameFromLanguageCode(languageCode);
+ this.contentElement.appendChild(this.languageName);
+ this.title =
+ LanguageList.getNativeDisplayNameFromLanguageCode(languageCode);
+ this.draggable = true;
+ },
+ };
+
+ /**
+ * Creates a new language list.
+ * @param {Object=} opt_propertyBag Optional properties.
+ * @constructor
+ * @extends {cr.ui.List}
+ */
+ var LanguageList = cr.ui.define('list');
+
+ /**
+ * Gets display name from the given language code.
+ * @param {string} languageCode Language code (ex. "fr").
+ */
+ LanguageList.getDisplayNameFromLanguageCode = function(languageCode) {
+ // Build the language code to display name dictionary at first time.
+ if (!this.languageCodeToDisplayName_) {
+ this.languageCodeToDisplayName_ = {};
+ var languageList = templateData.languageList;
+ for (var i = 0; i < languageList.length; i++) {
+ var language = languageList[i];
+ this.languageCodeToDisplayName_[language.code] = language.displayName;
+ }
+ }
+
+ return this.languageCodeToDisplayName_[languageCode];
+ }
+
+ /**
+ * Gets native display name from the given language code.
+ * @param {string} languageCode Language code (ex. "fr").
+ */
+ LanguageList.getNativeDisplayNameFromLanguageCode = function(languageCode) {
+ // Build the language code to display name dictionary at first time.
+ if (!this.languageCodeToNativeDisplayName_) {
+ this.languageCodeToNativeDisplayName_ = {};
+ var languageList = templateData.languageList;
+ for (var i = 0; i < languageList.length; i++) {
+ var language = languageList[i];
+ this.languageCodeToNativeDisplayName_[language.code] =
+ language.nativeDisplayName;
+ }
+ }
+
+ return this.languageCodeToNativeDisplayName_[languageCode];
+ }
+
+ /**
+ * Returns true if the given language code is valid.
+ * @param {string} languageCode Language code (ex. "fr").
+ */
+ LanguageList.isValidLanguageCode = function(languageCode) {
+ // Having the display name for the language code means that the
+ // language code is valid.
+ if (LanguageList.getDisplayNameFromLanguageCode(languageCode)) {
+ return true;
+ }
+ return false;
+ }
+
+ LanguageList.prototype = {
+ __proto__: DeletableItemList.prototype,
+
+ // The list item being dragged.
+ draggedItem: null,
+ // The drop position information: "below" or "above".
+ dropPos: null,
+ // The preference is a CSV string that describes preferred languages
+ // in Chrome OS. The language list is used for showing the language
+ // list in "Language and Input" options page.
+ preferredLanguagesPref: 'settings.language.preferred_languages',
+ // The preference is a CSV string that describes accept languages used
+ // for content negotiation. To be more precise, the list will be used
+ // in "Accept-Language" header in HTTP requests.
+ acceptLanguagesPref: 'intl.accept_languages',
+
+ /** @inheritDoc */
+ decorate: function() {
+ DeletableItemList.prototype.decorate.call(this);
+ this.selectionModel = new ListSingleSelectionModel;
+
+ // HACK(arv): http://crbug.com/40902
+ window.addEventListener('resize', this.redraw.bind(this));
+
+ // Listen to pref change.
+ if (cr.isChromeOS) {
+ Preferences.getInstance().addEventListener(this.preferredLanguagesPref,
+ this.handlePreferredLanguagesPrefChange_.bind(this));
+ } else {
+ Preferences.getInstance().addEventListener(this.acceptLanguagesPref,
+ this.handleAcceptLanguagesPrefChange_.bind(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));
+ },
+
+ createItem: function(languageCode) {
+ return new LanguageListItem(languageCode);
+ },
+
+ /*
+ * For each item, determines whether it's deletable.
+ */
+ updateDeletable: function() {
+ var items = this.items;
+ for (var i = 0; i < items.length; ++i) {
+ var item = items[i];
+ var languageCode = item.languageCode;
+ var languageOptions = options.LanguageOptions.getInstance();
+ item.deletable = languageOptions.languageIsDeletable(languageCode);
+ }
+ },
+
+ /*
+ * Adds a language to the language list.
+ * @param {string} languageCode language code (ex. "fr").
+ */
+ addLanguage: function(languageCode) {
+ // It shouldn't happen but ignore the language code if it's
+ // null/undefined, or already present.
+ if (!languageCode || this.dataModel.indexOf(languageCode) >= 0) {
+ return;
+ }
+ this.dataModel.push(languageCode);
+ // Select the last item, which is the language added.
+ this.selectionModel.selectedIndex = this.dataModel.length - 1;
+
+ this.savePreference_();
+ },
+
+ /*
+ * Gets the language codes of the currently listed languages.
+ */
+ getLanguageCodes: function() {
+ return this.dataModel.slice();
+ },
+
+ /*
+ * Gets the language code of the selected language.
+ */
+ getSelectedLanguageCode: function() {
+ return this.selectedItem;
+ },
+
+ /*
+ * Selects the language by the given language code.
+ * @returns {boolean} True if the operation is successful.
+ */
+ selectLanguageByCode: function(languageCode) {
+ var index = this.dataModel.indexOf(languageCode);
+ if (index >= 0) {
+ this.selectionModel.selectedIndex = index;
+ return true;
+ }
+ return false;
+ },
+
+ /** @inheritDoc */
+ deleteItemAtIndex: function(index) {
+ if (index >= 0) {
+ this.dataModel.splice(index, 1);
+ // Once the selected item is removed, there will be no selected item.
+ // Select the item pointed by the lead index.
+ index = this.selectionModel.leadIndex;
+ this.savePreference_();
+ }
+ return 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 ListItem)) {
+ target = target.parentNode;
+ }
+ return target;
+ },
+
+ /*
+ * Handles the dragstart event.
+ * @param {Event} e The dragstart event.
+ * @private
+ */
+ handleDragStart_: function(e) {
+ var target = e.target;
+ // ListItem should be the only draggable element type in the page,
+ // but just in case.
+ if (target instanceof ListItem) {
+ this.draggedItem = target;
+ e.dataTransfer.effectAllowed = 'move';
+ // We need to put some kind of data in the drag or it will be
+ // ignored. Use the display name in case the user drags to a text
+ // field or the desktop.
+ e.dataTransfer.setData('text/plain', target.title);
+ }
+ },
+
+ /*
+ * 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 ListItem.
+ if (!(dropTarget instanceof ListItem) ||
+ dropTarget == this.draggedItem) {
+ 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);
+ this.hideDropMarker_();
+
+ // Delete the language from the original position.
+ var languageCode = this.draggedItem.languageCode;
+ var originalIndex = this.dataModel.indexOf(languageCode);
+ this.dataModel.splice(originalIndex, 1);
+ // Insert the language to the new position.
+ var newIndex = this.dataModel.indexOf(dropTarget.languageCode);
+ if (this.dropPos == 'below')
+ newIndex += 1;
+ this.dataModel.splice(newIndex, 0, languageCode);
+ // The cursor should move to the moved item.
+ this.selectionModel.selectedIndex = newIndex;
+ // Save the preference.
+ this.savePreference_();
+ },
+
+ /*
+ * Handles the dragleave event.
+ * @param {Event} e The dragleave event
+ * @private
+ */
+ handleDragLeave_ : function(e) {
+ this.hideDropMarker_();
+ },
+
+ /*
+ * 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 = $('language-options-list-dropmarker');
+ var rect = target.getBoundingClientRect();
+ var markerHeight = 8;
+ 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() {
+ $('language-options-list-dropmarker').style.display = '';
+ }, 100);
+ },
+
+ /**
+ * Handles preferred languages pref change.
+ * @param {Event} e The change event object.
+ * @private
+ */
+ handlePreferredLanguagesPrefChange_: function(e) {
+ var languageCodesInCsv = e.value.value;
+ var languageCodes = languageCodesInCsv.split(',');
+
+ // Add the UI language to the initial list of languages. This is to avoid
+ // a bug where the UI language would be removed from the preferred
+ // language list by sync on first login.
+ // See: crosbug.com/14283
+ languageCodes.push(navigator.language);
+ languageCodes = this.filterBadLanguageCodes_(languageCodes);
+ this.load_(languageCodes);
+ },
+
+ /**
+ * Handles accept languages pref change.
+ * @param {Event} e The change event object.
+ * @private
+ */
+ handleAcceptLanguagesPrefChange_: function(e) {
+ var languageCodesInCsv = e.value.value;
+ var languageCodes = this.filterBadLanguageCodes_(
+ languageCodesInCsv.split(','));
+ this.load_(languageCodes);
+ },
+
+ /**
+ * Loads given language list.
+ * @param {Array} languageCodes List of language codes.
+ * @private
+ */
+ load_: function(languageCodes) {
+ // Preserve the original selected index. See comments below.
+ var originalSelectedIndex = (this.selectionModel ?
+ this.selectionModel.selectedIndex : -1);
+ this.dataModel = new ArrayDataModel(languageCodes);
+ if (originalSelectedIndex >= 0 &&
+ originalSelectedIndex < this.dataModel.length) {
+ // Restore the original selected index if the selected index is
+ // valid after the data model is loaded. This is neeeded to keep
+ // the selected language after the languge is added or removed.
+ this.selectionModel.selectedIndex = originalSelectedIndex;
+ // The lead index should be updated too.
+ this.selectionModel.leadIndex = originalSelectedIndex;
+ } else if (this.dataModel.length > 0){
+ // Otherwise, select the first item if it's not empty.
+ // Note that ListSingleSelectionModel won't select an item
+ // automatically, hence we manually select the first item here.
+ this.selectionModel.selectedIndex = 0;
+ }
+ },
+
+ /**
+ * Saves the preference.
+ */
+ savePreference_: function() {
+ // Encode the language codes into a CSV string.
+ if (cr.isChromeOS)
+ Preferences.setStringPref(this.preferredLanguagesPref,
+ this.dataModel.slice().join(','));
+ // Save the same language list as accept languages preference as
+ // well, but we need to expand the language list, to make it more
+ // acceptable. For instance, some web sites don't understand 'en-US'
+ // but 'en'. See crosbug.com/9884.
+ var acceptLanguages = this.expandLanguageCodes(this.dataModel.slice());
+ Preferences.setStringPref(this.acceptLanguagesPref,
+ acceptLanguages.join(','));
+ cr.dispatchSimpleEvent(this, 'save');
+ },
+
+ /**
+ * Expands language codes to make these more suitable for Accept-Language.
+ * Example: ['en-US', 'ja', 'en-CA'] => ['en-US', 'en', 'ja', 'en-CA'].
+ * 'en' won't appear twice as this function eliminates duplicates.
+ * @param {Array} languageCodes List of language codes.
+ * @private
+ */
+ expandLanguageCodes: function(languageCodes) {
+ var expandedLanguageCodes = [];
+ var seen = {}; // Used to eliminiate duplicates.
+ for (var i = 0; i < languageCodes.length; i++) {
+ var languageCode = languageCodes[i];
+ if (!(languageCode in seen)) {
+ expandedLanguageCodes.push(languageCode);
+ seen[languageCode] = true;
+ }
+ var parts = languageCode.split('-');
+ if (!(parts[0] in seen)) {
+ expandedLanguageCodes.push(parts[0]);
+ seen[parts[0]] = true;
+ }
+ }
+ return expandedLanguageCodes;
+ },
+
+ /**
+ * Filters bad language codes in case bad language codes are
+ * stored in the preference. Removes duplicates as well.
+ * @param {Array} languageCodes List of language codes.
+ * @private
+ */
+ filterBadLanguageCodes_: function(languageCodes) {
+ var filteredLanguageCodes = [];
+ var seen = {};
+ for (var i = 0; i < languageCodes.length; i++) {
+ // Check if the the language code is valid, and not
+ // duplicate. Otherwise, skip it.
+ if (LanguageList.isValidLanguageCode(languageCodes[i]) &&
+ !(languageCodes[i] in seen)) {
+ filteredLanguageCodes.push(languageCodes[i]);
+ seen[languageCodes[i]] = true;
+ }
+ }
+ return filteredLanguageCodes;
+ },
+ };
+
+ return {
+ LanguageList: LanguageList,
+ LanguageListItem: LanguageListItem
+ };
+});

Powered by Google App Engine
This is Rietveld 408576698