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

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