001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.ui.dataview; 029 030import org.opencms.ui.CmsVaadinUtils; 031import org.opencms.ui.components.OpenCmsTheme; 032import org.opencms.ui.dataview.CmsPagingControls.I_PagingCallback; 033import org.opencms.ui.util.CmsComponentField; 034import org.opencms.widgets.dataview.CmsDataViewColumn; 035import org.opencms.widgets.dataview.CmsDataViewFilter; 036import org.opencms.widgets.dataview.CmsDataViewQuery; 037import org.opencms.widgets.dataview.CmsDataViewResult; 038import org.opencms.widgets.dataview.I_CmsDataView; 039import org.opencms.widgets.dataview.I_CmsDataViewItem; 040 041import java.util.ArrayList; 042import java.util.Collection; 043import java.util.LinkedHashMap; 044import java.util.List; 045import java.util.Map; 046import java.util.Set; 047 048import com.google.common.collect.Lists; 049import com.google.common.collect.Maps; 050import com.google.common.collect.Sets; 051import com.vaadin.v7.data.Item; 052import com.vaadin.v7.data.Property.ValueChangeEvent; 053import com.vaadin.v7.data.Property.ValueChangeListener; 054import com.vaadin.v7.data.util.IndexedContainer; 055import com.vaadin.event.ShortcutAction.KeyCode; 056import com.vaadin.event.ShortcutListener; 057import com.vaadin.ui.Button; 058import com.vaadin.ui.Button.ClickEvent; 059import com.vaadin.ui.Button.ClickListener; 060import com.vaadin.v7.ui.CheckBox; 061import com.vaadin.v7.ui.ComboBox; 062import com.vaadin.ui.CssLayout; 063import com.vaadin.v7.ui.HorizontalLayout; 064import com.vaadin.v7.ui.Table; 065import com.vaadin.v7.ui.Table.ColumnGenerator; 066import com.vaadin.v7.ui.TextField; 067import com.vaadin.v7.ui.VerticalLayout; 068 069/** 070 * Panel containing both the interface elements used to search the data source (query field, filter select boxes) as well 071 * as the paged list of search results.<p> 072 */ 073public class CmsDataViewPanel extends VerticalLayout { 074 075 /** 076 * Subclass of Table, which we need because we want to trigger a complete refresh when sorting instead of just sorting the in-memory data.<p> 077 */ 078 public class PagedTable extends Table { 079 080 /** Serial version id. */ 081 private static final long serialVersionUID = 1L; 082 083 /** 084 * Creates a new instance.<p> 085 * 086 * @param container the data container 087 */ 088 public PagedTable(IndexedContainer container) { 089 super(); 090 setContainerDataSource(container); 091 } 092 093 /** 094 * @see com.vaadin.ui.Table#sort(java.lang.Object[], boolean[]) 095 */ 096 @Override 097 public void sort(Object[] propertyId, boolean[] ascending) throws UnsupportedOperationException { 098 099 m_sortCol = propertyId[0]; 100 m_ascending = ascending[0]; 101 refreshData(true, null); 102 } 103 } 104 105 /** Serial version id. */ 106 private static final long serialVersionUID = 1L; 107 108 /** The ID column name. */ 109 public static final Object ID_COLUMN = new Object(); 110 111 /** The data view instance used to access the data. */ 112 private I_CmsDataView m_dataView; 113 114 /** Search button. */ 115 private Button m_searchButton; 116 117 /** The container used to store the current page of search results. */ 118 private IndexedContainer m_container; 119 120 /** Widget used to move to different pages. */ 121 private CmsPagingControls m_pagingControls; 122 123 /** The table container. */ 124 private CssLayout m_tablePlaceholder; 125 126 /** The widget containing the filters. */ 127 private HorizontalLayout m_filterContainer; 128 129 /** Query text field. */ 130 private TextField m_fullTextSearch; 131 132 /** The sort column. */ 133 protected Object m_sortCol; 134 135 /** The sort direction. */ 136 protected boolean m_ascending; 137 138 /** The current list of filters. */ 139 private List<CmsDataViewFilter> m_filters = Lists.newArrayList(); 140 141 /** Map of check boxes. */ 142 private Map<Object, CheckBox> m_checkBoxes = Maps.newHashMap(); 143 144 /** The current map of filters, by id. */ 145 private Map<String, CmsDataViewFilter> m_filterMap = Maps.newLinkedHashMap(); 146 147 /** The table with the search results. */ 148 private CmsComponentField<Table> m_table = CmsComponentField.newInstance(); 149 150 /** True if we are currently in a recursive call of the value change event listener. */ 151 private boolean m_recursiveValueChange; 152 153 /** The real selection (includes item. */ 154 private Set<Object> m_realSelection = Sets.newHashSet(); 155 156 /** 157 * Creates a new instance.<p> 158 * 159 * @param viewInstance the data view instance 160 * @param multiselect true if multi-selection should be allowed 161 */ 162 public CmsDataViewPanel(I_CmsDataView viewInstance, boolean multiselect) { 163 m_dataView = viewInstance; 164 CmsVaadinUtils.readAndLocalizeDesign(this, CmsVaadinUtils.getWpMessagesForCurrentLocale(), null); 165 166 m_pagingControls.addCallback(new I_PagingCallback() { 167 168 public void pageChanged(int page) { 169 170 refreshData(false, null); 171 } 172 }); 173 m_fullTextSearch.addShortcutListener(new ShortcutListener("Save", KeyCode.ENTER, null) { 174 175 private static final long serialVersionUID = 1L; 176 177 @Override 178 public void handleAction(Object sender, Object target) { 179 180 refreshData(false, null); 181 182 } 183 }); 184 m_searchButton.addClickListener(new ClickListener() { 185 186 private static final long serialVersionUID = 1L; 187 188 public void buttonClick(ClickEvent event) { 189 190 refreshData(true, null); 191 } 192 193 }); 194 m_container = new IndexedContainer(); 195 for (CmsDataViewColumn column : m_dataView.getColumns()) { 196 m_container.addContainerProperty( 197 column.getId(), 198 CmsColumnValueConverter.getColumnClass(column.getType()), 199 null); 200 201 } 202 m_container.addContainerProperty(ID_COLUMN, String.class, null); 203 204 final PagedTable table = new PagedTable(m_container); 205 table.addStyleName(OpenCmsTheme.TABLE_CELL_PADDING); 206 table.setMultiSelect(multiselect); 207 208 table.addGeneratedColumn("checked", new ColumnGenerator() { 209 210 private static final long serialVersionUID = 1L; 211 212 @SuppressWarnings("synthetic-access") 213 public Object generateCell(final Table source, final Object itemId, final Object columnId) { 214 215 CheckBox cb = getCheckBox(itemId); 216 cb.setValue(Boolean.valueOf(source.isSelected(itemId))); 217 cb.addValueChangeListener(new ValueChangeListener() { 218 219 private static final long serialVersionUID = 1L; 220 221 public void valueChange(ValueChangeEvent event) { 222 223 boolean val = ((Boolean)(event.getProperty().getValue())).booleanValue(); 224 if (val) { 225 source.select(itemId); 226 } else { 227 source.unselect(itemId); 228 } 229 } 230 }); 231 return cb; 232 } 233 }); 234 235 table.addStyleName("o-wrap-table"); 236 Object[] visibleCols = new String[m_dataView.getColumns().size() + 1]; 237 visibleCols[0] = "checked"; 238 int i = 1; 239 for (CmsDataViewColumn col : m_dataView.getColumns()) { 240 visibleCols[i++] = col.getId(); 241 242 } 243 table.setVisibleColumns(visibleCols); 244 table.setColumnWidth("checked", 45); 245 table.setColumnHeader("checked", ""); 246 for (CmsDataViewColumn col : m_dataView.getColumns()) { 247 table.setColumnHeader(col.getId(), col.getNiceName()); 248 table.setColumnWidth(col.getId(), col.getPreferredWidth()); 249 } 250 251 table.setPageLength(0); 252 table.setWidth("100%"); // 253 table.setHeight("100%"); 254 m_table.set(table); 255 table.setSelectable(true); 256 replaceComponent(m_tablePlaceholder, table); 257 setExpandRatio(table, 1.0f); 258 259 addAttachListener(new AttachListener() { 260 261 private static final long serialVersionUID = 1L; 262 263 public void attach(AttachEvent event) { 264 265 refreshData(true, null); 266 } 267 }); 268 table.addValueChangeListener(new ValueChangeListener() { 269 270 private static final long serialVersionUID = 1L; 271 272 @SuppressWarnings("synthetic-access") 273 public void valueChange(ValueChangeEvent event) { 274 275 if (table.isMultiSelect()) { 276 if (m_recursiveValueChange) { 277 updateCheckboxesWithSelectedIds(m_realSelection); 278 } else { 279 updateRealSelection(getIdsFromSelection(event)); 280 if (m_realSelection.equals(event.getProperty().getValue())) { 281 updateCheckboxesWithSelectedIds(m_realSelection); 282 } else { 283 try { 284 m_recursiveValueChange = true; 285 m_table.get().setValue(m_realSelection); 286 } finally { 287 m_recursiveValueChange = false; 288 } 289 } 290 } 291 292 } else { 293 Set<Object> ids = getIdsFromSelection(event); 294 updateCheckboxesWithSelectedIds(ids); 295 296 } 297 298 } 299 300 /** 301 * Gets the ids from the selection event.<p> 302 * 303 * @param event a selection event 304 * 305 * @return the set of ids from the selection event 306 */ 307 protected Set<Object> getIdsFromSelection(ValueChangeEvent event) { 308 309 Set<Object> ids = Sets.newHashSet(); 310 if (event != null) { 311 if (event.getProperty().getValue() instanceof Collection) { 312 ids.addAll((Collection<?>)event.getProperty().getValue()); 313 } else { 314 ids.add(event.getProperty().getValue()); 315 } 316 } 317 return ids; 318 } 319 320 @SuppressWarnings("synthetic-access") 321 protected void updateCheckboxesWithSelectedIds(Set<Object> selectedIds) { 322 323 for (Map.Entry<Object, CheckBox> entry : m_checkBoxes.entrySet()) { 324 if (!(selectedIds.contains(entry.getKey()))) { 325 entry.getValue().setValue(Boolean.FALSE); 326 } 327 } 328 329 for (Object id : selectedIds) { 330 getCheckBox(id).setValue(Boolean.TRUE); 331 } 332 } 333 }); 334 List<CmsDataViewFilter> filters = new ArrayList<CmsDataViewFilter>(m_dataView.getFilters()); 335 updateFilters(filters); 336 } 337 338 /** 339 * Fills the given item.<p> 340 * 341 * @param item the data view item 342 * @param target the table item 343 */ 344 public void fillItem(I_CmsDataViewItem item, Item target) { 345 346 for (CmsDataViewColumn column : m_dataView.getColumns()) { 347 String name = column.getId(); 348 Object value = CmsColumnValueConverter.getColumnValue(item.getColumnData(name), column.getType()); 349 target.getItemProperty(name).setValue(value); 350 } 351 target.getItemProperty(CmsDataViewPanel.ID_COLUMN).setValue(item.getId()); 352 } 353 354 /** 355 * Gets the list of selected data items.<p> 356 * 357 * If this widget is not in multi-select mode, a list with a single result will be returned.<p> 358 * 359 * @return the selected results 360 */ 361 public List<I_CmsDataViewItem> getSelection() { 362 363 List<I_CmsDataViewItem> result = Lists.newArrayList(); 364 Object val = m_table.get().getValue(); 365 if (val == null) { 366 return result; 367 } 368 if (val instanceof Collection) { 369 Collection<?> results = (Collection<?>)val; 370 for (Object obj : results) { 371 result.add(m_dataView.getItemById((String)obj)); 372 } 373 } else { 374 result.add(m_dataView.getItemById((String)val)); 375 } 376 return result; 377 378 } 379 380 /** 381 * Gets the table.<p> 382 * 383 * @return the table 384 */ 385 public Table getTable() { 386 387 return m_table.get(); 388 } 389 390 /** 391 * Updates the data displayed in the table.<p> 392 * 393 * @param resetPaging true if we should go back to page 1 394 * @param textQuery the text query to use 395 */ 396 public void refreshData(boolean resetPaging, String textQuery) { 397 398 String fullTextQuery = textQuery != null ? textQuery : m_fullTextSearch.getValue(); 399 LinkedHashMap<String, String> filterValues = new LinkedHashMap<String, String>(); 400 for (Map.Entry<String, CmsDataViewFilter> entry : m_filterMap.entrySet()) { 401 filterValues.put(entry.getKey(), entry.getValue().getValue()); 402 403 } 404 CmsDataViewQuery query = new CmsDataViewQuery(); 405 String sortCol = (String)m_sortCol; 406 boolean ascending = m_ascending; 407 query.setFullTextQuery(fullTextQuery); 408 query.setFilterValues(filterValues); 409 query.setSortColumn(sortCol); 410 query.setSortAscending(ascending); 411 CmsDataViewResult result = m_dataView.getResults( 412 query, 413 resetPaging ? 0 : getOffset(), 414 m_dataView.getPageSize()); 415 m_container.removeAllItems(); 416 for (I_CmsDataViewItem item : result.getItems()) { 417 fillItem(item, m_container.addItem(item.getId())); 418 } 419 //m_tablePanel.setScrollTop(0); 420 421 if (resetPaging) { 422 int total = result.getHitCount(); 423 m_pagingControls.reset(result.getHitCount(), m_dataView.getPageSize(), false); 424 } 425 } 426 427 /** 428 * Updates the search results after a filter is changed by the user.<p> 429 * 430 * @param id the filter id 431 * @param value the filter value 432 */ 433 public void updateFilter(String id, String value) { 434 435 CmsDataViewFilter oldFilter = m_filterMap.get(id); 436 CmsDataViewFilter newFilter = oldFilter.copyWithValue(value); 437 m_filterMap.put(id, newFilter); 438 List<CmsDataViewFilter> filters = new ArrayList<CmsDataViewFilter>(m_filterMap.values()); 439 updateFilters(m_dataView.updateFilters(filters)); 440 } 441 442 /** 443 * Changes the displayed filters to a new set.<p> 444 * 445 * @param newFilters the new filters 446 */ 447 public void updateFilters(List<CmsDataViewFilter> newFilters) { 448 449 if (newFilters.isEmpty()) { 450 m_filterContainer.setVisible(false); 451 } 452 if (m_filters.equals(newFilters)) { 453 return; 454 } 455 m_filterContainer.removeAllComponents(); 456 m_filters = newFilters; 457 m_filterMap.clear(); 458 for (CmsDataViewFilter filter : newFilters) { 459 m_filterMap.put(filter.getId(), filter); 460 final CmsDataViewFilter finalFilter = filter; 461 ComboBox select = new ComboBox(filter.getNiceName()); 462 select.setWidth("175px"); 463 select.setNullSelectionAllowed(false); 464 select.setPageLength(0); 465 Map<String, String> options = filter.getOptions(); 466 for (Map.Entry<String, String> entry : options.entrySet()) { 467 String key = entry.getKey(); 468 String value = entry.getValue(); 469 select.addItem(key); 470 select.setItemCaption(key, value); 471 } 472 select.setValue(filter.getValue()); 473 if (filter.getHelpText() != null) { 474 select.setDescription(filter.getHelpText()); 475 } 476 477 select.addValueChangeListener(new ValueChangeListener() { 478 479 private static final long serialVersionUID = 1L; 480 481 public void valueChange(ValueChangeEvent event) { 482 483 String newValue = (String)(event.getProperty().getValue()); 484 updateFilter(finalFilter.getId(), newValue); 485 } 486 }); 487 m_filterContainer.addComponent(select); 488 } 489 } 490 491 /** 492 * Updates the real selection, given the item ids from the selection event.<p> 493 * 494 * @param selectionEventIds the item ids from the selection event 495 */ 496 protected void updateRealSelection(Set<Object> selectionEventIds) { 497 498 Set<Object> pageItems = Sets.newHashSet(m_table.get().getContainerDataSource().getItemIds()); 499 Set<Object> result = Sets.newHashSet(m_realSelection); 500 result.removeAll(pageItems); 501 result.addAll(selectionEventIds); 502 m_realSelection = result; 503 504 } 505 506 /** 507 * Gets the check box for the item with the given id.<p> 508 * 509 * @param id the item id 510 * @return the check box 511 */ 512 private CheckBox getCheckBox(Object id) { 513 514 if (!m_checkBoxes.containsKey(id)) { 515 m_checkBoxes.put(id, new CheckBox()); 516 } 517 return m_checkBoxes.get(id); 518 } 519 520 /** 521 * Gets the offset.<p> 522 * 523 * @return the offset 524 */ 525 private int getOffset() { 526 527 return m_pagingControls.getPage() * m_dataView.getPageSize(); 528 } 529 530}