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 13KB

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