arnehehe
arnehehe

Reputation: 1398

Dynamically generate tables for varying objects

For something I'm making in Java/jsp, I'm going to have a lot of visual tables that look alike but for varying objects. Each of the objects has a different number of fields, but shares at least an id that links through a more detailed page of the object.

So I was thinking about the possibility of making something that I could reuse every time, no matter of how many fields the object has.

Here's an example of what I'd like to achieve

Object A
-id
-name
-address

Object B
-id
-field2
-field3

Would like to translate that to:

Table for Object A's
id -- name -- address
1     Bert    Street
2     Jeff    Lane

Table for Object B's
id  -- field2  -- field3
1        AB         5
2        Foo        Bar

What would be the best way to achieve this? I was thinking of (other than the id), adding an enumerable getter to both objects with all the fields, then looping over that to generate the table.

Any better ways?

Upvotes: 1

Views: 2044

Answers (2)

Sinisha Mihajlovski
Sinisha Mihajlovski

Reputation: 1831

What I usually do is create a class like this to be my table model:

import java.io.Serializable;
import java.util.Arrays;
import java.util.List;

import com.tricode.mrc.ui.web.messages.Messages;

/**
 * 
 * @author Sinisa
 *
 * @param <T> the entity type
 */
public abstract class AbstractCrudForm<T extends Serializable> {

        private List<T> data;

        public AbstractCrudForm(List<T> data) {
                this.data = data;
        }

        /**
         * @param record
         * @return the value of the id field of the given record
         */
        protected abstract String getId(T record);

        /**
         * @return ui name of the entity that is displayed <br>i.e. for DataValidationConfiguration will return "Data Validation Configuration"
         */
        public abstract String getUserFriendlyTypeName();

        /**
         * @return the number of fields of the columns (fields)
         */
        public abstract int getNumberOfFields();

        /**
         * Return the column names as seen in the header of the crud table
         * @return
         */
        public abstract String[] getArrayOfColumnNames();

        /**
         * @param record
         * @param column
         * @return
         */
        protected abstract String getDataAtColumn(T record, int column);

        /**
         * Returns the data at the specified position
         * @param row
         * @param column
         * @return the data at the specified position
         */
        public String getDataAt(int row, int column) {
                return getDataAtColumn(data.get(row), column);
        }

        /**
         * @return a list of the data
         */
        protected List<T> getData() {
                return this.data;
        }

        /**
         * @return the user friendly name for the title of the ui form for editing
         */
        public String getUserFriendlyEditTypeName() {
                return Messages.getString("AbstractCrudForm.Edit") + getUserFriendlyTypeName(); //$NON-NLS-1$
        }

        /**
         * @return the user friendly name for the title of the ui form for editing
         */
        public String getUserFriendlySaveTypeName() {
                return Messages.getString("AbstractCrudForm.New") + getUserFriendlyTypeName(); //$NON-NLS-1$
        }

        /**
         * @return a list of the column names
         */
        public List<String> getColumns() {
                return Arrays.asList(getArrayOfColumnNames());
        }

        /**
         * @param position
         * @return the column name at a given position
         */
        public String getColumnNameAt(int position) {
                return getArrayOfColumnNames()[position];
        }

        /**
         * The result size
         * @return
         */
        public int resultsSize() {
                return data.size();
        }

        /**
         * @param row
         * @return the value of the id field for the given record
         */
        public String getId(int row) {
                return getId(data.get(row));
        }

}

For every class I want to implement(in your case object A and object B) I override this Abstract model, something like:

import java.util.List;

import com.tricode.misterchameleon.model.DataValidationConfiguration;
import com.tricode.mrc.ui.web.AbstractCrudForm;
import com.tricode.mrc.ui.web.messages.Messages;

public class DataValidationConfigurationCrudForm extends AbstractCrudForm<DataValidationConfiguration> {

        public DataValidationConfigurationCrudForm(List<DataValidationConfiguration> data) {
                super(data);
        }

        @Override
        public String getTypeName() {
                return DataValidationConfiguration.class.getSimpleName();
        }

        @Override
        public String getUserFriendlyTypeName() {
                return Messages.getString("DataValidationConfigurationCrudForm.DataValidationConfiguration"); //$NON-NLS-1$
        }

        @Override
        public int getNumberOfFields() {
                // TODO take it with generics
                return 8;
        }

        @Override
        public String[] getArrayOfColumnNames() {
                return new String[] {
                                Messages.getString("DataValidationConfigurationCrudForm.ID"), Messages.getString("DataValidationConfigurationCrudForm.DomainName"), Messages.getString("DataValidationConfigurationCrudForm.FormUrl"), Messages.getString("DataValidationConfigurationCrudForm.PostalCode"), Messages.getString("DataValidationConfigurationCrudForm.HouseNumber"), Messages.getString("DataValidationConfigurationCrudForm.Street"), Messages.getString("DataValidationConfigurationCrudForm.City"), //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$ //$NON-NLS-4$ //$NON-NLS-5$ //$NON-NLS-6$ //$NON-NLS-7$
                                Messages.getString("DataValidationConfigurationCrudForm.Country") }; //$NON-NLS-1$
        }

        @Override
        protected String getDataAtColumn(DataValidationConfiguration config, int column) {
                switch (column) {
                case 0:
                        return config.getId().toString();
                case 1:
                        return config.getDomainName();
                case 2:
                        return config.getFormUrl();
                case 3:
                        return config.getPostalCode();
                case 4:
                        return config.getHouseNumber();
                case 5:
                        return config.getStreet();
                case 6:
                        return config.getCity();
                case 7:
                        return config.getCountry();
                default:
                        throw new IllegalArgumentException(Messages.getString("DataValidationConfigurationCrudForm.YouShouldntHaveGottenHere")); //$NON-NLS-1$
                }
        }

        @Override
        protected String getId(DataValidationConfiguration record) {
                return record.getId().toString();
        }

}

I pass this object to the jsp, and then I have a generic jsp file for drawing the table:

<%@taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c"%>
<%@taglib uri="http://www.springframework.org/tags/form" prefix="form"%>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt"%>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags"%>
<div class="row">
    <div class="twelve columns">
        <h1>${genForm.getUserFriendlyTypeName()}</h1>

        <a href="${genForm.getCreateLink()}" class="btn_styled">Create</a>
        <table class="border">
            <thead>
                <tr>
                    <c:forEach var="col" items="${genForm.getColumns()}">
                        <th>${col}</th>
                    </c:forEach>
                    <!-- Add two more for delete and edit -->
                    <th></th>
                    <th></th>
                </tr>
            </thead>
            <c:if test="${genForm.resultsSize() == 0}">
                <tr colspan="${genForm.getNumberOfFields()}">
                    <td><spring:message code="common.crud.norecords" text="common.crud.norecords"/></td>
                </tr>
            </c:if>
            <c:if test="${genForm.resultsSize() > 0}">
                <c:forEach var="row" begin="0" end="${genForm.resultsSize() - 1}">
                    <tr>
                        <c:set var="edit" value="?" />
                        <c:forEach var="column" begin="0"
                            end="${genForm.getNumberOfFields() - 1}">
                            <td>${genForm.getDataAt(row, column)}</td>
                            <c:set var="edit"
                                value="${edit}${genForm.getJavaFieldNameAt(column)}=${genForm.getDataAt(row, column)}&" />
                        </c:forEach>
                        <!-- Add delete and edit buttons -->
                        <th><a id="edit-row" class="btn_styled"
                            href="${genForm.getEditLink()}${edit}"><spring:message
                                    code="common.button.edit" text="common.button.edit" /></a></th>
                        <th><a id="delete-row" class="btn_styled"
                            href="${genForm.getDeleteLink()}?id=${genForm.getId(row)}"><spring:message
                                    code="common.button.delete" text="common.button.delete" /></a></th>
                    </tr>
                </c:forEach>
            </c:if>

        </table>
    </div>
</div>

All the code I posted should not be taken as a copy/paste solution, since it will probably not work right away. It should be taken as an idea, and a clean way of solving your issue.

Upvotes: 1

Jerome
Jerome

Reputation: 2174

You can add a field to your main class that would be a Map<String,Object> where the key of the Map would be the name of your property.

Or, if your fields are always strings, you can use Properties, which are basically a Map<String,String> with some little twists (default value for example)

Upvotes: 2

Related Questions