Skip to content

Instantly share code, notes, and snippets.

@akbertram
Created June 26, 2018 19:55
Show Gist options
  • Save akbertram/381043f3dfd4a0ade95d396a27a46bc0 to your computer and use it in GitHub Desktop.
Save akbertram/381043f3dfd4a0ade95d396a27a46bc0 to your computer and use it in GitHub Desktop.
Fledgling vdom-based replacement for search box
package org.activityinfo.ui.client.search;
import com.google.common.base.Strings;
import com.google.gwt.dom.client.InputElement;
import com.google.gwt.event.dom.client.KeyCodes;
import org.activityinfo.i18n.shared.I18N;
import org.activityinfo.model.database.Resource;
import org.activityinfo.model.database.UserDatabaseMeta;
import org.activityinfo.observable.Observable;
import org.activityinfo.observable.StatefulValue;
import org.activityinfo.ui.client.base.NonIdeal;
import org.activityinfo.ui.client.store.FormStore;
import org.activityinfo.ui.vdom.shared.html.H;
import org.activityinfo.ui.vdom.shared.html.HtmlTag;
import org.activityinfo.ui.vdom.shared.tree.*;
import java.util.ArrayList;
import java.util.List;
import static org.activityinfo.ui.vdom.shared.html.H.div;
public class Search2 {
private static final String NO_SELECTION = "";
public static VTree render(FormStore formStore) {
StatefulValue<String> searchString = new StatefulValue<>("");
StatefulValue<Boolean> expanded = new StatefulValue<>(false);
StatefulValue<Integer> selectedIndex = new StatefulValue<>(0);
VNode searchIcon = NonIdeal.svg("icon icon--search", "#search");
PropMap inputProps = new PropMap();
inputProps.setClass("search__input");
inputProps.placeholder(I18N.CONSTANTS.formSearchPlaceholder());
inputProps.onfocus(event -> expanded.updateIfNotEqual(true));
inputProps.onkeyup(event -> {
if(event.getKeyCode() == KeyCodes.KEY_UP) {
if(selectedIndex.get() > 0) {
selectedIndex.updateValue(selectedIndex.get() - 1);
}
} else if(event.getKeyCode() == KeyCodes.KEY_DOWN) {
selectedIndex.updateValue(selectedIndex.get() + 1);
}
});
//inputProps.onblur(event -> expanded.updateIfNotEqual(false));
inputProps.oninput(event -> {
InputElement inputElement = event.getEventTarget().cast();
searchString.updateIfNotSame(Strings.nullToEmpty(inputElement.getValue()));
});
VNode input = new VNode(HtmlTag.INPUT, inputProps);
Observable<List<SearchResult>> resources = resourceList(formStore);
Observable<List<SearchResult>> matching = Observable.transform(resources, searchString, Search2::match);
return new ReactiveComponent(expanded.transform(ex -> {
if(!ex) {
return div("search",
div("search__wrapper",
searchIcon,
input));
} else {
return div("search search--expanded",
div("search__wrapper",
searchIcon,
input,
resultList(matching, selectedIndex)));
}
}));
}
private static List<SearchResult> match(List<SearchResult> list, String searchString) {
if(searchString.isEmpty()) {
return list;
} else {
String loweredSearchString = searchString.toLowerCase();
List<SearchResult> matching = new ArrayList<>();
for (SearchResult searchResult : list) {
if(searchResult.getLabel().toLowerCase().contains(loweredSearchString)) {
matching.add(searchResult);
}
}
return matching;
}
}
private static Observable<List<SearchResult>> resourceList(FormStore formStore) {
return formStore.getDatabases().transform(databases -> {
List<SearchResult> results = new ArrayList<>();
for (UserDatabaseMeta database : databases) {
results.add(new SearchResult(database));
for (Resource resource : database.getResources()) {
results.add(new SearchResult(database, resource));
}
}
return results;
});
}
private static VTree resultList(Observable<List<SearchResult>> matching, StatefulValue<Integer> activeId) {
VTree loading = H.div("search__results search__results--loading",
H.div("search__loading", H.t(I18N.CONSTANTS.loading())));
VTree empty = H.div("search__results search__results--empty",
H.div("search__empty", H.t(I18N.CONSTANTS.noMatchingRecords())));
Observable<VTree> resultList = Observable.transform(matching, activeId, (m, a) -> {
if(m.isEmpty()) {
return empty;
} else {
VTree[] items = new VTree[m.size()];
int selectedIndex = Math.min(activeId.get(), m.size() - 1);
for (int i = 0; i < m.size(); i++) {
items[i] = searchItem(m.get(i), i == selectedIndex);
}
return H.ul("search__results", items);
}
});
return new ReactiveComponent(resultList, loading);
}
private static VNode searchItem(SearchResult result, boolean selected) {
PropMap itemProps = new PropMap();
if(selected) {
itemProps.addClassName("search__item--active");
}
return H.li(itemProps,
NonIdeal.svg("icon", "#type_" + result.getTypeClassName() + "-inverse", "0 0 18 27"),
H.span("search__type", result.getTypeLabel()),
H.t(result.getLabel()));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment