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.

StaticSection.java 29KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. /*
  2. * Copyright 2000-2018 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.ui.components.grid;
  17. import java.io.Serializable;
  18. import java.util.ArrayList;
  19. import java.util.Collection;
  20. import java.util.Collections;
  21. import java.util.HashSet;
  22. import java.util.Iterator;
  23. import java.util.LinkedHashMap;
  24. import java.util.LinkedHashSet;
  25. import java.util.List;
  26. import java.util.Map;
  27. import java.util.Map.Entry;
  28. import java.util.Objects;
  29. import java.util.Optional;
  30. import java.util.Set;
  31. import java.util.stream.Collectors;
  32. import java.util.stream.Stream;
  33. import org.jsoup.nodes.Element;
  34. import org.jsoup.select.Elements;
  35. import com.vaadin.shared.ui.ContentMode;
  36. import com.vaadin.shared.ui.grid.GridStaticCellType;
  37. import com.vaadin.shared.ui.grid.SectionState;
  38. import com.vaadin.shared.ui.grid.SectionState.CellState;
  39. import com.vaadin.shared.ui.grid.SectionState.RowState;
  40. import com.vaadin.ui.Component;
  41. import com.vaadin.ui.Grid;
  42. import com.vaadin.ui.Grid.Column;
  43. import com.vaadin.ui.declarative.DesignAttributeHandler;
  44. import com.vaadin.ui.declarative.DesignContext;
  45. import com.vaadin.ui.declarative.DesignException;
  46. import com.vaadin.ui.declarative.DesignFormatter;
  47. /**
  48. * Represents the header or footer section of a Grid.
  49. *
  50. * @author Vaadin Ltd.
  51. *
  52. * @param <ROW>
  53. * the type of the rows in the section
  54. *
  55. * @since 8.0
  56. */
  57. public abstract class StaticSection<ROW extends StaticSection.StaticRow<?>>
  58. implements Serializable {
  59. /**
  60. * Abstract base class for Grid header and footer rows.
  61. *
  62. * @param <CELL>
  63. * the type of the cells in the row
  64. */
  65. public abstract static class StaticRow<CELL extends StaticCell>
  66. implements Serializable {
  67. private final RowState rowState = new RowState();
  68. private final StaticSection<?> section;
  69. private final Map<String, CELL> cells = new LinkedHashMap<>();
  70. /**
  71. * Creates a new row belonging to the given section.
  72. *
  73. * @param section
  74. * the section of the row
  75. */
  76. protected StaticRow(StaticSection<?> section) {
  77. this.section = section;
  78. }
  79. /**
  80. * Creates and returns a new instance of the cell type.
  81. *
  82. * @return the created cell
  83. */
  84. protected abstract CELL createCell();
  85. /**
  86. * Returns the declarative tag name used for the cells in this row.
  87. *
  88. * @return the cell tag name
  89. */
  90. protected abstract String getCellTagName();
  91. /**
  92. * Adds a cell to this section, corresponding to the given user-defined
  93. * column id.
  94. *
  95. * @param columnId
  96. * the id of the column for which to add a cell
  97. */
  98. protected void addCell(String columnId) {
  99. Column<?, ?> column = section.getGrid().getColumn(columnId);
  100. Objects.requireNonNull(column,
  101. "No column matching given identifier");
  102. addCell(column);
  103. }
  104. /**
  105. * Adds a cell to this section for given column.
  106. *
  107. * @param column
  108. * the column for which to add a cell
  109. */
  110. protected void addCell(Column<?, ?> column) {
  111. if (!section.getGrid().getColumns().contains(column)) {
  112. throw new IllegalArgumentException(
  113. "Given column does not exist in this Grid");
  114. }
  115. internalAddCell(section.getInternalIdForColumn(column));
  116. }
  117. /**
  118. * Adds a cell to this section, corresponding to the given internal
  119. * column id.
  120. *
  121. * @param internalId
  122. * the internal id of the column for which to add a cell
  123. */
  124. protected void internalAddCell(String internalId) {
  125. CELL cell = createCell();
  126. cell.setColumnId(internalId);
  127. cells.put(internalId, cell);
  128. rowState.cells.put(internalId, cell.getCellState());
  129. }
  130. /**
  131. * Removes the cell from this section that corresponds to the given
  132. * column id. If there is no such cell, does nothing.
  133. *
  134. * @param columnId
  135. * the id of the column from which to remove the cell
  136. */
  137. protected void removeCell(String columnId) {
  138. CELL cell = cells.remove(columnId);
  139. if (cell != null) {
  140. rowState.cells.remove(columnId);
  141. for (Iterator<Set<String>> iterator = rowState.cellGroups
  142. .values().iterator(); iterator.hasNext();) {
  143. Set<String> group = iterator.next();
  144. group.remove(columnId);
  145. if (group.size() < 2) {
  146. iterator.remove();
  147. }
  148. }
  149. cell.detach();
  150. }
  151. }
  152. /**
  153. * Returns the shared state of this row.
  154. *
  155. * @return the row state
  156. */
  157. protected RowState getRowState() {
  158. return rowState;
  159. }
  160. /**
  161. * Returns the cell in this section that corresponds to the given column
  162. * id.
  163. *
  164. * @see Column#setId(String)
  165. *
  166. * @param columnId
  167. * the id of the column
  168. * @return the cell for the given column
  169. *
  170. * @throws IllegalArgumentException
  171. * if no cell was found for the column id
  172. */
  173. public CELL getCell(String columnId) {
  174. Column<?, ?> column = section.getGrid().getColumn(columnId);
  175. Objects.requireNonNull(column,
  176. "No column matching given identifier");
  177. return getCell(column);
  178. }
  179. /**
  180. * Returns the cell in this section that corresponds to the given
  181. * column.
  182. *
  183. * @param column
  184. * the column
  185. * @return the cell for the given column
  186. *
  187. * @throws IllegalArgumentException
  188. * if no cell was found for the column
  189. */
  190. public CELL getCell(Column<?, ?> column) {
  191. return internalGetCell(section.getInternalIdForColumn(column));
  192. }
  193. /**
  194. * Returns the custom style name for this row.
  195. *
  196. * @return the style name or null if no style name has been set
  197. */
  198. public String getStyleName() {
  199. return getRowState().styleName;
  200. }
  201. /**
  202. * Sets a custom style name for this row.
  203. *
  204. * @param styleName
  205. * the style name to set or null to not use any style name
  206. */
  207. public void setStyleName(String styleName) {
  208. getRowState().styleName = styleName;
  209. }
  210. /**
  211. * Returns the cell in this section that corresponds to the given
  212. * internal column id.
  213. *
  214. * @param internalId
  215. * the internal id of the column
  216. * @return the cell for the given column
  217. *
  218. * @throws IllegalArgumentException
  219. * if no cell was found for the column id
  220. */
  221. protected CELL internalGetCell(String internalId) {
  222. CELL cell = cells.get(internalId);
  223. if (cell == null) {
  224. throw new IllegalArgumentException(
  225. "No cell found for column id " + internalId);
  226. }
  227. return cell;
  228. }
  229. /**
  230. * Reads the declarative design from the given table row element.
  231. *
  232. * @since 7.5.0
  233. * @param trElement
  234. * Element to read design from
  235. * @param designContext
  236. * the design context
  237. * @throws DesignException
  238. * if the given table row contains unexpected children
  239. */
  240. protected void readDesign(Element trElement,
  241. DesignContext designContext) throws DesignException {
  242. Elements cellElements = trElement.children();
  243. for (int i = 0; i < cellElements.size(); i++) {
  244. Element element = cellElements.get(i);
  245. if (!element.tagName().equals(getCellTagName())) {
  246. throw new DesignException(
  247. "Unexpected element in tr while expecting "
  248. + getCellTagName() + ": "
  249. + element.tagName());
  250. }
  251. int colspan = DesignAttributeHandler.readAttribute("colspan",
  252. element.attributes(), 1, int.class);
  253. String columnIdsString = DesignAttributeHandler.readAttribute(
  254. "column-ids", element.attributes(), "", String.class);
  255. if (columnIdsString.trim().isEmpty()) {
  256. throw new DesignException(
  257. "Unexpected 'column-ids' attribute value '"
  258. + columnIdsString
  259. + "'. It cannot be empty and must "
  260. + "be comma separated column identifiers");
  261. }
  262. String[] columnIds = columnIdsString.split(",");
  263. if (columnIds.length != colspan) {
  264. throw new DesignException(
  265. "Unexpected 'colspan' attribute value '" + colspan
  266. + "' whereas there is " + columnIds.length
  267. + " column identifiers specified : '"
  268. + columnIdsString + "'");
  269. }
  270. CELL cell;
  271. if (colspan > 1) {
  272. Set<String> columnGroup = new HashSet<>();
  273. for (String columnId : columnIds) {
  274. addCell(columnId);
  275. // convert the public columnIds into internal columnIds
  276. columnGroup.add(getCell(columnId).getColumnId());
  277. }
  278. cell = createCell();
  279. addMergedCell(cell, columnGroup);
  280. } else {
  281. String columnId = columnIds[0];
  282. addCell(columnId);
  283. cell = getCell(columnId);
  284. }
  285. cell.readDesign(element, designContext);
  286. }
  287. }
  288. /**
  289. * Writes the declarative design to the given table row element.
  290. *
  291. * @since 7.5.0
  292. * @param trElement
  293. * Element to write design to
  294. * @param designContext
  295. * the design context
  296. */
  297. protected void writeDesign(Element trElement,
  298. DesignContext designContext) {
  299. Set<String> visited = new HashSet<>();
  300. for (Entry<String, CELL> entry : cells.entrySet()) {
  301. if (visited.contains(entry.getKey())) {
  302. continue;
  303. }
  304. visited.add(entry.getKey());
  305. Element cellElement = trElement.appendElement(getCellTagName());
  306. Optional<Entry<CellState, Set<String>>> groupCell = getRowState().cellGroups
  307. .entrySet().stream().filter(groupEntry -> groupEntry
  308. .getValue().contains(entry.getKey()))
  309. .findFirst();
  310. Stream<String> columnIds = Stream.of(entry.getKey());
  311. if (groupCell.isPresent()) {
  312. Set<String> orderedSet = new LinkedHashSet<>(
  313. cells.keySet());
  314. orderedSet.retainAll(groupCell.get().getValue());
  315. columnIds = orderedSet.stream();
  316. visited.addAll(orderedSet);
  317. cellElement.attr("colspan", "" + orderedSet.size());
  318. writeCellState(cellElement, designContext,
  319. groupCell.get().getKey());
  320. } else {
  321. writeCellState(cellElement, designContext,
  322. entry.getValue().getCellState());
  323. }
  324. cellElement.attr("column-ids",
  325. columnIds.map(section::getColumnByInternalId)
  326. .map(Column::getId)
  327. .collect(Collectors.joining(",")));
  328. }
  329. }
  330. /**
  331. *
  332. * Writes declarative design for the cell using its {@code state} to the
  333. * given table cell element.
  334. * <p>
  335. * The method is used instead of StaticCell::writeDesign because
  336. * sometimes there is no a reference to the cell which should be written
  337. * (merged cell) but only its state is available (the cell is virtual
  338. * and is not stored).
  339. *
  340. * @param cellElement
  341. * Element to write design to
  342. * @param context
  343. * the design context
  344. * @param state
  345. * a cell state
  346. */
  347. protected void writeCellState(Element cellElement,
  348. DesignContext context, CellState state) {
  349. switch (state.type) {
  350. case TEXT:
  351. cellElement.attr("plain-text", true);
  352. cellElement
  353. .appendText(Optional.ofNullable(state.text).orElse(""));
  354. break;
  355. case HTML:
  356. cellElement.append(Optional.ofNullable(state.html).orElse(""));
  357. break;
  358. case WIDGET:
  359. cellElement.appendChild(
  360. context.createElement((Component) state.connector));
  361. break;
  362. }
  363. }
  364. void detach() {
  365. for (CELL cell : cells.values()) {
  366. cell.detach();
  367. }
  368. for (CellState cellState : rowState.cellGroups.keySet()) {
  369. if (cellState.type == GridStaticCellType.WIDGET
  370. && cellState.connector != null) {
  371. ((Component) cellState.connector).setParent(null);
  372. cellState.connector = null;
  373. }
  374. }
  375. }
  376. void checkIfAlreadyMerged(String columnId) {
  377. for (Set<String> cellGroup : getRowState().cellGroups.values()) {
  378. if (cellGroup.contains(columnId)) {
  379. throw new IllegalArgumentException(
  380. "Cell " + columnId + " is already merged");
  381. }
  382. }
  383. if (!cells.containsKey(columnId)) {
  384. throw new IllegalArgumentException(
  385. "Cell " + columnId + " does not exist on this row");
  386. }
  387. }
  388. void addMergedCell(CELL newCell, Set<String> columnGroup) {
  389. rowState.cellGroups.put(newCell.getCellState(), columnGroup);
  390. }
  391. public Collection<? extends Component> getComponents() {
  392. List<Component> components = new ArrayList<>();
  393. cells.forEach((id, cell) -> {
  394. if (cell.getCellType() == GridStaticCellType.WIDGET) {
  395. components.add(cell.getComponent());
  396. }
  397. });
  398. rowState.cellGroups.forEach((cellState, columnIds) -> {
  399. if (cellState.connector != null) {
  400. components.add((Component) cellState.connector);
  401. }
  402. });
  403. return components;
  404. }
  405. }
  406. /**
  407. * A header or footer cell. Has a simple textual caption.
  408. */
  409. abstract static class StaticCell implements Serializable {
  410. private final CellState cellState = new CellState();
  411. private final StaticRow<?> row;
  412. protected StaticCell(StaticRow<?> row) {
  413. this.row = row;
  414. }
  415. void setColumnId(String id) {
  416. cellState.columnId = id;
  417. }
  418. public String getColumnId() {
  419. return cellState.columnId;
  420. }
  421. /**
  422. * Gets the row where this cell is.
  423. *
  424. * @return row for this cell
  425. */
  426. public StaticRow<?> getRow() {
  427. return row;
  428. }
  429. /**
  430. * Returns the shared state of this cell.
  431. *
  432. * @return the cell state
  433. */
  434. protected CellState getCellState() {
  435. return cellState;
  436. }
  437. /**
  438. * Sets the textual caption of this cell.
  439. *
  440. * @param text
  441. * a plain text caption, not null
  442. */
  443. public void setText(String text) {
  444. Objects.requireNonNull(text, "text cannot be null");
  445. removeComponentIfPresent();
  446. cellState.text = text;
  447. cellState.type = GridStaticCellType.TEXT;
  448. row.section.markAsDirty();
  449. }
  450. /**
  451. * Returns the textual caption of this cell.
  452. *
  453. * @return the plain text caption
  454. */
  455. public String getText() {
  456. return cellState.text;
  457. }
  458. /**
  459. * Returns the HTML content displayed in this cell.
  460. *
  461. * @return the html
  462. *
  463. */
  464. public String getHtml() {
  465. if (cellState.type != GridStaticCellType.HTML) {
  466. throw new IllegalStateException(
  467. "Cannot fetch HTML from a cell with type "
  468. + cellState.type);
  469. }
  470. return cellState.html;
  471. }
  472. /**
  473. * Sets the HTML content displayed in this cell.
  474. *
  475. * @param html
  476. * the html to set, not null
  477. */
  478. public void setHtml(String html) {
  479. Objects.requireNonNull(html, "html cannot be null");
  480. removeComponentIfPresent();
  481. cellState.html = html;
  482. cellState.type = GridStaticCellType.HTML;
  483. row.section.markAsDirty();
  484. }
  485. /**
  486. * Returns the component displayed in this cell.
  487. *
  488. * @return the component
  489. */
  490. public Component getComponent() {
  491. if (cellState.type != GridStaticCellType.WIDGET) {
  492. throw new IllegalStateException(
  493. "Cannot fetch Component from a cell with type "
  494. + cellState.type);
  495. }
  496. return (Component) cellState.connector;
  497. }
  498. /**
  499. * Sets the component displayed in this cell.
  500. *
  501. * @param component
  502. * the component to set, not null
  503. */
  504. public void setComponent(Component component) {
  505. Objects.requireNonNull(component, "component cannot be null");
  506. removeComponentIfPresent();
  507. component.setParent(row.section.getGrid());
  508. cellState.connector = component;
  509. cellState.type = GridStaticCellType.WIDGET;
  510. row.section.markAsDirty();
  511. }
  512. /**
  513. * Returns the type of content stored in this cell.
  514. *
  515. * @return cell content type
  516. */
  517. public GridStaticCellType getCellType() {
  518. return cellState.type;
  519. }
  520. /**
  521. * Returns the custom style name for this cell.
  522. *
  523. * @return the style name or null if no style name has been set
  524. */
  525. public String getStyleName() {
  526. return cellState.styleName;
  527. }
  528. /**
  529. * Sets a custom style name for this cell.
  530. *
  531. * @param styleName
  532. * the style name to set or null to not use any style name
  533. */
  534. public void setStyleName(String styleName) {
  535. cellState.styleName = styleName;
  536. row.section.markAsDirty();
  537. }
  538. /**
  539. * Reads the declarative design from the given table cell element.
  540. *
  541. * @since 7.5.0
  542. * @param cellElement
  543. * Element to read design from
  544. * @param designContext
  545. * the design context
  546. */
  547. protected void readDesign(Element cellElement,
  548. DesignContext designContext) {
  549. if (!cellElement.hasAttr("plain-text")) {
  550. if (!cellElement.children().isEmpty()
  551. && cellElement.child(0).tagName().contains("-")) {
  552. setComponent(
  553. designContext.readDesign(cellElement.child(0)));
  554. } else {
  555. setHtml(cellElement.html());
  556. }
  557. } else {
  558. // text – need to unescape HTML entities
  559. setText(DesignFormatter.decodeFromTextNode(cellElement.html()));
  560. }
  561. }
  562. private void removeComponentIfPresent() {
  563. Component component = (Component) cellState.connector;
  564. if (component != null) {
  565. component.setParent(null);
  566. cellState.connector = null;
  567. }
  568. }
  569. void detach() {
  570. removeComponentIfPresent();
  571. }
  572. /**
  573. * Gets the tooltip for the cell.
  574. * <p>
  575. * The tooltip is shown in the mode returned by
  576. * {@link #getDescriptionContentMode()}.
  577. *
  578. * @since 8.4
  579. */
  580. public String getDescription() {
  581. return cellState.description;
  582. }
  583. /**
  584. * Sets the tooltip for the cell.
  585. * <p>
  586. * By default, tooltips are shown as plain text. For HTML tooltips, see
  587. * {@link #setDescription(String, ContentMode)} or
  588. * {@link #setDescriptionContentMode(ContentMode)}.
  589. *
  590. * @param description
  591. * the tooltip to show when hovering the cell
  592. * @since 8.4
  593. */
  594. public void setDescription(String description) {
  595. cellState.description = description;
  596. }
  597. /**
  598. * Sets the tooltip for the cell to be shown with the given content
  599. * mode.
  600. *
  601. * @see ContentMode
  602. * @param description
  603. * the tooltip to show when hovering the cell
  604. * @param descriptionContentMode
  605. * the content mode to use for the tooltip (HTML or plain
  606. * text)
  607. * @since 8.4
  608. */
  609. public void setDescription(String description,
  610. ContentMode descriptionContentMode) {
  611. setDescription(description);
  612. setDescriptionContentMode(descriptionContentMode);
  613. }
  614. /**
  615. * Gets the content mode for the tooltip.
  616. *
  617. * @see ContentMode
  618. * @return the content mode for the tooltip
  619. * @since 8.4
  620. */
  621. public ContentMode getDescriptionContentMode() {
  622. return cellState.descriptionContentMode;
  623. }
  624. /**
  625. * Sets the content mode for the tooltip.
  626. *
  627. * @see ContentMode
  628. * @param descriptionContentMode
  629. * the content mode for the tooltip
  630. * @since 8.4
  631. */
  632. public void setDescriptionContentMode(
  633. ContentMode descriptionContentMode) {
  634. cellState.descriptionContentMode = descriptionContentMode;
  635. }
  636. }
  637. private final List<ROW> rows = new ArrayList<>();
  638. /**
  639. * Creates a new row instance.
  640. *
  641. * @return the new row
  642. */
  643. protected abstract ROW createRow();
  644. /**
  645. * Returns the shared state of this section.
  646. *
  647. * @param markAsDirty
  648. * {@code true} to mark the state as modified, {@code false}
  649. * otherwise
  650. * @return the section state
  651. */
  652. protected abstract SectionState getState(boolean markAsDirty);
  653. protected abstract Grid<?> getGrid();
  654. protected abstract Column<?, ?> getColumnByInternalId(String internalId);
  655. protected abstract String getInternalIdForColumn(Column<?, ?> column);
  656. /**
  657. * Marks the state of this section as modified.
  658. */
  659. protected void markAsDirty() {
  660. getState(true);
  661. }
  662. /**
  663. * Adds a new row at the given index.
  664. *
  665. * @param index
  666. * the index of the new row
  667. * @return the added row
  668. * @throws IndexOutOfBoundsException
  669. * if {@code index < 0 || index > getRowCount()}
  670. */
  671. public ROW addRowAt(int index) {
  672. ROW row = createRow();
  673. rows.add(index, row);
  674. getState(true).rows.add(index, row.getRowState());
  675. getGrid().getColumns().stream().forEach(row::addCell);
  676. return row;
  677. }
  678. /**
  679. * Removes the row at the given index.
  680. *
  681. * @param index
  682. * the index of the row to remove
  683. * @throws IndexOutOfBoundsException
  684. * if {@code index < 0 || index >= getRowCount()}
  685. */
  686. public void removeRow(int index) {
  687. ROW row = rows.remove(index);
  688. row.detach();
  689. getState(true).rows.remove(index);
  690. }
  691. /**
  692. * Removes the given row from this section.
  693. *
  694. * @param row
  695. * the row to remove, not null
  696. * @throws IllegalArgumentException
  697. * if this section does not contain the row
  698. */
  699. public void removeRow(Object row) {
  700. Objects.requireNonNull(row, "row cannot be null");
  701. int index = rows.indexOf(row);
  702. if (index < 0) {
  703. throw new IllegalArgumentException(
  704. "Section does not contain the given row");
  705. }
  706. removeRow(index);
  707. }
  708. /**
  709. * Returns the row at the given index.
  710. *
  711. * @param index
  712. * the index of the row
  713. * @return the row at the index
  714. * @throws IndexOutOfBoundsException
  715. * if {@code index < 0 || index >= getRowCount()}
  716. */
  717. public ROW getRow(int index) {
  718. return rows.get(index);
  719. }
  720. /**
  721. * Returns the number of rows in this section.
  722. *
  723. * @return the number of rows
  724. */
  725. public int getRowCount() {
  726. return rows.size();
  727. }
  728. /**
  729. * Adds a cell corresponding to the given column id to this section.
  730. *
  731. * @param columnId
  732. * the id of the column for which to add a cell
  733. */
  734. public void addColumn(String columnId) {
  735. for (ROW row : rows) {
  736. row.internalAddCell(columnId);
  737. }
  738. }
  739. /**
  740. * Removes the cell corresponding to the given column id.
  741. *
  742. * @param columnId
  743. * the id of the column whose cell to remove
  744. */
  745. public void removeColumn(String columnId) {
  746. for (ROW row : rows) {
  747. row.removeCell(columnId);
  748. }
  749. markAsDirty();
  750. }
  751. /**
  752. * Writes the declarative design to the given table section element.
  753. *
  754. * @param tableSectionElement
  755. * Element to write design to
  756. * @param designContext
  757. * the design context
  758. */
  759. public void writeDesign(Element tableSectionElement,
  760. DesignContext designContext) {
  761. for (ROW row : getRows()) {
  762. Element tr = tableSectionElement.appendElement("tr");
  763. row.writeDesign(tr, designContext);
  764. }
  765. }
  766. /**
  767. * Reads the declarative design from the given table section element.
  768. *
  769. * @since 7.5.0
  770. * @param tableSectionElement
  771. * Element to read design from
  772. * @param designContext
  773. * the design context
  774. * @throws DesignException
  775. * if the table section contains unexpected children
  776. */
  777. public void readDesign(Element tableSectionElement,
  778. DesignContext designContext) throws DesignException {
  779. while (getRowCount() > 0) {
  780. removeRow(0);
  781. }
  782. for (Element row : tableSectionElement.children()) {
  783. if (!row.tagName().equals("tr")) {
  784. throw new DesignException("Unexpected element in "
  785. + tableSectionElement.tagName() + ": " + row.tagName());
  786. }
  787. addRowAt(getRowCount()).readDesign(row, designContext);
  788. }
  789. }
  790. /**
  791. * Returns an unmodifiable list of the rows in this section.
  792. *
  793. * @return the rows in this section
  794. */
  795. protected List<ROW> getRows() {
  796. return Collections.unmodifiableList(rows);
  797. }
  798. /**
  799. * Sets the visibility of this section.
  800. *
  801. * @param visible
  802. * {@code true} if visible; {@code false} if not
  803. *
  804. * @since 8.1.1
  805. */
  806. public void setVisible(boolean visible) {
  807. if (getState(false).visible != visible) {
  808. getState(true).visible = visible;
  809. }
  810. }
  811. /**
  812. * Gets the visibility of this section.
  813. *
  814. * @return {@code true} if visible; {@code false} if not
  815. *
  816. * @since 8.1.1
  817. */
  818. public boolean isVisible() {
  819. return getState(false).visible;
  820. }
  821. }