Implement DataSource.setFilterScript
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/.settings/org.moreunit.prefs b/bundles/org.eclipse.rap.addons.autosuggest/.settings/org.moreunit.prefs
index f561a5b..42757bc 100644
--- a/bundles/org.eclipse.rap.addons.autosuggest/.settings/org.moreunit.prefs
+++ b/bundles/org.eclipse.rap.addons.autosuggest/.settings/org.moreunit.prefs
@@ -1,5 +1,5 @@
 eclipse.preferences.version=1
 org.moreunit.preferences.version=2
 org.moreunit.testClassNameTemplate=${srcFile}_Test
-org.moreunit.unitsourcefolder=org.eclipse.rap.addons.dropdown\:src\:org.eclipse.rap.addons.dropdown.test\:src
+org.moreunit.unitsourcefolder=org.eclipse.rap.addons.autosuggest\:src\:org.eclipse.rap.addons.autosuggest.test\:src
 org.moreunit.useprojectsettings=true
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/DataSource.java b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/DataSource.java
index 7e3b7a2..c933b2c 100644
--- a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/DataSource.java
+++ b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/DataSource.java
@@ -40,6 +40,10 @@
     setInitialData();
   }
 
+  public void setFilterScript( String script ) {
+    remoteObject.set( "filterScript", script );
+  }
+
   private void setInitialData() {
     JsonArray array = new JsonArray();
     for( Object element : dataProvider.getSuggestions() ) {
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/AutoSuggest.js b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/AutoSuggest.js
index 686c94b..e3d38bc 100644
--- a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/AutoSuggest.js
+++ b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/AutoSuggest.js
@@ -120,8 +120,9 @@
   fetchSuggestions.apply( this );
   var userText = this.get( "userText" ) || "";
   this.set( "replacementText", null, { "action" : "sync" } );
+  var filter = getFilter.apply( this );
   var filterWrapper = function( suggestion ) {
-    return defaultFilter( suggestion, userText );
+    return filter( suggestion, userText );
   }
   var currentSuggestions = filterArray( this.get( "suggestions" ), filterWrapper );
   this.set( "currentSuggestions", currentSuggestions, { "action" : options.action } );
@@ -129,8 +130,8 @@
 
 function fetchSuggestions() {
   if( this.get( "suggestions" ) == null ) {
-    if( this.get( "dataSourceId" ) != null ) {
-      var dataSource = rap.getObject( this.get( "dataSourceId" ) );
+    var dataSource = getDataSource.apply( this );
+    if( dataSource != null ) {
       this.set( "suggestions", dataSource.get( "data" ) );
     } else {
       this.set( "suggestions", [] );
@@ -138,6 +139,31 @@
   }
 }
 
+function getFilter() {
+  var filter = this.get( "filter" );
+  if( filter == null ) {
+    var dataSource = getDataSource.apply( this );
+    if( dataSource != null && dataSource.get( "filterScript" ) != null ) {
+      try {
+        filter = secureEval( "var result = " + dataSource.get( "filterScript" ) + "; result;" );
+      } catch( ex ) {
+        throw new Error( "AutoSuggest could not eval filter function: " + ex.message );
+      }
+    } else {
+      filter = defaultFilter;
+    }
+    this.set( "filter", filter );
+  }
+  return filter;
+}
+
+function getDataSource() {
+  if( this.get( "dataSourceId" ) != null ) {
+    return rap.getObject( this.get( "dataSourceId" ) );
+  }
+  return null;
+}
+
 ////////////////////////
 // Helper - autoComplete
 
@@ -216,3 +242,8 @@
   return result;
 }
 
+function secureEval() {
+  // TODO : protect against global var access
+  return eval( arguments[ 0 ] );
+}
+
diff --git a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/AutoSuggestSnippet.java b/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/AutoSuggestSnippet.java
index 4f7a048..d88743f 100644
--- a/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/AutoSuggestSnippet.java
+++ b/examples/org.eclipse.rap.addons.dropdown.demo/src/org/eclipse/rap/addons/dropdown/demo/AutoSuggestSnippet.java
@@ -28,6 +28,11 @@
     AutoSuggest autoSuggest = new AutoSuggest( text );
     autoSuggest.setAutoComplete( true );
     DataSource dataSource = new DataSource();
+    dataSource.setFilterScript(
+        "functfsdfion( suggestion, userText ) { "
+      + "  return suggestion.indexOf( userText ) !== -1;"
+      + "}"
+    );
     dataSource.setDataProvider( new ArrayDataProvider( "foo", "food", "foobar", "bar" ) );
     autoSuggest.setDataSource( dataSource );
     autoSuggest.addSelectionListener( new SuggestionSelectedListener() {
diff --git a/tests/org.eclipse.rap.addons.autosuggest.test/jasmine/jasmine/specs/AutoSuggestSpec.js b/tests/org.eclipse.rap.addons.autosuggest.test/jasmine/jasmine/specs/AutoSuggestSpec.js
index 863669a..db08e6b 100644
--- a/tests/org.eclipse.rap.addons.autosuggest.test/jasmine/jasmine/specs/AutoSuggestSpec.js
+++ b/tests/org.eclipse.rap.addons.autosuggest.test/jasmine/jasmine/specs/AutoSuggestSpec.js
@@ -31,6 +31,27 @@
     return result;
   };
 
+  describe( "secureEval", function() {
+
+    var secureEval;
+
+    beforeEach( function() {
+      if( !secureEval ) {
+        secureEval = getVarFromScript( "AutoSuggest", "secureEval" );
+      }
+    } );
+
+    it( "evals code", function() {
+      expect( secureEval( "1+2;" ) ).toBe( 3 );
+    } );
+
+    it( "can not access local variables", function() {
+      var foo = 1;
+      expect( secureEval( "typeof foo;" ) ).toBe( "undefined" );
+    } );
+
+  } );
+
   describe( "commonText", function() {
 
     var commonText;
@@ -218,6 +239,59 @@
         expect( log[ 0 ][ 0 ].options.action ).toBe( "foo" );
       } );
 
+      it( "uses custom filter from dataSource if present", function() {
+        model.addListener( "change:userText", createClientListener( "AutoSuggest" ) );
+        model.set( "suggestions", null );
+        var dataSource = rap.typeHandler[ "rwt.remote.Model" ].factory();
+        dataSource.set( "data", [ "foo", "bar" ] );
+        spyOn( rap, "getObject" ).andReturn( dataSource );
+        model.set( "dataSourceId", "fooId" );
+        dataSource.set( "filterScript",
+            "function( suggestion, userText ) { "
+          + "  return suggestion.indexOf( userText ) !== -1;"
+          + "}"
+        );
+
+        model.set( "userText", "a" );
+
+        expect( model.get( "currentSuggestions" ) ).toEqual( [ "bar" ] );
+      } );
+
+      it( "caches evaluated filter function", function() {
+        model.addListener( "change:userText", createClientListener( "AutoSuggest" ) );
+        model.set( "suggestions", null );
+        var dataSource = rap.typeHandler[ "rwt.remote.Model" ].factory();
+        dataSource.set( "data", [ "foo" ] );
+        spyOn( rap, "getObject" ).andReturn( dataSource );
+        model.set( "dataSourceId", "fooId" );
+        dataSource.set( "filterScript", "function() { return false; }" );
+        model.set( "userText", "a" );
+
+        dataSource.set( "filterScript", "function() { return true; }" );
+        model.set( "userText", "b" );
+
+        expect( model.get( "currentSuggestions" ) ).toEqual( [] );
+      } );
+
+      it( "throws custom exception when filterScript not parse", function() {
+        model.addListener( "change:userText", createClientListener( "AutoSuggest" ) );
+        model.set( "suggestions", null );
+        var dataSource = rap.typeHandler[ "rwt.remote.Model" ].factory();
+        dataSource.set( "data", [ "foo", "bar" ] );
+        spyOn( rap, "getObject" ).andReturn( dataSource );
+        model.set( "dataSourceId", "fooId" );
+        dataSource.set( "filterScript", "funasdfction() { }" );
+        var error;
+
+        try {
+          model.set( "userText", "a" );
+        } catch( ex ) {
+          error = ex;
+        }
+
+        expect( error.message ).toContain( "AutoSuggest" );
+      } );
+
     } );
 
     describe( "change:suggestions", function() {
diff --git a/tests/org.eclipse.rap.addons.autosuggest.test/src/org/eclipse/rap/addons/autosuggest/DataSource_Test.java b/tests/org.eclipse.rap.addons.autosuggest.test/src/org/eclipse/rap/addons/autosuggest/DataSource_Test.java
index 9848b74..99a9437 100644
--- a/tests/org.eclipse.rap.addons.autosuggest.test/src/org/eclipse/rap/addons/autosuggest/DataSource_Test.java
+++ b/tests/org.eclipse.rap.addons.autosuggest.test/src/org/eclipse/rap/addons/autosuggest/DataSource_Test.java
@@ -79,4 +79,13 @@
     verify( remoteObject ).set( eq( "data" ), eq( array ) );
   }
 
+  @Test
+  public void testSetFilterScript_setsFilterScriptOnRemoteObject() {
+    DataSource dataSource = new DataSource();
+
+    dataSource.setFilterScript( "foobar" );
+
+    verify( remoteObject ).set( eq( "filterScript" ), eq( "foobar" ) );
+  }
+
 }