You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

IFilterSelect.java 15KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532
  1. package com.itmill.toolkit.terminal.gwt.client.ui;
  2. import java.util.ArrayList;
  3. import java.util.Collection;
  4. import java.util.Iterator;
  5. import com.google.gwt.user.client.Command;
  6. import com.google.gwt.user.client.DOM;
  7. import com.google.gwt.user.client.Element;
  8. import com.google.gwt.user.client.Event;
  9. import com.google.gwt.user.client.Window;
  10. import com.google.gwt.user.client.ui.ClickListener;
  11. import com.google.gwt.user.client.ui.Composite;
  12. import com.google.gwt.user.client.ui.FlowPanel;
  13. import com.google.gwt.user.client.ui.HTML;
  14. import com.google.gwt.user.client.ui.Image;
  15. import com.google.gwt.user.client.ui.KeyboardListener;
  16. import com.google.gwt.user.client.ui.PopupPanel;
  17. import com.google.gwt.user.client.ui.TextBox;
  18. import com.google.gwt.user.client.ui.Widget;
  19. import com.google.gwt.user.client.ui.PopupPanel.PositionCallback;
  20. import com.google.gwt.user.client.ui.SuggestOracle.Suggestion;
  21. import com.itmill.toolkit.terminal.gwt.client.ApplicationConnection;
  22. import com.itmill.toolkit.terminal.gwt.client.Paintable;
  23. import com.itmill.toolkit.terminal.gwt.client.UIDL;
  24. import com.itmill.toolkit.terminal.gwt.client.Util;
  25. /**
  26. *
  27. * TODO needs major refactoring to be easily expandable TODO add new items TODO
  28. * null selections
  29. */
  30. public class IFilterSelect extends Composite implements Paintable,
  31. KeyboardListener, ClickListener {
  32. public class FilterSelectSuggestion implements Suggestion, Command {
  33. private String key;
  34. private String caption;
  35. private String iconUri;
  36. public FilterSelectSuggestion(UIDL uidl) {
  37. this.key = uidl.getStringAttribute("key");
  38. this.caption = uidl.getStringAttribute("caption");
  39. if (uidl.hasAttribute("icon")) {
  40. this.iconUri = uidl.getStringAttribute("icon");
  41. }
  42. }
  43. public String getDisplayString() {
  44. StringBuffer sb = new StringBuffer();
  45. if (iconUri != null) {
  46. sb.append("<img src=\"");
  47. sb.append(iconUri);
  48. sb.append("\" alt=\"icon\" class=\"i-icon\" />");
  49. }
  50. sb.append(caption);
  51. return sb.toString();
  52. }
  53. public String getReplacementString() {
  54. return caption;
  55. }
  56. public int getOptionKey() {
  57. return Integer.parseInt(key);
  58. }
  59. public String getIconUri() {
  60. return iconUri;
  61. }
  62. public void execute() {
  63. IFilterSelect.this.onSuggestionSelected(this);
  64. }
  65. }
  66. /**
  67. * @author mattitahvonen
  68. *
  69. */
  70. public class SuggestionPopup extends PopupPanel implements PositionCallback {
  71. private SuggestionMenu menu;
  72. private Element up = DOM.createDiv();
  73. private Element down = DOM.createDiv();
  74. private Element status = DOM.createDiv();
  75. private boolean isPagingEnabled = true;
  76. SuggestionPopup() {
  77. super(true);
  78. this.menu = new SuggestionMenu();
  79. setWidget(menu);
  80. setStyleName(CLASSNAME + "-suggestpopup");
  81. Element root = getElement();
  82. DOM.setInnerText(up, "prev");
  83. DOM.sinkEvents(up, Event.ONCLICK);
  84. DOM.setInnerText(down, "next");
  85. DOM.sinkEvents(down, Event.ONCLICK);
  86. DOM.insertChild(root, up, 0);
  87. DOM.appendChild(root, down);
  88. DOM.appendChild(root, status);
  89. DOM.setElementProperty(status, "className", CLASSNAME + "-status");
  90. }
  91. public void showSuggestions(Collection currentSuggestions,
  92. int currentPage, int totalSuggestions) {
  93. menu.setSuggestions(currentSuggestions);
  94. int x = IFilterSelect.this.tb.getAbsoluteLeft();
  95. int y = IFilterSelect.this.tb.getAbsoluteTop();
  96. y += IFilterSelect.this.tb.getOffsetHeight();
  97. this.setPopupPosition(x, y);
  98. int first = currentPage * PAGELENTH + 1;
  99. int last = first + currentSuggestions.size() - 1;
  100. DOM.setInnerText(status, first + "-" + last + "/"
  101. + totalSuggestions);
  102. setPrevButtonActive(first > 1);
  103. setNextButtonActive(last < totalSuggestions);
  104. setPopupPositionAndShow(this);
  105. }
  106. private void setNextButtonActive(boolean b) {
  107. if (b) {
  108. DOM.sinkEvents(down, Event.ONCLICK);
  109. DOM.setElementProperty(down, "className", CLASSNAME
  110. + "-nextpage-on");
  111. } else {
  112. DOM.sinkEvents(down, 0);
  113. DOM.setElementProperty(down, "className", CLASSNAME
  114. + "-nextpage-off");
  115. }
  116. }
  117. private void setPrevButtonActive(boolean b) {
  118. if (b) {
  119. DOM.sinkEvents(up, Event.ONCLICK);
  120. DOM.setElementProperty(up, "className", CLASSNAME
  121. + "-prevpage-on");
  122. } else {
  123. DOM.sinkEvents(up, 0);
  124. DOM.setElementProperty(up, "className", CLASSNAME
  125. + "-prevpage-off");
  126. }
  127. }
  128. public void selectNextItem() {
  129. MenuItem cur = menu.getSelectedItem();
  130. int index = 1 + menu.getItems().indexOf(cur);
  131. if (menu.getItems().size() > index)
  132. menu.selectItem((MenuItem) menu.getItems().get(index));
  133. else if (!clientSideFiltering && hasNextPage())
  134. filterOptions(currentPage + 1);
  135. }
  136. public void selectPrevItem() {
  137. MenuItem cur = menu.getSelectedItem();
  138. int index = -1 + menu.getItems().indexOf(cur);
  139. if (index > -1)
  140. menu.selectItem((MenuItem) menu.getItems().get(index));
  141. else if (index == -1) {
  142. if (currentPage > 0)
  143. filterOptions(currentPage - 1);
  144. } else {
  145. menu.selectItem((MenuItem) menu.getItems().get(
  146. menu.getItems().size() - 1));
  147. }
  148. }
  149. public void onBrowserEvent(Event event) {
  150. Element target = DOM.eventGetTarget(event);
  151. if (DOM.compare(target, up)) {
  152. filterOptions(currentPage - 1, lastFilter);
  153. } else if (DOM.compare(target, down)) {
  154. filterOptions(currentPage + 1, lastFilter);
  155. }
  156. tb.setFocus(true);
  157. }
  158. public void setPagingEnabled(boolean paging) {
  159. if (isPagingEnabled == paging)
  160. return;
  161. if (paging) {
  162. DOM.setStyleAttribute(this.down, "display", "block");
  163. DOM.setStyleAttribute(this.up, "display", "block");
  164. DOM.setStyleAttribute(this.status, "display", "block");
  165. } else {
  166. DOM.setStyleAttribute(this.down, "display", "none");
  167. DOM.setStyleAttribute(this.up, "display", "none");
  168. DOM.setStyleAttribute(this.status, "display", "none");
  169. }
  170. isPagingEnabled = paging;
  171. }
  172. /*
  173. * (non-Javadoc)
  174. *
  175. * @see com.google.gwt.user.client.ui.PopupPanel$PositionCallback#setPosition(int,
  176. * int)
  177. */
  178. public void setPosition(int offsetWidth, int offsetHeight) {
  179. ApplicationConnection.getConsole().log("callback");
  180. // reset menu size and retrieve its "natural"; size
  181. menu.setHeight("");
  182. menu.setWidth("");
  183. offsetHeight = menu.getOffsetHeight();
  184. if (!isPagingEnabled && offsetHeight > Window.getClientHeight()) {
  185. offsetHeight = Window.getClientHeight();
  186. menu.setHeight(offsetHeight + "px");
  187. DOM.setStyleAttribute(menu.getElement(), "overflow", "auto");
  188. // add scrollbar width
  189. menu
  190. .setWidth((menu.getOffsetWidth() * 2 - DOM
  191. .getElementPropertyInt(menu.getElement(),
  192. "clientWidth"))
  193. + "px");
  194. }
  195. if (offsetHeight + getPopupTop() > Window.getClientHeight()) {
  196. int top = Window.getClientHeight() - offsetHeight;
  197. setPopupPosition(getPopupLeft(), top);
  198. }
  199. }
  200. }
  201. public class SuggestionMenu extends MenuBar {
  202. SuggestionMenu() {
  203. super(true);
  204. setStyleName(CLASSNAME + "-suggestmenu");
  205. }
  206. public void setSuggestions(Collection suggestions) {
  207. this.clearItems();
  208. Iterator it = suggestions.iterator();
  209. while (it.hasNext()) {
  210. FilterSelectSuggestion s = (FilterSelectSuggestion) it.next();
  211. MenuItem mi = new MenuItem(s.getDisplayString(), true, s);
  212. this.addItem(mi);
  213. if (s == currentSuggestion)
  214. selectItem(mi);
  215. }
  216. }
  217. public void doSelectedItemAction() {
  218. MenuItem item = this.getSelectedItem();
  219. if (item != null) {
  220. doItemAction(item, true);
  221. }
  222. suggestionPopup.hide();
  223. }
  224. }
  225. private static final String CLASSNAME = "i-filterselect";
  226. public static final int PAGELENTH = 20;
  227. private final FlowPanel panel = new FlowPanel();
  228. private final TextBox tb = new TextBox();
  229. private final SuggestionPopup suggestionPopup = new SuggestionPopup();
  230. private final HTML popupOpener = new HTML("");
  231. private final Image selectedItemIcon = new Image();
  232. private ApplicationConnection client;
  233. private String paintableId;
  234. private int currentPage;
  235. private Collection currentSuggestions = new ArrayList();
  236. private boolean immediate;
  237. private String selectedOptionKey;
  238. private boolean filtering = false;
  239. private String lastFilter;
  240. private int totalSuggestions;
  241. private FilterSelectSuggestion currentSuggestion;
  242. private boolean clientSideFiltering;
  243. private ArrayList allSuggestions;
  244. public IFilterSelect() {
  245. selectedItemIcon.setVisible(false);
  246. panel.add(selectedItemIcon);
  247. panel.add(tb);
  248. panel.add(popupOpener);
  249. initWidget(panel);
  250. setStyleName(CLASSNAME);
  251. tb.addKeyboardListener(this);
  252. tb.setStyleName(CLASSNAME + "-input");
  253. popupOpener.setStyleName(CLASSNAME + "-button");
  254. popupOpener.addClickListener(this);
  255. }
  256. public boolean hasNextPage() {
  257. if (totalSuggestions > (this.currentPage + 1) * PAGELENTH)
  258. return true;
  259. else
  260. return false;
  261. }
  262. public void filterOptions(int page) {
  263. filterOptions(page, tb.getText());
  264. }
  265. public void filterOptions(int page, String filter) {
  266. if (filter.equals(lastFilter) && currentPage == page) {
  267. if (!suggestionPopup.isAttached())
  268. suggestionPopup.showSuggestions(currentSuggestions,
  269. currentPage, totalSuggestions);
  270. return;
  271. }
  272. if (!filter.equals(lastFilter)) {
  273. // we are on subsequant page and text has changed -> reset page
  274. page = 0;
  275. }
  276. if (clientSideFiltering) {
  277. currentSuggestions.clear();
  278. for (Iterator it = allSuggestions.iterator(); it.hasNext();) {
  279. FilterSelectSuggestion s = (FilterSelectSuggestion) it.next();
  280. String string = s.getDisplayString().toLowerCase();
  281. if (string.startsWith(filter.toLowerCase())) {
  282. currentSuggestions.add(s);
  283. }
  284. }
  285. lastFilter = filter;
  286. currentPage = page;
  287. suggestionPopup.showSuggestions(currentSuggestions, page,
  288. currentSuggestions.size());
  289. } else {
  290. filtering = true;
  291. client.updateVariable(paintableId, "filter", filter, false);
  292. client.updateVariable(paintableId, "page", page, true);
  293. lastFilter = filter;
  294. currentPage = page;
  295. }
  296. }
  297. public void updateFromUIDL(UIDL uidl, ApplicationConnection client) {
  298. this.paintableId = uidl.getId();
  299. this.client = client;
  300. if (client.updateComponent(this, uidl, true))
  301. return;
  302. if (uidl.hasAttribute("immediate"))
  303. immediate = true;
  304. else
  305. immediate = false;
  306. if (uidl.hasVariable("page")) {
  307. this.suggestionPopup.setPagingEnabled(true);
  308. clientSideFiltering = false;
  309. } else {
  310. this.suggestionPopup.setPagingEnabled(false);
  311. clientSideFiltering = true;
  312. }
  313. currentSuggestions.clear();
  314. UIDL options = uidl.getChildUIDL(0);
  315. totalSuggestions = options.getIntAttribute("totalMatches");
  316. String captions = "";
  317. if (clientSideFiltering) {
  318. allSuggestions = new ArrayList();
  319. }
  320. for (Iterator i = options.getChildIterator(); i.hasNext();) {
  321. UIDL optionUidl = (UIDL) i.next();
  322. FilterSelectSuggestion suggestion = new FilterSelectSuggestion(
  323. optionUidl);
  324. currentSuggestions.add(suggestion);
  325. if (clientSideFiltering) {
  326. allSuggestions.add(suggestion);
  327. }
  328. if (optionUidl.hasAttribute("selected")) {
  329. tb.setText(suggestion.getReplacementString());
  330. currentSuggestion = suggestion;
  331. }
  332. // Collect captions so we can calculate minimum width for textarea
  333. if (captions.length() > 0)
  334. captions += "|";
  335. captions += suggestion.getReplacementString();
  336. }
  337. if (filtering && lastFilter.equals(uidl.getStringVariable("filter"))) {
  338. suggestionPopup.showSuggestions(currentSuggestions, currentPage,
  339. totalSuggestions);
  340. filtering = false;
  341. }
  342. // Calculate minumum textarea width
  343. int minw = minWidth(captions);
  344. if (Util.isIE()) {
  345. Element spacer = DOM.createDiv();
  346. DOM.setStyleAttribute(spacer, "width", minw + "px");
  347. DOM.setStyleAttribute(spacer, "height", "0");
  348. DOM.setStyleAttribute(spacer, "overflow", "hidden");
  349. DOM.appendChild(panel.getElement(), spacer);
  350. } else {
  351. DOM.setStyleAttribute(tb.getElement(), "minWidth", minw + "px");
  352. }
  353. // Set columns (width) is given
  354. if (uidl.hasAttribute("cols"))
  355. DOM.setStyleAttribute(getElement(), "width", uidl
  356. .getIntAttribute("cols")
  357. + "em");
  358. }
  359. public void onSuggestionSelected(FilterSelectSuggestion suggestion) {
  360. currentSuggestion = suggestion;
  361. String newKey = String.valueOf(suggestion.getOptionKey());
  362. tb.setText(suggestion.getReplacementString());
  363. setSelectedItemIcon(suggestion.getIconUri());
  364. if (!newKey.equals(selectedOptionKey)) {
  365. selectedOptionKey = newKey;
  366. client.updateVariable(paintableId, "selected",
  367. new String[] { selectedOptionKey }, immediate);
  368. lastFilter = tb.getText();
  369. }
  370. suggestionPopup.hide();
  371. }
  372. private void setSelectedItemIcon(String iconUri) {
  373. if (iconUri == null) {
  374. selectedItemIcon.setVisible(false);
  375. } else {
  376. selectedItemIcon.setUrl(iconUri);
  377. selectedItemIcon.setVisible(true);
  378. }
  379. }
  380. public void onKeyDown(Widget sender, char keyCode, int modifiers) {
  381. if (suggestionPopup.isAttached()) {
  382. switch (keyCode) {
  383. case KeyboardListener.KEY_DOWN:
  384. suggestionPopup.selectNextItem();
  385. break;
  386. case KeyboardListener.KEY_UP:
  387. suggestionPopup.selectPrevItem();
  388. break;
  389. case KeyboardListener.KEY_PAGEDOWN:
  390. if (hasNextPage())
  391. filterOptions(currentPage + 1, lastFilter);
  392. break;
  393. case KeyboardListener.KEY_PAGEUP:
  394. if (currentPage > 0)
  395. filterOptions(currentPage - 1, lastFilter);
  396. break;
  397. case KeyboardListener.KEY_ENTER:
  398. case KeyboardListener.KEY_TAB:
  399. suggestionPopup.menu.doSelectedItemAction();
  400. break;
  401. }
  402. }
  403. }
  404. public void onKeyPress(Widget sender, char keyCode, int modifiers) {
  405. }
  406. public void onKeyUp(Widget sender, char keyCode, int modifiers) {
  407. switch (keyCode) {
  408. case KeyboardListener.KEY_ENTER:
  409. case KeyboardListener.KEY_TAB:
  410. ; // NOP
  411. break;
  412. case KeyboardListener.KEY_DOWN:
  413. case KeyboardListener.KEY_UP:
  414. case KeyboardListener.KEY_PAGEDOWN:
  415. case KeyboardListener.KEY_PAGEUP:
  416. if (suggestionPopup.isAttached()) {
  417. break;
  418. } else {
  419. // open popup as from gadget
  420. filterOptions(0, "");
  421. tb.selectAll();
  422. break;
  423. }
  424. default:
  425. filterOptions(currentPage);
  426. break;
  427. }
  428. }
  429. /**
  430. * Listener for popupopener
  431. */
  432. public void onClick(Widget sender) {
  433. filterOptions(0, "");
  434. tb.setFocus(true);
  435. tb.selectAll();
  436. }
  437. /*
  438. * Calculate minumum width for FilterSelect textarea
  439. */
  440. private native int minWidth(String captions) /*-{
  441. if(!captions || captions.length <= 0)
  442. return 0;
  443. captions = captions.split("|");
  444. var d = $wnd.document.createElement("div");
  445. var html = "";
  446. for(var i=0; i < captions.length; i++) {
  447. html += "<div>" + captions[i] + "</div>";
  448. // TODO apply same CSS classname as in suggestionmenu
  449. }
  450. d.style.position = "absolute";
  451. d.style.top = "0";
  452. d.style.left = "0";
  453. d.style.visibility = "hidden";
  454. d.innerHTML = html;
  455. $wnd.document.body.appendChild(d);
  456. var w = d.offsetWidth;
  457. $wnd.document.body.removeChild(d);
  458. return w;
  459. }-*/;
  460. }