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