Remove DataBinding.js, introduce EventDelegator.js

EventDelegator will forward all relevant events from Text and DropDown
to AutoSuggest.js (ClientModelListener) which will also handle data
synchronization and the logic itself.
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/AutoSuggest.java b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/AutoSuggest.java
index 669e3fb..60c2f20 100644
--- a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/AutoSuggest.java
+++ b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/AutoSuggest.java
@@ -19,7 +19,7 @@
 import org.eclipse.rap.addons.autosuggest.internal.Model;
 import org.eclipse.rap.addons.autosuggest.internal.ModelListener;
 import org.eclipse.rap.addons.autosuggest.internal.resources.AutoSuggestScript;
-import org.eclipse.rap.addons.autosuggest.internal.resources.DataBindingScript;
+import org.eclipse.rap.addons.autosuggest.internal.resources.EventDelegatorScript;
 import org.eclipse.rap.addons.dropdown.DropDown;
 import org.eclipse.rap.clientscripting.ClientListener;
 import org.eclipse.rap.clientscripting.Script;
@@ -150,9 +150,8 @@
 
   protected void attachClientListeners() {
     int[] dropDownEventTypes = new int[] { SWT.Show, SWT.Hide, SWT.Selection, SWT.DefaultSelection };
-    attachClientListenerToDropDown( DataBindingScript.getInstance(), dropDownEventTypes );
-    attachClientListenerToText( DataBindingScript.getInstance(), SWT.Modify, SWT.Verify );
-    attachClientListenerToModel( DataBindingScript.getInstance(), "change" );
+    attachClientListenerToDropDown( EventDelegatorScript.getInstance(), dropDownEventTypes );
+    attachClientListenerToText( EventDelegatorScript.getInstance(), SWT.Modify, SWT.Verify );
     attachClientListenerToModel( AutoSuggestScript.getInstance(), "change", "accept" );
   }
 
@@ -179,6 +178,7 @@
     for( String type : types ) {
       model.addListener( type, clientModelListener );
     }
+    model.set( "autoSuggestListenerId", clientModelListener.getId() );
   }
 
   private void connectClientObjects() {
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/ClientModelListener.java b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/ClientModelListener.java
index 84529b7..f34a44d 100644
--- a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/ClientModelListener.java
+++ b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/ClientModelListener.java
@@ -29,6 +29,10 @@
 
   public void handleEvent( JsonObject argument ) { }
 
+  public String getId() {
+    return getRemoteId();
+  }
+
   void addTo( Model model, String eventType ) {
     final ClientListenerBinding binding = addTo( model.getId(), eventType );
     if( binding != null ) {
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 288a7f1..4ee5315 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
@@ -14,22 +14,51 @@
 ///////////////////
 // Event Delegation
 
+var MODEL_KEY = "org.eclipse.rap.addons.autosuggest#Model";
+
 function handleEvent( event ) {
+  if( event.widget ) {
+    var model = rap.getObject( event.widget.getData( MODEL_KEY ) );
+    if( event.widget.classname === "rwt.dropdown.DropDown" ) {
+      handleDropDownEvent( model, event );
+    } else {
+      handleTextEvent( model, event );
+    }
+  } else {
+    handleModelEvent( event.source, event );
+  }
+}
+
+function handleModelEvent( model, event ) {
+  var textWidget;
+  try {
+    textWidget = rap.getObject( model.get( "textWidgetId" ) );
+  } catch( ex ) {
+    // When Text is disposed, AutoSuggest may perform a set operation before it is also disposed
+    return;
+  }
+  var dropDown = rap.getObject( model.get( "dropDownWidgetId" ) );
   if( event.type === "accept" ) {
-    onAcceptSuggestion.apply( event.source, [ event ] );
+    onAcceptSuggestion.apply( model, [ event ] );
   } else {
     switch( event.property ) {
       case "dataSourceId":
-        onChangeDataSourceId.apply( event.source, [ event ] );
+        onChangeDataSourceId.apply( model, [ event ] );
       break;
       case "suggestions":
-        onChangeSuggestions.apply( event.source, [ event ] );
+        onChangeSuggestions.apply( model, [ event ] );
       break;
       case "userText":
-        onChangeUserText.apply( event.source, [ event ] );
+        onChangeUserText.apply( model, [ event ] );
+      break;
+      case "suggestionsVisible":
+        syncModelSuggestionsVisible.apply( model, [ dropDown, event ] );
       break;
       case "currentSuggestions":
-        onChangeCurrentSuggestions.apply( event.source, [ event ] );
+        onChangeCurrentSuggestions.apply( model, [ event ] );
+      break;
+      case "currentSuggestionTexts":
+        syncModelCurrentSuggestionTexts.apply( model, [ dropDown, event ] );
       break;
       case "selectedSuggestionIndex":
         onChangeSelectedSuggestionIndex.apply( event.source, [ event ] );
@@ -37,10 +66,84 @@
       case "replacementText":
         onChangeReplacementText.apply( event.source, [ event ] );
       break;
+      case "text":
+        syncModelText.apply( model, [ textWidget, event ] );
+      break;
+      case "textSelection":
+        syncModelTextSelection.apply( model, [ textWidget, event ] );
+      break;
     }
   }
 }
 
+function handleDropDownEvent( model, event ) {
+  switch( event.type ) {
+    case SWT.Show:
+    case SWT.Hide:
+      syncDropDownVisible.apply( model, [ event.widget, event ] );
+    break;
+    case SWT.Selection:
+      syncDropDownSelection.apply( model, [ event.widget, event ] );
+    break;
+    case SWT.DefaultSelection:
+      forwardDropDownDefaultSelection.apply( model, [ event.widget, event ] );
+    break;
+  }
+}
+
+function handleTextEvent( model, event ) {
+  var userAction = getUserAction( event );
+  switch( event.type ) {
+    case SWT.Modify:
+      syncTextText.apply( model, [ event.widget, event, userAction ] );
+    break;
+  }
+  setUserAction( event );
+}
+
+////////////////////////////////
+// Synchronize Model <-> Widgets
+
+function syncTextText( textWidget, event, userAction ) {
+  var text = textWidget.getText();
+  this.set( "text", text, { "action" : "sync" } );
+  if( userAction ) {
+    this.set( "userText", text, { "action" : userAction } );
+  }
+}
+
+function syncDropDownVisible( dropDown, event ) {
+  this.set( "suggestionsVisible", dropDown.getVisible(), { "action" : "sync" }  );
+}
+
+function syncModelSuggestionsVisible( dropDown, event ) {
+  if( event.options.action !== "sync" ) {
+    dropDown.setVisible( event.value );
+  }
+}
+
+function syncDropDownSelection( dropDown, event ) {
+  this.set( "selectedSuggestionIndex", event.index, { "action" : "sync" }  );
+}
+
+function forwardDropDownDefaultSelection( dropDown, event ) {
+  this.notify( "accept", { type : "accept", "source" : this }  );
+}
+
+function syncModelCurrentSuggestionTexts( dropDown, event ) {
+  dropDown.setItems( this.get( "currentSuggestionTexts" ) );
+}
+
+function syncModelText( textWidget, event ) {
+  if( event.options.action !== "sync" ) {
+    textWidget.setText( event.value );
+  }
+}
+
+function syncModelTextSelection( textWidget, event ) {
+  textWidget.setSelection( event.value );
+}
+
 //////////////////
 // Event Handling
 
@@ -96,6 +199,7 @@
         this.set( "textSelection", [ 0, text.length ] );
       }
     } else {
+      // TODO : not always working?
       this.set( "textSelection", [ userText.length, text.length ] );
     }
   }
@@ -288,3 +392,17 @@
     return result;
   }
 }
+
+function setUserAction( event ) {
+  if( event.type === SWT.Verify ) {
+    // See Bug 404896 - [ClientScripting] Verify event keyCode is always zero when replacing txt
+    var action = ( event.text !== "" /* && event.keyCode !== 0 */ ) ? "typing" : "deleting";
+    event.widget.setData( "userAction", action );
+  }
+}
+
+function getUserAction( event ) {
+  var action = event.widget.getData( "userAction" );
+  event.widget.setData( "userAction", null );
+  return action;
+}
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/DataBinding.js b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/DataBinding.js
deleted file mode 100644
index 42e84ee..0000000
--- a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/DataBinding.js
+++ /dev/null
@@ -1,138 +0,0 @@
-/*******************************************************************************
- * Copyright (c) 2013 EclipseSource.
- * All rights reserved. This program and the accompanying materials
- * are made available under the terms of the Eclipse Public License v1.0
- * which accompanies this distribution, and is available at
- * http://www.eclipse.org/legal/epl-v10.html
- *
- * Contributors:
- *    EclipseSource - initial API and implementation
- ******************************************************************************/
-
-//@ sourceURL=DataBinding.js
-
-///////////////////
-// Event Delegation
-
-var MODEL_KEY = "org.eclipse.rap.addons.autosuggest#Model";
-
-function handleEvent( event ) {
-  if( event.widget ) {
-    var model = rap.getObject( event.widget.getData( MODEL_KEY ) );
-    if( event.widget.classname === "rwt.dropdown.DropDown" ) {
-      handleDropDownEvent( model, event );
-    } else {
-      handleTextEvent( model, event );
-    }
-  } else {
-    handleModelEvent.apply( this, arguments );
-  }
-}
-
-function handleDropDownEvent( model, event ) {
-  switch( event.type ) {
-    case SWT.Show:
-    case SWT.Hide:
-      onDropDownChangeVisible( model, event );
-    break;
-    case SWT.Selection:
-      onDropDownSelection( model, event );
-    break;
-    case SWT.DefaultSelection:
-      onDropDownDefaultSelection( model, event );
-    break;
-  }
-}
-
-function handleTextEvent( model, event ) {
-  var userAction = getUserAction( event );
-  switch( event.type ) {
-    case SWT.Modify:
-      onTextModify( model, event, userAction );
-    break;
-  }
-  setUserAction( event );
-}
-
-function handleModelEvent( event ) {
-  var model = event.source;
-  var textWidget;
-  try {
-    textWidget = rap.getObject( model.get( "textWidgetId" ) );
-  } catch( ex ) {
-    // When Text is disposed, AutoSuggest may perform a set operation before it is disposed as well
-    return;
-  }
-  var dropDown = rap.getObject( model.get( "dropDownWidgetId" ) );
-  switch( event.property ) {
-    case "text":
-      onModelChangeText( textWidget, model, event );
-    break;
-    case "textSelection":
-      onModelChangeTextSelection( textWidget, model, event );
-    break;
-    case "currentSuggestionTexts":
-      onModelChangeCurrentSuggestionTexts( dropDown, model, event );
-    break;
-    case "suggestionsVisible":
-      onModelChangeSuggestionsVisible( dropDown, model, event );
-    break;
-  }
-}
-
-function setUserAction( event ) {
-  if( event.type === SWT.Verify ) {
-    // See Bug 404896 - [ClientScripting] Verify event keyCode is always zero when replacing txt
-    var action = ( event.text !== "" /* && event.keyCode !== 0 */ ) ? "typing" : "deleting";
-    event.widget.setData( "userAction", action );
-  }
-}
-
-function getUserAction( event ) {
-  var action = event.widget.getData( "userAction" );
-  event.widget.setData( "userAction", null );
-  return action;
-}
-
-/////////////////
-// Event Handling
-
-function onTextModify( model, event, userAction ) {
-  var text = event.widget.getText();
-  model.set( "text", text, { "action" : "sync" } );
-  if( userAction ) {
-    model.set( "userText", text, { "action" : userAction } );
-  }
-}
-
-function onDropDownChangeVisible( model, event ) {
-  model.set( "suggestionsVisible", event.widget.getVisible(), { "action" : "sync" }  );
-}
-
-function onDropDownSelection( model, event ) {
-  model.set( "selectedSuggestionIndex", event.index, { "action" : "sync" }  );
-}
-
-function onDropDownDefaultSelection( model, event ) {
-  model.notify( "accept", { type : "accept", "source" : model }  );
-}
-
-function onModelChangeCurrentSuggestionTexts( dropDown, model, event ) {
-  dropDown.setItems( model.get( "currentSuggestionTexts" ) );
-}
-
-function onModelChangeSuggestionsVisible( dropDown, model, event ) {
-  if( event.options.action !== "sync" ) {
-    dropDown.setVisible( event.value );
-  }
-}
-
-function onModelChangeText( textWidget, model, event ) {
-  if( event.options.action !== "sync" ) {
-    textWidget.setText( event.value );
-  }
-}
-
-function onModelChangeTextSelection( textWidget, model, event ) {
-  textWidget.setSelection( event.value );
-}
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/EventDelegator.js b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/EventDelegator.js
new file mode 100644
index 0000000..ca52007
--- /dev/null
+++ b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/EventDelegator.js
@@ -0,0 +1,24 @@
+/*******************************************************************************
+ * Copyright (c) 2013 EclipseSource.
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *    EclipseSource - initial API and implementation
+ ******************************************************************************/
+
+//@ sourceURL=DataBinding.js
+
+///////////////////
+// Event Delegation
+
+var MODEL_KEY = "org.eclipse.rap.addons.autosuggest#Model";
+
+function handleEvent( event ) {
+  var model = rap.getObject( event.widget.getData( MODEL_KEY ) );
+  var autoSuggestListener
+    = rwt.remote.ObjectRegistry.getObject( model.get( "autoSuggestListenerId" ) );
+  autoSuggestListener.call( event );
+}
diff --git a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/DataBindingScript.java b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/EventDelegatorScript.java
similarity index 78%
rename from bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/DataBindingScript.java
rename to bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/EventDelegatorScript.java
index f92a20d..7267335 100644
--- a/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/DataBindingScript.java
+++ b/bundles/org.eclipse.rap.addons.autosuggest/src/org/eclipse/rap/addons/autosuggest/internal/resources/EventDelegatorScript.java
@@ -14,18 +14,18 @@
 import org.eclipse.rap.rwt.SingletonUtil;
 
 
-public class DataBindingScript extends Script {
+public class EventDelegatorScript extends Script {
 
-  public static DataBindingScript getInstance() {
-    return SingletonUtil.getSessionInstance( DataBindingScript.class );
+  public static EventDelegatorScript getInstance() {
+    return SingletonUtil.getSessionInstance( EventDelegatorScript.class );
   }
 
-  private DataBindingScript() {
+  private EventDelegatorScript() {
     super( getText() );
   }
 
   private static String getText() {
-    String path = "org/eclipse/rap/addons/autosuggest/internal/resources/DataBinding.js";
+    String path = "org/eclipse/rap/addons/autosuggest/internal/resources/EventDelegator.js";
     return ResourceLoaderUtil.readTextContent( path );
   }