package com.sksamuel.jqm4gwt.form.elements;
import com.google.gwt.core.client.JavaScriptObject;
import com.google.gwt.dom.client.Element;
import com.sksamuel.jqm4gwt.Empty;
import com.sksamuel.jqm4gwt.JQMCommon;
/**
* See <a href="http://demos.jquerymobile.com/1.4.5/selectmenu-custom-filter/">Filterable inside custom select</a>
* <br> See also <a href="https://github.com/jquery/jquery-mobile/blob/9cb1040f1da9309c30b70eccbbfb54a8ddf253aa/demos/selectmenu-custom-filter/index.php">Github Demos</a>
*
* @author SlavaP
*/
public class JQMSelectFilterable extends JQMSelect {
public static String CLEAR_BUTTON_TEXT = "-----";
private static final String SELECT_FILTERABLE_STYLENAME = "jqm4gwt-select-filterable";
private static final String FILTERABLE_SELECT = "filterable-select";
private static boolean jsServed;
private Element filterable;
private boolean showClearButton;
public JQMSelectFilterable() {
super();
addStyleName(SELECT_FILTERABLE_STYLENAME);
select.addStyleName(FILTERABLE_SELECT);
setNative(false);
}
@Override
public String getMenuStyleNames() {
String s = super.getMenuStyleNames();
// for CSS styling
String rslt = "ui-select-filterable";
if (!Empty.is(s)) rslt += " " + s;
return rslt;
}
/**
* There is a predefined ui-select-filterable CSS class, which is always added to menu dialog/popup.
* <br> You can style listview by defining rule .ui-selectmenu.ui-select-filterable .ui-selectmenu-list { ... }
* <br> For additional flexibility you can specify custom classes to be added together with ui-select-filterable
* @param menuStyleNames - space separated custom classes
*/
@Override
public void setMenuStyleNames(String menuStyleNames) {
super.setMenuStyleNames(menuStyleNames);
}
/* @Override
public Boolean doFiltering(Element elt, Integer index, String searchValue) {
// TODO: remove when this bug is fixed: https://github.com/jquery/jquery-mobile/issues/7677
String s = JQMCommon.getFilterText(elt);
if (s == null || s.isEmpty()) {
s = JQMCommon.getAttribute(elt, "data-option-index");
if (s != null && !s.isEmpty()) {
try {
int i = Integer.parseInt(s);
OptionElement opt = getOption(i);
if (opt != null) {
s = JQMCommon.getFilterText(opt);
if (s != null && !s.isEmpty()) {
JQMCommon.setFilterText(elt, s);
}
}
} catch (NumberFormatException ex) {
// nothing, can continue safely
}
}
}
return super.doFiltering(elt, index, searchValue);
} */
private void setFilterableElt(Element filterableElt) {
filterable = filterableElt;
if (filterable != null && JQMCommon.isFilterableReady(filterable)) {
JavaScriptObject origFilter = JQMCommon.getFilterCallback(filterable);
JQMCommon.bindFilterCallback(this, filterable, origFilter);
}
}
@Override
protected void onLoad() {
super.onLoad();
if (!jsServed) {
jsServed = true;
serveSelectsFilterable();
}
}
@Override
protected void onUnload() {
super.onUnload();
}
private static native boolean isPageSelectFilterableDialog(String pageId) /*-{
var isDialog = false;
var SEL = "." + @com.sksamuel.jqm4gwt.form.elements.JQMSelectFilterable::FILTERABLE_SELECT;
$wnd.$(SEL).each(function() {
var id = $wnd.$(this).attr("id");
if (id) {
var s = id + "-dialog";
if (s === pageId) {
isDialog = true;
return false;
}
}
});
return isDialog;
}-*/;
private static native void serveSelectsFilterable() /*-{
if ($wnd.$ === undefined || $wnd.$ === null) return; // jQuery is not loaded
var SEL = "." + @com.sksamuel.jqm4gwt.form.elements.JQMSelectFilterable::FILTERABLE_SELECT;
$wnd.$.mobile.document
// Upon creation of the select menu, we want to make use of the fact that the ID of the
// listview it generates starts with the ID of the select menu itself, plus the suffix "-menu".
// We retrieve the listview and insert a search input before it.
.on( "selectmenucreate", SEL, function( event ) {
var input,
selectmenu = $wnd.$( event.target ),
id = selectmenu.attr( "id" ),
listview = $wnd.$( "#" + id + "-menu" ),
form = listview.jqmData( "filter-form" );
var combo = @com.sksamuel.jqm4gwt.form.elements.JQMSelect::findCombo(Lcom/google/gwt/dom/client/Element;)
(event.target);
// We store the generated form in a variable attached to the popup so we avoid creating a
// second form/input field when the listview is destroyed/rebuilt during a refresh.
if ( !form ) {
input = $wnd.$( "<input data-type='search'></input>" );
form = $wnd.$( "<form style='padding-top: 1px;'></form>" ).append( input );
form.submit( function () { return false; } ); // fix for ENTER key press
input.textinput();
listview.before( form ).jqmData( "filter-form", form ) ;
form.jqmData( "listview", listview );
var isClear = combo.@com.sksamuel.jqm4gwt.form.elements.JQMSelectFilterable::isShowClearButton()();
if (isClear === true) {
var clearText = @com.sksamuel.jqm4gwt.form.elements.JQMSelectFilterable::CLEAR_BUTTON_TEXT;
var clearBtn = $wnd.$("<button class='ui-btn ui-mini' style='margin:0'>"
+ clearText + "</button>");
clearBtn.on('click', function() {
combo.@com.sksamuel.jqm4gwt.form.elements.JQMSelectFilterable::closeAndClearValue()();
});
listview.before( clearBtn ).jqmData( "clear-button", clearBtn );
}
} else {
input = form.find( "input" );
}
// Instantiate a filterable widget on the newly created selectmenu widget and indicate that
// the generated input form element is to be used for the filtering.
selectmenu.filterable({
input: input,
children: "> option[data-placeholder != 'true'][value]"
})
// Rebuild the custom select menu's list items to reflect the results of the filtering
// done on the select menu.
.on( "filterablefilter", function() {
selectmenu.selectmenu( "refresh" );
});
combo.@com.sksamuel.jqm4gwt.form.elements.JQMSelectFilterable::setFilterableElt(Lcom/google/gwt/dom/client/Element;)
(selectmenu[0]); // listview[0]
})
// The custom select list may show up as either a popup or a dialog, depending on how much
// vertical room there is on the screen. If it shows up as a dialog, then the form containing
// the filter input field must be transferred to the dialog so that the user can continue to
// use it for filtering list items.
.on( "pagecontainerbeforeshow", function( event, data ) {
var pageId = data.toPage && data.toPage.attr("id");
// We only handle the appearance of a dialog generated by a filterable selectmenu
var isDlg = @com.sksamuel.jqm4gwt.form.elements.JQMSelectFilterable::isPageSelectFilterableDialog(Ljava/lang/String;)
(pageId);
if (!isDlg) return;
var dialog = data.toPage;
var listview = dialog.find( "ul" );
var form = listview.jqmData( "filter-form" );
var clearBtn = listview.jqmData( "clear-button" );
// Attach a reference to the listview as a data item to the dialog, because during the
// pagecontainerhide handler below the selectmenu widget will already have returned the
// listview to the popup, so we won't be able to find it inside the dialog with a selector.
dialog.jqmData( "listview", listview );
// Place the form before the listview in the dialog.
listview.before( form );
if (clearBtn) {
// 110% is a hack, we should just add 32px
clearBtn.css("margin", "0 -16px 0 -16px").css("width", "110%");
listview.before( clearBtn );
}
})
// After the dialog is closed, the form containing the filter input is returned to the popup.
.on( "pagecontainerhide", function( event, data ) {
var pageId = data.prevPage && data.prevPage.attr("id");
// We only handle the disappearance of a dialog generated by a filterable selectmenu
var isDlg = @com.sksamuel.jqm4gwt.form.elements.JQMSelectFilterable::isPageSelectFilterableDialog(Ljava/lang/String;)
(pageId);
if (!isDlg) return;
var listview = data.prevPage.jqmData( "listview" );
var form = listview.jqmData( "filter-form" );
var clearBtn = listview.jqmData( "clear-button" );
// Put the form back in the popup. It goes ahead of the listview.
listview.before( form );
if (clearBtn) {
clearBtn.css("margin", "0").css("width", "100%");
listview.before( clearBtn );
}
});
}-*/;
public boolean isShowClearButton() {
return showClearButton;
}
/** Clear button will be shown on filtering dialog with text defined by CLEAR_BUTTON_TEXT field. */
public void setShowClearButton(boolean showClearButton) {
this.showClearButton = showClearButton;
}
protected void closeAndClearValue() {
this.close();
this.setValue(null, true/*fireEvents*/);
}
@Override
public void setValue(String value, boolean fireEvents) {
if (value == null) clearSearchInput();
super.setValue(value, fireEvents);
}
/** Clears entered text in search input on popup form. */
public void clearSearchInput() {
if (filterable != null && JQMCommon.isFilterableReady(filterable)) {
clearInput(filterable);
}
}
private static native void clearInput(Element filterable) /*-{
var fltr = $wnd.$(filterable);
var input = fltr.filterable("option", "input");
if (input) {
var t = $wnd.$(input);
if (t.val()) {
t.val("");
t.next(".ui-input-clear").addClass("ui-input-clear-hidden");
fltr.filterable("refresh");
}
}
}-*/;
}