In keycloak how to deploy a custom validator for custom user attribute within declarative user profile? - keycloak

Hello keycloak extension experts,
I have enabled successfully the declarative user profile (https://www.keycloak.org/docs/latest/server_admin/#user-profile).
I would like now to "deploy" a custom-made validator. (KC 18.0 embedded wildfly)
I have trouble to understand how I need to package my validator to make it available in the admin console UI.
I did with maven a jar as for an eventListener extension spi (which used to work very fine) with a ProviderFactory and a Provider.
Yet, the validator is not proposed in the console UI as a validator. Deployment seems successful.
I wonder if my packaging is wrong or if there is an extra step required.
By the way I had a look to https://github.com/thomasdarimont/keycloak-extension-playground/blob/master/custom-user-profile-extension/src/main/java/com/github/thomasdarimont/keycloak/userprofile/validator/AgeValidator.java but here it lacks the packaging stage as far as I can see
You can find the code below. I duplicated the code for the out of the box LengthValidator.
org.keycloak.validate.ValidatorFactory
lu.lns.keycloak.custom.validator.LengthValidatorProviderFactory
LengthValidatorProviderFactory.java
package lu.lns.keycloak.custom.validator;
import org.keycloak.Config;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;
import org.keycloak.validate.Validator;
import org.keycloak.validate.ValidatorFactory;
public class LengthValidatorProviderFactory implements ValidatorFactory {
#Override
public Validator create(KeycloakSession session) {
return new LengthValidatorProvider();
}
#Override
public void init(Config.Scope config) {
}
#Override
public void postInit(KeycloakSessionFactory factory) {
}
#Override
public String getId() {
return "lns-length-validator";
}
}
LengthValidatorProvider
package lu.lns.keycloak.custom.validator;
import java.util.ArrayList;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Set;
import org.keycloak.models.KeycloakSession;
import org.keycloak.provider.ConfiguredProvider;
import org.keycloak.provider.ProviderConfigProperty;
import org.keycloak.validate.AbstractStringValidator;
import org.keycloak.validate.ValidationContext;
import org.keycloak.validate.ValidationError;
import org.keycloak.validate.ValidationResult;
import org.keycloak.validate.ValidatorConfig;
import org.keycloak.validate.validators.ValidatorConfigValidator;
/**
* String value length validation - accepts plain string and collection of strings, for basic behavior like null/blank
* values handling and collections support see {#link AbstractStringValidator}. Validator trims String value before the
* length validation, can be disabled by {#link #KEY_TRIM_DISABLED} boolean configuration entry set to
* <code>true</code>.
* <p>
* Configuration have to be always provided, with at least one of {#link #KEY_MIN} and {#link #KEY_MAX}.
*/
public class LengthValidatorProvider extends AbstractStringValidator implements ConfiguredProvider {
public static final LengthValidatorProvider INSTANCE = new LengthValidatorProvider();
public static final String ID = "lns-length";
public static final String MESSAGE_INVALID_LENGTH = "LNS-error-invalid-length";
public static final String KEY_MIN = "min";
public static final String KEY_MAX = "max";
public static final String KEY_TRIM_DISABLED = "trim-disabled";
private static final List<ProviderConfigProperty> configProperties = new ArrayList<>();
static {
ProviderConfigProperty property;
property = new ProviderConfigProperty();
property.setName(KEY_MIN);
property.setLabel("Minimum length");
property.setHelpText("The minimum length");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
property = new ProviderConfigProperty();
property.setName(KEY_MAX);
property.setLabel("Maximum length");
property.setHelpText("The maximum length");
property.setType(ProviderConfigProperty.STRING_TYPE);
configProperties.add(property);
}
#Override
public String getId() {
return ID;
}
#Override
protected void doValidate(String value, String inputHint, ValidationContext context, ValidatorConfig config) {
Integer min = config.getInt(KEY_MIN);
Integer max = config.getInt(KEY_MAX);
if (!config.getBooleanOrDefault(KEY_TRIM_DISABLED, Boolean.FALSE)) {
value = value.trim();
}
int length = value.length();
if (config.containsKey(KEY_MIN) && length < min.intValue()) {
context.addError(new ValidationError(ID, inputHint, MESSAGE_INVALID_LENGTH, min, max));
return;
}
if (config.containsKey(KEY_MAX) && length > max.intValue()) {
context.addError(new ValidationError(ID, inputHint, MESSAGE_INVALID_LENGTH, min, max));
return;
}
}
#Override
public ValidationResult validateConfig(KeycloakSession session, ValidatorConfig config) {
Set<ValidationError> errors = new LinkedHashSet<>();
if (config == null || config == ValidatorConfig.EMPTY) {
errors.add(new ValidationError(ID, KEY_MIN, ValidatorConfigValidator.MESSAGE_CONFIG_MISSING_VALUE));
errors.add(new ValidationError(ID, KEY_MAX, ValidatorConfigValidator.MESSAGE_CONFIG_MISSING_VALUE));
} else {
if (config.containsKey(KEY_TRIM_DISABLED) && (config.getBoolean(KEY_TRIM_DISABLED) == null)) {
errors.add(new ValidationError(ID, KEY_TRIM_DISABLED, ValidatorConfigValidator.MESSAGE_CONFIG_INVALID_BOOLEAN_VALUE, config.get(KEY_TRIM_DISABLED)));
}
boolean containsMin = config.containsKey(KEY_MIN);
boolean containsMax = config.containsKey(KEY_MAX);
if (!(containsMin || containsMax)) {
errors.add(new ValidationError(ID, KEY_MIN, ValidatorConfigValidator.MESSAGE_CONFIG_MISSING_VALUE));
errors.add(new ValidationError(ID, KEY_MAX, ValidatorConfigValidator.MESSAGE_CONFIG_MISSING_VALUE));
} else {
if (containsMin && config.getInt(KEY_MIN) == null) {
errors.add(new ValidationError(ID, KEY_MIN, ValidatorConfigValidator.MESSAGE_CONFIG_INVALID_NUMBER_VALUE, config.get(KEY_MIN)));
}
if (containsMax && config.getInt(KEY_MAX) == null) {
errors.add(new ValidationError(ID, KEY_MAX, ValidatorConfigValidator.MESSAGE_CONFIG_INVALID_NUMBER_VALUE, config.get(KEY_MAX)));
}
if (errors.isEmpty() && containsMin && containsMax && (config.getInt(KEY_MIN) > config.getInt(KEY_MAX))) {
errors.add(new ValidationError(ID, KEY_MAX, ValidatorConfigValidator.MESSAGE_CONFIG_INVALID_VALUE));
}
}
}
return new ValidationResult(errors);
}
#Override
public String getHelpText() {
return "LNS Length validator";
}
#Override
public List<ProviderConfigProperty> getConfigProperties() {
return configProperties;
}
}
NB: I cross-posted the same question into keycloak forum.

AbstractStringValidator implements both the Validator and ValidatorFactory. Therefore, you don't need to implement a separate factory for you custom SPI.
Also, the META-INF directory should have services as a subdirectory, not META-INF.services (this is probably why it isn't working).
If you remove the factory and put the LengthValidatorProvider in the META-INF file then it should work.

Related

Change 2nd parameter of method 'PlaceAutocompleteAdapter' from 'GeoDataClient' to 'GoogleApiClient'

i ma making AutoCompleteTextView in my adapter for which when i make object of PlaceAutocompleteAdapter when i pass GoogleApiClient object as second parameter to my PlaceAutocompleteAdapter it gives me red error and sys to pass GEODataClient object instead of GoogleApiClient
while the code for my Fragment containing map is this
package com.example.anonymous.ghar_ka_khana;
import android.app.FragmentManager;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.location.Address;
import android.location.Geocoder;
import android.location.Location;
import android.net.Uri;
import android.os.Bundle;
import android.app.Fragment;
import android.provider.Settings;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.support.design.widget.Snackbar;
import android.support.v4.app.ActivityCompat;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import com.google.android.gms.common.ConnectionResult;
import com.google.android.gms.common.api.GoogleApiClient;
import com.google.android.gms.location.FusedLocationProviderClient;
import com.google.android.gms.location.LocationListener;
import com.google.android.gms.location.LocationServices;
import com.google.android.gms.location.places.Places;
import com.google.android.gms.maps.CameraUpdateFactory;
import com.google.android.gms.maps.GoogleMap;
import com.google.android.gms.maps.MapFragment;
import com.google.android.gms.maps.OnMapReadyCallback;
import com.google.android.gms.maps.model.LatLng;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.maps.model.MarkerOptions;
import com.google.android.gms.tasks.OnCompleteListener;
import com.google.android.gms.tasks.Task;
import android.Manifest;
import android.widget.AutoCompleteTextView;
import com.google.android.gms.common.api.GoogleApiClient.ConnectionCallbacks;
import java.io.IOException;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.Executor;
public class GoogleFragment extends Fragment implements OnMapReadyCallback,LocationListener,GoogleApiClient.OnConnectionFailedListener {
private MapFragment m;
private GoogleMap mMap;
FusedLocationProviderClient fusedLocationProviderClient;
private int REQUEST_PERMISSIONS_REQUEST_CODE = 125;
protected Location mLastLocation;
private AutoCompleteTextView autoCompleteTextView;
private PlaceAutocompleteAdapter autocompleteAdapter;
private String mLatitiudelabel;
private String mLongitudelabel;
private GoogleApiClient mGoogleApiClient;
private static LatLngBounds Lat_Long_Bounds = new LatLngBounds(new LatLng(-40,-168),new LatLng(71,136));
View v;
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
}
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
Log.i("pahlywala", "onCreateView");
v = inflater.inflate(R.layout.fragment_google, container, false);
mGoogleApiClient = new GoogleApiClient
.Builder(getActivity())
.addApi(Places.GEO_DATA_API)
.addApi(Places.PLACE_DETECTION_API)
.enableAutoManage(new DrawerActivity(), this)
.build();
autoCompleteTextView = (AutoCompleteTextView)v.findViewById(R.id.mapsearch);
autocompleteAdapter = new PlaceAutocompleteAdapter(getActivity(),mGoogleApiClient,Lat_Long_Bounds,null);
autoCompleteTextView.setAdapter(autocompleteAdapter);
return v;
}
#Override
public void onViewCreated(View view, #Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
mLatitiudelabel = "Latitude";
mLongitudelabel = "Longitude";
FragmentManager fragmentManager = getActivity().getFragmentManager();
final MapFragment myMapFragment = (MapFragment) fragmentManager.findFragmentById(R.id.googlemap);
myMapFragment.getMapAsync(this);
Log.i("pahlywalass", "onViewCreated");
fusedLocationProviderClient = LocationServices.getFusedLocationProviderClient(getActivity());
}
#Override
public void onMapReady(GoogleMap googleMap)
{
mMap=googleMap;
mMap.addMarker(new MarkerOptions()
.position(new LatLng(0, 0))
.title("Marker"));
}
#Override
public void onLocationChanged(Location location) {
}
#Override
public void onStart() {
super.onStart();
if (!checkPermissions()) {
requestPermissions();
} else {
getLastLocation();
}
}
private boolean checkPermissions() {
int permissionState = ActivityCompat.checkSelfPermission(getActivity(),
Manifest.permission.ACCESS_FINE_LOCATION);
return permissionState == PackageManager.PERMISSION_GRANTED;
}
private void requestPermissions() {
boolean shouldProvideRationale =
ActivityCompat.shouldShowRequestPermissionRationale(getActivity(),
Manifest.permission.ACCESS_FINE_LOCATION);
// Provide an additional rationale to the user. This would happen if the user denied the
// request previously, but didn't check the "Don't ask again" checkbox.
if (shouldProvideRationale) {
Log.i("displaying permission", "Displaying permission rationale to provide additional context.");
/* showSnackbar("warning", android.R.string.ok,
new View.OnClickListener() {
#Override
public void onClick(View view) {
// Request permission
startLocationPermissionRequest();
}
});*/
} else {
Log.i("requesting permission", "Requesting permission");
// Request permission. It's possible this can be auto answered if device policy
// sets the permission in a given state or the user denied the permission
// previously and checked "Never ask again".
startLocationPermissionRequest();
}
}
private void startLocationPermissionRequest() {
ActivityCompat.requestPermissions(new DrawerActivity(),
new String[]{Manifest.permission.ACCESS_FINE_LOCATION},
REQUEST_PERMISSIONS_REQUEST_CODE);
}
#Override
public void onRequestPermissionsResult(int requestCode, #NonNull String[] permissions,
#NonNull int[] grantResults) {
Log.i("onRequestPerm", "onRequestPermissionResult");
if (requestCode == REQUEST_PERMISSIONS_REQUEST_CODE) {
if (grantResults.length <= 0) {
// If user interaction was interrupted, the permission request is cancelled and you
// receive empty arrays.
Log.i("onRequestPerm", "User interaction was cancelled.");
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
// Permission granted.
getLastLocation();
} else {
// Permission denied.
// Notify the user via a SnackBar that they have rejected a core permission for the
// app, which makes the Activity useless. In a real app, core permissions would
// typically be best requested during a welcome-screen flow.
// Additionally, it is important to remember that a permission might have been
// rejected without asking the user for permission (device policy or "Never ask
// again" prompts). Therefore, a user interface affordance is typically implemented
// when permissions are denied. Otherwise, your app could appear unresponsive to
// touches or interactions which have required permissions.
/*showSnackbar(R.string.textwarn, R.string.settings,
new View.OnClickListener() {
#Override
public void onClick(View view) {
// Build intent that displays the App settings screen.
Intent intent = new Intent();
intent.setAction(
Settings.ACTION_APPLICATION_DETAILS_SETTINGS);
Uri uri = Uri.fromParts("package",
BuildConfig.APPLICATION_ID, null);
intent.setData(uri);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
});*/
}
}
}
private void getLastLocation() {
if (ActivityCompat.checkSelfPermission(getActivity(), android.Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED && ActivityCompat.checkSelfPermission(getActivity(), android.Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) {
// TODO: Consider calling
// ActivityCompat#requestPermissions
// here to request the missing permissions, and then overriding
// public void onRequestPermissionsResult(int requestCode, String[] permissions,
// int[] grantResults)
// to handle the case where the user grants the permission. See the documentation
// for ActivityCompat#requestPermissions for more details.
return;
}
fusedLocationProviderClient.getLastLocation()
.addOnCompleteListener(getActivity(), new OnCompleteListener<Location>() {
#Override
public void onComplete(#NonNull Task<Location> task) {
if (task.isSuccessful() && task.getResult() != null) {
mLastLocation = task.getResult();
Log.i("Longitutde", mLastLocation.getLongitude()+"");
Log.i("Longitutde", mLastLocation.getLatitude()+"");
LatLng cur_Latlng=new LatLng(mLastLocation.getLatitude(),mLastLocation.getLongitude()); // giving your marker to zoom to your location area.
mMap.addMarker(new MarkerOptions()
.position(cur_Latlng)
.title("umair"));
mMap.moveCamera(CameraUpdateFactory.newLatLng(cur_Latlng));
mMap.animateCamera(CameraUpdateFactory.zoomTo(12));
Geocoder geocoder;
List<Address> addresses = null;
geocoder = new Geocoder(getActivity(), Locale.getDefault());
try {
addresses = geocoder.getFromLocation(mLastLocation.getLatitude(),mLastLocation.getLongitude(), 1); // Here 1 represent max location result to returned, by documents it recommended 1 to 5
} catch (IOException e) {
e.printStackTrace();
}
String address = addresses.get(0).getAddressLine(0); // If any additional address line present than only, check with max available address lines by getMaxAddressLineIndex()
String city = addresses.get(0).getLocality();
/*String state = addresses.get(0).getAdminArea();
String country = addresses.get(0).getCountryName();
String postalCode = addresses.get(0).getPostalCode();
String knownName = addresses.get(0).getFeatureName();*/
Log.i("pat", ""+city);
}
else {
Log.i("getlasts", "getLastLocation:exception", task.getException());
}
}
});
}
#Override
public void onConnectionFailed(#NonNull ConnectionResult connectionResult) {
}
}
while the code for PlaceAutoCompleteAdapter is following
package com.example.anonymous.ghar_ka_khana;
/**
* Created by Anonymous on 2/19/2018.
*/
import android.content.Context;
import android.graphics.Typeface;
import android.text.style.CharacterStyle;
import android.text.style.StyleSpan;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.Filterable;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.gms.common.data.DataBufferUtils;
import com.google.android.gms.location.places.AutocompleteFilter;
import com.google.android.gms.location.places.AutocompletePrediction;
import com.google.android.gms.location.places.AutocompletePredictionBufferResponse;
import com.google.android.gms.location.places.GeoDataClient;
import com.google.android.gms.maps.model.LatLngBounds;
import com.google.android.gms.tasks.RuntimeExecutionException;
import com.google.android.gms.tasks.Task;
import com.google.android.gms.tasks.Tasks;
import java.util.ArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
/**
* Adapter that handles Autocomplete requests from the Places Geo Data Client.
* {#link AutocompletePrediction} results from the API are frozen and stored directly in this
* adapter. (See {#link AutocompletePrediction#freeze()}.)
*/
public class PlaceAutocompleteAdapter
extends ArrayAdapter<AutocompletePrediction> implements Filterable {
private static final String TAG = "PlaceAutocompleteAd";
private static final CharacterStyle STYLE_BOLD = new StyleSpan(Typeface.BOLD);
/**
* Current results returned by this adapter.
*/
private ArrayList<AutocompletePrediction> mResultList;
/**
* Handles autocomplete requests.
*/
private GeoDataClient mGeoDataClient;
/**
* The bounds used for Places Geo Data autocomplete API requests.
*/
private LatLngBounds mBounds;
/**
* The autocomplete filter used to restrict queries to a specific set of place types.
*/
private AutocompleteFilter mPlaceFilter;
/**
* Initializes with a resource for text rows and autocomplete query bounds.
*
* #see android.widget.ArrayAdapter#ArrayAdapter(android.content.Context, int)
*/
public PlaceAutocompleteAdapter(Context context, GeoDataClient geoDataClient,
LatLngBounds bounds, AutocompleteFilter filter) {
super(context, android.R.layout.simple_expandable_list_item_2, android.R.id.text1);
mGeoDataClient = geoDataClient;
mBounds = bounds;
mPlaceFilter = filter;
}
/**
* Sets the bounds for all subsequent queries.
*/
public void setBounds(LatLngBounds bounds) {
mBounds = bounds;
}
/**
* Returns the number of results received in the last autocomplete query.
*/
#Override
public int getCount() {
return mResultList.size();
}
/**
* Returns an item from the last autocomplete query.
*/
#Override
public AutocompletePrediction getItem(int position) {
return mResultList.get(position);
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
View row = super.getView(position, convertView, parent);
// Sets the primary and secondary text for a row.
// Note that getPrimaryText() and getSecondaryText() return a CharSequence that may contain
// styling based on the given CharacterStyle.
AutocompletePrediction item = getItem(position);
TextView textView1 = (TextView) row.findViewById(android.R.id.text1);
TextView textView2 = (TextView) row.findViewById(android.R.id.text2);
textView1.setText(item.getPrimaryText(STYLE_BOLD));
textView2.setText(item.getSecondaryText(STYLE_BOLD));
return row;
}
/**
* Returns the filter for the current set of autocomplete results.
*/
#Override
public Filter getFilter() {
return new Filter() {
#Override
protected FilterResults performFiltering(CharSequence constraint) {
FilterResults results = new FilterResults();
// We need a separate list to store the results, since
// this is run asynchronously.
ArrayList<AutocompletePrediction> filterData = new ArrayList<>();
// Skip the autocomplete query if no constraints are given.
if (constraint != null) {
// Query the autocomplete API for the (constraint) search string.
filterData = getAutocomplete(constraint);
}
results.values = filterData;
if (filterData != null) {
results.count = filterData.size();
} else {
results.count = 0;
}
return results;
}
#Override
protected void publishResults(CharSequence constraint, FilterResults results) {
if (results != null && results.count > 0) {
// The API returned at least one result, update the data.
mResultList = (ArrayList<AutocompletePrediction>) results.values;
notifyDataSetChanged();
} else {
// The API did not return any results, invalidate the data set.
notifyDataSetInvalidated();
}
}
#Override
public CharSequence convertResultToString(Object resultValue) {
// Override this method to display a readable result in the AutocompleteTextView
// when clicked.
if (resultValue instanceof AutocompletePrediction) {
return ((AutocompletePrediction) resultValue).getFullText(null);
} else {
return super.convertResultToString(resultValue);
}
}
};
}
/**
* Submits an autocomplete query to the Places Geo Data Autocomplete API.
* Results are returned as frozen AutocompletePrediction objects, ready to be cached.
* Returns an empty list if no results were found.
* Returns null if the API client is not available or the query did not complete
* successfully.
* This method MUST be called off the main UI thread, as it will block until data is returned
* from the API, which may include a network request.
*
* #param constraint Autocomplete query string
* #return Results from the autocomplete API or null if the query was not successful.
* #see GeoDataClient#getAutocompletePredictions(String, LatLngBounds, AutocompleteFilter)
* #see AutocompletePrediction#freeze()
*/
private ArrayList<AutocompletePrediction> getAutocomplete(CharSequence constraint) {
Log.i(TAG, "Starting autocomplete query for: " + constraint);
// Submit the query to the autocomplete API and retrieve a PendingResult that will
// contain the results when the query completes.
Task<AutocompletePredictionBufferResponse> results =
mGeoDataClient.getAutocompletePredictions(constraint.toString(), mBounds,
mPlaceFilter);
// This method should have been called off the main UI thread. Block and wait for at most
// 60s for a result from the API.
try {
Tasks.await(results, 60, TimeUnit.SECONDS);
} catch (ExecutionException | InterruptedException | TimeoutException e) {
e.printStackTrace();
}
try {
AutocompletePredictionBufferResponse autocompletePredictions = results.getResult();
Log.i(TAG, "Query completed. Received " + autocompletePredictions.getCount()
+ " predictions.");
// Freeze the results immutable representation that can be stored safely.
return DataBufferUtils.freezeAndClose(autocompletePredictions);
} catch (RuntimeExecutionException e) {
// If the query did not complete successfully return null
Toast.makeText(getContext(), "Error contacting API: " + e.toString(),
Toast.LENGTH_SHORT).show();
Log.e(TAG, "Error getting autocomplete prediction API call", e);
return null;
}
}
}
my gradle file is following
apply plugin: 'com.android.application'
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.example.anonymous.ghar_ka_khana"
minSdkVersion 17
targetSdkVersion 26
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
productFlavors {
}
}
dependencies {
implementation fileTree(include: ['*.jar'], dir: 'libs')
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
implementation 'com.google.firebase:firebase-database:11.8.0'
//11.0.4
implementation 'com.android.support:support-v4:26.1.0'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
implementation 'com.google.android.gms:play-services:11.8.0'
implementation 'com.firebaseui:firebase-ui-auth:3.1.3'
// FirebaseUI for Firebase Auth
implementation 'com.firebaseui:firebase-ui-database:3.1.3'
// FirebaseUI for Firebase Realtime Database
implementation 'com.firebaseui:firebase-ui-firestore:3.1.3'
// FirebaseUI for Cloud Firestore
compile 'com.google.firebase:firebase-firestore:11.8.0'
//Firestore dependency
implementation 'com.firebaseui:firebase-ui-storage:3.1.3'
// FirebaseUI for Cloud Storage
implementation 'com.facebook.android:facebook-login:4.29.0'
// Required only if Facebook login support is required
implementation 'de.hdodenhof:circleimageview:2.2.0'
implementation 'com.github.florent37:materialtextfield:1.0.7'
compile 'com.scalified:fab:1.1.3'
compile 'com.google.firebase:firebase-storage:11.8.0'
compile 'com.google.firebase:firebase-auth:11.8.0'
compile 'com.github.glomadrian:Grav:1.1'
implementation('com.mikepenz:materialdrawer:6.0.6#aar') {
transitive = true
}
compile 'com.wdullaer:materialdatetimepicker:3.5.1'
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support:recyclerview-v7:26.1.0'
implementation 'com.android.support:support-annotations:26.1.0'
implementation 'com.google.android.gms:play-services:11.8.0'
implementation 'com.google.android.gms:play-services-location:11.8.0'
}
apply plugin: 'com.google.gms.google-services'
configurations.all {
resolutionStrategy.eachDependency { DependencyResolveDetails details ->
def requested = details.requested
if (requested.group == 'com.android.support') {
if (!requested.name.startsWith("multidex")) {
details.useVersion '26.1.0'
}
}
}
}
Ok. I've got the solution instead of using:
autocompleteAdapter = new PlaceAutocompleteAdapter(getActivity(),mGoogleApiClient,Lat_Long_Bounds,null);
you will have to use:
autocompleteAdapter = new PlaceAutocompleteAdapter(getActivity(),Places.getGeoDataClient(getActivity(), null),Lat_Long_Bounds,null);

TreeTableView disable any cell in parent row

How can I disable any cell editable in parent row in treetableview? Please look the pictures and check the sample code. Shortly I want to disable row editable if row is expandable (root row or sub root row)
this picture is correct
but this is not correct
**Example code **
import javafx.application.Application;
import javafx.beans.property.ReadOnlyStringWrapper;
import javafx.beans.property.SimpleStringProperty;
import javafx.beans.property.StringProperty;
import javafx.beans.value.ObservableValue;
import javafx.scene.Scene;
import javafx.scene.control.TextField;
import javafx.scene.control.TreeItem;
import javafx.scene.control.TreeTableCell;
import javafx.scene.control.TreeTableColumn;
import javafx.scene.control.TreeTableView;
import javafx.scene.control.cell.TreeItemPropertyValueFactory;
import javafx.scene.layout.HBox;
import javafx.stage.Stage;
import javafx.util.Callback;
public class TreeTableExample extends Application {
public static void main(String[] args) {
Application.launch(args);
}
#Override
#SuppressWarnings("unchecked")
public void start(Stage stage) {
HBox root = new HBox(createTable());
Scene scene = new Scene(root);
stage.setScene(scene);
stage.setTitle("Using a TreeTableView");
stage.show();
}
public TreeTableView createTable() {
TreeTableView<Person> treeTable = new TreeTableView<>();
treeTable.setEditable(true);
Callback<TreeTableColumn<Person, String>,
TreeTableCell<Person, String>> cellFactory
= (TreeTableColumn<Person, String> p) -> new EditingCell();
TreeTableColumn<Person, String> firstName = new TreeTableColumn<>("First Name");
firstName.setCellValueFactory(new TreeItemPropertyValueFactory<>("firstName"));
firstName.setCellFactory(cellFactory);
firstName.setOnEditCommit((TreeTableColumn.CellEditEvent<Person, String> event) -> {
if(event.getNewValue()!=null)
event.getRowValue().getValue().setFirstName(event.getNewValue());
});
TreeTableColumn<Person, String> lastName = new TreeTableColumn<>("Last Name");
lastName.setCellValueFactory(new TreeItemPropertyValueFactory<>("lastName"));
lastName.setCellFactory(cellFactory);
lastName.setOnEditCommit((TreeTableColumn.CellEditEvent<Person, String> event) -> {
if(event.getNewValue()!=null)
event.getRowValue().getValue().setLastName(event.getNewValue());
});
treeTable.getColumns().addAll(firstName, lastName);
TreeItem<Person> root = new TreeItem<>();
for (int i = 0; i < 5; i++) {
root.getChildren().add(new TreeItem<>(new Person()));
}
treeTable.setRoot(root);
return treeTable;
}
public class Person {
private SimpleStringProperty firstName;
private SimpleStringProperty lastName;
public Person(){
firstName = new SimpleStringProperty(this, "firstName");
lastName = new SimpleStringProperty(this, "lastName");
};
public String getFirstName() {
return firstName.get();
}
public void setFirstName(String fName) {
firstName.set(fName);
}
public String getLastName() {
return lastName.get();
}
public void setLastName(String fName) {
lastName.set(fName);
}
}
class EditingCell extends TreeTableCell<Person, String> {
private TextField textField;
public EditingCell() {
}
#Override
public void startEdit() {
if (!isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
}
}
#Override
public void cancelEdit() {
super.cancelEdit();
setText((String) getItem());
setGraphic(null);
}
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
if (empty) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
if(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
setEditable(false);
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
private void createTextField() {
textField = new TextField(getString());
textField.setMinWidth(this.getWidth() - this.getGraphicTextGap() * 2);
textField.focusedProperty().addListener(
(ObservableValue<? extends Boolean> arg0,
Boolean arg1, Boolean arg2) -> {
if (!arg2) {
commitEdit(textField.getText());
}
});
}
private String getString() {
return getItem() == null ? "" : getItem();
}
}
}
just run it and double click on the root item
make-individual-cell-editable-in-javafx-tableview I checked the solution works for tableview but for treetaleview does not work.
It seems that TreeTableCell does not properly check its editable property before deciding whether or not to call startEdit(). I think that's a bug. You can work around it by checking that yourself in your startEdit() method:
#Override
public void startEdit() {
if (isEditable() && !isEmpty()) {
super.startEdit();
createTextField();
setText(null);
setGraphic(textField);
textField.selectAll();
}
}
and now in your updateItem() method, you can check the current tree item from the row, and update editable as required:
#Override
public void updateItem(String item, boolean empty) {
super.updateItem(item, empty);
TreeItem<Person> treeItem = getTreeTableRow().getTreeItem();
setEditable(treeItem != null && treeItem.isLeaf());
if (empty) {
setText(null);
setGraphic(null);
} else if (isEditing()) {
if(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
setEditable(false);
if (textField != null) {
textField.setText(getString());
}
setText(null);
setGraphic(textField);
} else {
setText(getString());
setGraphic(null);
}
}
Actually I disagree with the reasoning in the other answer: there is nothing wrong with core TreeTableCell (it does check for its editability before actually starting an edit) - instead the logic in the custom cell implementation is broken. Particularly, the part of updateItem that sets the editable property:
} else if (isEditing()) {
if(!getTreeTableView().getTreeItem(getIndex()).isLeaf())
setEditable(false);
Besides being incomplete in not resetting the editable back to true anywhere (remember: cells are re-used), we allow super to first start editing and only after it started, it's disabled.
This logic error is fixed (in the other answer, copied here for convenience) by unconditionally setting the editability in updateItem:
super.updateItem(item, empty);
TreeItem<Person> treeItem = getTreeTableRow().getTreeItem();
setEditable(treeItem != null && treeItem.isLeaf());
The other usage error (as already noted) was not fully checking cell state before actually configuring the editor. The suggested fix - check cell's editable - isn't quite complete because table/column editability might be disabled as well. To take that into account, I would tend to let super do its job and only configure the editor if editability actually changed, like
super.startEdit();
// super changed state into editing
if (isEditing()) {
// create and install the textField
}

Preference activity seekbar

I have created a custom android seek bar from lukehorvat tutorial
and added to my preference xml file as below
<?xml version="1.0" encoding="utf-8"?>
<PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:custom="http://schemas.android.com/apk/res-auto" >
<PreferenceCategory
android:title="Color RGB channels"
android:order="100">
<com.heroku.android.SeekBarDialogPreference
android:defaultValue="20"
android:id="#+id/redchannel"
android:key="redchannel"
android:dialogMessage="Please select red channel:"
android:max="50"
android:title="Red channel"
custom:progressTextSuffix="%"
custom:min="1" />
<com.heroku.android.SeekBarDialogPreference
android:defaultValue="20"
android:dialogMessage="Please select green channel:"
android:max="50"
android:title="Select green channel"
custom:progressTextSuffix="%"
custom:min="1" />
<com.heroku.android.SeekBarDialogPreference
android:defaultValue="20"
android:dialogMessage="Please select blue channel:"
android:max="50"
android:title="Select blue channel"
custom:progressTextSuffix="%"
custom:min="1" />
</PreferenceCategory>
</PreferenceScreen>
And I have added to my preference activity these three seekbars as below
package com.heroku.android;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.view.View;
import android.widget.SeekBar;
public class Preferences extends PreferenceActivity
implements SharedPreferences.OnSharedPreferenceChangeListener {
#Override
protected void onCreate(Bundle icicle) {
super.onCreate(icicle);
addPreferencesFromResource(com.yuldashev.android.R.xml.preferences);
getPreferenceManager().getSharedPreferences().registerOnSharedPreferenceChangeListener(
this);
}
#Override
protected void onResume() {
super.onResume();
}
#Override
protected void onDestroy() {
getPreferenceManager().getSharedPreferences().unregisterOnSharedPreferenceChangeListener(
this);
super.onDestroy();
}
public void onSharedPreferenceChanged(SharedPreferences sharedPreferences,
String key) {
}
}
The problem is all of them refers to the same seekbardialog custom class and I cannot get the values for these three seekbars seperately. I have tried to seperate them by entitling #+id in xml file but it does not works for me by findviewbyID because the custom SeekBardialogPreference does not support such an option. For example if you adding and single SeekBar by id you do
SeekBar seek1=(SeekBar)findviewByID(resource)
and you get the progress value from seek1 object.
Is there any suggestion how to do the same with custom seekbardialog below
package com.heroku.android;
import android.content.Context;
import android.content.res.TypedArray;
import android.os.Parcel;
import android.os.Parcelable;
import android.preference.DialogPreference;
import android.util.AttributeSet;
import android.view.View;
import android.widget.SeekBar;
import android.widget.SeekBar.OnSeekBarChangeListener;
import android.widget.TextView;
/**
* A {#link DialogPreference} that provides a user with the means to select an integer from a {#link SeekBar}, and persist it.
*
* #author lukehorvat
*
*/
public class SeekBarDialogPreference extends DialogPreference
{
private static final int DEFAULT_MIN_PROGRESS = 0;
private static final int DEFAULT_MAX_PROGRESS = 100;
private static final int DEFAULT_PROGRESS = 0;
private int mMinProgress;
private int mMaxProgress;
private int mProgress;
private CharSequence mProgressTextSuffix;
private TextView mProgressText;
private SeekBar mSeekBar;
public SeekBarDialogPreference(Context context)
{
this(context, null);
}
public SeekBarDialogPreference(Context context, AttributeSet attrs)
{
super(context, attrs);
// get attributes specified in XML
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, com.yuldashev.android.R.styleable.SeekBarDialogPreference, 0, 0);
try
{
setMinProgress(a.getInteger(com.yuldashev.android.R.styleable.SeekBarDialogPreference_min, DEFAULT_MIN_PROGRESS));
setMaxProgress(a.getInteger(com.yuldashev.android.R.styleable.SeekBarDialogPreference_android_max, DEFAULT_MAX_PROGRESS));
setProgressTextSuffix(a.getString(com.yuldashev.android.R.styleable.SeekBarDialogPreference_progressTextSuffix));
}
finally
{
a.recycle();
}
// set layout
setDialogLayoutResource(com.yuldashev.android.R.layout.preference_seek_bar_dialog);
setPositiveButtonText(android.R.string.ok);
setNegativeButtonText(android.R.string.cancel);
setDialogIcon(null);
}
#Override
protected void onSetInitialValue(boolean restore, Object defaultValue)
{
setProgress(restore ? getPersistedInt(DEFAULT_PROGRESS) : (Integer) defaultValue);
}
#Override
protected Object onGetDefaultValue(TypedArray a, int index)
{
return a.getInt(index, DEFAULT_PROGRESS);
}
#Override
protected void onBindDialogView(View view)
{
super.onBindDialogView(view);
TextView dialogMessageText = (TextView) view.findViewById(com.yuldashev.android.R.id.text_dialog_message);
dialogMessageText.setText(getDialogMessage());
mProgressText = (TextView) view.findViewById(com.yuldashev.android.R.id.text_progress);
mSeekBar = (SeekBar) view.findViewById(com.yuldashev.android.R.id.seek_bar);
mSeekBar.setOnSeekBarChangeListener(new OnSeekBarChangeListener()
{
#Override
public void onStopTrackingTouch(SeekBar seekBar)
{
}
#Override
public void onStartTrackingTouch(SeekBar seekBar)
{
}
#Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser)
{
// update text that displays the current SeekBar progress value
// note: this does not persist the progress value. that is only ever done in setProgress()
String progressStr = String.valueOf(progress + mMinProgress);
mProgressText.setText(mProgressTextSuffix == null ? progressStr : progressStr.concat(mProgressTextSuffix.toString()));
}
});
mSeekBar.setMax(mMaxProgress - mMinProgress);
mSeekBar.setProgress(mProgress - mMinProgress);
}
public int getMinProgress()
{
return mMinProgress;
}
public void setMinProgress(int minProgress)
{
mMinProgress = minProgress;
setProgress(Math.max(mProgress, mMinProgress));
}
public int getMaxProgress()
{
return mMaxProgress;
}
public void setMaxProgress(int maxProgress)
{
mMaxProgress = maxProgress;
setProgress(Math.min(mProgress, mMaxProgress));
}
public int getProgress()
{
return mProgress;
}
public void setProgress(int progress)
{
progress = Math.max(Math.min(progress, mMaxProgress), mMinProgress);
if (progress != mProgress)
{
mProgress = progress;
persistInt(progress);
notifyChanged();
}
}
public CharSequence getProgressTextSuffix()
{
return mProgressTextSuffix;
}
public void setProgressTextSuffix(CharSequence progressTextSuffix)
{
mProgressTextSuffix = progressTextSuffix;
}
#Override
protected void onDialogClosed(boolean positiveResult)
{
super.onDialogClosed(positiveResult);
// when the user selects "OK", persist the new value
if (positiveResult)
{
int seekBarProgress = mSeekBar.getProgress() + mMinProgress;
if (callChangeListener(seekBarProgress))
{
setProgress(seekBarProgress);
}
}
}
#Override
protected Parcelable onSaveInstanceState()
{
// save the instance state so that it will survive screen orientation changes and other events that may temporarily destroy it
final Parcelable superState = super.onSaveInstanceState();
// set the state's value with the class member that holds current setting value
final SavedState myState = new SavedState(superState);
myState.minProgress = getMinProgress();
myState.maxProgress = getMaxProgress();
myState.progress = getProgress();
return myState;
}
#Override
protected void onRestoreInstanceState(Parcelable state)
{
// check whether we saved the state in onSaveInstanceState()
if (state == null || !state.getClass().equals(SavedState.class))
{
// didn't save the state, so call superclass
super.onRestoreInstanceState(state);
return;
}
// restore the state
SavedState myState = (SavedState) state;
setMinProgress(myState.minProgress);
setMaxProgress(myState.maxProgress);
setProgress(myState.progress);
super.onRestoreInstanceState(myState.getSuperState());
}
private static class SavedState extends BaseSavedState
{
int minProgress;
int maxProgress;
int progress;
public SavedState(Parcelable superState)
{
super(superState);
}
public SavedState(Parcel source)
{
super(source);
minProgress = source.readInt();
maxProgress = source.readInt();
progress = source.readInt();
}
#Override
public void writeToParcel(Parcel dest, int flags)
{
super.writeToParcel(dest, flags);
dest.writeInt(minProgress);
dest.writeInt(maxProgress);
dest.writeInt(progress);
}
#SuppressWarnings("unused")
public static final Parcelable.Creator<SavedState> CREATOR = new Parcelable.Creator<SavedState>()
{
#Override
public SavedState createFromParcel(Parcel in)
{
return new SavedState(in);
}
#Override
public SavedState[] newArray(int size)
{
return new SavedState[size];
}
};
}
}
Thank you all!

GWT CellTable and ListBoxes complex identifiers

I'm following Goolge's example on how to add ListBoxes/SelectionCells to a CellTable, but I can't figure how to change the behaviour so the matching is done not with the string value displayed.
The items I display #SelectionCell are not unique (i.e there can be 2 elements with the same name), so I need to use other fields associated with the object to know which one was selected
for (IrrigationProgramDTO program: programOptions)
categoryNames.add(program.getName());
SelectionCell categoryCell = new SelectionCell(categoryNames);
Column<IrrigationGapDTO, String> categoryColumn = new Column<IrrigationGapDTO, String> (categoryCell) {
#Override
public String getValue(IrrigationGapDTO object) {
if (object.getProgramSelected()!=null)
return object.getProgramSelected().getName();
else
return "";
}
};
categoryColumn.setFieldUpdater(new FieldUpdater<IrrigationGapDTO, String>() {
public void update(int index, IrrigationGapDTO object, String value) {
for (IrrigationProgramDTO program: programOptions) {
//not valid as there could be more than 1 program with the same name
if (program.getName().equals(value)) {
object.setProgramSelected(program);
break;
}
}
}
Here is my new implementation of solution #3 (note that you must add a FieldUpdater to the column for it to work):
import java.util.Arrays;
import java.util.List;
import com.google.gwt.cell.client.AbstractEditableCell;
import com.google.gwt.cell.client.Cell;
import com.google.gwt.cell.client.ValueUpdater;
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.BrowserEvents;
import com.google.gwt.dom.client.Element;
import com.google.gwt.dom.client.NativeEvent;
import com.google.gwt.dom.client.SelectElement;
import com.google.gwt.safehtml.client.SafeHtmlTemplates;
import com.google.gwt.safehtml.client.SafeHtmlTemplates.Template;
import com.google.gwt.safehtml.shared.SafeHtml;
import com.google.gwt.safehtml.shared.SafeHtmlBuilder;
/**
* A {#link Cell} used to render a drop-down list.
*
* #author Gaspard van Koningsveld
*/
public class ItemSelectionCell<C> extends AbstractEditableCell<C, C> {
interface Template extends SafeHtmlTemplates {
#Template("<select tabindex=\"-1\" style=\"width:100%\">")
SafeHtml beginSelect();
#Template("<option value=\"{0}\">{1}</option>")
SafeHtml deselected(int hash, String option);
#Template("<option value=\"{0}\" selected=\"selected\">{1}</option>")
SafeHtml selected(int hash, String option);
#Template("</select>")
SafeHtml endSelect();
}
private static Template template;
private List<C> items;
public ItemSelectionCell(C itemsArray[]) {
this(Arrays.asList(itemsArray));
}
public ItemSelectionCell(List<C> items) {
super(BrowserEvents.CHANGE);
if (template == null) {
template = GWT.create(Template.class);
}
this.items = items;
}
#Override
public void onBrowserEvent(Context context, Element parent, C value, NativeEvent event, ValueUpdater<C> valueUpdater) {
super.onBrowserEvent(context, parent, value, event, valueUpdater);
if (BrowserEvents.CHANGE.equals(event.getType())) {
SelectElement select = parent.getFirstChild().cast();
int newIndex = select.getSelectedIndex();
valueUpdater.update(items.get(newIndex));
}
}
#Override
public void render(Context context, C value, SafeHtmlBuilder sb) {
sb.append(template.beginSelect());
for (int i = 0; i < items.size(); i++) {
C item = items.get(i);
if (item.equals(value)) {
sb.append(template.selected(i, getItemDisplayString(item)));
} else {
sb.append(template.deselected(i, getItemDisplayString(item)));
}
}
sb.append(template.endSelect());
}
public String getItemDisplayString(C item) {
return item.toString();
}
public List<C> getItems() {
return items;
}
public void setItems(List<C> items) {
this.items = items;
}
#Override
public boolean isEditing(Context context, Element parent, C value) {
return false;
}
}
3 possible solutions:
1. Dirty workaround:
Instead of getName() return getName() + some unique identifier:
public String getValue(IrrigationGapDTO object) {
if (object.getProgramSelected()!=null)
return object.getProgramSelected().getName()+"_"+object.getUniqueIdentiufier();
else
return "";
}
then in the FieldUpdater you can split on the "_" character and deal with duplicates
2. Use a unique id instead of getName():
Just generate/assign a unique id to your programms and use it instead of name.
3. Use IrrigationProgramDTO type instead of String:
Instead of String you can use IrrigationProgramDTO class in the Column definition. However you probably have to use a user-defined SelectionCell which takes IrrigationProgramDTO type instead of String as Data-type.
Column<IrrigationGapDTO, IrrigationProgramDTO> categoryColumn = new Column<IrrigationGapDTO, IrrigationProgramDTO> (categoryCell) {
#Override
public IrrigationProgramDTO (IrrigationGapDTO object) {
if (object.getProgramSelected()!=null)
return object.getProgramSelected();
else
return null;
}
};
categoryColumn.setFieldUpdater(new FieldUpdater<IrrigationGapDTO, IrrigationProgramDTO>() {
public void update(int index, IrrigationGapDTO object, IrrigationProgramDTO value) {
object.setProgramSelected(program);
}
}
Here is my implementation of Solution #3 of #Ümit:
public static abstract class EditSelectColumn<E, S> {
Map<String, S> selectionMap = new HashMap<String, S>();
EditColumn<E> column;
protected final String relationshipFieldName;
public EditSelectColumn(String relationshipFieldName) {
this.relationshipFieldName = relationshipFieldName;
for (S option : getOptions()) {
assert getOptionString(option) != null : "Option string cannot be null, please check your database";
selectionMap.put(getOptionString(option), option);
}
SelectionCell cell = new SelectionCell(new ArrayList<String>(selectionMap.keySet()));
column = new EditColumn<E>(cell) {
#Override
public String getValue(E object) {
if (getOption(object) == null)
return "";
return getOptionString(getOption(object));
}
#Override
public void setValue(E object, String value) {
setOption(object, selectionMap.get(value));
}
};
}
public EditColumn<E> getColumn() {
return column;
}
public abstract List<S> getOptions();
public abstract String getOptionString(S option);
public abstract S getOption(E object);
public abstract void setOption(E object, S value);
}

GXT - ComoboBox with Multi select feature

I have a task to design a control of size ComboBox (GXT) with Multi-select feature. I tried to set CheckBoxListView using setView of ComboBox but did not seemed to work. Can anybody please guide me if there is any way using the GXT framework I can achieve this?
PS: I found a component called XComboBox in sencha forum (java class, source code) which works good, but cant be used as its under GNU GPL License
Thanks in advance!
Thanks #smiletolead for your guidance, I found a solution by integrating Dialog with CheckBoxListView and TriggerField class.
The complete code listing is..
package com.ui.test.client;
import java.util.List;
import com.extjs.gxt.ui.client.data.ModelData;
import com.extjs.gxt.ui.client.event.ComponentEvent;
import com.extjs.gxt.ui.client.event.WindowEvent;
import com.extjs.gxt.ui.client.event.WindowListener;
import com.extjs.gxt.ui.client.store.ListStore;
import com.extjs.gxt.ui.client.widget.CheckBoxListView;
import com.extjs.gxt.ui.client.widget.Dialog;
import com.extjs.gxt.ui.client.widget.form.TriggerField;
import com.extjs.gxt.ui.client.widget.layout.FillLayout;
import com.google.gwt.user.client.Element;
public class MultiSelectComboBox extends TriggerField {
private Dialog checkBoxListHolder;
private CheckBoxListView listView;
private ListStore store;
private String delimiter = ",";
private boolean readOnly;
public MultiSelectComboBox() {
store = new ListStore();
listView = new CheckBoxListView();
}
#Override
protected void onTriggerClick(ComponentEvent ce) {
super.onTriggerClick(ce);
if(readOnly) {
return;
}
checkBoxListHolder.setSize(getWidth(), 200);
listView.setWidth(getWidth());
checkBoxListHolder.setPosition(getAbsoluteLeft(),
getAbsoluteTop() + getHeight());
if(checkBoxListHolder.isVisible()) {
checkBoxListHolder.hide();
}
else {
checkBoxListHolder.show();
}
}
#Override
protected void onRender(Element target, int index) {
super.onRender(target, index);
checkBoxListHolder = new Dialog();
checkBoxListHolder.setClosable(false);
checkBoxListHolder.setHeaderVisible(false);
checkBoxListHolder.setFooter(false);
checkBoxListHolder.setFrame(false);
checkBoxListHolder.setResizable(false);
checkBoxListHolder.setAutoHide(false);
checkBoxListHolder.getButtonBar().setVisible(false);
checkBoxListHolder.setLayout(new FillLayout());
checkBoxListHolder.add(listView);
listView.setStore(store);
checkBoxListHolder.addWindowListener(new WindowListener(){
#Override
public void windowHide(WindowEvent we) {
setValue(parseCheckedValues(listView));
}
});
}
private String parseCheckedValues(CheckBoxListView checkBoxView) {
StringBuffer buf = new StringBuffer();
if(checkBoxView != null) {
List selected = checkBoxView.getChecked();
int index = 1, len = selected.size();
for(D c : selected) {
buf.append(c.get(listView.getDisplayProperty()));
if(index getListView() {
return listView;
}
public void setListView(CheckBoxListView listView) {
this.listView = listView;
}
public ListStore getStore() {
return store;
}
public void setStore(ListStore store) {
this.store = store;
}
public String getDelimiter() {
return delimiter;
}
public void setDelimiter(String delimiter) {
this.delimiter = delimiter;
}
public boolean isReadOnly() {
return readOnly;
}
public void setReadOnly(boolean readOnly) {
this.readOnly = readOnly;
}
}
The code has been explained here...
http://bhat86.blogspot.com/2012/02/gxt-comobobox-with-multi-select-feature.html
Thank you!
Refer the examples listview and advanced list view. They may be of some help to you in developing combobox with multi select option