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