Hi Pablo,

This all can be achieved with Mixins.

For the autocomplete-select I add my own ForceAutocomplete Mixin (which extends Autocomplete) to a TextField. I have my ForceAutocomplete add a link (downward arrow img) behind the input which will trigger the "autocomplete" event. In stead of passing the textfield value, I pass a static identifier indicating this is a call from the link allowing the Tapestry providecompletions event to return full list, or whatever other requirements you might have.

I've posted some code here below. I apologize where it might be slightly unclear as I have not yet had the time to refactor so it is clear and clean. A fair bit of code is also in place to cater for the FORCE_AUTOCOMPLETE_ON_CHANGE which is triggered when a user selects an item from the list to allow for another list of options to be shown. We use this for selections where you need to drill down to a deeper child.

As for the drag and drop; this one is quite easy. Though we have the code embedded in our calendar component, it's quite easy to write a mixin that take a handleDomId and a dropableDomId (or whatever other droppable method you wish to use) and have it create the appropriate javascript. In the Draggable.onEnd() method you can have it trigger an ajax call with an T5 event identifier, so that your Tapestry component can respond appropriately if needed.

I hope this is not too unclear and can help you a little bit in getting started. Don't focus too much on our code I would say but just get started with your own mixin.

Cheers,
Joost


[1]:
ForceAutocomplete
@IncludeJavaScriptLibrary("ForceAutocomplete.js")
@IncludeStylesheet("ForceAutocomplete.css")
public class ForceAutocomplete extends Autocomplete{
public static final String NAME_ID_DEVIDER = "_NID_"; public static final String DOM_ID_ITEM_ID_DEVIDER = "_DIIID_";

public static final String FORCE_AUTOCOMPLETE_ON_CLICK = "FORCE_AUTOCOMPLETE_ON_CLICK"; public static final String FORCE_AUTOCOMPLETE_ON_CHANGE = "FORCE_AUTOCOMPLETE_ON_CHANGE"; public static final String OPTIONS_DEVIDER_END = " ----";

   public static final String OPTIONS_DEVIDER_START = "---- ";

@Parameter(value = "prop:componentResources.id", defaultPrefix = "literal")
   private String clientId;
@Parameter(required = false, defaultPrefix = "literal")
   private String idFieldClass;
@Inject
   private RenderSupport renderSupport;
@Inject
   private ComponentResources resources;
@Persist
   private String autocompleteFieldId;
/** * setting this to true will force an autocomplete call to be fired on every change
    */
   @Parameter(required = true, defaultPrefix = "literal")
   private boolean addOnchangeCall;
@Inject
   @Path("drop_down_arrow.gif")
   private Asset dropdownArrow;
@Inject
   @Path("context:/static/images/themes/jsportal/loader.gif")
   private Asset jsLoaderImage;
private String loaderId; @AfterRender
   private void after(MarkupWriter writer) {
       Element container = writer.getElement();
       Element autoCompField = null;
       for(Node node : container.getChildren()) {
           if(node instanceof Element) {
               Element el = (Element) node;
               if(el.getName().toLowerCase().equals("input") &&
                       el.getAttribute("type").equals("text")) {
                   autoCompField = el;
                   break;
               }
           }
       }
       autoCompField.addClassName("forceAutocompleteField");
       autocompleteFieldId = autoCompField.getAttribute("id");
loaderId = autocompleteFieldId + ":js_loader"; Element forceAutocompleteFieldContainer = writer.element("div",
               "class",
               "forceAutocompleteFieldContainer");
       writer.end();
autoCompField.moveToBottom(forceAutocompleteFieldContainer); forceAutocompleteFieldContainer.element("img",
               "src", jsLoaderImage.toClientURL(),
               "class", "t-autoloader-icon " + CSSClassConstants.INVISIBLE,
               "id", loaderId);
String handleId = autocompleteFieldId + ":handle_anchor";
       Element handleAnchor = forceAutocompleteFieldContainer.element("a",
               "class", "handleAnchor",
               "id", handleId);
       handleAnchor.element("img",
               "class", "forceAutocompleteHandler",
               "src", dropdownArrow.toClientURL());
StringBuffer script = new StringBuffer(); script.append("$T('" + autocompleteFieldId + "').forceAutocompleter = new ForceAutocomplete($('" + autocompleteFieldId + "'), '" + getAutocompleteUrl() + "', " + addOnchangeCall);
       if(idFieldClass != null) {
           script.append(", '" + idFieldClass + "'");
       }
script.append(");"); renderSupport.addScript(script.toString());
   }
protected void configure(JSONObject config){
       config.remove("indicator");
       config.put("indicator", loaderId);
   }
public String getAutocompleteUrl() { return resources.getContainerResources().createEventLink("autocomplete").toAbsoluteURI();
   }
/**
    * Overwrites the Autocomplete method to add a title to each li
    */
   @Override
protected void generateResponseMarkup(MarkupWriter writer, List matches){
       writer.element("ul");

       for (Object o : matches){
           String name = o.toString();
Element li = writer.element("li",
                   "title", name);
if(name.indexOf(ForceAutocomplete.OPTIONS_DEVIDER_START) != -1 && name.indexOf(ForceAutocomplete.OPTIONS_DEVIDER_END) != -1) {
               li.addClassName("devider");
           }
           writer.write(name);
           writer.end();
       }

       writer.end(); // ul
   }
}


ForceAutocomplete.js:

Ajax.Autocompleter.addMethods({
   onBlur: function() {
       fa = $T(this.element).forceAutocompleter;
       fa.inputFocus = false;
       fa.onBlurSelectorItem();
   }
});

var FORCE_AUTOCOMPLETE_CHANGED_EVENT = 'joostschouten:FORCE_AUTOCOMPLETE_CHANGED_EVENT';

function ForceAutocomplete (elem, ajaxUrl, includeChange, idFieldClass) {
   this.elem = elem;
   $T(this.elem).forceAutocompleter = this;
   this.hideTimeoutIndex = null;
   this.ajaxUrl = ajaxUrl;
   this.includeChange = includeChange;
this.forceAutocompleteFieldContainer = this.elem.up('.forceAutocompleteFieldContainer'); this.handle = this.forceAutocompleteFieldContainer.down('.handleAnchor');
   this.lastSelected = null;
   this.selectedIdField = null;
   if(idFieldClass) {
this.selectedIdField = this.forceAutocompleteFieldContainer.previous('.' + idFieldClass);
       this.lastSelected = this.selectedIdField.value;
   }
this.handle.observe('click', this.handleForcedUpdate.bindAsEventListener(this));
   this.elem.observe('focus', this.onInputFocus.bindAsEventListener(this));
//this.elem.observe('keydown', this.handleKeyDown.bindAsEventListener(this)); window.setTimeout(this.registerNewOnCompleteMethod.bindAsEventListener(this), 5);
}

ForceAutocomplete.prototype = {
   registerNewOnCompleteMethod : function () {
       this.autocompleter = $T(this.elem).autocompleter;
       if(this.autocompleter) {
           //stop observing the blur eveny on the AutoComplete
this.autocompleter.onBlur = this.onBlurSelectorItem.bindAsEventListener(this); this.autocompleter.options.onComplete = this.onComplete.bindAsEventListener(this); this.autocompleter.options.onHide = this.onHide.bindAsEventListener(this); this.autocompleter.options.onShow = this.onShow.bindAsEventListener(this);
       }
       else {
window.setTimeout(this.registerNewOnCompleteMethod.bindAsEventListener(this), 100);
       }
   },
   onHide : function () {
       this.autocompleter.update.hide();
   },
   onShow : function () {
       update = this.autocompleter.update;
       if(!update.style.position || update.style.position=='absolute') {
         update.style.position = 'absolute';
         Position.clone(this.elem, update, {
           setHeight: false,
           offsetTop: this.elem.offsetHeight
         });
       }
       update.show();
   },
   onComplete : function (transport) {
var autoCompleter = $T(this.elem).autocompleter;
       autoCompleter.changed = false;
       autoCompleter.hasFocus = true;
       autoCompleter.active = true;

       autoCompleter.update.update(transport.responseText);
       lis = autoCompleter.update.down().childElements();
if(lis.length>0) {
           valueMatchesOption = false;
           for(i = 0;i<lis.length;i++) {
if(!valueMatchesOption && lis[i].innerHTML == this.elem.value) {
                   valueMatchesOption = true;
               }
if(lis[i].innerHTML != this.elem.value && !lis[i].hasClassName('devider')) { lis[i].observe('click', this.handleForcedUpdateFromChange.bindAsEventListener(this));
               }
           }
if(!valueMatchesOption && this.selectedIdField != null) { //set the selected value to null as the option does not exist
               this.selectedIdField.value = '';
           }
if(lis.length == 1 && lis[0].innerHTML == this.elem.value) { // if there is one exact match, set it and don't show suggestions
               this.loadValueFromItem(lis[0]);
           }
           else {
//focus on the input field so that the update list will not be hidden
               this.elem.focus();
               autoCompleter.show();
           }
       }
       else {
           autoCompleter.hide();
       }
       autoCompleter.stopIndicator();
   },
   onCompleteFromChange : function (transport) {
       this.onComplete(transport);
       this.elem.fire(FORCE_AUTOCOMPLETE_CHANGED_EVENT);
   },
   handleForcedUpdate : function(event) {
       var autoCompleter = $T(this.elem).autocompleter;
//focus on the input field so that the update list will not be hidden
       this.elem.focus();
actionName = 'FORCE_AUTOCOMPLETE_ON_CLICK_' + this.elem.value; if(this.lastSelected != null) {
           actionName = actionName + '_NID_' + this.lastSelected;
       }
//in case when the list is already showing, hide it when clicked
       if(autoCompleter.update.visible()) {
           autoCompleter.update.hide();
       }
       else {
           autoCompleter.startIndicator();
           event.stop();
var myAjax = new Ajax.Request( this.ajaxUrl , {method: 'post', parameters: 't:input=' + actionName, onSuccess: this.onComplete.bindAsEventListener(this)} ); }
   },
handleForcedUpdateFromChange : function (event) { li = Event.findElement(event, 'LI');
       this.loadValueFromItem(li);
   },
   loadValueFromItem : function(li) {
liCont = li.innerHTML;
       if(li.hasClassName('devider')) {
           return false;
       }
if(this.elem.value != liCont) {
           this.elem.value = liCont;
       }
//set the value to the content of the li
       if(this.includeChange) {
           $T(this.elem).autocompleter.startIndicator();
           passValue = liCont;
           //strip of the DOM id before passing
           if(li.id && li.id != null) {
               itemId = li.id;
               if(itemId.indexOf('_DIIID_') != -1) {
                   itemId = itemId.split('_DIIID_')[1];
               }
               passValue = passValue + '_NID_' + itemId;
           }
           tokens =  passValue.split('_')
           this.lastSelected = tokens[tokens.length-1];
           actionName = 'FORCE_AUTOCOMPLETE_ON_CHANGE_' + passValue;
var myAjax = new Ajax.Request( this.ajaxUrl , {method: 'post', parameters: 't:input=' + actionName, onSuccess: this.onCompleteFromChange.bindAsEventListener(this)} );
       }
   },
   /**
* called by each item of the selector. Checks to see if any field of the selector is
    * focus'ed if so, don't hide the options, if not, hide
    */
   onBlurSelectorItem:function(event) {
       autoComp = $T(this.elem).autocompleter;
this.hideTimeoutIndex = setTimeout(autoComp.hide.bind(autoComp), 250);
       this.hasFocus = false;
       this.active = false;
   },
   onInputFocus:function() {
       if(this.hideTimeoutIndex != null) {
           clearTimeout(this.hideTimeoutIndex);
       }
   }
}

Pablo dos Reis wrote:
Joost,

I liked the video very much.
If you can pass at least the idea of components I thank.
How was it implemented? What javascript was use?
The code would bee good too.


And I think interesting you post here for all users can see.



tks

2010/4/30 Joost Schouten (ml) <joost...@jsportal.com>

Pablo,

If interested we have created a full drag and drop calendar and select with
auto-complete for our internal project. I could share some of our code to
give you a head start. The product is not yet launched but these these
features are partly shown in this video [1]

Let me know if you would like to have a look

Cheers,
Joost

[1]:http://www.youtube.com/watch?v=_GVW0JvkDkg


Pablo dos Reis wrote:

Tks everybody

Now I am studing about Scriptaculous
I already create a wiki
http://wiki.apache.org/general/PabloGSOC2010

I accept sugestions for it

Soon I' ll post my doubts here.



2010/4/28 Charith Madusanka <charithc...@gmail.com>



Hi Pablo,

Congratulations and Good luck...............!!!

Charith

On Thu, Apr 29, 2010 at 5:05 AM, Thiago H. de Paula Figueiredo <
thiag...@gmail.com> wrote:



On Wed, 28 Apr 2010 20:01:23 -0300, Pablo dos Reis <


pablodosr...@gmail.com>


wrote:

 There are ohters students working in tapestry project through GSOC?
     You're the only one with an approved proposal.

--
Thiago H. de Paula Figueiredo
Independent Java, Apache Tapestry 5 and Hibernate consultant, developer,
and instructor
Owner, Ars Machina Tecnologia da Informação Ltda.
http://www.arsmachina.com.br

---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org






---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org






---------------------------------------------------------------------
To unsubscribe, e-mail: users-unsubscr...@tapestry.apache.org
For additional commands, e-mail: users-h...@tapestry.apache.org

Reply via email to