Repository: syncope Updated Branches: refs/heads/master 992f6002f -> 9840cd0d9
[SYNCOPE-156] changed AjaxPalette in order to support filtering Project: http://git-wip-us.apache.org/repos/asf/syncope/repo Commit: http://git-wip-us.apache.org/repos/asf/syncope/commit/9840cd0d Tree: http://git-wip-us.apache.org/repos/asf/syncope/tree/9840cd0d Diff: http://git-wip-us.apache.org/repos/asf/syncope/diff/9840cd0d Branch: refs/heads/master Commit: 9840cd0d93f1138167d8ffc3c2bc66ac0e7585fc Parents: 992f600 Author: fmartelli <fabio.marte...@gmail.com> Authored: Fri Nov 27 16:17:24 2015 +0100 Committer: fmartelli <fabio.marte...@gmail.com> Committed: Fri Nov 27 16:17:24 2015 +0100 ---------------------------------------------------------------------- .../client/console/commons/Constants.java | 2 + .../markup/html/form/AjaxPaletteConf.java | 35 +++ .../markup/html/form/AjaxPalettePanel.java | 215 ++++++++++++++++--- .../client/console/wizards/any/AuxClasses.java | 120 ++++++----- .../console/wizards/any/UserWizardBuilder.java | 2 +- .../META-INF/resources/css/syncopeConsole.css | 2 +- .../markup/html/form/AjaxPalettePanel.html | 8 + 7 files changed, 302 insertions(+), 82 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java b/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java index 9d0043c..473e348 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/commons/Constants.java @@ -24,6 +24,8 @@ public final class Constants { public static final String ON_CHANGE = "onchange"; + public static final String ON_KEYUP = "onkeyup"; + public static final String ON_BLUR = "onblur"; public static final String PNG_EXT = ".png"; http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java new file mode 100644 index 0000000..e18f788 --- /dev/null +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPaletteConf.java @@ -0,0 +1,35 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.syncope.client.console.wicket.markup.html.form; + +import java.io.Serializable; + +/** + * To be overridden in order to change the default filter option. + */ +public class AjaxPaletteConf implements Serializable { + + private static final long serialVersionUID = -1; + + private static final String DEFAULT_FILTER = "*"; + + public String getDefaultFilter() { + return DEFAULT_FILTER; + } +} http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java index 33b04da..76f6285 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.java @@ -19,54 +19,113 @@ package org.apache.syncope.client.console.wicket.markup.html.form; import java.io.Serializable; +import java.util.ArrayList; import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.regex.Pattern; +import org.apache.commons.collections4.ListUtils; +import org.apache.commons.lang3.StringUtils; import org.apache.wicket.Component; +import org.apache.wicket.ajax.AjaxRequestTarget; +import org.apache.wicket.ajax.markup.html.form.AjaxSubmitLink; import org.apache.wicket.extensions.markup.html.form.palette.Palette; import org.apache.wicket.markup.html.basic.Label; +import org.apache.wicket.markup.html.form.Form; import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.model.IModel; +import org.apache.wicket.model.LoadableDetachableModel; +import org.apache.wicket.model.Model; import org.apache.wicket.model.ResourceModel; import org.apache.wicket.model.util.ListModel; +import org.apache.wicket.util.string.Strings; -public class AjaxPalettePanel<T> extends AbstractFieldPanel<List<T>> { +public class AjaxPalettePanel<T extends Serializable> extends AbstractFieldPanel<List<T>> { private static final long serialVersionUID = 7738499668258805567L; - protected final Palette<T> palette; + protected Palette<T> palette; - public AjaxPalettePanel(final String id, - final IModel<List<T>> model, final ListModel<T> choices, - final IChoiceRenderer<T> renderer, final boolean allowOrder, - final boolean allowMoveAll, final String availableLabel, final String selectedLabel) { + private final Model<String> queryFilter = new Model<String>(StringUtils.EMPTY); + private final List<T> availableBefore = new ArrayList<T>(); + + private final LoadableDetachableModel<List<T>> choicesModel; + + public AjaxPalettePanel( + final String id, final IModel<List<T>> model, final Builder.Query<T> choices, final Builder<T> builder) { super(id, id, model); - this.palette = createPalette(model, choices, renderer, allowOrder, allowMoveAll, availableLabel, selectedLabel); - add(palette.setOutputMarkupId(true)); - setOutputMarkupId(true); + choicesModel = new PaletteLoadableDetachableModel(builder) { + + private static final long serialVersionUID = 1L; + + @Override + protected List<T> getChoices() { + return choices.execute(getFilter()); + } + }; + initialize(model, builder); } - protected final Palette<T> createPalette( - final IModel<List<T>> model, final ListModel<T> choices, - final IChoiceRenderer<T> renderer, - final boolean allowOrder, final boolean allowMoveAll, - final String availableLabel, final String selectedLabel) { + public AjaxPalettePanel( + final String id, final IModel<List<T>> model, final ListModel<T> choices, final Builder<T> builder) { + super(id, id, model); - return new NonI18nPalette<T>("paletteField", model, choices, renderer, 8, allowOrder, allowMoveAll) { + choicesModel = new PaletteLoadableDetachableModel(builder) { - private static final long serialVersionUID = -3074655279011678437L; + private static final long serialVersionUID = 1L; @Override - protected Component newAvailableHeader(final String componentId) { - return new Label(componentId, new ResourceModel("palette.available", availableLabel)); + protected List<T> getChoices() { + return builder.filtered + ? getFilteredList(choices.getObject(), getFilter().replaceAll("\\*", "\\.\\*")) + : choices.getObject(); } + }; + initialize(model, builder); + } + + private void initialize(final IModel<List<T>> model, final Builder<T> builder) { + setOutputMarkupId(true); + + this.palette = new NonI18nPalette<T>( + "paletteField", model, choicesModel, builder.renderer, 8, builder.allowOrder, builder.allowMoveAll) { + + private static final long serialVersionUID = -3074655279011678437L; + + @Override + protected Component newAvailableHeader(final String componentId) { + return new Label(componentId, new ResourceModel("palette.available", builder.availableLabel)); + } + + @Override + protected Component newSelectedHeader(final String componentId) { + return new Label(componentId, new ResourceModel("palette.selected", builder.selectedLabel)); + } + }; + + add(palette.setOutputMarkupId(true)); + + final Form<?> form = new Form<>("form"); + add(form.setEnabled(builder.filtered).setVisible(builder.filtered)); + + final AjaxTextFieldPanel filter = new AjaxTextFieldPanel("filter", "filter", queryFilter, false); + filter.hideLabel().setOutputMarkupId(true); + form.add(filter); + + form.add(new AjaxSubmitLink("search") { + + private static final long serialVersionUID = 1L; @Override - protected Component newSelectedHeader(final String componentId) { - return new Label(componentId, new ResourceModel("palette.selected", selectedLabel)); + protected void onAfterSubmit(final AjaxRequestTarget target, final Form<?> form) { + super.onAfterSubmit(target, form); + target.add(palette); } - }; + }); } @Override @@ -79,7 +138,9 @@ public class AjaxPalettePanel<T> extends AbstractFieldPanel<List<T>> { return palette.getModelCollection(); } - public static class Builder<T extends Serializable> { + public static class Builder<T extends Serializable> implements Serializable { + + private static final long serialVersionUID = 1L; private IChoiceRenderer<T> renderer; @@ -91,9 +152,16 @@ public class AjaxPalettePanel<T> extends AbstractFieldPanel<List<T>> { private String availableLabel; + private boolean filtered; + + private final AjaxPaletteConf conf = new AjaxPaletteConf(); + + private String filter = conf.getDefaultFilter(); + public Builder() { this.allowMoveAll = false; this.allowOrder = false; + this.filtered = false; this.renderer = new SelectChoiceRenderer<>(); } @@ -122,10 +190,105 @@ public class AjaxPalettePanel<T> extends AbstractFieldPanel<List<T>> { return this; } - public AjaxPalettePanel<T> build( - final String id, final IModel<List<T>> model, final ListModel<T> choices) { - return new AjaxPalettePanel<>(id, model, - choices, renderer, allowOrder, allowMoveAll, availableLabel, selectedLabel); + public Builder<T> withFilter() { + this.filtered = true; + return this; + } + + public Builder<T> withFilter(final String defaultFilter) { + this.filtered = true; + this.filter = defaultFilter; + return this; + } + + public AjaxPalettePanel<T> build(final String id, final IModel<List<T>> model, final ListModel<T> choices) { + return new AjaxPalettePanel<>(id, model, choices, this); + } + + public AjaxPalettePanel<T> build(final String id, final IModel<List<T>> model, final Query<T> choices) { + return new AjaxPalettePanel<>(id, model, choices, this); + } + + public abstract static class Query<T extends Serializable> implements Serializable { + + private static final long serialVersionUID = 1L; + + public abstract List<T> execute(final String filter); + } + } + + private abstract class PaletteLoadableDetachableModel extends LoadableDetachableModel<List<T>> { + + private static final long serialVersionUID = 1L; + + private final Builder<T> builder; + + PaletteLoadableDetachableModel(final Builder<T> builder) { + super(); + this.builder = builder; + } + + protected abstract List<T> getChoices(); + + protected String getFilter() { + return StringUtils.isBlank(queryFilter.getObject()) ? builder.filter : queryFilter.getObject(); + } + + @Override + protected List<T> load() { + final List<T> selected = availableBefore.isEmpty() + ? new ArrayList<>(palette.getModelCollection()) + : getSelectedList(availableBefore, palette.getRecorderComponent().getValue()); + + availableBefore.clear(); + availableBefore.addAll(ListUtils.sum(selected, getChoices())); + return availableBefore; + } + + private List<T> getSelectedList(final Collection<T> choices, final String selection) { + final IChoiceRenderer<? super T> renderer = palette.getChoiceRenderer(); + final List<T> selected = new ArrayList<>(); + + final Map<T, String> idForChoice = new HashMap<>(); + for (final T choice : choices) { + idForChoice.put(choice, renderer.getIdValue(choice, 0)); + } + + for (final String id : Strings.split(selection, ',')) { + final Iterator<T> iter = choices.iterator(); + boolean found = false; + while (!found && iter.hasNext()) { + final T choice = iter.next(); + final String idValue = idForChoice.get(choice); + if (id.equals(idValue)) { + selected.add(choice); + found = true; + } + } + } + + return selected; + } + + protected List<T> getFilteredList(final Collection<T> choices, final String filter) { + final IChoiceRenderer<? super T> renderer = palette.getChoiceRenderer(); + final List<T> selected = new ArrayList<>(choices.size()); + + final Map<T, String> idForChoice = new HashMap<>(); + for (final T choice : choices) { + idForChoice.put(choice, renderer.getIdValue(choice, 0)); + } + + final Pattern pattern = Pattern.compile(filter, Pattern.CASE_INSENSITIVE); + + for (T choice : choices) { + final String idValue = idForChoice.get(choice); + if (pattern.matcher(idValue).matches()) { + selected.add(choice); + } + } + + return selected; } } } http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java index 8c091a8..e90be42 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/AuxClasses.java @@ -22,13 +22,12 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.regex.Matcher; -import java.util.regex.Pattern; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.Transformer; import org.apache.syncope.client.console.rest.AnyTypeRestClient; import org.apache.syncope.client.console.rest.GroupRestClient; import org.apache.syncope.client.console.wicket.markup.html.form.AjaxPalettePanel; +import org.apache.syncope.client.lib.SyncopeClient; import org.apache.syncope.common.lib.to.AnyObjectTO; import org.apache.syncope.common.lib.to.AnyTO; import org.apache.syncope.common.lib.to.AnyTypeClassTO; @@ -37,7 +36,9 @@ import org.apache.syncope.common.lib.to.MembershipTO; import org.apache.syncope.common.lib.to.UserTO; import org.apache.wicket.extensions.markup.html.repeater.util.SortParam; import org.apache.wicket.extensions.wizard.WizardStep; +import org.apache.wicket.markup.html.form.IChoiceRenderer; import org.apache.wicket.markup.html.panel.Fragment; +import org.apache.wicket.model.IModel; import org.apache.wicket.model.PropertyModel; import org.apache.wicket.model.util.ListModel; @@ -47,8 +48,6 @@ public class AuxClasses extends WizardStep { private final GroupRestClient groupRestClient = new GroupRestClient(); - private static final Pattern GROUP_ID_PATTERN = Pattern.compile("\\[(\\d*)\\]? (.*)"); - public <T extends AnyTO> AuxClasses(final T entityTO, final String... anyTypeClass) { this.setOutputMarkupId(true); @@ -58,16 +57,6 @@ public class AuxClasses extends WizardStep { } else { fragment = new Fragment("groups", "groupsFragment", this); - final ArrayList<String> available = CollectionUtils.collect( - groupRestClient.list(entityTO.getRealm(), -1, -1, new SortParam<>("name", true), null), - new Transformer<GroupTO, String>() { - - @Override - public String transform(final GroupTO input) { - return String.format("[%d] %s", input.getKey(), input.getName()); - } - }, new ArrayList<String>()); - final List<MembershipTO> memberships; final List<Long> dyngroups; @@ -82,56 +71,79 @@ public class AuxClasses extends WizardStep { dyngroups = Collections.<Long>emptyList(); } - fragment.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build( - "groups", new ListModel<String>(CollectionUtils.collect(memberships, - new Transformer<MembershipTO, String>() { - - @Override - public String transform(final MembershipTO input) { - return String.format("[%d] %s", input.getRightKey(), input.getGroupName()); - } - }, new ArrayList<String>())) { + final AjaxPalettePanel.Builder<MembershipTO> builder + = new AjaxPalettePanel.Builder<MembershipTO>().setRenderer(new IChoiceRenderer<MembershipTO>() { private static final long serialVersionUID = 1L; @Override - public void setObject(final List<String> object) { - super.setObject(object); - memberships.clear(); - CollectionUtils.collect(getObject(), new Transformer<String, MembershipTO>() { - - @Override - public MembershipTO transform(final String input) { - final Matcher m = GROUP_ID_PATTERN.matcher(input); - final String name; - final long key; - if (m.matches()) { - key = Long.parseLong(m.group(1)); - name = m.group(2); - } else { - key = -1L; - name = input; - } + public Object getDisplayValue(final MembershipTO object) { + return object.getGroupName(); + } - return new MembershipTO.Builder(). - left(entityTO.getType(), entityTO.getKey()).group(key, name).build(); - } - }, memberships); + @Override + public String getIdValue(final MembershipTO object, final int index) { + return object.getGroupName(); } - }, - new ListModel<>(available)).setOutputMarkupId(true)); - fragment.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build( - "dyngroups", new ListModel<String>(CollectionUtils.collect(dyngroups, - new Transformer<Long, String>() { + @Override + public MembershipTO getObject( + final String id, final IModel<? extends List<? extends MembershipTO>> choices) { + for (MembershipTO membershipTO : choices.getObject()) { + if (id.equalsIgnoreCase(membershipTO.getGroupName())) { + return membershipTO; + } + } + return null; + } + }); + + fragment.add(builder.setAllowOrder(true).withFilter().build( + "groups", new ListModel<MembershipTO>(memberships), + new AjaxPalettePanel.Builder.Query<MembershipTO>() { + + private static final long serialVersionUID = 1L; + + @Override + public List<MembershipTO> execute(final String filter) { + return CollectionUtils.collect( + groupRestClient.search( + entityTO.getRealm(), + SyncopeClient.getGroupSearchConditionBuilder(). + isAssignable().and().is("name").equalTo(filter).query(), + -1, -1, + new SortParam<>("name", true), + null), + new Transformer<GroupTO, MembershipTO>() { @Override - public String transform(final Long input) { - final GroupTO groupTO = groupRestClient.read(input); - return String.format("[%d] %s", groupTO.getKey(), groupTO.getName()); + public MembershipTO transform(final GroupTO input) { + final MembershipTO membershipTO = new MembershipTO(); + membershipTO.setGroupName(input.getName()); + membershipTO.setRightKey(input.getKey()); + membershipTO.setRightType(input.getType()); + membershipTO.setLeftKey(entityTO.getKey()); + membershipTO.setLeftType(entityTO.getType()); + return membershipTO; } - }, new ArrayList<String>())), - new ListModel<>(available)).setEnabled(false).setOutputMarkupId(true)); + }, new ArrayList<MembershipTO>()); + } + }).setOutputMarkupId(true)); + + final ArrayList<String> dynamics = CollectionUtils.collect(dyngroups, + new Transformer<Long, String>() { + + @Override + public String transform(final Long input) { + final GroupTO groupTO = groupRestClient.read(input); + return String.format("[%d] %s", groupTO.getKey(), groupTO.getName()); + } + }, new ArrayList<String>()); + + fragment.add(new AjaxPalettePanel.Builder<String>().setAllowOrder(true).build( + "dyngroups", + new ListModel<String>(dynamics), + new ListModel<>(dynamics)).setEnabled(false).setOutputMarkupId(true)); } add(fragment); http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java ---------------------------------------------------------------------- diff --git a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java index f18e6dd..56fd134 100644 --- a/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java +++ b/client/console/src/main/java/org/apache/syncope/client/console/wizards/any/UserWizardBuilder.java @@ -59,7 +59,7 @@ public class UserWizardBuilder extends AnyWizardBuilder<UserTO> { if (modelObject.getKey() == 0) { actual = userRestClient.create(modelObject, StringUtils.isNotBlank(modelObject.getPassword())); } else { - final UserPatch patch = AnyOperations.diff(modelObject, getOriginalItem(), true); + final UserPatch patch = AnyOperations.diff(modelObject, getOriginalItem(), false); if (!statusModel.getObject().isEmpty()) { patch.setPassword(StatusUtils.buildPasswordPatch(modelObject.getPassword(), statusModel.getObject())); } http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css ---------------------------------------------------------------------- diff --git a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css index e491bb5..ed2cda2 100644 --- a/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css +++ b/client/console/src/main/resources/META-INF/resources/css/syncopeConsole.css @@ -223,7 +223,7 @@ div.basepage-content{ .wizard-buttons { padding: 10px 0px 5px 0px; position: fixed; - top: 590px; + top: 605px; right: 30px; } http://git-wip-us.apache.org/repos/asf/syncope/blob/9840cd0d/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html ---------------------------------------------------------------------- diff --git a/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html b/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html index dad3a36..17b6919 100644 --- a/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html +++ b/client/console/src/main/resources/org/apache/syncope/client/console/wicket/markup/html/form/AjaxPalettePanel.html @@ -19,6 +19,14 @@ under the License. --> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:wicket="http://wicket.apache.org"> <wicket:extend> + <form wicket:id="form"> + <div class="form-group input-group"> + <span wicket:id="filter">[FILTER]</span> + <span class="input-group-addon"> + <a href="#" wicket:id="search"><i class="glyphicon glyphicon-search"></i></a> + </span> + </div> + </form> <span wicket:id="paletteField">[Palette]</span> </wicket:extend> </html>