1

Chosenbox dropdown and selection styles

asked 2017-05-25 23:24:33 +0800

mfarmer gravatar image mfarmer
13 2

I am trying to render a chosenbox with different styles based on an active flag. In the drop down, I would like to bold the item based on the flag, and when selected I would like to have it with a different background color. Is this possible?

Here is the way I am working with the chosenbox. I have tried to change the style on the label with no success.

<chosenbox style="overflow-y:auto;" width="40%" height="10%" model="@load(vm.list)" selectedobjects="@bind(vm.selectedList)"> <template name="model"> <label value="@load(each.name)"/>
</template>
</chosenbox>

delete flag offensive retag edit

2 Answers

Sort by ยป oldest newest most voted
0

answered 2017-05-26 07:23:29 +0800

chillworld gravatar image chillworld flag of Belgium
5367 4 9
https://github.com/chillw...

updated 2017-05-31 05:29:10 +0800

yes, its possible but you need to rewrite the Chosenbox.java and the js for it.

I have a sample in mine project, but I need to say that the java file isn't very clean (I check for mine classes).
very fast the changes :

  • Added private transient Map<Object, String> _optElementSclassMap; with getters,...
  • Line 761 : I start comparing if the object is one of those classes and add the concerning sclass to it.
  • It's zk 7 complient (The Java file altered from ZK 8 to ZK by Cor3000, where we take the ZK 8 js and removed the shadow elements because ZK 7 has a bug with some special chars.

Chosenbox.java :

/* Chosenbox.java

    Purpose:

    Description:

    History:
        Tue Nov 16 15:15:52 TST 2011, Created by benbai

Copyright (C) 2011 Potix Corporation. All Rights Reserved.

 */
package org.zkoss.zkmax.zul;

import be.chillworld.model.office.Office;
import be.chillworld.model.office.enums.OfficeType;
import be.chillworld.model.tag.Tag;
import java.io.IOException;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.zkoss.lang.Classes;
import org.zkoss.lang.Objects;
import org.zkoss.lang.Strings;
import org.zkoss.xel.VariableResolver;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.HtmlBasedComponent;
import org.zkoss.zk.ui.Page;
import org.zkoss.zk.ui.UiException;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.InputEvent;
import org.zkoss.zk.ui.event.OpenEvent;
import org.zkoss.zk.ui.event.SelectEvent;
import org.zkoss.zk.ui.sys.DesktopCtrl;
import org.zkoss.zk.ui.util.ComponentCloneListener;
import org.zkoss.zk.ui.util.ForEachStatus;
import org.zkoss.zk.ui.util.Template;
import org.zkoss.zul.ItemRenderer;
import org.zkoss.zul.Label;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListSubModel;
import org.zkoss.zul.event.ListDataEvent;
import org.zkoss.zul.event.ListDataListener;
import org.zkoss.zul.ext.Selectable;

/**
 * A component that similar to Combobox but handle the multi-selection and the
 * select order.
 * <p>
 * Default {@link #getZclass}: z-chosenbox. It does not create child widgets for
 * each data, so the memory usage is much lower at the server.
 *
 * @author benbai
 * @since 6.0.1
 *
 */
public class Chosenbox extends HtmlBasedComponent {

    private static final long serialVersionUID = -4281187335573856218L;

    /**
     * The name of this component.
     */
    private String _name;
    private String _value = "";
    /**
     * Whether this component is disabled.
     */
    private boolean _disabled;

    // for Bug ZK-1746, we use three states, positive, -1, and Integer.MIN_VALUE
    private int _jsel = -1;
    private boolean _disabledEventListener = false; // for Bug ZK-1746
    /**
     * The open state of this component.
     */
    private boolean _open;
    /**
     * Whether send event back to server when user do select operation with a
     * value that not in the model.
     */
    private boolean _creatable;
    /**
     * The place holder of this component.
     */
    private String _emptyMessage;
    /**
     * The text that displayed while no match result, and not creatable.<br/>
     * Creatable denotes the component is set to creatable and the value not in
     * the model.
     */
    private String _noResultsText;
    /**
     * The text that displayed while no match result and creatable.<br/>
     * Creatable denotes the component is set to creatable and the value not in
     * the model.
     */
    private String _createMessage;
    /**
     * The specified keys that works as ENTER key, will not considered as input
     * but do select action.
     */
    private String _separator;
    /**
     * The model of this component.
     */
    private transient ListModel<?> _model;
    /**
     * The submodel used if _model is ListSubModel.
     */
    private transient ListModel<?> _subModel;
    private transient ListDataListener _dataListener;
    private transient EventListener<InputEvent> _eventListener;
    private transient ItemRenderer<?> _renderer;
    private transient boolean _childable;
    /**
     * For uuid - element - renderedContent
     */
    private transient Map<String, Object> _optIdElementMap;
    private transient Map<Object, String> _optElementIdMap;
    private transient Map<Object, String> _optElementContentMap;
    private transient Map<Object, String> _optElementSclassMap;
    private transient List<Object> _options;
    private transient List<Object> _chgSel;
    private static final String ATTR_ON_INIT_RENDER_POSTED = "org.zkoss.zul.onInitLaterPosted";
    private static final String ATTR_ON_INIT_RENDERSEL_POSTED = "org.zkoss.zul.onInitSelLaterPosted";

    static {
        addClientEvent(Chosenbox.class, Events.ON_SELECT, CE_DUPLICATE_IGNORE | CE_IMPORTANT);
        addClientEvent(Chosenbox.class, Events.ON_OPEN, CE_IMPORTANT);
        addClientEvent(Chosenbox.class, "onSelectSubModel", CE_IMPORTANT);
        addClientEvent(Chosenbox.class, "onSearch", CE_DUPLICATE_IGNORE | CE_IMPORTANT);
        addClientEvent(Chosenbox.class, "onSearching", CE_DUPLICATE_IGNORE | CE_IMPORTANT);

        // ZK-1053
        addClientEvent(Chosenbox.class, Events.ON_FOCUS, CE_DUPLICATE_IGNORE);
        addClientEvent(Chosenbox.class, Events.ON_BLUR, CE_DUPLICATE_IGNORE);
        // ZK-3098
        addClientEvent(Chosenbox.class, "onItemClick", CE_DUPLICATE_IGNORE);
    }

    public String getZclass() {
        return _zclass == null ? "z-chosenbox" : _zclass;
    }

    public void setOpen(boolean open) {
        if (_open != open) {
            _open = open;
            smartUpdate("open", _open);
        }
    }

    public boolean isOpen() {
        return _open;
    }

    /**
     * Returns whether it is disabled.
     * <p>
     * Default: false.
     */
    public boolean isDisabled() {
        return _disabled;
    }

    /**
     * Sets whether it is disabled.
     */
    public void setDisabled(boolean disabled) {
        if (_disabled != disabled) {
            _disabled = disabled;
            smartUpdate("disabled", _disabled);
        }
    }

    /**
     * Returns the name of this component.
     * <p>
     * Default: null.
     * <p>
     * The name is used only to work with "legacy" Web application that handles
     * user's request by servlets. It works only with HTTP/HTML-based browsers.
     * It doesn't work with other kind of clients.
     * <p>
     * Don't use this method if your application is purely based on ZK's
     * event-driven model.
     */
    public String getName() {
        return _name;
    }

    /**
     * Sets the name of the input element of this component.
     * <p>
     * The name is used only to work with "legacy" Web application that handles
     * user's request by servlets. It works only with HTTP/HTML-based browsers.
     * It doesn't work with other kind of clients.
     * <p>
     * Don't use this method if your application is purely based on ZK's
     * event-driven model.
     *
     * @param name the name of this component.
     */
    public void setName(String name) {
        if (name != null && name.length() == 0) {
            name = null;
        }
        if (!Objects.equals(_name, name)) {
            _name = name;
            smartUpdate("name", name);
        }
    }

    /**
     * Returns the emptyMessage of the input of this component.
     * <p>
     * Default: null.
     * <p>
     * The emptyMessage will be displayed in input if nothing selected and not
     * focused.
     */
    public String getEmptyMessage() {
        return _emptyMessage;
    }

    /**
     * Sets the emptyMessage of the input of this component.
     * <p>
     * The emptyMessage will be displayed in input if nothing selected and not
     * focused.
     *
     * @param emptyMessage the emptyMessage of the input of this component.
     */
    public void setEmptyMessage(String emptyMessage) {
        if (emptyMessage != null && emptyMessage.length() == 0) {
            emptyMessage = null;
        }
        if (!Objects.equals(_emptyMessage, emptyMessage)) {
            _emptyMessage = emptyMessage;
            smartUpdate("emptyMessage", getEmptyMessage());
        }
    }

    /**
     * Returns the no-result text of this component.
     * <p>
     * Default: null.
     * <p>
     * The no-result text will be displayed in popup if nothing match to the
     * input value and can not create either, the syntax "{0}" will be replaced
     * with the input value at client side.
     */
    public String getNoResultsText() {
        return _noResultsText;
    }

    /**
     * Sets the no-result text of this component.
     * <p>
     * The no-result text will be displayed in popup if nothing match to the
     * input value and can not create either, the syntax "{0}" will be replaced
     * with the input value at client side.
     *
     * @param noResultsText the no-result text of this component.
     */
    public void setNoResultsText(String noResultsText) {
        if (noResultsText != null && noResultsText.length() == 0) {
            noResultsText = null;
        }
        if (!Objects.equals(_noResultsText, noResultsText)) {
            _noResultsText = noResultsText;
            smartUpdate("noResultsText", getNoResultsText());
        }
    }

    /**
     * Returns the create message of this component.
     * <p>
     * Default: null.
     * <p>
     * The create message will be displayed in popup if nothing match to the
     * input value but can create as new label, the syntax "{0}" will be
     * replaced with the input value at client side.
     */
    public String getCreateMessage() {
        return _createMessage;
    }

    /**
     * Sets the create message of this component.
     * <p>
     * The create message will be displayed in popup if nothing match to the
     * input value but can create as new label, the syntax "{0}" will be
     * replaced with the input value at client side.
     *
     * @param createMessage the create message of this component.
     */
    public void setCreateMessage(String createMessage) {
        if (createMessage != null && createMessage.length() == 0) {
            createMessage = null;
        }
        if (!Objects.equals(_createMessage, createMessage)) {
            _createMessage = createMessage;
            smartUpdate("createMessage", getCreateMessage());
        }
    }

    /**
     * Returns the separate chars of this component.
     * <p>
     * Support: 0-9, A-Z (case insensitive), and ,.;'[]/\-=
     * <p>
     * Default: null.
     * <p>
     * The separate chars will work as 'Enter' key, it will not considered as
     * input value but send onSerch or onSearching while key up.
     */
    public String getSeparator() {
        return _separator;
    }

    /**
     * Sets the separate chars of this component.
     * <p>
     * Support: 0-9, A-Z (case insensitive), and ,.;'[]/\-=
     * <p>
     * The separate chars will work as 'Enter' key, it will not considered as
     * input value but send onSerch or onSelect while key up.
     */
    public void setSeparator(String separator) {
        if (separator != null && separator.length() == 0) {
            separator = null;
        }
        if (!Objects.equals(_separator, separator)) {
            _separator = separator;
            smartUpdate("separator", getSeparator());
        }
    }

    /**
     * Returns the selected objects.
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public <T> Set<T> getSelectedObjects() {
        if (getModel() == null) {
            return null;
        }
        Set selection = new LinkedHashSet();
        selection.addAll(getSelectableModel().getSelection());
        return selection;
    }

    /**
     * Sets the selected objects.
     *
     * @param objects the objects to select.
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public void setSelectedObjects(Collection<?> objects) {
        if (getModel() != null) {
            Set selection = new LinkedHashSet();
            Selectable smodel = getSelectableModel();
            ListModel<String> lm = getModel();
            boolean found = false;
            Iterator it = objects.iterator();
            while (it.hasNext()) {
                Object obj = it.next();
                for (int i = 0; i < lm.getSize(); i++) {
                    if (Objects.equals(lm.getElementAt(i), obj)) {
                        if (_jsel <= -1 || _jsel > i) {
                            _jsel = i;
                        }
                        found = true;
                        selection.add(lm.getElementAt(i));
                        break;
                    }
                }
                if (!found) {
                    throw new UiException("No such item: " + obj);
                }
                found = false;
            }
            smodel.setSelection(selection);
            prepareChgSel();
            smartUpdate("chgSel", encloseOptions(getChgSel()));
        }
    }

    /**
     * Returns the index of the selected item (-1 if no one is selected).
     */
    public int getSelectedIndex() {
        if (_jsel == Integer.MIN_VALUE) {
            Selectable<Object> selectableModel = this.getSelectableModel();
            if (selectableModel != null) {
                if (selectableModel.isSelectionEmpty()) {
                    _jsel = -1;
                } else {
                    syncSelectedIndex(selectableModel.getSelection().iterator().next());
                }
            } else {
                _jsel = -1;
            }
        }
        return _jsel;
    }

    /**
     * Sets the index of the selected item (-1 if no one is selected). If model
     * already exists, it will update the selection of model.
     *
     * @param jsel the index to select.
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    public void setSelectedIndex(int jsel) {
        // update selection if model already exists
        ListModel model = getModel();
        if (jsel < -1) {
            jsel = -1;
        }
        if (jsel != _jsel || (model != null && model.getSize() > 1)) {
            _jsel = jsel;
            if (model != null) {
                if (model.getSize() <= _jsel) {
                    _jsel = model.getSize() - 1;
                }
                Set selection = new LinkedHashSet();
                if (_jsel >= 0) {
                    selection.add(model.getElementAt(_jsel));
                }
                getSelectableModel().setSelection(selection);
            }
            smartUpdate("selectedIndex", _jsel);
        }
    }

    /**
     * Returns whether can create new item.
     * <p>
     * Default: false.
     * <p>
     * true: will show create message while value of input not exists.
     * <p>
     * false: will show no result message while value of input not exists.
     */
    public boolean isCreatable() {
        return _creatable;
    }

    /**
     * Sets whether can create new item.
     * <p>
     * Default: false.
     * <p>
     * true: will show create message while value of input not exists.
     * <p>
     * false: will show no-result text while value of input not exists.
     *
     * @param creatable the boolean value.
     */
    public void setCreatable(boolean creatable) {
        if (_creatable != creatable) {
            _creatable = creatable;
            smartUpdate("creatable", _creatable);
        }
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public <T> ItemRenderer<T> getRealRenderer() {
        final ItemRenderer renderer = getItemRenderer();
        return renderer != null ? renderer : (ItemRenderer) _defRend;
    }

    /**
     * Returns the renderer to render each item, or null if the default renderer
     * is used.
     */
    @SuppressWarnings("unchecked")
    public <T> ItemRenderer<T> getItemRenderer() {
        return (ItemRenderer<T>) _renderer;
    }

    /**
     * Returns the model associated with this chosenbox, or null if this
     * chosenbox is not associated with any list data model.
     */
    @SuppressWarnings("unchecked")
    public <T> ListModel<T> getModel() {
        return (ListModel<T>) _model;
    }

    /**
     * Sets the list model associated with this chosenbox. If a non-null model
     * is assigned, no matter whether it is the same as the previous, it will
     * always cause re-render.
     *
     * @param model the list model to associate, or null to dis-associate any
     * previous model.
     * @exception UiException if failed to initialize with the model
     */
    @SuppressWarnings({"rawtypes"})
    public void setModel(ListModel<?> model) {
        if (model != null) {
            if (!(model instanceof Selectable)) {
                throw new UiException(model.getClass() + " must implement " + Selectable.class);
            }
            if (_model != model) {
                Selectable smodel;
                if (_model != null) {
                    _model.removeListDataListener(_dataListener);
                    // B70-ZK-2004: Remove event listener and sub model.
                    if (_model instanceof ListSubModel) {
                        removeEventListener("onSearching", _eventListener);
                        _subModel = null;
                    }
                }
                _model = model;
                resetOpts();
                smodel = getSelectableModel();
                smodel.setMultiple(true);
                // B70-ZK-2004: Fix selected index, clear all the previous selection.
                if (_jsel >= 0) {
                    _jsel = -1;
                }
                initDataListener();
            }
        } else if (_model != null) {
            _model.removeListDataListener(_dataListener);
            if (_model instanceof ListSubModel) {
                removeEventListener("onSearching", _eventListener);
                // B70-ZK-2004: Remove sub model.
                _subModel = null;
            }
            _model = null;
        }
        smartUpdate("renderByServer", _model instanceof ListSubModel);
        postOnInitRender();
    }

    private String getOptIdByElement(Object element, boolean autoCreate) {
        String uuid = getOptElementIdMap().get(element);
        if (uuid == null && autoCreate) {
            uuid = ((DesktopCtrl) getDesktop()).getNextUuid(this);
            getOptIdElementMap().put(uuid, element);
            getOptElementIdMap().put(element, uuid);
        }
        return uuid;
    }

    private Object getOptElementById(String uuid) {
        return getOptIdElementMap().get(uuid);
    }

    private String getOptContentByElement(Object element) {
        return getOptElementContentMap().get(element);
    }

    private String getOptSclassByElement(Object element) {
        return getOptElementSclassMap().get(element);
    }

    private Map<String, Object> getOptIdElementMap() {
        if (_optIdElementMap == null) {
            _optIdElementMap = new HashMap<String, Object>();
        }
        return _optIdElementMap;
    }

    private Map<Object, String> getOptElementContentMap() {
        if (_optElementContentMap == null) {
            _optElementContentMap = new HashMap<Object, String>();
        }
        return _optElementContentMap;
    }

    private Map<Object, String> getOptElementSclassMap() {
        if (_optElementSclassMap == null) {
            _optElementSclassMap = new HashMap<Object, String>();
        }
        return _optElementSclassMap;
    }

    private Map<Object, String> getOptElementIdMap() {
        if (_optElementIdMap == null) {
            _optElementIdMap = new HashMap<Object, String>();
        }
        return _optElementIdMap;
    }

    private List<Object> getChgSel() {
        if (_chgSel == null) {
            _chgSel = new LinkedList<Object>();
        }
        return _chgSel;
    }

    private List<Object> getOptions() {
        if (_options == null) {
            _options = new LinkedList<Object>();
        }
        return _options;
    }

    private void resetOpts() {
        _optElementContentMap = null;
        _optIdElementMap = null;
        _optElementIdMap = null;
        _options = null;
        _chgSel = null;
    }

    private List<Map<String, Object>> encloseOptions(List<Object> opts) {
        List<Map<String, Object>> result = new LinkedList<Map<String, Object>>();
        if (opts != null) {
            for (Object opt : opts) {
                Map<String, Object> map = new HashMap<String, Object>(2);
                map.put("id", getOptIdByElement(opt, true));
                map.put("content", getOptContentByElement(opt));
                map.put("sclass", getOptSclassByElement(opt));
                result.add(map);
            }
        }
        return result;
    }

    /**
     * Clear all selected objects.
     */
    @SuppressWarnings("rawtypes")
    public void clearSelection() {
        Selectable smodel = getSelectableModel();
        if (smodel != null) {
            _jsel = -1;
            smodel.clearSelection();
            _chgSel = null;
            smartUpdate("chgSel", encloseOptions(null));
        }
    }

    /**
     * Add an item into selection.
     *
     * @param o the object to add.
     */
    @SuppressWarnings("rawtypes")
    public void addItemToSelection(Object o) {
        if (getModel() != null) {
            ListModel lm = getModel();
            int size = lm.getSize();
            for (int i = 0; i < size; i++) {
                Object elmn = lm.getElementAt(i);
                if (Objects.equals(elmn, o)) {
                    getSelectableModel().addToSelection(elmn);
                    if (i < _jsel || _jsel <= 0) {
                        _jsel = i;
                    }
                }
            }
        }
    }

    /**
     * Remove an item from selection.
     *
     * @param o the object to remove.
     */
    @SuppressWarnings({"rawtypes", "unchecked"})
    public void removeItemFromSelection(Object o) {
        if (getModel() != null) {
            Selectable smodel = getSelectableModel();
            Set selection = new LinkedHashSet();
            selection.addAll(smodel.getSelection());
            if (selection.remove(o)) {
                smodel.setSelection(selection);
            }
        }
    }

    // prepare the selected items
    private void prepareChgSel() {
        prepareItems(null, true, _model);
    }

    // used to 'fake' that it can has children
    protected boolean isChildable() {
        return _childable;
    }

    // prepare all data, include options and selected items
    private void prepareData() {
        if (!(_model instanceof ListSubModel)) {
            prepareItems(null, false, _model);
        }
        if (_model != null && !getSelectableModel().isSelectionEmpty()) {
            prepareChgSel();
        }
    }

    /**
     * prepare the list content or selected items to render,
     *
     * @param prefix Only add the item starts with it if it is not null.
     * @param excludeUnselected Only add selected item, with select order.
     * @param model the model to render.
     */
    @SuppressWarnings({"unchecked", "rawtypes"})
    private void prepareItems(String prefix, boolean excludeUnselected, ListModel model) {
        if (model != null) {
            List<Object> optList = new LinkedList<Object>();
            final boolean old = _childable;
            final boolean isSubModel = getModel() instanceof ListSubModel;
            if (prefix != null) {
                prefix = prefix.toLowerCase(java.util.Locale.ENGLISH);
            }
            try {
                _childable = true;
                final ItemRenderer renderer = getRealRenderer();
                if (excludeUnselected) { // render selected item
                    Selectable smodel = getSelectableModel();
                    Set selection = smodel.getSelection();

                    // render selected items with index
                    for (Iterator it = selection.iterator(); it.hasNext();) {
                        Object item = it.next();
                        for (int i = 0, sz = model.getSize(); i < sz; i++) { // the index i is required by render
                            if (Objects.equals(model.getElementAt(i), item)) {
                                String s = renderer.render(this, item, i);
                                // B65-ZK-2019: Add all if is ListSubModel
                                if (isSubModel || prefix == null
                                        || s.toLowerCase(java.util.Locale.ENGLISH).startsWith(prefix)) {
                                    getOptElementContentMap().put(item, s);
                                    optList.add(item);
                                }
                            }
                        }
                    }
                    if (!optList.isEmpty()) {
                        _chgSel = optList;
                    } else {
                        _chgSel = new LinkedList<Object>();
                    }
                } else {
                    Template temp = getTemplate("model");
//                    Label createdLabel = (Label) (temp.create(null, null, null, null)[0]);
//                    String itemSclass = createdLabel.getSclass();
                    for (int i = 0, sz = model.getSize(); i < sz; i++) {
                        Object o = model.getElementAt(i);
                        String s = renderer.render(this, o, i);
                        // B65-ZK-2019: Add all if is ListSubModel
                        // ZK-2080: should skip if template exists
                        if ((isSubModel && temp == null) || prefix == null
                                || s.toLowerCase(java.util.Locale.ENGLISH).startsWith(prefix)) {
                            getOptElementContentMap().put(o, s);
                            if (o instanceof Office) {
                                Office office = (Office) o;
                                String color = office.getActive()?(OfficeType.values(OfficeType.SubType.GROUP).contains(office.getType())?"blue":""):"red";
                                getOptElementSclassMap().put(o, color);
                            }else if (o instanceof Tag) {
                                getOptElementSclassMap().put(o,((Tag) o).getColor().getCssClass());
                            }else {
                                getOptElementSclassMap().put(o,"");
                            }
                            optList.add(o);
                        }
                    }
                    if (!optList.isEmpty()) {
                        _options = optList;
                    } else {
                        _options = new LinkedList<Object>();
                    }
                }
            } catch (Exception e) {
                throw UiException.Aide.wrap(e);
            } finally {
                //clear possible children created in renderer
                _childable = old;
                getChildren().clear();
            }
        } else // model is null
        {
            if (excludeUnselected) {
                _chgSel = new LinkedList<Object>();
            } else {
                _options = new LinkedList<Object>();
            }
        }
    }

    // selectable
    @SuppressWarnings({"unchecked", "rawtypes"})
    private Selectable<Object> getSelectableModel() {
        return (Selectable) _model;
    }

    // update drop down content only
    @SuppressWarnings("rawtypes")
    private void updateListContent(String prefix, ListModel subModel) {
        if (Strings.isEmpty(prefix)) {
            smartUpdate("listContent", new String[0]);
        } else {
            prepareItems(prefix, false, subModel);
            smartUpdate("listContent", encloseOptions(getOptions()));
        }
    }

    @SuppressWarnings("rawtypes")
    private void syncSelectedIndex(Object item) {
        ListModel lm = getModel();
        if (lm != null) {
            Set selection = getSelectableModel().getSelection();
            if (!selection.isEmpty()) {
                // B65-ZK-2028: Sync SelectedIndex when using ListSubModel
                Object element;
                if (item != null) {
                    int end = _jsel >= 0 ? _jsel : lm.getSize();
                    for (int i = 0; i < end; i++) {
                        element = lm.getElementAt(i);
                        if (Objects.equals(element, item) && selection.contains(element)) {
                            _jsel = i;
                            break;
                        }
                    }
                } else {
                    int end = lm.getSize();
                    for (int i = 0; i < end; i++) {
                        element = lm.getElementAt(i);
                        if (selection.contains(element)) {
                            _jsel = i;
                            break;
                        }
                    }
                }
            } else {
                _jsel = -1;
            }
        }
    }

    @SuppressWarnings("rawtypes")
    private void initDataListener() {
        if (_dataListener == null) {
            _dataListener = new ListDataListener() {
                public void onChange(ListDataEvent event) {
                    // do not need invalidate if just SELECTION_CHANGED
                    // only update selection
                    if (event.getType() == ListDataEvent.SELECTION_CHANGED) {
                        if (!_disabledEventListener) // Bug ZK-1746, ignore wrong loop sent from client
                        {
                            postOnInitSelection();
                        }
                    } else // update all data
                    {
                        postOnInitRender();
                    }
                }
            };
        }
        if (_eventListener == null) {
            _eventListener = new EventListener<InputEvent>() {
                public void onEvent(InputEvent event) throws Exception {
                    if (getModel() instanceof ListSubModel) {
                        // use -1 to be consistent with Combobox

                        // ZK-2080: should not use getSubModel() when use template 
                        Template temp = getTemplate("model");
                        if (temp == null) {
                            _subModel = ((ListSubModel) _model).getSubModel(event.getValue(), -1);
                        } else {
                            _subModel = _model;
                        }
                        updateListContent(event.getValue(), _subModel);

                    }
                }
            };
        }
        _model.addListDataListener(_dataListener);

        if (_model instanceof ListSubModel) {
            addEventListener("onSearching", _eventListener);
        }
    }

    /**
     * Sets the renderer which is used to render each item if {@link #getModel}
     * is not null.
     *
     * <p>
     * Note: changing a render will not cause the chosenbox to re-render. If you
     * want it to re-render, you could assign the same model again (i.e.,
     * setModel(getModel())), or fire an {@link ListDataEvent} event.
     *
     * @param renderer the renderer, or null to use the default.
     * @exception UiException if failed to initialize with the model
     */
    public void setItemRenderer(ItemRenderer<?> renderer) {
        if (_renderer != renderer) {
            _renderer = renderer;
            invalidate();
        }
    }

    /**
     * Sets the renderer by use of a class name. It creates an instance
     * automatically.
     */
    @SuppressWarnings("rawtypes")
    public void setItemRenderer(String clsnm) throws ClassNotFoundException, NoSuchMethodException,
            IllegalAccessException, InstantiationException, java.lang.reflect.InvocationTargetException {
        if (clsnm != null) {
            setItemRenderer((ItemRenderer) Classes.newInstanceByThread(clsnm));
        }
    }

    // post the event that update data
    private void postOnInitRender() {
        if (getAttribute(ATTR_ON_INIT_RENDER_POSTED) == null) {
            setAttribute(ATTR_ON_INIT_RENDER_POSTED, Boolean.TRUE);
            // remove onInitSelection if exist
            removeAttribute(ATTR_ON_INIT_RENDERSEL_POSTED);
            Events.postEvent("onInitRender", this, null);
        }
    }

    public void onInitRender() {
        removeAttribute(ATTR_ON_INIT_RENDER_POSTED);
        // remove onInitSelection if exist
        removeAttribute(ATTR_ON_INIT_RENDERSEL_POSTED);
        resetOpts();
        prepareData();
        super.invalidate();
    }

    private void postOnInitSelection() {
        // do not post if already posted or onInitRender is posted
        if (getAttribute(ATTR_ON_INIT_RENDER_POSTED) == null && getAttribute(ATTR_ON_INIT_RENDERSEL_POSTED) == null) {
            setAttribute(ATTR_ON_INIT_RENDERSEL_POSTED, Boolean.TRUE);
            Events.postEvent("onInitSelection", this, null);
        }
    }

    public void onInitSelection() {
        // do nothing if attribute is removed
        if (getAttribute(ATTR_ON_INIT_RENDERSEL_POSTED) != null) {
            removeAttribute(ATTR_ON_INIT_RENDERSEL_POSTED);
            prepareData();
            smartUpdate("chgSel", encloseOptions(getChgSel()));
        }
    }

    // -- ComponentCtrl --//
    public void invalidate() {
        if (_model != null && (_model.getSize() > 0 && ((_options == null && !(_model instanceof ListSubModel))
                || (getSelectableModel().getSelection().size() > 0)))) {
            // post onInitRender to rerender content
            postOnInitRender();
        } else {
            super.invalidate();
        }

        org.zkoss.zkex.rt.Runtime.init(this);
    }

    // Cloneable//
    @SuppressWarnings("rawtypes")
    public Object clone() {
        final Chosenbox clone = (Chosenbox) super.clone();
        if (clone._model != null) {
            if (clone._model instanceof ComponentCloneListener) {
                final ListModel model = (ListModel) ((ComponentCloneListener) clone._model).willClone(clone);
                if (model != null) {
                    clone._model = model;
                }
            }
            clone.postOnInitRender();
            // we use the same data model but we have to create a new listener
            clone._dataListener = null;
            clone.initDataListener();
        }
        return clone;
    }

    // -- Serializable --//
    // NOTE: they must be declared as private
    private synchronized void writeObject(java.io.ObjectOutputStream s) throws java.io.IOException {
        s.defaultWriteObject();

        willSerialize(_model);
        s.writeObject(
                _model instanceof java.io.Serializable || _model instanceof java.io.Externalizable ? _model : null);
        willSerialize(_renderer);
        s.writeObject(_renderer instanceof java.io.Serializable || _renderer instanceof java.io.Externalizable
                ? _renderer : null);
    }

    @SuppressWarnings("rawtypes")
    private void readObject(java.io.ObjectInputStream s) throws java.io.IOException, ClassNotFoundException {
        s.defaultReadObject();

        _model = (ListModel) s.readObject();
        didDeserialize(_model);
        _renderer = (ItemRenderer) s.readObject();
        didDeserialize(_renderer);
        if (_model != null) {
            initDataListener();
        }
        postOnInitRender();
    }

    public void sessionWillPassivate(Page page) {
        super.sessionWillPassivate(page);
        willPassivate(_model);
        willPassivate(_renderer);
    }

    public void sessionDidActivate(Page page) {
        super.sessionDidActivate(page);
        didActivate(_model);
        didActivate(_renderer);
        if (_model != null) {
            postOnInitRender();
        }
    }

    protected void renderProperties(org.zkoss.zk.ui.sys.ContentRenderer renderer) throws IOException {
        super.renderProperties(renderer);
        org.zkoss.zkex.rt.Runtime.init(this);

        render(renderer, "items", encloseOptions(getOptions()));
        render(renderer, "chgSel", encloseOptions(getChgSel()));
        render(renderer, "name", _name);
        render(renderer, "disabled", isDisabled());

        render(renderer, "emptyMessage", getEmptyMessage());
        render(renderer, "noResultsText", getNoResultsText());
        render(renderer, "separator", getSeparator());
        render(renderer, "createMessage", getCreateMessage());

        renderer.render("creatable", _creatable);
        renderer.render("renderByServer", _model instanceof ListSubModel);
        render(renderer, "open", _open);
    }

    private <T> Set<T> collectUnselectedObjects(Set<T> previousSelection, Set<T> currentSelection) {
        Set<T> prevSeldItems = previousSelection != null ? new LinkedHashSet<T>(previousSelection)
                : new LinkedHashSet<T>();
        if (currentSelection != null && prevSeldItems.size() > 0) {
            prevSeldItems.removeAll(currentSelection);
        }
        return prevSeldItems;
    }

    @SuppressWarnings({"unchecked", "rawtypes"})
    public void service(org.zkoss.zk.au.AuRequest request, boolean everError) {
        final String cmd = request.getCommand();
        if (cmd.equals(Events.ON_SELECT)) {
            Map<String, Object> data = request.getData();
            int newMin = (Integer) data.get("min");

            List<String> selItemUuids = (List<String>) data.get("selectedUuids");
            final Set<Object> prevSet = getSelectableModel().getSelection();
            final Set prevSelection = prevSet != null && !prevSet.isEmpty() ? new LinkedHashSet(prevSet) : null;

            ListModel model = getModel();
            Selectable smodel = getSelectableModel();
            Set selection = new LinkedHashSet();
            //Bug ZK-1660: selection should be Object in ListModel instead of String
            for (String itemUuid : selItemUuids) {
                Object o = getOptElementById(itemUuid);
                if (o != null) {
                    selection.add(o);
                }
            }
            if (selection.size() == 0) { // selection is empty
                _jsel = -1;
            } else if (_jsel > newMin) {
                _jsel = newMin;
            }
            try {
                _disabledEventListener = true;
                smodel.setSelection(selection);
            } finally {
                _disabledEventListener = false;
            }

            Set unselection = collectUnselectedObjects(prevSelection, selection);

            Events.postEvent(new SelectEvent(Events.ON_SELECT, this, null, null, null, selection, prevSelection,
                    unselection, null, null, 0));
        } else if (cmd.equals("onSelectSubModel")) {
            // B65-ZK-2028: Select or deselect item when using ListSubModel
            String itemUuid = (String) request.getData().get("itemUuid");
            Boolean select = (Boolean) request.getData().get("select");

            Selectable smodel = getSelectableModel();
            final Set<Object> prevSet = getSelectableModel().getSelection();
            final Set prevSelection = prevSet != null && !prevSet.isEmpty() ? new LinkedHashSet(prevSet) : null;
            Set selection;
            Object selectItem = null;
            try {
                _disabledEventListener = true;
                selectItem = getOptElementById(itemUuid);
                if (selectItem != null) {
                    List<Object> chgSel = getChgSel();
                    if (select) {
                        smodel.addToSelection(selectItem);
                        chgSel.add(getOptElementById(itemUuid));
                    } else {
                        smodel.removeFromSelection(selectItem);
                        for (int i = 0; i < chgSel.size(); i++) {
                            if (itemUuid.equals(chgSel.get(i))) {
                                chgSel.remove(i);
                                break;
                            }
                        }
                    }
                    _chgSel = chgSel;
                }
                selection = smodel.getSelection();
                if (selection.isEmpty()) { // selection is empty
                    _jsel = -1;
                } else {
                    _jsel = Integer.MIN_VALUE; // force to resync;
                }
            } finally {
                _disabledEventListener = false;
            }

            Set unselection = collectUnselectedObjects(prevSelection, selection);

            Events.postEvent(new SelectEvent(Events.ON_SELECT, this, null, null, null, selection, prevSet, unselection,
                    null, null, 0));
        } else if (cmd.equals(Events.ON_OPEN)) {
            _open = (Boolean) request.getData().get("open");
            Events.postEvent(OpenEvent.getOpenEvent(request));
        } else if (cmd.equals("onSearch")) {
            Events.postEvent(new InputEvent(cmd, this, (String) request.getData().get("text"), ""));
        } else if (cmd.equals("onSearching")) {
            String data = (String) request.getData().get("text");
            Events.postEvent(new InputEvent(cmd, this, data, _value));
            _value = data;
        } else if (cmd.equals("onItemClick")) { // ZK-3098
            String clickedItemUuid = (String) request.getData().get("itemUuid");
            if (clickedItemUuid != null && clickedItemUuid.length() != 0) {
                Events.postEvent("onItemClick", this, getOptElementById(clickedItemUuid));
            }
        } else // ZK-1053
        {
            super.service(request, everError);
        }
    }

    private static final ItemRenderer<Object> _defRend = new ItemRenderer<Object>() {
        public String render(final Component owner, final Object data, final int index) {
            final Chosenbox self = (Chosenbox) owner;
            final Template tm = self.getTemplate("model");
            if (tm == null) {
                return Objects.toString(data);
            } else {
                final Component[] items = tm.create(owner, null,
                        new VariableResolver() {
                    public Object resolveVariable(String name) {
                        if ("each".equals(name)) {
                            return data;
                        } else if ("forEachStatus".equals(name)) {
                            return new ForEachStatus() {
                                public ForEachStatus getPrevious() {
                                    return null;
                                }

                                public Object getEach() {
                                    return data;
                                }

                                public int getIndex() {
                                    return index;
                                }

                                public Integer getBegin() {
                                    return 0;
                                }

                                public Integer getEnd() {
                                    return ((Chosenbox) owner).getModel().getSize();
                                }
                            };
                        } else {
                            return null;
                        }
                    }
                }, null);
                if (items.length != 1) {
                    throw new UiException(
                            "The model template must have exactly one item, not "
                            + items.length);
                }
                if (!(items[0] instanceof Label)) {
                    throw new UiException(
                            "The model template can only support Label component, not "
                            + items[0]);
                }
                items[0].detach(); //remove the label from owner
                return ((Label) items[0]).getValue();
            }
        }
    };

}

For the js, put it under Web Pages/WEB_INF/cwr/js/zkmax/inp.
Don't forget, this is the src file, you will need Chosenbox.js to have it work.
That's the minified js file.

Chosenbox.src.js :

/*ZK 8.0.2.1 version of chosenbox*/
(function () {

    function clearAllData(wgt) {
        wgt._ppMaxHeight = wgt.fixDisplay = wgt._separatorCode = wgt._startOnSearching = wgt._chgSel = wgt.fixInputWidth = null;
    }
    function startOnSearching(wgt) {
        if (!wgt._startOnSearching)
            wgt._startOnSearching = setTimeout(function () {
                if (wgt.$n('inp')) 
                    wgt._fireOnSearching(wgt.$n('inp').value);
                wgt._startOnSearching = null;
            }, wgt._getSearchingInterval());
    }

zkmax.inp.Chosenbox = zk.$extends(zul.Widget, {
    $init: function () {
        this.$supers('$init', arguments);
        this._selItems = [];
        this._separatorCode = [];
        this._ppMaxHeight = 350;
    },
    $define: {
        items: function (v) {
            if (!this._renderByServer)
                this.setListContent();
        },


        tabindex: function (tabindex) {
            var n = this.$n('inp');
            if (n) {
                if (tabindex == null)
                    n.removeAttribute('tabindex');
                else
                    n.tabIndex = tabindex;
            }
        },


        selectedIndex: function (v) {
            var options,
                sel;

            if (!this._renderByServer) {
                this._clearSelection();
                if ((sel = this.$n('sel')) && v >= 0) {
                    options = jq(sel).children();
                    if (v < options.length)
                        this._doSelect(options[v]);
                }
            }
            if (v == -1)
                this._fixEmptyMessage(true);
        },
        renderByServer: function (v) {
            if (v && this.$n())
                this._clearListContent();
        },


        disabled: function (disabled) {
            var n = this.$n('inp');
            if (n)
                n.disabled = disabled ? 'disabled' : '';
            n = this.$n();
            if (n)
                jq(n)[disabled ? 'addClass' : 'removeClass'](this.$s('disabled'));
        },


        name: function (name) {
            var n = this.$n('inp');
            if (n) n.name = name;
        },


        emptyMessage: null,


        noResultsText: null,


        createMessage: null,


        separator: function (v) {
            var separatorCode = this._separatorCode;
            separatorCode.length = 0;






            if (v.indexOf(',') != -1)
                separatorCode.push(44);
            if (v.indexOf('.') != -1)
                separatorCode.push(46);
            if (v.indexOf('/') != -1)
                separatorCode.push(47);
            if (v.indexOf(';') != -1)
                separatorCode.push(59);
            if (v.indexOf("'") != -1)
                separatorCode.push(39);
            if (v.indexOf('[') != -1)
                separatorCode.push(91);
            if (v.indexOf(']') != -1)
                separatorCode.push(93);
            if (v.indexOf('\\') != -1)
                separatorCode.push(92);
            if (v.indexOf('-') != -1)
                separatorCode.push(45);
            if (v.indexOf('=') != -1)
                separatorCode.push(61);
        },


        creatable: null,


        open: null
    },
    setListContent: function (v) {
        var sel,
            out,
            oldHlite,
            id;
        if (sel = this.$n('sel')) {

            if (oldHlite = jq(this.$n('sel')).find('.' + this.$s('option-hover'))[0])
                id = oldHlite.id.replace('-opt', '');
            out = new zk.Buffer();
            this._renderItems(out, v);
            this._clearListContent();
            sel.innerHTML = out.join('');

            if (id && (oldHlite = this._getOptionById(id)))
                this._hliteOpt(oldHlite, true);
            this._startFixDisplay({hliteFirst: true, fromServer: true});
        }
    },



    _clearListContent: function () {
        if (this.$n()) {
            this.$n('sel').innerHTML = '';
            this.$n('empty').style.display = 'none';
        }
    },

    _renderItems: function (out, content) {
        var s = $eval(content ? content : this._items) || [];
        for (var i = 0, j = s.length; i < j; i++) {
            out.push('<div id="', s[i].id ,'-opt" class="',this.$s('option'),' ', s[i].sclass,'">', s[i].content, '</div>');
        }
    },

    setChgSel: function (val) { 
        if (val && val.$equals(this._selItems)) 
            return;
        this._clearSelection();
        var sel,
            options;
        if (sel = this.$n('sel')) { 
            options = jq(sel).children();
            var s = $eval(val),
                item,
                value;
            for (var i = 0; i < s.length; i++) {
                value = s[i];
                if (item = this._getOptionById(value.id))
                    this._doSelect(item);
                else
                    this._selectItemDirectly(value);
            }
        } else
            this._chgSel = val; 
        this._fixEmptyMessage(true);
    },
    bind_: function () {
        this.$supers(zkmax.inp.Chosenbox, 'bind_', arguments);
        var n = this.$n(),
            inp = this.$n('inp'),
            chgSel = this._chgSel || this._selItems;

        this.domListen_(inp, 'onFocus', 'doFocus_')
            .domListen_(inp, 'onBlur', 'doBlur_');


        zWatch.listen({onFloatUp: this, onSize: this, onScroll: this});

        if (chgSel) {
            if (this._selItems && this._selItems.length)
                this._selItems = []; 
            this.setChgSel(chgSel);
            this._chgSel = null;
        }

        this._fixEmptyMessage(true);
        if (this._open && !this.isDisabled())
            this.setOpen(true);
    },
    unbind_: function () {
        var inp = this.$n('inp');
        this.domUnlisten_(inp, 'onFocus', 'doFocus_')
            .domUnlisten_(inp, 'onBlur', 'doBlur_');
        zWatch.unlisten({onFloatUp: this, onSize: this, onScroll: this});
        this.setOpen(false); 
        clearAllData(this);
        this.$supers(zkmax.inp.Chosenbox, 'unbind_', arguments);
    },
    onSize: function () {
        this._fixInputWidth();
        this._fixWidth(this.$n());
    },
    _fixWidth: function (n) {
        if (this._width)
            n.style.width = this._width;
        var pp = this.$n('pp');
        pp.style.width = zk(pp).revisedWidth(n.offsetWidth) + 'px';
    },
    doBlur_: function (evt) {
        jq(this.$n()).removeClass(this.$s('focus'));


        var self = this;
        setTimeout(function () {
            if (self.desktop && zk.currentFocus != self) {
                self._fixEmptyMessage(true);
            }
        }, 200);
        return this.$supers('doBlur_', arguments);
    },
    doFocus_: function (evt) {
        if (!this.isDisabled()) {
            jq(this.$n()).addClass(this.$s('focus'));
            var inp = this.$n('inp');
            if (inp && inp.value == this._emptyMessage) {
                inp.value = '';
            }

        }
        return this.$supers('doFocus_', arguments);
    },
    doMouseOver_: function (evt) {
        var target = evt.domTarget;

        if (jq(target).hasClass(this.$s('option')))
            this._hliteOpt(target, true);
    },
    doMouseOut_: function (evt) {
        var target = evt.domTarget;

        if (jq(target).hasClass(this.$s('option-hover')))
            this._hliteOpt(target, false);
    },
    _hliteOpt: function (target, highlight) {
        var zcls = this.$s('option-hover');
        if (highlight) {

            var oldHlite = jq(this.$n('pp')).find('.' + zcls)[0];
            if (oldHlite)
                jq(oldHlite).removeClass(zcls);
            jq(target).addClass(zcls);
        } else
            jq(target).removeClass(zcls);
    },
    _doArrowDown: function (key, evt) {
        if (key == 'up')
            this._moveOptionFocus('prev');
        else if (key == 'down')
            this._moveOptionFocus('next');
        else {
            var inp = this.$n('inp'),
                pos = zk(inp).getSelectionRange(),
                label = jq(this.$n()).find('.' + this.$s('item-focus'))[0];

            if (pos[0] == 0 && pos[1] == 0) {
                if (key == 'left')
                    this._moveLabelFocus(label, 'prev');
                else if (key == 'right') {
                    if (label)
                        evt.stop();
                    this._moveLabelFocus(label, 'next');
                }
            }
        }
    },


    _moveOptionFocus: function (dir) {
        var sel = this.$n('sel'),
            pp = this.$n('pp'),
            $pp = jq(pp),
            oldHlite = $pp.find('.' + this.$s('option-hover'))[0],
            newHlite,
            next = dir == 'next';
        if (!oldHlite) { 
            oldHlite = $pp.find('.' + this.$s('option:hover'))[0];
            this._hliteOpt(oldHlite, true);
        }
        if (next && !this._open) 
            this.setOpen(true, {sendOnOpen: true});
        else {
            var creatableItem = $pp.find('.' + this.$s('empty-creatable'))[0],
                selFirstItem = sel.firstChild,
                selLastItem = sel.lastChild;
            if (this._creatable && creatableItem) {
                if (oldHlite) {
                    var prevS = (oldHlite == creatableItem) ? selLastItem : (oldHlite == selFirstItem) ? creatableItem : oldHlite.previousSibling,
                        nextS = (oldHlite == creatableItem) ? selFirstItem : (oldHlite == selLastItem) ? creatableItem : oldHlite.nextSibling;
                    newHlite = next ? nextS : prevS;
                } else {
                    newHlite = next ? creatableItem : selLastItem;
                }
                if (newHlite) 
                    while (newHlite && newHlite.style.display == 'none') {
                        var prevS = (newHlite == selFirstItem) ? creatableItem : newHlite.previousSibling,
                            nextS = (newHlite == selLastItem) ? creatableItem : newHlite.nextSibling;
                        newHlite = next ? nextS : prevS;
                    }
            } else {

                if (oldHlite) 
                    newHlite = next ? oldHlite.nextSibling : oldHlite.previousSibling;
                else 
                    newHlite = next ? sel.firstChild : 
                        !next ? sel.lastChild : null;
                if (newHlite) 
                    while (newHlite && newHlite.style.display == 'none')
                        newHlite = next ? newHlite.nextSibling : newHlite.previousSibling;
            }

            if (newHlite) {
                this._hliteOpt(newHlite, true);

                zk(newHlite).scrollIntoView(pp);
                this._currentTop = pp.scrollTop;
            } else if (oldHlite) {
                this._hliteOpt(oldHlite, false);

                zk(oldHlite).scrollIntoView(pp);
                this._currentTop = pp.scrollTop;
            }
        }
    },


    _moveLabelFocus: function (label, dir) {
        var zcls = this.$s('item-focus'),
            newLabel,
            next = dir == 'next';
        if (label) {
            jq(label).removeClass(zcls);
            newLabel = next ? label.nextSibling : label.previousSibling;
            if (!next && !newLabel)
                newLabel = label;
            else if (next && newLabel == this.$n('inp'))
                    newLabel = null;
        } else if (!next)
            newLabel = this.$n('inp').previousSibling;
        if (newLabel)
            jq(newLabel).addClass(zcls);
    },

    _deleteLabel: function (key, evt) {
        var inp = this.$n('inp'),
            pos = zk(inp).getSelectionRange(),
            label;


        if (pos[0] == 0 && pos[1] == 0) {
            var zcls = this.$s('item-focus');
            if (label = jq(this.$n()).find('.' + zcls)[0]) {
                var dir = (label.previousSibling && key == 'backspace') ? 'prev' : 'next';
                this._moveLabelFocus(label, dir);
                this._doDeselect(label, {sendOnSelect: true});
                evt.stop(); 

                this._startFixDisplay();
            }
            else if ((label = inp.previousSibling) && key == 'backspace')
                jq(label).addClass(zcls);
        }
    },
    _removeLabelFocus: function () {
        var zcls = this.$s('item-focus'),
            label = jq(this.$n()).find('.' + zcls)[0];
        if (label)
            jq(label).removeClass(zcls);
    },

    _doEnterPressed: function () {
        var $sel,
            hlited,
            old;

        if (old = this.fixDisplay)
            clearTimeout(old);
        this._fixDisplay();
        if (this._open) {
            if ((hlited = this.$n('empty')) && jq(hlited).hasClass(this.$s('empty-creatable')) && jq(hlited).hasClass(this.$s('option-hover'))) {
                this._fireOnSearch(this.$n('inp').value);
                if (this._open)
                    this.setOpen(false, {sendOnOpen: true});
            } else if ($sel = jq(this.$n('sel'))) {
                hlited = $sel.find('.' + this.$s('option-hover'))[0];
                if (!hlited && !(hlited = $sel.find('.' + this.$s('option:hover'))[0])) return;
                var options = $sel.children();
                this._doSelect(hlited, {sendOnSelect: true});
                if (this._open)
                    this.setOpen(false, {sendOnOpen: true});
            }
        }
    },
    doClick_: function (evt) {
        if (!this.isDisabled()) {
            var target = evt.domTarget,
                $target = jq(target),
                inp = this.$n('inp'),
                stopBuble = false,
                pcls = 'z-page',
                zcls = this.getZclass(),
                inpCls = this.$s('input'),
                itemCls = this.$s('item'),
                optCls = this.$s('option'),
                opthoverCls = this.$s('option-hover'),
                isOpt = false,
                isOptHover = false;

            this._removeLabelFocus();
            if (inp.value == this._emptyMessage)
                inp.value = '';

            if (!$target.hasClass(inpCls) && !$target.hasClass(zcls)) {
                do {
                    if ($target.hasClass(this.$s('empty-creatable'))) { 
                        this._fireOnSearch(this.$n('inp').value);
                        if (this._open)
                            this.setOpen(false, {sendOnOpen: true});
                        return;
                    } else if ($target.hasClass(optCls)) { 
                        this._doSelect($target[0], {sendOnSelect: true});
                        stopBuble = true;
                        isOpt = true;
                        break;
                    } else if ($target.hasClass(opthoverCls)) { 
                        this._fireOnSearch(inp.value);
                        isOpt = isOptHover = true;
                        break;
                    } else if ($target.hasClass(itemCls)) {
                        $target.addClass(this.$s('item-focus'));
                        var uuid = $target.attr('id').replace('-sel', '');
                        this.fire('onItemClick', {itemUuid: uuid});
                        break;
                    }
                    $target = $target.parent();
                } while (!$target.is('body') && !$target.hasClass(pcls));
            }

            if (!this._open && !isOpt)
                this.setOpen(true, {sendOnOpen: true});
            else if (this._open && isOpt) {
                if (isOptHover)
                    this.setOpen(false, {sendOnOpen: true});
                else
                    this.setOpen(false, {sendOnOpen: true, fixEmptyMessage: true});
            }

            inp.focus();
            if (stopBuble) 
                evt.stop();
            this.$supers('doClick_', arguments);
        }
    },

    _doSelect: function (target, opts) {
        this._hliteOpt(target, false);
        var id = target.id.replace('-opt', '');
        if (this._getItemById(id, true) == null) {
            var item = this._getItemById(id);
            if (item == null) 
                item = {id: id, content: target.innerHTML};
            this._createLabel(item);
            target.style.display = 'none'; 

            this._selItems.push(item);
            this._fixEmptyMessage(true);

            if (opts && opts.sendOnSelect) {
                if (!this._renderByServer)
                    this.fireOnSelect(); 
                else 
                    this.selectSubModel(id);
            }
        }
    },

    doSelect_: function (evt) {

        if (evt.domTarget == this.$n()) {
            evt.stop({revoke: true});
        }
        this.$super('doSelect_', arguments);
    },

    _selectItemDirectly: function (value) {
        if (this._selItems.indexOf(value) == -1) {
            this._createLabel(value);

            this._selItems.push(value);
            this._fixEmptyMessage(true);
        }
    },


    _doDeselect: function (selectedOption, opts) {
        var id = selectedOption.id.replace('-sel', ''),
            element = this._getOptionById(id),
            _selItems = this._selItems,
            index = -1;
        if (this._open)
            this.setOpen(false, {sendOnOpen: true});

        for (var i = 0, length = _selItems.length; i < length; i++)
            if (_selItems[i].id == id) index = i;
        _selItems.splice(index, 1);

        if (element)
            element.style.display = 'block';

        jq(selectedOption).remove();
        this._updatePopupPosition();
        if (opts && opts.sendOnSelect) {
            if (!this._renderByServer)
                this.fireOnSelect(); 
            else 
                this.deselectSubModel(id);
        }

        this._startFixDisplay();
    },
    _getItemById: function (id, isSel) {
        var opt = null,
            options = isSel ? this._selItems : this._items;

        if (options) {
            for (var i = 0, length = options.length; i < length; i++) {
                if (options[i].id == id) opt = options[i];
            }
        }
        return opt;
    },

    _getOptionById: function (id) {
        var options = jq(this.$n('sel')).children(),
            opt;
        id += '-opt';
        for (var i = 0; i < options.length; i++) {
            if ((opt = options[i]) && opt.id == id)
                return opt;
            else if (!opt) 
                return null;
        }
    },

    _createLabel: function (item) {
        var span = document.createElement('span'),
            content = document.createElement('div'),
            delbtn = document.createElement('div'),
            delbtnicon = document.createElement('i'),
            wgt = this;
        span.className = this.$s('item');
        span.id = item.id + '-sel';
        content.innerHTML = item.content;
        content.className = this.$s('item-content ') + ' ' + item.sclass;
        delbtn.className = this.$s('button') + ' ' + this.$s('delete');
        delbtnicon.className = this.$s('icon') + ' ' + 'z-icon-times';

        span.appendChild(content);
        span.appendChild(delbtn);
        delbtn.appendChild(delbtnicon);
        jq(delbtn).bind('click', function () {
            if (!wgt.isDisabled()) {
                wgt.$n('inp').focus();
                wgt._doDeselect(span, {sendOnSelect: true});
            }
        });
        this.$n().insertBefore(span, this.$n('inp')); 
    },

    _clearSelection: function (opts) {
        var n = this.$n(),
            inp = this.$n('inp'),
            c, 
            del;
        if (n)
            c = n.firstChild;
        while (c && c != inp) {
            del = c;
            c = c.nextSibling;
            this._doDeselect(del, opts);
        }
        this._selItems.length = 0;
    },

    fireOnSelect: function () {
        var options = jq(this.$n('sel')).children(),
            minIdx = -1, 
            selItems = this._selItems,
            selItemUuids = []; 


        for (var i = 0; i < options.length; i++)
            if (options[i].style.display == 'none') {
                minIdx = i;
                break;
            }
        for (var i = 0; i < selItems.length; i++)
            selItemUuids.push(selItems[i].id);

        this.fire('onSelect', {selectedUuids: selItemUuids, min: minIdx});
    },

    selectSubModel: function (optId) {
        data = {itemUuid: optId, select: true};
        this.fire('onSelectSubModel', data, {toServer: this.isListen('onSelect')});
    },

    deselectSubModel: function (optId) {
        data = {itemUuid: optId, select: false};
        this.fire('onSelectSubModel', data, {toServer: this.isListen('onSelect')});
    },

    _fireOnSearch: function (value) {
        var data = {text: value};
        this.fire('onSearch', data);
    },

    _fireOnSearching: function (value) {


        if (value || this._prevSearchingValue) {
            var data = {text: value};
            this.fire('onSearching', data);
            this._prevSearchingValue = value;
        }
    },

    onFloatUp: function (ctl) {
        if (ctl.origin != this) {
            if (this._open)
                this.setOpen(false, {sendOnOpen: true, fixEmptyMessage: true});
            this._removeLabelFocus();
        }
    },

    doKeyDown_: function (evt) {
        var keyCode = evt.keyCode;
        switch (keyCode) {
            case 8:
                this._deleteLabel('backspace', evt);
                break;
            case 9:

                break;
            case 13:
                break;
            case 27:
                break;
            case 37:
                this._doArrowDown('left', evt);
                break;
            case 38:
                this._doArrowDown('up');
                break;
            case 39:
                this._doArrowDown('right', evt);
                break;
            case 40:
                this._doArrowDown('down');
                break;
            case 46:
                this._deleteLabel('del', evt);
                break;
            default:
                this._updateInput(evt);
                if (!this._open)
                    this.setOpen(true, {sendOnOpen: true});
        }
        if (!(keyCode == 39 || keyCode == 46 || keyCode == 8 || keyCode == 37))
            this._removeLabelFocus();
    },
    doKeyPress_: function (evt) {

        var keyCode = evt.which || evt.charCode || evt.keyCode || 0;
        this._keyPressedIsSeparator = this._isSeparator(keyCode);
        if (this._keyPressedIsSeparator)
            evt.stop();
    },
    doKeyUp_: function (evt) {
        var keyCode = evt.keyCode,
            opts = {hliteFirst: true};
        switch (keyCode) {
            case 13:
                this._doEnterPressed();
                break;
            case 27:
                if (this._open)
                    this.setOpen(false, {sendOnOpen: true});
                this._fixEmptyMessage();
                break;
            default:
                if (this._keyPressedIsSeparator)
                    this._doEnterPressed();
                else {
                    this._fixInputWidth();
                    if (keyCode == 38 || keyCode == 40)
                        opts = null;
                    if (!this._renderByServer)
                        this._startFixDisplay(opts);
                }
        }
        if (!(keyCode >= 37 && keyCode <= 40 || keyCode == 13 || keyCode == 9))
            startOnSearching(this);
        this._keyPressedIsSeparator = false;
    },

    focus_: function (timeout) {
        if (zk.ie11_ && !timeout)
            timeout = 0;
        zk(this.getInputNode()).focus(timeout);
        return true;
    },

    _isSeparator: function (keyCode) {
        var separator = this._separator,
            separatorCode = this._separatorCode;
        return (separatorCode && separatorCode.indexOf(keyCode) != -1)
            || ((keyCode >= 48 && keyCode <= 122) && separator
                && separator.toUpperCase().indexOf(String.fromCharCode(keyCode)) != -1);
    },

    _updateInput: function (evt) {
        var wgt = this;



        if (!this.fixInputWidth)
            this.fixInputWidth = setTimeout(function () {
                if (wgt.$n()) 
                    wgt._fixInputWidth();
            }, 100);
    },

    setOpen: function (open, opts) {
        if (!this.isDisabled())
            this._open = open;
        if (this.$n('pp')) {
            var pp = this.$n('pp');
            if (open)
                this.open(this.$n(), pp, opts);
            else
                this.close(pp, opts);
        }
    },
    open: function (n, pp, opts) {
        var offset,
            ppstyle = pp.style;

        this._fixsz(pp);

        zk(pp).makeVParent();

        this.setFloating_(true);
        this.setTopmost();
        offset = jq(n).offset();
        ppstyle.left = offset.left + 'px';
        ppstyle.top = offset.top + jq(n).outerHeight() + 'px';
        ppstyle.zIndex = n.style.zIndex;

        zk(pp).slideDown(this, {duration: 100});
        this._startFixDisplay({hliteFirst: true});

        if (opts && opts.sendOnOpen)
            this.fire('onOpen', {open: true});
    },
    close: function (pp, opts) {
        zk(pp).undoVParent();
        this.setFloating_(false);
        pp.style.display = 'none';

        if (opts) {
            if (opts.sendOnOpen)
                this.fire('onOpen', {open: false});
            if (opts.fixEmptyMessage)
                this._fixEmptyMessage();
        }
        if (this._renderByServer)
            this._clearListContent();
    },

    _fixsz: function (pp) {
        var ppstyle = pp.style,
            maxh = this._ppMaxHeight;
        ppstyle.height = 'auto';
        ppstyle.left = '-10000px';
        ppstyle.display = 'block';
        ppstyle.visibility = 'hidden';
        if (jq(pp).height() > maxh)
            ppstyle.height = maxh + 'px';
        ppstyle.display = 'none';
        ppstyle.visibility = 'visible';
    },

    _fixInputWidth: function () {
        var n = this.$n(),
            inp = this.$n('inp'),
            txcnt = this.$n('txcnt'),
            oldh = jq(n).height(),
            width,
            max = parseInt(this._width) - 10;

        txcnt.innerHTML = inp.value;

        width = jq(txcnt).width() + 30;

        if (width > max)
            inp.style.width = max + 'px';
        else
            inp.style.width = width + 'px';
        if (jq(n).height() != oldh)
            this._updatePopupPosition(n, this.$n('pp'));
        if (this.fixInputWidth)
            clearTimeout(this.fixInputWidth);
        this.fixInputWidth = null;
    },

    _startFixDisplay: function (opts) {

        if (opts && opts.fromServer)
            this._fixDisplay(opts);
        else { 
            var wgt = this,
                old;
            if (old = this.fixDisplay)
                clearTimeout(old);
            this.fixDisplay = setTimeout(function () {
                if (wgt.$n()) {
                    wgt._fixDisplay(opts);
                    if (wgt._currentTop) 
                        wgt.$n('pp').scrollTop = wgt._currentTop;
                }
            }, 200);
        }
    },

    _fixDisplay: function (opts) {
        if (!this._open) return;
        var fromServer = opts && opts.fromServer;
        if (!this._renderByServer || fromServer) {
            var str = this.$n('inp').value,
                oldhlite = jq(this.$n('sel')).find('.' + this.$s('option-hover'))[0],
                existance = this._fixSelDisplay(opts && opts.hliteFirst, str, fromServer);
            str = str ? str.trim() : '';
            this._fixEmptyDisplay({showExistance: true}, str, existance._found, existance._exist, opts && opts.hliteFirst);
        } else {
            this._fixEmptyDisplay({showBlank: !this.$n('sel').firstChild});
        }
    },

    _fixSelDisplay: function (hliteFirst, str, fromServer) {
        var pp = this.$n('pp'),
            $pp = jq(pp),
            maxh = this._ppMaxHeight,
            ppstyle = pp.style,
            selItems = this._selItems,
            options = jq(this.$n('sel')).children(),
            found = false, 
            exist = false, 
            index, element, showAll, selected;
        str = str ? str.trim() : '';
        showAll = str && str == this._emptyMessage || str == '';

        for (index = 0, element = options[index];
            index < options.length;
            index++, element = options[index]) {

            selected = this._getItemById(element.id.replace('-opt', ''), true) != null;
            if (fromServer || !selected) {
                if (!selected &&    
                        (showAll || this._renderByServer || str && element.innerHTML.toLowerCase().startsWith(str.toLowerCase()))) {
                    if (!found) {
                        found = true;
                        if (hliteFirst)
                            this._hliteOpt(element, true);
                    }
                    element.style.display = 'block';
                }
                else {
                    this._hliteOpt(element, false);
                    element.style.display = 'none'; 
                }
            }
            if (!exist && str && element.innerHTML.toLowerCase() == str.toLowerCase())
                exist = true;
        }
        ppstyle.height = 'auto';
        if ($pp.height() > maxh)
            ppstyle.height = maxh + 'px';
        return {_found: found, _exist: exist};
    },

    _fixEmptyDisplay: function (type, str, found, exist, hliteFirst) {
        var ecls = this.$s('empty-creatable'),
            eOptCls = this.$s('option'),
            empty = this.$n('empty');
        if (type && (type.showBlank
                    || type.showExistance && this._renderByServer && !str)) {
            empty.innerHTML = '&nbsp;';
            empty.style.display = 'block';
        } else if (type && type.showExistance) {

            if (this._creatable && !exist && str) {
                var createMsg = this._createMessage,
                    icon = document.createElement('i');

                icon.className = this.$s('icon') + ' ' + this.$s('create') + ' ' + 'z-icon-plus-square';

                if (createMsg)
                    createMsg = zUtl.encodeXML(createMsg.replace(/\{0\}/g, str)).replace(/\n/g, '<br />');
                else
                    createMsg = '&nbsp;';

                var text = document.createElement('span');
                text.innerHTML = createMsg;
                empty.innerHTML = '';
                empty.appendChild(text);
                empty.insertBefore(icon, text);
                jq(empty).addClass(ecls);
                jq(empty).addClass(eOptCls);
                empty.style.display = 'block';
                if (hliteFirst)
                    this._hliteOpt(empty, true);
            } else {
                if (!found) { 
                    var empMsg = this._noResultsText;
                    if (empMsg)
                        empMsg = zUtl.encodeXML(empMsg.replace(/\{0\}/g, str)).replace(/\n/g, '<br />');
                    else
                        empMsg = '&nbsp;';
                    empty.innerHTML = empMsg;

                    jq(empty).removeClass(ecls);
                    jq(empty).removeClass(eOptCls);
                    empty.style.display = 'block';
                } else {
                    empty.style.display = 'none';
                    jq(empty).removeClass(ecls);
                    jq(empty).removeClass(eOptCls);
                }
            }
        }
    },
    _updatePopupPosition: function () {
        var n = this.$n(),
            pp = this.$n('pp'),
            offset = jq(n).offset();

        pp.style.left = offset.left + 'px';
        pp.style.top = offset.top + jq(n).outerHeight() + 'px';
    },

    _fixEmptyMessage: function (force) {
        var inp;
        if ((!this._open || force) && (inp = this.$n('inp'))) {

            inp.value = this._selItems.length == 0 ? zUtl.encodeXML(this.getEmptyMessage()) : '';
            this._fixInputWidth();
            if (this._open) {
                this._startFixDisplay();
            }
        }
    },
    domClass_: function (no) {
        var s1 = this.$supers('domClass_', arguments),
            s2 = this.isDisabled() ? this.$s('disabled') : '';
        return s1 + (s1 && s2 ? ' ' : '') + s2;
    },

    _getSearchingInterval: function () {
        return 350;
    },
    onScroll: function (wgt) {

        if (this._open) { 
            if (wgt) {
                var n = this.$n();
                if (n && zk(n).isRealScrollIntoView(true)) 
                    zk(this.$n('pp')).position(n, 'after_start');
                else
                    this.setOpen(false, {sendOnOpen: true});
            }
        }
    },
    getInputNode: function () {
        return this.$n('inp') || this.$n();
    }
});
})();

Chill.

link publish delete flag offensive edit
0

answered 2017-05-30 14:42:03 +0800

mfarmer gravatar image mfarmer
13 2

Thank you!

link publish delete flag offensive edit

Comments

You are welcome

chillworld ( 2017-05-31 05:30:14 +0800 )edit
Your answer
Please start posting your answer anonymously - your answer will be saved within the current session and published after you log in or create a new account. Please try to give a substantial answer, for discussions, please use comments and please do remember to vote (after you log in)!

[hide preview]

Question tools

Follow
1 follower

RSS

Stats

Asked: 2017-05-25 23:24:33 +0800

Seen: 37 times

Last updated: May 31 '17

Support Options
  • Email Support
  • Training
  • Consulting
  • Outsourcing
Learn More