You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@struts.apache.org by he...@apache.org on 2006/12/01 17:47:26 UTC

svn commit: r481288 - in /struts/struts2/trunk: apps/showcase/src/main/java/org/apache/struts2/showcase/ajax/ apps/showcase/src/main/webapp/ajax/ core/src/main/resources/org/apache/struts2/static/dojo/struts/widget/

Author: hermanns
Date: Fri Dec  1 08:47:25 2006
New Revision: 481288

URL: http://svn.apache.org/viewvc?view=rev&rev=481288
Log:
Autocompleter problems
o Added missing files

Issue Number: WW-1529
Submitted by: Musachy Barroso

Added:
    struts/struts2/trunk/apps/showcase/src/main/java/org/apache/struts2/showcase/ajax/AutocompleterExampleAction.java
    struts/struts2/trunk/apps/showcase/src/main/webapp/ajax/options.ftl
    struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/struts/widget/ComboBox.js

Added: struts/struts2/trunk/apps/showcase/src/main/java/org/apache/struts2/showcase/ajax/AutocompleterExampleAction.java
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/apps/showcase/src/main/java/org/apache/struts2/showcase/ajax/AutocompleterExampleAction.java?view=auto&rev=481288
==============================================================================
--- struts/struts2/trunk/apps/showcase/src/main/java/org/apache/struts2/showcase/ajax/AutocompleterExampleAction.java (added)
+++ struts/struts2/trunk/apps/showcase/src/main/java/org/apache/struts2/showcase/ajax/AutocompleterExampleAction.java Fri Dec  1 08:47:25 2006
@@ -0,0 +1,39 @@
+package org.apache.struts2.showcase.ajax;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import com.opensymphony.xwork2.ActionSupport;
+
+public class AutocompleterExampleAction extends ActionSupport {
+  private String select;
+  private List<String> options = new ArrayList<String>();
+
+  private static final long serialVersionUID = -8481638176160014396L;
+
+  public String execute() throws Exception {
+    if ("fruits".equals(select)) {
+      options.add("apple");
+      options.add("banana");
+      options.add("grape");
+      options.add("pear");
+    } else if ("colors".equals(select)) {
+      options.add("red");
+      options.add("green");
+      options.add("blue");
+    }
+    return SUCCESS;
+  }
+
+  public String getSelect() {
+    return select;
+  }
+
+  public void setSelect(String select) {
+    this.select = select;
+  }
+
+  public List<String> getOptions() {
+    return options;
+  }
+}

Added: struts/struts2/trunk/apps/showcase/src/main/webapp/ajax/options.ftl
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/apps/showcase/src/main/webapp/ajax/options.ftl?view=auto&rev=481288
==============================================================================
--- struts/struts2/trunk/apps/showcase/src/main/webapp/ajax/options.ftl (added)
+++ struts/struts2/trunk/apps/showcase/src/main/webapp/ajax/options.ftl Fri Dec  1 08:47:25 2006
@@ -0,0 +1,5 @@
+[
+<#list options as option>
+	["${option}"],
+</#list>
+]
\ No newline at end of file

Added: struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/struts/widget/ComboBox.js
URL: http://svn.apache.org/viewvc/struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/struts/widget/ComboBox.js?view=auto&rev=481288
==============================================================================
--- struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/struts/widget/ComboBox.js (added)
+++ struts/struts2/trunk/core/src/main/resources/org/apache/struts2/static/dojo/struts/widget/ComboBox.js Fri Dec  1 08:47:25 2006
@@ -0,0 +1,586 @@
+dojo.provide("struts.widget.ComboBox");
+
+dojo.require("dojo.html.*");
+dojo.require("dojo.widget.ComboBox");
+
+struts.widget.ComboBoxDataProvider = function(/*Array*/ dataPairs, /*Number*/ limit, /*Number*/ timeout){
+  // NOTE: this data provider is designed as a naive reference
+  // implementation, and as such it is written more for readability than
+  // speed. A deployable data provider would implement lookups, search
+  // caching (and invalidation), and a significantly less naive data
+  // structure for storage of items.
+
+  this.data = [];
+  this.searchTimeout = timeout || 500;
+  this.searchLimit = limit || 30;
+  this.searchType = "STARTSTRING"; // may also be "STARTWORD" or "SUBSTRING"
+  this.caseSensitive = false;
+  // for caching optimizations
+  this._lastSearch = "";
+  this._lastSearchResults = null;
+
+  this.beforeLoading = "";
+  this.afterLoading = "";
+
+  this.formId = "";
+  this.formFilter = "";
+
+  this.init = function(/*Widget*/ cbox, /*DomNode*/ node){
+    this.beforeLoading = cbox.beforeLoading;
+    this.afterLoading = cbox.afterLoading;
+
+    this.formId = cbox.formId;
+    this.formFilter = cbox.formFilter;
+
+    if(!dojo.string.isBlank(cbox.dataUrl)){
+      this.getData(cbox.dataUrl);
+    }else{
+      // check to see if we can populate the list from <option> elements
+      if((node)&&(node.nodeName.toLowerCase() == "select")){
+        // NOTE: we're not handling <optgroup> here yet
+        var opts = node.getElementsByTagName("option");
+        var ol = opts.length;
+        var data = [];
+        for(var x=0; x<ol; x++){
+          var text = opts[x].textContent || opts[x].innerText || opts[x].innerHTML;
+          var keyValArr = [String(text), String(opts[x].value)];
+          data.push(keyValArr);
+          if(opts[x].selected){
+            cbox.setAllValues(keyValArr[0], keyValArr[1]);
+          }
+        }
+        this.setData(data);
+      }
+    }
+  };
+
+  this.getData = function(/*String*/ url){
+    if(!dojo.string.isBlank(this.beforeLoading)) {
+      eval(this.beforeLoading);
+    }
+
+    dojo.io.bind({
+      url: url,
+      formNode: dojo.byId(this.formId),
+      formFilter: window[this.formFilter],
+      load: dojo.lang.hitch(this, function(type, data, evt){
+        if(!dojo.string.isBlank(this.afterLoading)) {
+          eval(this.afterLoading);
+        }
+        if(!dojo.lang.isArray(data)){
+          var arrData = [];
+          for(var key in data){
+            arrData.push([data[key], key]);
+          }
+          data = arrData;
+        }
+        this.setData(data);
+      }),
+      mimetype: "text/json"
+    });
+  };
+
+  this.startSearch = function(/*String*/ searchStr, /*String*/ type, /*Boolean*/ ignoreLimit){
+    // FIXME: need to add timeout handling here!!
+    this._preformSearch(searchStr, type, ignoreLimit);
+  };
+
+  this._preformSearch = function(/*String*/ searchStr, /*String*/ type, /*Boolean*/ ignoreLimit){
+    //
+    //  NOTE: this search is LINEAR, which means that it exhibits perhaps
+    //  the worst possible speed characteristics of any search type. It's
+    //  written this way to outline the responsibilities and interfaces for
+    //  a search.
+    //
+    var st = type||this.searchType;
+    // FIXME: this is just an example search, which means that we implement
+    // only a linear search without any of the attendant (useful!) optimizations
+    var ret = [];
+    if(!this.caseSensitive){
+      searchStr = searchStr.toLowerCase();
+    }
+    for(var x=0; x<this.data.length; x++){
+      if((!ignoreLimit)&&(ret.length >= this.searchLimit)){
+        break;
+      }
+      // FIXME: we should avoid copies if possible!
+      var dataLabel = new String((!this.caseSensitive) ? this.data[x][0].toLowerCase() : this.data[x][0]);
+      if(dataLabel.length < searchStr.length){
+        // this won't ever be a good search, will it? What if we start
+        // to support regex search?
+        continue;
+      }
+
+      if(st == "STARTSTRING"){
+        if(searchStr == dataLabel.substr(0, searchStr.length)){
+          ret.push(this.data[x]);
+        }
+      }else if(st == "SUBSTRING"){
+        // this one is a gimmie
+        if(dataLabel.indexOf(searchStr) >= 0){
+          ret.push(this.data[x]);
+        }
+      }else if(st == "STARTWORD"){
+        // do a substring search and then attempt to determine if the
+        // preceeding char was the beginning of the string or a
+        // whitespace char.
+        var idx = dataLabel.indexOf(searchStr);
+        if(idx == 0){
+          // implicit match
+          ret.push(this.data[x]);
+        }
+        if(idx <= 0){
+          // if we didn't match or implicily matched, march onward
+          continue;
+        }
+        // otherwise, we have to go figure out if the match was at the
+        // start of a word...
+        // this code is taken almost directy from nWidgets
+        var matches = false;
+        while(idx!=-1){
+          // make sure the match either starts whole string, or
+          // follows a space, or follows some punctuation
+          if(" ,/(".indexOf(dataLabel.charAt(idx-1)) != -1){
+            // FIXME: what about tab chars?
+            matches = true; break;
+          }
+          idx = dataLabel.indexOf(searchStr, idx+1);
+        }
+        if(!matches){
+          continue;
+        }else{
+          ret.push(this.data[x]);
+        }
+      }
+    }
+    this.provideSearchResults(ret);
+  };
+
+  this.provideSearchResults = function(/*Array*/ resultsDataPairs){
+  };
+
+  this.addData = function(/*Array*/ pairs){
+    // FIXME: incredibly naive and slow!
+    this.data = this.data.concat(pairs);
+  };
+
+  this.setData = function(/*Array*/ pdata){
+    // populate this.data and initialize lookup structures
+    this.data = pdata;
+  };
+
+  if(dataPairs){
+    this.setData(dataPairs);
+  }
+};
+
+dojo.widget.defineWidget(
+  "struts.widget.ComboBox",
+  dojo.widget.ComboBox, {
+  widgetType : "ComboBox",
+
+  dropdownHeight: 120,
+  dropdownWidth: 0,
+  itemHeight: 0,
+
+  refreshListenTopic : "",
+  onValueChangedPublishTopic : "",
+
+  //callbacks
+  beforeLoading : "",
+  afterLoading : "",
+
+  formId : "",
+  formFilter : "",
+  dataProviderClass: "struts.widget.ComboBoxDataProvider",
+  //from Dojo's  ComboBox
+  showResultList: function() {
+  // Our dear friend IE doesnt take max-height so we need to calculate that on our own every time
+    var childs = this.optionsListNode.childNodes;
+    if(childs.length){
+
+      this.optionsListNode.style.width = this.dropdownWidth === 0 ? (dojo.html.getMarginBox(this.domNode).width-2)+"px" : this.dropdownWidth + "px";
+
+      if(this.itemHeight === 0 || dojo.string.isBlank(this.textInputNode.value)) {
+        this.optionsListNode.style.height = this.dropdownHeight + "px";
+        this.optionsListNode.style.display = "";
+        this.itemHeight = dojo.html.getMarginBox(childs[0]).height;
+      }
+
+      //if there is extra space, adjust height
+      var totalHeight = this.itemHeight * childs.length;
+      if(totalHeight < this.dropdownHeight) {
+        this.optionsListNode.style.height = totalHeight + 2 + "px";
+      }
+
+      this.popupWidget.open(this.domNode, this, this.downArrowNode);
+    } else {
+        this.hideResultList();
+    }
+  },
+
+  openResultList: function(/*Array*/ results){
+    if (!this.isEnabled){
+        return;
+    }
+    this.clearResultList();
+    if(!results.length){
+        this.hideResultList();
+    }
+
+    if( (this.autoComplete)&&
+        (results.length)&&
+        (!this._prev_key_backspace)&&
+        (this.textInputNode.value.length > 0)){
+        var cpos = this.getCaretPos(this.textInputNode);
+        // only try to extend if we added the last character at the end of the input
+        if((cpos+1) > this.textInputNode.value.length){
+            // only add to input node as we would overwrite Capitalisation of chars
+            this.textInputNode.value += results[0][0].substr(cpos);
+            // build a new range that has the distance from the earlier
+            // caret position to the end of the first string selected
+            this.setSelectedRange(this.textInputNode, cpos, this.textInputNode.value.length);
+        }
+    }
+    var typedText = this.textInputNode.value;
+    var even = true;
+    while(results.length){
+        var tr = results.shift();
+        if(tr){
+            var td = document.createElement("div");
+            var text = tr[0];
+            var i = text.toLowerCase().indexOf(typedText.toLowerCase());
+            if(i >= 0) {
+                var pre = text.substring(0, i);
+                var matched = text.substring(i, typedText.length);
+                var post = text.substring(i + typedText.length);
+
+                td.appendChild(document.createTextNode(pre));
+                var boldNode = document.createElement("b");
+                td.appendChild(boldNode);
+                boldNode.appendChild(document.createTextNode(matched));
+                td.appendChild(document.createTextNode(post));
+            } else {
+                td.appendChild(document.createTextNode(tr[0]));
+            }
+
+            td.setAttribute("resultName", tr[0]);
+            td.setAttribute("resultValue", tr[1]);
+            td.className = "dojoComboBoxItem "+((even) ? "dojoComboBoxItemEven" : "dojoComboBoxItemOdd");
+            even = (!even);
+            this.optionsListNode.appendChild(td);
+        }
+    }
+
+    // show our list (only if we have content, else nothing)
+    this.showResultList();
+  },
+
+  postCreate : function() {
+    struts.widget.ComboBox.superclass.postCreate.apply(this);
+
+    //events
+    if(!dojo.string.isBlank(this.refreshListenTopic)) {
+      var self = this;
+      dojo.event.topic.subscribe(this.refreshListenTopic, function() {
+        self.dataProvider.getData(self.dataUrl);
+      });
+    }
+    if(!dojo.string.isBlank(this.onValueChangedPublishTopic)) {
+      dojo.event.topic.registerPublisher(this.onValueChangedPublishTopic, this, "onValueChanged");
+    }
+  }
+});
+dojo.provide("struts.widget.ComboBox");
+
+dojo.require("dojo.html.*");
+dojo.require("dojo.widget.ComboBox");
+
+struts.widget.ComboBoxDataProvider = function(/*Array*/ dataPairs, /*Number*/ limit, /*Number*/ timeout){
+  // NOTE: this data provider is designed as a naive reference
+  // implementation, and as such it is written more for readability than
+  // speed. A deployable data provider would implement lookups, search
+  // caching (and invalidation), and a significantly less naive data
+  // structure for storage of items.
+
+  this.data = [];
+  this.searchTimeout = timeout || 500;
+  this.searchLimit = limit || 30;
+  this.searchType = "STARTSTRING"; // may also be "STARTWORD" or "SUBSTRING"
+  this.caseSensitive = false;
+  // for caching optimizations
+  this._lastSearch = "";
+  this._lastSearchResults = null;
+
+  this.beforeLoading = "";
+  this.afterLoading = "";
+
+  this.formId = "";
+  this.formFilter = "";
+
+  this.init = function(/*Widget*/ cbox, /*DomNode*/ node){
+    this.beforeLoading = cbox.beforeLoading;
+    this.afterLoading = cbox.afterLoading;
+
+    this.formId = cbox.formId;
+    this.formFilter = cbox.formFilter;
+
+    if(!dojo.string.isBlank(cbox.dataUrl)){
+      this.getData(cbox.dataUrl);
+    }else{
+      // check to see if we can populate the list from <option> elements
+      if((node)&&(node.nodeName.toLowerCase() == "select")){
+        // NOTE: we're not handling <optgroup> here yet
+        var opts = node.getElementsByTagName("option");
+        var ol = opts.length;
+        var data = [];
+        for(var x=0; x<ol; x++){
+          var text = opts[x].textContent || opts[x].innerText || opts[x].innerHTML;
+          var keyValArr = [String(text), String(opts[x].value)];
+          data.push(keyValArr);
+          if(opts[x].selected){
+            cbox.setAllValues(keyValArr[0], keyValArr[1]);
+          }
+        }
+        this.setData(data);
+      }
+    }
+  };
+
+  this.getData = function(/*String*/ url){
+    if(!dojo.string.isBlank(this.beforeLoading)) {
+      eval(this.beforeLoading);
+    }
+
+    dojo.io.bind({
+      url: url,
+      formNode: dojo.byId(this.formId),
+      formFilter: window[this.formFilter],
+      load: dojo.lang.hitch(this, function(type, data, evt){
+        if(!dojo.string.isBlank(this.afterLoading)) {
+          eval(this.afterLoading);
+        }
+        if(!dojo.lang.isArray(data)){
+          var arrData = [];
+          for(var key in data){
+            arrData.push([data[key], key]);
+          }
+          data = arrData;
+        }
+        this.setData(data);
+      }),
+      mimetype: "text/json"
+    });
+  };
+
+  this.startSearch = function(/*String*/ searchStr, /*String*/ type, /*Boolean*/ ignoreLimit){
+    // FIXME: need to add timeout handling here!!
+    this._preformSearch(searchStr, type, ignoreLimit);
+  };
+
+  this._preformSearch = function(/*String*/ searchStr, /*String*/ type, /*Boolean*/ ignoreLimit){
+    //
+    //  NOTE: this search is LINEAR, which means that it exhibits perhaps
+    //  the worst possible speed characteristics of any search type. It's
+    //  written this way to outline the responsibilities and interfaces for
+    //  a search.
+    //
+    var st = type||this.searchType;
+    // FIXME: this is just an example search, which means that we implement
+    // only a linear search without any of the attendant (useful!) optimizations
+    var ret = [];
+    if(!this.caseSensitive){
+      searchStr = searchStr.toLowerCase();
+    }
+    for(var x=0; x<this.data.length; x++){
+      if((!ignoreLimit)&&(ret.length >= this.searchLimit)){
+        break;
+      }
+      // FIXME: we should avoid copies if possible!
+      var dataLabel = new String((!this.caseSensitive) ? this.data[x][0].toLowerCase() : this.data[x][0]);
+      if(dataLabel.length < searchStr.length){
+        // this won't ever be a good search, will it? What if we start
+        // to support regex search?
+        continue;
+      }
+
+      if(st == "STARTSTRING"){
+        if(searchStr == dataLabel.substr(0, searchStr.length)){
+          ret.push(this.data[x]);
+        }
+      }else if(st == "SUBSTRING"){
+        // this one is a gimmie
+        if(dataLabel.indexOf(searchStr) >= 0){
+          ret.push(this.data[x]);
+        }
+      }else if(st == "STARTWORD"){
+        // do a substring search and then attempt to determine if the
+        // preceeding char was the beginning of the string or a
+        // whitespace char.
+        var idx = dataLabel.indexOf(searchStr);
+        if(idx == 0){
+          // implicit match
+          ret.push(this.data[x]);
+        }
+        if(idx <= 0){
+          // if we didn't match or implicily matched, march onward
+          continue;
+        }
+        // otherwise, we have to go figure out if the match was at the
+        // start of a word...
+        // this code is taken almost directy from nWidgets
+        var matches = false;
+        while(idx!=-1){
+          // make sure the match either starts whole string, or
+          // follows a space, or follows some punctuation
+          if(" ,/(".indexOf(dataLabel.charAt(idx-1)) != -1){
+            // FIXME: what about tab chars?
+            matches = true; break;
+          }
+          idx = dataLabel.indexOf(searchStr, idx+1);
+        }
+        if(!matches){
+          continue;
+        }else{
+          ret.push(this.data[x]);
+        }
+      }
+    }
+    this.provideSearchResults(ret);
+  };
+
+  this.provideSearchResults = function(/*Array*/ resultsDataPairs){
+  };
+
+  this.addData = function(/*Array*/ pairs){
+    // FIXME: incredibly naive and slow!
+    this.data = this.data.concat(pairs);
+  };
+
+  this.setData = function(/*Array*/ pdata){
+    // populate this.data and initialize lookup structures
+    this.data = pdata;
+  };
+
+  if(dataPairs){
+    this.setData(dataPairs);
+  }
+};
+
+dojo.widget.defineWidget(
+  "struts.widget.ComboBox",
+  dojo.widget.ComboBox, {
+  widgetType : "ComboBox",
+
+  dropdownHeight: 120,
+  dropdownWidth: 0,
+  itemHeight: 0,
+
+  refreshListenTopic : "",
+  onValueChangedPublishTopic : "",
+
+  //callbacks
+  beforeLoading : "",
+  afterLoading : "",
+
+  formId : "",
+  formFilter : "",
+  dataProviderClass: "struts.widget.ComboBoxDataProvider",
+  //from Dojo's  ComboBox
+  showResultList: function() {
+  // Our dear friend IE doesnt take max-height so we need to calculate that on our own every time
+    var childs = this.optionsListNode.childNodes;
+    if(childs.length){
+
+      this.optionsListNode.style.width = this.dropdownWidth === 0 ? (dojo.html.getMarginBox(this.domNode).width-2)+"px" : this.dropdownWidth + "px";
+
+      if(this.itemHeight === 0 || dojo.string.isBlank(this.textInputNode.value)) {
+        this.optionsListNode.style.height = this.dropdownHeight + "px";
+        this.optionsListNode.style.display = "";
+        this.itemHeight = dojo.html.getMarginBox(childs[0]).height;
+      }
+
+      //if there is extra space, adjust height
+      var totalHeight = this.itemHeight * childs.length;
+      if(totalHeight < this.dropdownHeight) {
+        this.optionsListNode.style.height = totalHeight + 2 + "px";
+      }
+
+      this.popupWidget.open(this.domNode, this, this.downArrowNode);
+    } else {
+        this.hideResultList();
+    }
+  },
+
+  openResultList: function(/*Array*/ results){
+    if (!this.isEnabled){
+        return;
+    }
+    this.clearResultList();
+    if(!results.length){
+        this.hideResultList();
+    }
+
+    if( (this.autoComplete)&&
+        (results.length)&&
+        (!this._prev_key_backspace)&&
+        (this.textInputNode.value.length > 0)){
+        var cpos = this.getCaretPos(this.textInputNode);
+        // only try to extend if we added the last character at the end of the input
+        if((cpos+1) > this.textInputNode.value.length){
+            // only add to input node as we would overwrite Capitalisation of chars
+            this.textInputNode.value += results[0][0].substr(cpos);
+            // build a new range that has the distance from the earlier
+            // caret position to the end of the first string selected
+            this.setSelectedRange(this.textInputNode, cpos, this.textInputNode.value.length);
+        }
+    }
+    var typedText = this.textInputNode.value;
+    var even = true;
+    while(results.length){
+        var tr = results.shift();
+        if(tr){
+            var td = document.createElement("div");
+            var text = tr[0];
+            var i = text.toLowerCase().indexOf(typedText.toLowerCase());
+            if(i >= 0) {
+                var pre = text.substring(0, i);
+                var matched = text.substring(i, typedText.length);
+                var post = text.substring(i + typedText.length);
+
+                td.appendChild(document.createTextNode(pre));
+                var boldNode = document.createElement("b");
+                td.appendChild(boldNode);
+                boldNode.appendChild(document.createTextNode(matched));
+                td.appendChild(document.createTextNode(post));
+            } else {
+                td.appendChild(document.createTextNode(tr[0]));
+            }
+
+            td.setAttribute("resultName", tr[0]);
+            td.setAttribute("resultValue", tr[1]);
+            td.className = "dojoComboBoxItem "+((even) ? "dojoComboBoxItemEven" : "dojoComboBoxItemOdd");
+            even = (!even);
+            this.optionsListNode.appendChild(td);
+        }
+    }
+
+    // show our list (only if we have content, else nothing)
+    this.showResultList();
+  },
+
+  postCreate : function() {
+    struts.widget.ComboBox.superclass.postCreate.apply(this);
+
+    //events
+    if(!dojo.string.isBlank(this.refreshListenTopic)) {
+      var self = this;
+      dojo.event.topic.subscribe(this.refreshListenTopic, function() {
+        self.dataProvider.getData(self.dataUrl);
+      });
+    }
+    if(!dojo.string.isBlank(this.onValueChangedPublishTopic)) {
+      dojo.event.topic.registerPublisher(this.onValueChangedPublishTopic, this, "onValueChanged");
+    }
+  }
+});