Add suggestionPicker to CalendarPicker
authorkeishi@webkit.org <keishi@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 24 Sep 2012 04:39:09 +0000 (04:39 +0000)
committerkeishi@webkit.org <keishi@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Mon, 24 Sep 2012 04:39:09 +0000 (04:39 +0000)
https://bugs.webkit.org/show_bug.cgi?id=97201

Reviewed by Kent Tamura.

.:

* ManualTests/forms/calendar-picker.html: Added tests for SuggestionPicker.

Source/WebCore:

This adds the suggestionPicker to CalendarPicker. It is not available yet as a datalist UI but it can be used inside ManualTests/forms/calendar-picker.html.

No new tests. Suggestion Picker can be tested in ManualTests/forms/calendar-picker.html.

* Resources/pagepopups/calendarPicker.js:
(initialize):
(openSuggestionPicker):
* Resources/pagepopups/pickerCommon.js:
(enclosingNodeOrSelfWithClass):
* Resources/pagepopups/suggestionPicker.css: Added.
(.suggestion-list):
(.suggestion-list-entry):
(.suggestion-list-entry:focus):
(.suggestion-list-entry:focus .label):
(.suggestion-list-entry .content):
(.suggestion-list-entry .title):
(.suggestion-list-entry .label):
(.measuring-width .suggestion-list-entry .label):
(.suggestion-list .separator):
* Resources/pagepopups/suggestionPicker.js: Added.
(SuggestionPicker):
(SuggestionPicker.validateConfig):
(SuggestionPicker.prototype.cleanup):
(SuggestionPicker.prototype._setColors): Creates css rules that sets highlight colors.
(SuggestionPicker.prototype._createSuggestionEntryElement): Creates an entry that when selected submits the value.
(SuggestionPicker.prototype._createActionEntryElement): Creates an entry that causes an action. (e.x. "Other...")
(SuggestionPicker.prototype._measureMaxContentWidth): Temporarily left align everything to measure the width.
(SuggestionPicker.prototype._fixWindowSize):
(SuggestionPicker.prototype._layout):
(SuggestionPicker.prototype.selectEntry):
(SuggestionPicker.prototype._handleEntryClick):
(SuggestionPicker.prototype._findFirstVisibleEntry):
(SuggestionPicker.prototype._findLastVisibleEntry):
(SuggestionPicker.prototype._handleBodyKeyDown):
(SuggestionPicker.prototype._handleEntryMouseOver):
(SuggestionPicker.prototype._handleMouseOut):
* WebCore.gyp/WebCore.gyp:

Source/WebKit/chromium:

* src/DateTimeChooserImpl.cpp:
(WebKit::DateTimeChooserImpl::writeDocument): Add js/css for SuggestionPicker to page picker. Add highlight color to args.

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@129326 268f45cc-cd09-0410-ab3c-d52691b4dbfc

ChangeLog
ManualTests/forms/calendar-picker.html
Source/WebCore/ChangeLog
Source/WebCore/Resources/pagepopups/calendarPicker.js
Source/WebCore/Resources/pagepopups/pickerCommon.js
Source/WebCore/Resources/pagepopups/suggestionPicker.css [new file with mode: 0644]
Source/WebCore/Resources/pagepopups/suggestionPicker.js [new file with mode: 0644]
Source/WebCore/WebCore.gyp/WebCore.gyp
Source/WebKit/chromium/ChangeLog
Source/WebKit/chromium/src/DateTimeChooserImpl.cpp

index 8dc53f5..d9115da 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,12 @@
+2012-09-23  Keishi Hattori  <keishi@webkit.org>
+
+        Add suggestionPicker to CalendarPicker
+        https://bugs.webkit.org/show_bug.cgi?id=97201
+
+        Reviewed by Kent Tamura.
+
+        * ManualTests/forms/calendar-picker.html: Added tests for SuggestionPicker.
+
 2012-09-21  Ami Fischman  <fischman@chromium.org>
 
         HTMLMediaElement isn't garbage collected between document reloads
index 438eb6f..3e3e33e 100644 (file)
@@ -23,6 +23,8 @@ iframe {
  <option>English</option>
  <option>Japanese</option>
  <option>Arabic</option>
+ <option>with datalist</option>
+ <option>with long datalist</option>
 </select>
 
 <div><input type="text" id="date"></div>
@@ -68,6 +70,83 @@ var arabicArguments = {
     step : 1,
     max : '2020-05-15',
 };
+var datalistArguments = {
+    locale: 'en-US',
+    monthLabels : ['January', 'February', 'March', 'April', 'May', 'June',
+    'July', 'August', 'September', 'October', 'November', 'December'],
+    dayLabels : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+    todayLabel : 'Today',
+    clearLabel : 'Clear',
+    cancelLabel : 'Cancel',
+    weekStartDay : 0,
+    step : 1,
+    max : '2099-12-31',
+    suggestionValues : ['2012-01-01', '2012-06-03', '2012-09-06', '2012-12-24'],
+    localizedSuggestionValues : ['1/1/12', '6/3/12', '9/6/12', '12/24/12'],
+    suggestionLabels : ['', 'Birthday', '', 'Christmas'],
+    showOtherDateEntry: true,
+    otherDateLabel: 'Other...',
+    inputWidth: 127,
+    suggestionHighlightColor: "#0000ff",
+    suggestionHighlightTextColor: "#ffffff"
+};
+var longDatalistArguments = {
+    locale: 'en-US',
+    monthLabels : ['January', 'February', 'March', 'April', 'May', 'June',
+    'July', 'August', 'September', 'October', 'November', 'December'],
+    dayLabels : ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat'],
+    todayLabel : 'Today',
+    clearLabel : 'Clear',
+    cancelLabel : 'Cancel',
+    weekStartDay : 0,
+    step : 1,
+    max : '2099-12-31',
+    suggestionValues: ["2012-01-01", "2012-01-02", "2012-01-03", "2012-01-04",
+        "2012-01-05", "2012-01-06", "2012-01-07", "2012-01-08", "2012-01-09",
+        "2012-01-10", "2012-01-11", "2012-01-12", "2012-01-13", "2012-01-14",
+        "2012-01-15", "2012-01-16", "2012-01-17", "2012-01-18", "2012-01-19",
+        "2012-01-20", "2012-01-21", "2012-01-22", "2012-01-23", "2012-01-24",
+        "2012-01-25", "2012-01-26", "2012-01-27", "2012-01-28", "2012-01-29",
+        "2012-01-30", "2012-01-31", "2012-02-01", "2012-02-02", "2012-02-03",
+        "2012-02-04", "2012-02-05", "2012-02-06", "2012-02-07", "2012-02-08",
+        "2012-02-09", "2012-02-10", "2012-02-11", "2012-02-12", "2012-02-13",
+        "2012-02-14", "2012-02-15", "2012-02-16", "2012-02-17", "2012-02-18",
+        "2012-02-19", "2012-02-20", "2012-02-21", "2012-02-22", "2012-02-23",
+        "2012-02-24", "2012-02-25", "2012-02-26", "2012-02-27", "2012-02-28",
+        "2012-02-29", "2012-03-01", "2012-03-02", "2012-03-03", "2012-03-04",
+        "2012-03-05", "2012-03-06", "2012-03-07", "2012-03-08", "2012-03-09",
+        "2012-03-10", "2012-03-11", "2012-03-12", "2012-03-13", "2012-03-14",
+        "2012-03-15", "2012-03-16", "2012-03-17", "2012-03-18", "2012-03-19",
+        "2012-03-20", "2012-03-21", "2012-03-22", "2012-03-23", "2012-03-24",
+        "2012-03-25", "2012-03-26", "2012-03-27", "2012-03-28", "2012-03-29",
+        "2012-03-30", "2012-03-31"],
+    localizedSuggestionValues: ["1/1/12", "1/2/12", "1/3/12", "1/4/12",
+        "1/5/12", "1/6/12", "1/7/12", "1/8/12", "1/9/12", "1/10/12", "1/11/12",
+        "1/12/12", "1/13/12", "1/14/12", "1/15/12", "1/16/12", "1/17/12",
+        "1/18/12", "1/19/12", "1/20/12", "1/21/12", "1/22/12", "1/23/12",
+        "1/24/12", "1/25/12", "1/26/12", "1/27/12", "1/28/12", "1/29/12",
+        "1/30/12", "1/31/12", "2/1/12", "2/2/12", "2/3/12", "2/4/12", "2/5/12",
+        "2/6/12", "2/7/12", "2/8/12", "2/9/12", "2/10/12", "2/11/12", "2/12/12",
+        "2/13/12", "2/14/12", "2/15/12", "2/16/12", "2/17/12", "2/18/12",
+        "2/19/12", "2/20/12", "2/21/12", "2/22/12", "2/23/12", "2/24/12",
+        "2/25/12", "2/26/12", "2/27/12", "2/28/12", "2/29/12", "3/1/12",
+        "3/2/12", "3/3/12", "3/4/12", "3/5/12", "3/6/12", "3/7/12", "3/8/12",
+        "3/9/12", "3/10/12", "3/11/12", "3/12/12", "3/13/12", "3/14/12", 
+        "3/15/12", "3/16/12", "3/17/12", "3/18/12", "3/19/12", "3/20/12",
+        "3/21/12", "3/22/12", "3/23/12", "3/24/12", "3/25/12", "3/26/12",
+        "3/27/12", "3/28/12", "3/29/12", "3/30/12", "3/31/12"],
+    suggestionLabels: ["Today", "Tomorrow", "", "", "", "", "", "", "", "", "",
+        "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+        "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+        "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+        "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "", "",
+        "", "", "", "", "", "", "", ""],
+    showOtherDateEntry: true,
+    otherDateLabel: 'Other...',
+    inputWidth: 127,
+    suggestionHighlightColor: "#0000ff",
+    suggestionHighlightTextColor: "#ffffff"
+};
 
 function openCalendar(args) {
     var frame = document.getElementsByTagName('iframe')[0];
@@ -77,6 +156,10 @@ function openCalendar(args) {
     commonCssLink.rel = 'stylesheet';
     commonCssLink.href = '../../Source/WebCore/Resources/pagepopups/pickerCommon.css?' + (new Date()).getTime();
     doc.head.appendChild(commonCssLink);
+    var suggestionPickerCssLink = doc.createElement('link');
+    suggestionPickerCssLink.rel = 'stylesheet';
+    suggestionPickerCssLink.href = '../../Source/WebCore/Resources/pagepopups/suggestionPicker.css?' + (new Date()).getTime();
+    doc.head.appendChild(suggestionPickerCssLink);
     var link = doc.createElement('link');
     link.rel = 'stylesheet';
     link.href = '../../Source/WebCore/Resources/pagepopups/calendarPicker.css?' + (new Date()).getTime();
@@ -84,6 +167,9 @@ function openCalendar(args) {
     var commonJsScript = doc.createElement('script');
     commonJsScript.src = '../../Source/WebCore/Resources/pagepopups/pickerCommon.js?' + (new Date()).getTime();
     doc.body.appendChild(commonJsScript);
+    var suggestionPickerJsScript = doc.createElement('script');
+    suggestionPickerJsScript.src = '../../Source/WebCore/Resources/pagepopups/suggestionPicker.js?' + (new Date()).getTime();
+    doc.body.appendChild(suggestionPickerJsScript);
     var script = doc.createElement('script');
     script.src = '../../Source/WebCore/Resources/pagepopups/calendarPicker.js?' + (new Date()).getTime();
     doc.body.appendChild(script);
@@ -121,6 +207,12 @@ function selected(select) {
     case 2:
         openCalendar(arabicArguments);
         break;
+    case 3:
+        openCalendar(datalistArguments);
+        break;
+    case 4:
+        openCalendar(longDatalistArguments);
+        break;
     }
 }
 
index 983a5be..54011e9 100644 (file)
@@ -1,3 +1,48 @@
+2012-09-23  Keishi Hattori  <keishi@webkit.org>
+
+        Add suggestionPicker to CalendarPicker
+        https://bugs.webkit.org/show_bug.cgi?id=97201
+
+        Reviewed by Kent Tamura.
+
+        This adds the suggestionPicker to CalendarPicker. It is not available yet as a datalist UI but it can be used inside ManualTests/forms/calendar-picker.html.
+
+        No new tests. Suggestion Picker can be tested in ManualTests/forms/calendar-picker.html.
+
+        * Resources/pagepopups/calendarPicker.js:
+        (initialize):
+        (openSuggestionPicker):
+        * Resources/pagepopups/pickerCommon.js:
+        (enclosingNodeOrSelfWithClass):
+        * Resources/pagepopups/suggestionPicker.css: Added.
+        (.suggestion-list):
+        (.suggestion-list-entry):
+        (.suggestion-list-entry:focus):
+        (.suggestion-list-entry:focus .label):
+        (.suggestion-list-entry .content):
+        (.suggestion-list-entry .title):
+        (.suggestion-list-entry .label):
+        (.measuring-width .suggestion-list-entry .label):
+        (.suggestion-list .separator):
+        * Resources/pagepopups/suggestionPicker.js: Added.
+        (SuggestionPicker):
+        (SuggestionPicker.validateConfig):
+        (SuggestionPicker.prototype.cleanup):
+        (SuggestionPicker.prototype._setColors): Creates css rules that sets highlight colors.
+        (SuggestionPicker.prototype._createSuggestionEntryElement): Creates an entry that when selected submits the value.
+        (SuggestionPicker.prototype._createActionEntryElement): Creates an entry that causes an action. (e.x. "Other...")
+        (SuggestionPicker.prototype._measureMaxContentWidth): Temporarily left align everything to measure the width.
+        (SuggestionPicker.prototype._fixWindowSize):
+        (SuggestionPicker.prototype._layout):
+        (SuggestionPicker.prototype.selectEntry):
+        (SuggestionPicker.prototype._handleEntryClick):
+        (SuggestionPicker.prototype._findFirstVisibleEntry):
+        (SuggestionPicker.prototype._findLastVisibleEntry):
+        (SuggestionPicker.prototype._handleBodyKeyDown):
+        (SuggestionPicker.prototype._handleEntryMouseOver):
+        (SuggestionPicker.prototype._handleMouseOut):
+        * WebCore.gyp/WebCore.gyp:
+
 2012-09-23  Andreas Kling  <kling@webkit.org>
 
         REGRESSION(r128239): Mutable ElementAttributeData leak their Attribute vectors.
index 20a85fd..db984a5 100644 (file)
@@ -268,13 +268,18 @@ CalendarPicker.validateConfig = function(config) {
  */
 function initialize(args) {
     var errorString = CalendarPicker.validateConfig(args);
+    if (args.suggestionValues)
+        errorString = errorString || SuggestionPicker.validateConfig(args)
     if (errorString) {
         var main = $("main");
         main.textContent = "Internal error: " + errorString;
         resizeWindow(main.offsetWidth, main.offsetHeight);
     } else {
         global.params = args;
-        openCalendarPicker();
+        if (global.params.suggestionValues && global.params.suggestionValues.length)
+            openSuggestionPicker();
+        else
+            openCalendarPicker();
     }
 }
 
@@ -286,6 +291,11 @@ function closePicker() {
     main.className = "";
 };
 
+function openSuggestionPicker() {
+    closePicker();
+    global.picker = new SuggestionPicker($("main"), global.params);
+};
+
 function openCalendarPicker() {
     closePicker();
     global.picker = new CalendarPicker($("main"), global.params);
index ad9542c..619f2ee 100644 (file)
@@ -76,6 +76,19 @@ function getScrollbarWidth() {
 }
 
 /**
+ * @param {!string} className
+ * @return {?Element}
+ */
+function enclosingNodeOrSelfWithClass(selfNode, className)
+{
+    for (var node = selfNode; node && node !== selfNode.ownerDocument; node = node.parentNode) {
+        if (node.nodeType === Node.ELEMENT_NODE && node.classList.contains(className))
+            return node;
+    }
+    return null;
+};
+
+/**
  * @constructor
  * @param {!Element} element
  * @param {!Object} config
diff --git a/Source/WebCore/Resources/pagepopups/suggestionPicker.css b/Source/WebCore/Resources/pagepopups/suggestionPicker.css
new file mode 100644 (file)
index 0000000..aca47dd
--- /dev/null
@@ -0,0 +1,43 @@
+.suggestion-list {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+    font: -webkit-small-control;
+    border: 1px solid #7f9db9;
+    background-color: white;
+    overflow-y: auto;
+}
+
+.suggestion-list-entry {
+    white-space: nowrap;
+    height: 1.73em;
+    line-height: 1.73em;
+    -webkit-select: none;
+    cursor: default;
+}
+
+.suggestion-list-entry:focus {
+    outline: none;
+}
+
+.suggestion-list-entry .content {
+    padding: 0 4px;
+}
+
+.suggestion-list-entry .label {
+    padding-left: 20px;
+    text-align: right;
+    color: #737373;
+    float: right;
+    margin-right: 4px;
+}
+
+.measuring-width .suggestion-list-entry .label {
+    float: none;
+    margin-right: 0;
+}
+
+.suggestion-list .separator {
+    border-top: 1px solid #dcdcdc;
+    height: 0;
+}
diff --git a/Source/WebCore/Resources/pagepopups/suggestionPicker.js b/Source/WebCore/Resources/pagepopups/suggestionPicker.js
new file mode 100644 (file)
index 0000000..c78b1e0
--- /dev/null
@@ -0,0 +1,311 @@
+"use strict";
+/*
+ * Copyright (C) 2012 Google Inc. All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ *    notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ *    notice, this list of conditions and the following disclaimer in the
+ *    documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+ * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+ * DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+ * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+ * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+ * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+ * ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+/**
+ * @param {!Element} element
+ * @param {!Object} config
+ */
+function SuggestionPicker(element, config) {
+    Picker.call(this, element, config);
+    this._isFocusByMouse = false;
+    this._containerElement = null;
+    this._setColors();
+    this._layout();
+    this._fixWindowSize();
+    this._handleBodyKeyDownBound = this._handleBodyKeyDown.bind(this);
+    document.body.addEventListener("keydown", this._handleBodyKeyDownBound);
+    this._element.addEventListener("mouseout", this._handleMouseOut.bind(this), false);
+};
+SuggestionPicker.prototype = Object.create(Picker.prototype);
+
+SuggestionPicker.NumberOfVisibleEntries = 20;
+
+// An entry needs to be at least this many pixels visible for it to be a visible entry.
+SuggestionPicker.VisibleEntryThresholdHeight = 4;
+
+SuggestionPicker.ActionNames = {
+    OpenCalendarPicker: "openCalendarPicker"
+};
+
+SuggestionPicker.ListEntryClass = "suggestion-list-entry";
+
+SuggestionPicker.validateConfig = function(config) {
+    if (config.showOtherDateEntry && !config.otherDateLabel)
+        return "No otherDateLabel.";
+    if (config.suggestionHighlightColor && !config.suggestionHighlightColor)
+        return "No suggestionHighlightColor.";
+    if (config.suggestionHighlightTextColor && !config.suggestionHighlightTextColor)
+        return "No suggestionHighlightTextColor.";
+    if (config.suggestionValues.length !== config.localizedSuggestionValues.length)
+        return "localizedSuggestionValues.length must equal suggestionValues.length.";
+    if (config.suggestionValues.length !== config.suggestionLabels.length)
+        return "suggestionLabels.length must equal suggestionValues.length.";
+    if (typeof config.inputWidth === "undefined")
+        return "No inputWidth.";
+    return null;
+};
+
+SuggestionPicker.prototype._setColors = function() {
+    var text = "." + SuggestionPicker.ListEntryClass + ":focus {\
+        background-color: " + this._config.suggestionHighlightColor + ";\
+        color: " + this._config.suggestionHighlightTextColor + "; }";
+    text += "." + SuggestionPicker.ListEntryClass + ":focus .label { color: " + this._config.suggestionHighlightTextColor + "; }";
+    document.head.appendChild(createElement("style", null, text));
+};
+
+SuggestionPicker.prototype.cleanup = function() {
+    document.body.removeEventListener("keydown", this._handleBodyKeyDownBound, false);
+};
+
+/**
+ * @param {!string} title
+ * @param {!string} label
+ * @param {!string} value
+ * @return {!Element}
+ */
+SuggestionPicker.prototype._createSuggestionEntryElement = function(title, label, value) {
+    var entryElement = createElement("li", SuggestionPicker.ListEntryClass);
+    entryElement.tabIndex = 0;
+    entryElement.dataset.value = value;
+    var content = createElement("span", "content");
+    entryElement.appendChild(content);
+    var titleElement = createElement("span", "title", title);
+    content.appendChild(titleElement);
+    if (label) {
+        var labelElement = createElement("span", "label", label);
+        content.appendChild(labelElement);
+    }
+    entryElement.addEventListener("mouseover", this._handleEntryMouseOver.bind(this), false);
+    return entryElement;
+};
+
+/**
+ * @param {!string} title
+ * @param {!string} actionName
+ * @return {!Element}
+ */
+SuggestionPicker.prototype._createActionEntryElement = function(title, actionName) {
+    var entryElement = createElement("li", SuggestionPicker.ListEntryClass);
+    entryElement.tabIndex = 0;
+    entryElement.dataset.action = actionName;
+    var content = createElement("span", "content");
+    entryElement.appendChild(content);
+    var titleElement = createElement("span", "title", title);
+    content.appendChild(titleElement);
+    entryElement.addEventListener("mouseover", this._handleEntryMouseOver.bind(this), false);
+    return entryElement;
+};
+
+/**
+* @return {!number}
+*/
+SuggestionPicker.prototype._measureMaxContentWidth = function() {
+    // To measure the required width, we first set the class to "measuring-width" which
+    // left aligns all the content including label.
+    this._containerElement.classList.add("measuring-width");
+    var maxContentWidth = 0;
+    if (typeof this._config.inputWidth === "number")
+        maxContentWidth = this._config.inputWidth;
+    var contentElements = this._containerElement.getElementsByClassName("content");
+    for (var i=0; i < contentElements.length; ++i) {
+        maxContentWidth = Math.max(maxContentWidth, contentElements[i].offsetWidth);
+    }
+    this._containerElement.classList.remove("measuring-width");
+    return maxContentWidth;
+};
+
+SuggestionPicker.prototype._fixWindowSize = function() {
+    var ListBorder = 2;
+    var desiredWindowWidth = this._measureMaxContentWidth() + ListBorder;
+    var totalHeight = ListBorder;
+    var maxHeight = 0;
+    var entryCount = 0;
+    for (var i = 0; i < this._containerElement.childNodes.length; ++i) {
+        var node = this._containerElement.childNodes[i];
+        if (node.classList.contains(SuggestionPicker.ListEntryClass))
+            entryCount++;
+        totalHeight += node.offsetHeight;
+        if (maxHeight === 0 && entryCount == SuggestionPicker.NumberOfVisibleEntries)
+            maxHeight = totalHeight;
+    }
+    var desiredWindowHeight = totalHeight;
+    if (maxHeight !== 0 && totalHeight > maxHeight) {
+        this._containerElement.style.maxHeight = (maxHeight - ListBorder) + "px";
+        desiredWindowWidth += getScrollbarWidth();
+        desiredWindowHeight = maxHeight;
+    }
+
+    resizeWindow(desiredWindowWidth, desiredWindowHeight);
+};
+
+SuggestionPicker.prototype._layout = function() {
+    this._containerElement = createElement("ul", "suggestion-list");
+    this._containerElement.addEventListener("click", this._handleEntryClick.bind(this), false);
+    for (var i = 0; i < this._config.suggestionValues.length; ++i) {
+        this._containerElement.appendChild(this._createSuggestionEntryElement(this._config.localizedSuggestionValues[i], this._config.suggestionLabels[i], this._config.suggestionValues[i]));
+    }
+    if (this._config.showOtherDateEntry) {
+        // Add separator
+        var separator = createElement("div", "separator");
+        this._containerElement.appendChild(separator);
+
+        // Add "Other..." entry
+        var otherEntry = this._createActionEntryElement(this._config.otherDateLabel, SuggestionPicker.ActionNames.OpenCalendarPicker);
+        this._containerElement.appendChild(otherEntry);
+    }
+    this._element.appendChild(this._containerElement);
+};
+
+/**
+ * @param {!Element} entry
+ */
+SuggestionPicker.prototype.selectEntry = function(entry) {
+    if (typeof entry.dataset.value !== "undefined") {
+        this.submitValue(entry.dataset.value);
+    } else if (entry.dataset.action === SuggestionPicker.ActionNames.OpenCalendarPicker) {
+        openCalendarPicker();
+    }
+};
+
+/**
+ * @param {!Event} event
+ */
+SuggestionPicker.prototype._handleEntryClick = function(event) {
+    var entry = enclosingNodeOrSelfWithClass(event.target, SuggestionPicker.ListEntryClass);
+    if (!entry)
+        return;
+    this.selectEntry(entry);
+    event.preventDefault();
+};
+
+/**
+ * @return {?Element}
+ */
+SuggestionPicker.prototype._findFirstVisibleEntry = function() {
+    var scrollTop = this._containerElement.scrollTop;
+    var childNodes = this._containerElement.childNodes;
+    for (var i = 0; i < childNodes.length; ++i) {
+        var node = childNodes[i];
+        if (node.nodeType !== Node.ELEMENT_NODE || !node.classList.contains(SuggestionPicker.ListEntryClass))
+            continue;
+        if (node.offsetTop + node.offsetHeight - scrollTop > SuggestionPicker.VisibleEntryThresholdHeight)
+            return node;
+    }
+    return null;
+};
+
+/**
+ * @return {?Element}
+ */
+SuggestionPicker.prototype._findLastVisibleEntry = function() {
+    var scrollBottom = this._containerElement.scrollTop + this._containerElement.offsetHeight;
+    var childNodes = this._containerElement.childNodes;
+    for (var i = childNodes.length - 1; i >= 0; --i){
+        var node = childNodes[i];
+        if (node.nodeType !== Node.ELEMENT_NODE || !node.classList.contains(SuggestionPicker.ListEntryClass))
+            continue;
+        if (scrollBottom - node.offsetTop > SuggestionPicker.VisibleEntryThresholdHeight)
+            return node;
+    }
+    return null;
+};
+
+/**
+ * @param {!Event} event
+ */
+SuggestionPicker.prototype._handleBodyKeyDown = function(event) {
+    var eventHandled = false;
+    var key = event.keyIdentifier;
+    if (key === "U+001B") { // ESC
+        this.handleCancel();
+        eventHandled = true;
+    } else if (key == "Up") {
+        if (document.activeElement && document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) {
+            for (var node = document.activeElement.previousElementSibling; node; node = node.previousElementSibling) {
+                if (node.classList.contains(SuggestionPicker.ListEntryClass)) {
+                    this._isFocusByMouse = false;
+                    node.focus();
+                    break;
+                }
+            }
+        } else {
+            this._element.querySelector("." + SuggestionPicker.ListEntryClass + ":last-child").focus();
+        }
+        eventHandled = true;
+    } else if (key == "Down") {
+        if (document.activeElement && document.activeElement.classList.contains(SuggestionPicker.ListEntryClass)) {
+            for (var node = document.activeElement.nextElementSibling; node; node = node.nextElementSibling) {
+                if (node.classList.contains(SuggestionPicker.ListEntryClass)) {
+                    this._isFocusByMouse = false;
+                    node.focus();
+                    break;
+                }
+            }
+        } else {
+            this._element.querySelector("." + SuggestionPicker.ListEntryClass + ":first-child").focus();
+        }
+        eventHandled = true;
+    } else if (key === "Enter") {
+        this.selectEntry(document.activeElement);
+        eventHandled = true;
+    } else if (key === "PageUp") {
+        this._containerElement.scrollTop -= this._containerElement.clientHeight;
+        // Scrolling causes mouseover event to be called and that tries to move the focus too.
+        // To prevent flickering we won't focus if the current focus was caused by the mouse.
+        if (!this._isFocusByMouse)
+            this._findFirstVisibleEntry().focus();
+        eventHandled = true;
+    } else if (key === "PageDown") {
+        this._containerElement.scrollTop += this._containerElement.clientHeight;
+        if (!this._isFocusByMouse)
+            this._findLastVisibleEntry().focus();
+        eventHandled = true;
+    }
+    if (eventHandled)
+        event.preventDefault();
+};
+
+/**
+ * @param {!Event} event
+ */
+SuggestionPicker.prototype._handleEntryMouseOver = function(event) {
+    var entry = enclosingNodeOrSelfWithClass(event.target, SuggestionPicker.ListEntryClass);
+    if (!entry)
+        return;
+    this._isFocusByMouse = true;
+    entry.focus();
+    event.preventDefault();
+};
+
+/**
+ * @param {!Event} event
+ */
+SuggestionPicker.prototype._handleMouseOut = function(event) {
+    if (!document.activeElement.classList.contains(SuggestionPicker.ListEntryClass))
+        return;
+    this._isFocusByMouse = false;
+    document.activeElement.blur();
+    event.preventDefault();
+};
index c663b28..d9a5591 100644 (file)
           'inputs': [
             '../Resources/pagepopups/calendarPicker.css',
             '../Resources/pagepopups/calendarPicker.js',
+            '../Resources/pagepopups/suggestionPicker.css',
+            '../Resources/pagepopups/suggestionPicker.js',
           ],
           'outputs': [
             '<(SHARED_INTERMEDIATE_DIR)/webkit/CalendarPicker.h',
index 771f147..ea34ae0 100644 (file)
@@ -1,3 +1,13 @@
+2012-09-23  Keishi Hattori  <keishi@webkit.org>
+
+        Add suggestionPicker to CalendarPicker
+        https://bugs.webkit.org/show_bug.cgi?id=97201
+
+        Reviewed by Kent Tamura.
+
+        * src/DateTimeChooserImpl.cpp:
+        (WebKit::DateTimeChooserImpl::writeDocument): Add js/css for SuggestionPicker to page picker. Add highlight color to args.
+
 2012-09-21  Kenichi Ishibashi  <bashi@chromium.org>
 
         [Chromium] Use OpenTypeVerticalData on Linux
index fc3f6cc..4e2c69e 100644 (file)
@@ -88,6 +88,7 @@ void DateTimeChooserImpl::writeDocument(WebCore::DocumentWriter& writer)
 
     addString("<!DOCTYPE html><head><meta charset='UTF-8'><style>\n", writer);
     writer.addData(WebCore::pickerCommonCss, sizeof(WebCore::pickerCommonCss));
+    writer.addData(WebCore::suggestionPickerCss, sizeof(WebCore::suggestionPickerCss));
     writer.addData(WebCore::calendarPickerCss, sizeof(WebCore::calendarPickerCss));
     CString extraStyle = WebCore::RenderTheme::defaultTheme()->extraCalendarPickerStyleSheet();
     if (extraStyle.length())
@@ -114,10 +115,13 @@ void DateTimeChooserImpl::writeDocument(WebCore::DocumentWriter& writer)
         addProperty("suggestionLabels", m_parameters.suggestionLabels, writer);
         addProperty("showOtherDateEntry", m_parameters.type == WebCore::InputTypeNames::date(), writer);
         addProperty("otherDateLabel", Platform::current()->queryLocalizedString(WebLocalizedString::OtherDateLabel), writer);
+        addProperty("suggestionHighlightColor", WebCore::RenderTheme::defaultTheme()->activeListBoxSelectionBackgroundColor().serialized(), writer);
+        addProperty("suggestionHighlightTextColor", WebCore::RenderTheme::defaultTheme()->activeListBoxSelectionForegroundColor().serialized(), writer);
     }
     addString("}\n", writer);
 
     writer.addData(WebCore::pickerCommonJs, sizeof(WebCore::pickerCommonJs));
+    writer.addData(WebCore::suggestionPickerJs, sizeof(WebCore::suggestionPickerJs));
     writer.addData(WebCore::calendarPickerJs, sizeof(WebCore::calendarPickerJs));
     addString("</script></body>\n", writer);
 }