Skip to content

Instantly share code, notes, and snippets.

@xfyre
Last active August 29, 2015 14:02
Show Gist options
  • Save xfyre/5a235a2ff95df7609194 to your computer and use it in GitHub Desktop.
Save xfyre/5a235a2ff95df7609194 to your computer and use it in GitHub Desktop.
Tapestry 5.4 Autocomplete mixin replacement
/**
* Example of mixin usage
*/
public class Example {
/**
* provide suggestions list
*/
List<?> onCompletionsRequestedFromAccountLookupField ( String partial ) {
List<?> list = hibernateSession.createCriteria ( MyAccountType.class )
.add ( Restrictions.disjunction ()
.add ( Restrictions.ilike ( "firstName", partial, MatchMode.START ) )
.add ( Restrictions.ilike ( "lastName", partial, MatchMode.START ) )
).list ();
return list;
}
/**
* provide suggestion template
*/
public String getSuggestionTemplate () {
return "<p>{{fullname}}<br/><small>{{cityAndState}}</small></p>";
}
/**
* provide keys for suggestion data model
*/
public String getSuggestionKeys () {
return "fullname,cityAndState";
}
}
<!-- component template -->
<t:container
xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd"
xmlns:p="tapestry:parameter">
<t:textfield t:id="accountLookupField" t:mixins="typeahead" p:keys="suggestionKeys" p:template="suggestionTemplate"/>
</t:container>
.twitter-typeahead {
width: 100%;
position: relative;
}
.twitter-typeahead .tt-query,
.twitter-typeahead .tt-hint {
margin: 0;
width: 100%;
color: #555555;
vertical-align: middle;
background-color: #ffffff;
border: 1px solid #cccccc;
border-radius: 4px;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
-webkit-transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
}
.has-warning .twitter-typeahead .tt-query,
.has-warning .twitter-typeahead .tt-hint {
border-color: #c09853;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.has-error .twitter-typeahead .tt-query,
.has-error .twitter-typeahead .tt-hint {
border-color: #b94a48;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.has-success .twitter-typeahead .tt-query,
.has-success .twitter-typeahead .tt-hint {
border-color: #468847;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
}
.twitter-typeahead .tt-query:focus,
.twitter-typeahead .tt-hint:focus {
border-color: #66afe9;
outline: 0;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(102, 175, 233, 0.6);
}
.has-warning .twitter-typeahead .tt-query:focus,
.has-warning .twitter-typeahead .tt-hint:focus {
border-color: #a47e3c;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #dbc59e;
}
.has-error .twitter-typeahead .tt-query:focus,
.has-error .twitter-typeahead .tt-hint:focus {
border-color: #953b39;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #d59392;
}
.has-success .twitter-typeahead .tt-query:focus,
.has-success .twitter-typeahead .tt-hint:focus {
border-color: #356635;
-webkit-box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 6px #7aba7b;
}
.twitter-typeahead .tt-hint {
color: #a1a1a1;
border: 1px solid transparent;
}
.input-group .twitter-typeahead .tt-query {
z-index: 2;
}
.input-group .twitter-typeahead:first-child .tt-query,
.input-group .twitter-typeahead:first-child .tt-hint {
border-radius: 4px 0 0 4px !important;
}
.input-group .twitter-typeahead:last-child .tt-query,
.input-group .twitter-typeahead:last-child .tt-hint {
border-radius: 0 4px 4px 0 !important;
}
.input-group .twitter-typeahead .tt-query,
.input-group .twitter-typeahead .tt-hint {
padding: 6px 12px;
font-size: 14px;
line-height: 1.428571429;
}
.input-group.input-group-sm .twitter-typeahead:first-child .tt-query,
.input-group.input-group-sm .twitter-typeahead:first-child .tt-hint {
border-radius: 3px 0 0 3px !important;
}
.input-group.input-group-sm .twitter-typeahead:last-child .tt-query,
.input-group.input-group-sm .twitter-typeahead:last-child .tt-hint {
border-radius: 0 3px 3px 0 !important;
}
.input-group.input-group-sm .tt-query,
.input-group.input-group-sm .tt-hint {
height: 30px;
padding: 5px 10px;
font-size: 12px;
line-height: 1.5;
}
.input-group.input-group-lg .twitter-typeahead:first-child .tt-query,
.input-group.input-group-lg .twitter-typeahead:first-child .tt-hint {
border-radius: 6px 0 0 6px !important;
}
.input-group.input-group-lg .twitter-typeahead:last-child .tt-query,
.input-group.input-group-lg .twitter-typeahead:last-child .tt-hint {
border-radius: 0 6px 6px 0 !important;
}
.input-group.input-group-lg .tt-query,
.input-group.input-group-lg .tt-hint {
height: 45px;
padding: 10px 16px;
font-size: 18px;
line-height: 1.33;
}
.tt-dropdown-menu {
width: 100%;
min-width: 160px;
margin-top: 2px;
padding: 5px 0;
background-color: #fff;
border: 1px solid #ccc;
border: 1px solid rgba(0, 0, 0, 0.2);
*border-right-width: 2px;
*border-bottom-width: 2px;
-webkit-border-radius: 6px;
-moz-border-radius: 6px;
border-radius: 6px;
-webkit-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-moz-box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
box-shadow: 0 5px 10px rgba(0, 0, 0, 0.2);
-webkit-background-clip: padding-box;
-moz-background-clip: padding;
background-clip: padding-box;
}
.tt-suggestion {
display: block;
padding: 3px 20px;
}
.tt-suggestion.tt-is-under-cursor {
color: #fff;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
}
.tt-suggestion.tt-is-under-cursor a {
color: #fff;
}
.tt-suggestion p {
margin: 0;
}
.tt-cursor {
color: #fff;
background-color: #0081c2;
background-image: -moz-linear-gradient(top, #0088cc, #0077b3);
background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3));
background-image: -webkit-linear-gradient(top, #0088cc, #0077b3);
background-image: -o-linear-gradient(top, #0088cc, #0077b3);
background-image: linear-gradient(to bottom, #0088cc, #0077b3);
background-repeat: repeat-x;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0);
cursor: pointer;
}
package com.xdance.mixins;
import java.util.List;
import java.util.Map;
import org.apache.commons.beanutils.BeanUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.tapestry5.BindingConstants;
import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.ComponentEventCallback;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.*;
import org.apache.tapestry5.internal.util.Holder;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONArray;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.javascript.InitializationPriority;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
@Events("completionsRequested")
public class Typeahead {
@Parameter(required=false,defaultPrefix=BindingConstants.PROP,value="literal:<p>{{value}}</p>")
private String template;
@Parameter(required=false,defaultPrefix=BindingConstants.PROP,value="literal:value")
private String keys;
@Inject
private ComponentResources resources;
@InjectContainer
private ClientElement ownerElement;
@Environmental
private JavaScriptSupport jsSupport;
@Import(library={"context:js/typeahead.bundle.js", "context:js/handlebars-v1.3.0.js"}, stylesheet={"context:css/typeahead-fixes.css"})
void afterRender () {
String[] iterableKeys = StringUtils.split ( keys, ',' );
JSONObject params = new JSONObject (
"id", ownerElement.getClientId (),
"url", resources.createEventLink ( "typeahead" ).toAbsoluteURI (),
"displayKey", iterableKeys[0].trim (),
"template", template
);
jsSupport.require ( "mymodules/typeaheadmixin" ).priority ( InitializationPriority.LATE ).with ( params );
}
Object onTypeahead ( @RequestParameter("t:input") String input ) {
final Holder<List<?>> holder = new Holder<List<?>> ();
ComponentEventCallback<List<?>> callback = new ComponentEventCallback<List<?>> () {
@Override
public boolean handleResult ( List<?> result ) {
holder.put ( result );
return true;
}
};
resources.triggerEvent ( "completionsRequested", new Object[] { input }, callback );
JSONArray matches = new JSONArray ();
for ( Object obj : holder.get () ) {
JSONObject suggestion = new JSONObject ();
if ( obj instanceof String ) {
String value = (String) obj;
suggestion.put ( "value", value );
} else if ( obj instanceof Map<?, ?> ) {
Map<?, ?> map = (Map<?, ?>) obj;
for ( String key : StringUtils.split ( keys, ',' ) )
if ( map.containsKey ( key.trim () ) )
suggestion.put ( key.trim (), map.get ( key.trim () ) );
if ( suggestion.keys ().size () == 0 )
throw new IllegalArgumentException ( String.format ( "map %s doesn't have any of provided keys %s", map, keys ) );
} else {
for ( String key : StringUtils.split ( keys, ',' ) )
try {
suggestion.put ( key.trim (), BeanUtils.getProperty ( obj, key.trim () ) );
} catch ( Exception e ) {
throw new IllegalArgumentException ( String.format ( "cannot get property value of object %s for key %s: %s", obj, key, e.getMessage () ) );
}
}
matches.put ( suggestion );
}
return new JSONObject ( "matches", matches );
}
}
define(["jquery", "t5/core/utils"], function($, utils) {
return function(spec) {
var engine = new Bloodhound({
datumTokenizer: Bloodhound.tokenizers.obj.whitespace(spec.displayKey),
queryTokenizer: Bloodhound.tokenizers.whitespace,
remote: {
url: spec.url,
replace: function(uri, query) {
return utils.extendURL(uri, {"t:input": query});
},
filter: function(response) {
return response.matches;
}
}
});
engine.initialize ();
var $ttField = $('#' + spec.id);
$ttField.typeahead(
{
minLength: 2,
highlight: true
},
{
name: 'suggestions-list',
displayKey: spec.displayKey,
source: engine.ttAdapter(),
templates: { suggestion: Handlebars.compile(spec.template) }
}
);
};
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment