Home Reference Source

src/admin.js

import React from "react";
import _ from "lodash";
import Set from "./set.js";
import Select from "react-select";
import Modal from "react-modal";
const uuidv1 = require("uuid/v1");

//require("./admin.scss");
export const display_type = {
  list: "list",
  change: "change"
};
const customStyles = {
  content: {
    top: "50%",
    left: "50%",
    right: "auto",
    bottom: "auto",
    marginRight: "-50%",
    transform: "translate(-50%, -50%)"
  }
};
/** Admin Class extends React.Component. To implement a CRUD interface similar to Django Admin you need to extend the Admin class. */
class Admin extends React.Component {
  constructor(props) {
    super(props);

    this.name = "default";
    this.name_plural = "defaults";
    this.live_search = false;
    this.field_transforms = {};
    this.header_transforms = {};
    this.extra_fields = {};
    this.list_display = [];
    this.list_display_links = [];
    this.list_per_page = 10;
    this.sort_fields = [];
    this.is_object_equal = (a, b) => a == b;
    this.pages_in_pagination = 3;
    this.actions = {
      delete: selected_objects => {}
    };
    this._change_uuid = uuidv1();
    this.queryset = [];
    this._all_selected = false;

    if (props) {
      this.bind_exclude = props.bind_exclude ? props.bind_exclude : [];
      _.keys(props).map(key => {
        if (typeof props[key] == "function") {
          if (_.includes(key, this.bind_exclude)) {
            this[key] = props[key];

            this[key].bind(this);
          }
        } else {
          this[key] = props[key];
        }
      });
    }

    /**
     * Initialize the state of the component
     */
    this.state = {
      display_type: display_type.list,
      page_number: 1,
      object: null,
      selected_objects: new Set([], this.is_object_equal),
      total: 1,
      filter_options: {},
      filter_values: []
    };

    let queryset = this.get_queryset(
      this.state.page_number,
      this.list_per_page,
      this.state.queryset
    )
      ? this.get_queryset(
          this.state.page_number,
          this.list_per_page,
          this.state.queryset
        )
      : [];
    this.state.queryset = queryset;
    this.state.total = queryset.length;
    this._handle_search = this._handle_search.bind(this);
    this._select_all = this._select_all.bind(this);
    this._select_one = this._select_one.bind(this);
    this.response_change = this.response_change.bind(this);
    this.response_add = this.response_add.bind(this);
  }
  /**
   * This function returns an array of objects that will serve as the
   * queryset for the admin interface. Typically involves an HTTP request
   * to a backend.
   * @param {number} page_number - The current page number
   * @param {number} list_per_page - Number items to list per page. Defaults to `list_per_page`
   * @param {object[]} queryset - The current queryset
   * @returns {object[]} An array of objects. - Objects to display
   */

  get_queryset(
    page_number = this.state.page_number,
    list_per_page = this.get_list_per_page(),
    queryset = this.state.queryset
  ) {
    return this.queryset;
  }

  /**
   * This functions returns a JSON Schema Form for editing
   * the objects in the array returned by get_queryset(). This method needs to be overridden to
   * so as to return a Form Component for the object.  Learn more on
   * JSON schema forms from https://github.com/mozilla-services/react-jsonschema-form
   * and JSON Schema from https://spacetelescope.github.io/understanding-json-schema/
   * @param {object} object - The current selected object.
   * @returns  A JSON Schema Form Component.
   */

  get_form(object = null) {
    return {};
  }
  /**
   * Returns a true/false value. Controls wether search is implement on live input or not.
   * Can be overriden by the live_search member variable. Default is false.
   *
   *@return {boolean}
   */

  get_live_search() {
    return this.live_search;
  }

  /**
   *Returns an object whose properties are field names corresponding to properties of any object
   *in the queryset and whose values are transform functions. The example below will transform each "name"
   *property of objects in the queryset to upper case in the list display view.
   *@example
   *get_field_transforms()
   *{
   * return { 'name' : function(name,object)
   *                   {
   *                       return name.toUpperCase()
   *                   }
   *         }
   *
   *}
   *@return {object}
   */

  get_field_transforms() {
    if (this.field_transforms) {
      return this.field_transforms;
    }
    return {};
  }

  /**
   *Returns an object whose properties are field names corresponding to properties of any object
   *in the queryset and whose values are transform functions. The example below will transform the header "name" to upper case in the list display view.
   *@example
   *get_header_transforms()
   *{
   * return { 'name' : function(name)
   *                   {
   *                       return name.toUpperCase()
   *                   }
   *         }
   *}
   *
   *@return {object}
   */

  get_header_transforms() {
    if (this.header_transforms) {
      return this.header_transforms;
    }

    return {};
  }

  /**
   * Returns an object whose properties are extra  field names not corresponding to properties of any object
   * in the queryset and whose values are display functions. This will create extra fields that are not tied to objects. Extra fields have to be manually included in the `list_display` in order to appear in the list display page. The display functions take the current object being rendered in the table row and  the field name
   *@example
   *get_extra_fields()
   *{
   * return { 'now' : function(object,label)
   *                   {
   *                      return moment(new Date()).format('LLL');
   *                   }
   *         }
   *}
   *
   *@return {object}
   */

  get_extra_fields() {
    if (this.extra_fields) {
      return this.extra_fields;
    }

    return {};
  }
  /**
   * Returns the number of items to be listed in a page. Can be overridden by `list_per_page`.
   *@return {number} The number of objects in a page
   */

  get_list_per_page() {
    if (this.list_per_page) {
      return this.list_per_page;
    }
    return 10;
  }

  /**
     * Gets an actions object whose properties are action names and values are action methods.
     * This can be overridden by the action member variable. The default "delete" method is not
     * implemented.
     *
     * Each actions object property (e.g. "delete") is passed an array of selected objects. One
     * can then handle those objects. Actions will appear on the list display page within a
     * dropdown. Selecting an action should have the action method applied to all currently 
     * selected objects.
     * @example
     *
     * actions = { "delete" : (selected_objects)=>{ } }

     *@return {object} An actions object
     */

  get_actions() {
    if (this.actions) {
      return this.actions;
    }

    return {};
  }
  /**
   *   Gets the list/array of properties of the objects in the queryset that are clickable
   *    when displayed on the list display page. It can be overridden by the member variable
   *   `list_display_links`. A property is any string that should exist as a property in the objects within
   *   a queryset and works with lodash's `_.at` function.
   *
   *   The properties "name","address.street" and "emails[0]" are all acceptable by `_.at` in the example below
   *   @example
   *   let object={ name : "any name",{ address : { street : "any"}},emails: ["any@any.com"]}
   *
   *
   *
   * @return {string[]} A list of properties of the object to be displayed
   */

  get_list_display_links() {
    if (this.list_display_links) {
      return this.list_display_links;
    }
    return [];
  }
  /**
   *   Gets the list/array of properties/field names of the objects in the queryset to be
   *   displayed on the list display page. It can be overridden by the member variable
   *   list_display. A property is any string that should exist in the objects within
   *   a queryset and works with lodash's _.at function. See more at
   *   https://lodash.com/docs/#at
   *
   *   @example
   *   let object={ name : "any name",{ address : { street : "any"}},emails: ["any@any.com"]}
   *
   *   The properties "name","address.street" and "emails[0]" are all acceptable
   *
   * @return {string[]} A list of properties of the object to be displayed
   */

  get_list_display() {
    if (this.list_display) {
      return this.list_display;
    }
    return [];
  }
  /**
   * Returns an ordered queryset. The method checks to see if sorting is active and sorts
   * the current queryset based on the sort order.
   * @private
   * @return {object[]} An ordered queryset
   */

  _get_ordered_queryset() {
    if (this.sort_fields.length > 0) {
      return this.sort_by(this.sort_fields, this.state.queryset);
    }

    return this.state.queryset ? this.state.queryset : [];
  }
  /**
   * Implements search. This method should be overridden to implement a custom search
   *
   *@param {string} term - the search term
   *@param {object[]} queryset - the current queryset
   *@return {object[]} the queryset as a result of the search
   */

  search(term, queryset) {
    return queryset;
  }
  /**
   * Grants permission to delete object. This method is not implemented and can be handled via
   *  implementing actions.
   *
   *@param {object} object -
   *@return {boolean} Returns true is the object has been deleted or false otherwise
   */

  has_delete_permission(object) {
    return true;
  }
  /**
   * Grants permission to add an object. This method controls rendering of the Add
   * button
   *
   *
   *@return {boolean} Returns true is the object can be added or false otherwise
   */

  has_add_permission() {
    return true;
  }
  /**
   * Grants permission to change an object. It disables all links to the add/change page when
   * enabled
   *
   *@return {boolean} Returns true is the object can be changed or false otherwise
   */

  has_change_permission(object = null) {
    return true;
  }
  /**
   * Grants permission to the this admin interface.
   *
   *@return {boolean} Returns true if access is allowed false otherwise
   */

  has_module_permission() {
    return true;
  }
  /*
    _order_state(field)
    {

	let index=_.findIndex(this._sort_fields,item=> item.hasOwnProperty(field));
	
	if(index<0)
	{
	    return 'sort-not-active';

	}
	if(this._sort_fields[index][field]=='desc')
	{
	   
	    return 'sort-reverse-active';
	}
	return 'sort-active';
	}*/

  /**
     * This method adds up/down arrows to field headers on the list display page table
     *
     * @private 
     * @param {string} field -  the field/property name
     
     
     */

  _order_state_arrow(field) {
    let index = _.findIndex(this.sort_fields, item =>
      item.hasOwnProperty(field)
    );

    if (index < 0) {
      return null;
    }
    if (this.sort_fields[index][field] == "desc") {
      return <span>&#8681;</span>;
    }
    return <span> &#8679;</span>;
  }
  /**
   *  This method  should be overriden and called after saving an object in the add/change view.       *  This method is not called at all here but provides hints on what to do after saving
   *  an object. Change the state display_type to "list", object to "null" and refresh the quer         *  yset.
   */

  response_add() {
    this._display_will_change();
    this.setState(
      {
        display_type: display_type.list,
        object: null,
        queryset: this.get_queryset(
          this.state.page_number,
          this.get_list_per_page(),
          this.state.queryset
        )
      },
      this._display_changed
    );

    return true;
  }
  /**
   *  This method should be overriden and called after saving an object in the add/change view.         *  This method is not called at all here but provides hints on what to do after saving
   *  an object. Change the state display_type to "list", object to "null" and refresh the quer         *  yset.
   */

  response_change() {
    this._display_will_change();
    this.setState(
      {
        display_type: display_type.list,
        object: null,
        queryset: this.get_queryset(
          this.state.page_number,
          this.get_list_per_page(),
          this.state.queryset
        )
      },
      this._display_changed
    );

    return true;
  }
  /**
     * A private method to wrap a table entry in an <a></a> tag in the display page.
     * The method checks if permission is given to display links using the has_change_permission method
     * @private
     * @param {object} object - the current object to be displayed as a table
     * entry in the display page
     * @param {string} label - the name of the field 
     
     */

  _create_object_link(object, label) {
    if (this.has_change_permission(object)) {
      return (
        <a onClick={this._object_link_clicked(object)} href="#">
          {" "}
          {label}{" "}
        </a>
      );
    } else {
      return <span> {label} </span>;
    }
  }

  set_queryset(queryset) {
    this.setState({ queryset: queryset });
  }

  set_total(total) {
    this.setState({ total: total });
  }

  _get_prop_label(label) {
    let labels = label.split(".");
    return labels[labels.length - 1];
  }
  _get_table_header() {
    return this.get_list_display().map(item => {
      return (
        <th key={item} onClick={this._sort_handler(item)}>
          {" "}
          {this.get_header_transforms()[item]
            ? this.get_header_transforms()[item](item)
            : _.startCase(this._get_prop_label(item))}
          {this._order_state_arrow(item)}{" "}
        </th>
      );
    });
  }
  _display_changed() {
    this.display_changed(this.state.display_type, this.state.object);
  }
  /**
     * An event listener that listens to state changes in the display type i.e.
     * from list to add/change. It is fired after state changes are made.
     * 
     * @param {string} display_type - the current display type of the component either "list" or "change" 
     * @param {object} object - the current object or null
     
     */

  display_changed(display_type, object) {}
  _display_will_change() {
    this.display_will_change(this.state.display_type, this.state.object);
  }
  /**
     * An event listener that listens to state changes in the display type i.e.
     * from list to add/change . It is fired before state changes are made.
     * 
     * @param {string} display_type - the current display type of the component either "list" or "change" 
     * @param {object} object - the current object or null
     
     */

  display_will_change(display_type, object) {}
  _object_link_clicked(object) {
    return event => {
      this._display_will_change();
      this.setState(
        { display_type: display_type.change, object: object, loading: false },
        this._display_changed()
      );
      event.preventDefault();
    };
  }

  _display_field(object, item) {
    let label = _.at(object, item)["0"];

    if (_.has(this.get_field_transforms(), item)) {
      return this.get_field_transforms()[item](label, object);
    }

    if (_.at(this.get_extra_fields(), item)) {
      if (this.get_extra_fields()[item]) {
        label = this.get_extra_fields()[item](object, item);
      }
    }

    return label;
  }

  set_filter_options(filter_name, options) {
    let t = {};
    t[filter_name] = options.map(option => {
      option._filter_ = filter_name;
      return option;
    });
    this.setState({ filter_options: _.assign(this.state.filter_options, t) });
  }

  get_filter_values() {
    return this.state.filter_values;
  }
  get_filters() {
    /*
	return {

	    "by_id" : { "options" : [
			    { value: 'one', label: 'One' },
	    { value: 'two', label: 'Two' },

		
	    ],
	     "filter_function" : (option,queryset)=>
	     {


		 return queryset;
	     }
           }
	    
	}*/
  }
  _handle_filter_change(values) {
    if (!(values instanceof Array) && values != null) {
      values = [values];
    }

    this.setState({ filter_values: values || [] });

    if (values == null || values.length <= 0) {
      this.set_queryset(
        this.get_queryset(
          this.state.page_number,
          this.get_list_per_page(),
          this.state.queryset
        )
      );
      return;
    }

    let filters = this.get_filters();
    for (let value of values) {
      let queryset = filters[value._filter_].filter_function(
        value,
        this.state.queryset
      );
      this.setState({ queryset: queryset });
    }
  }
  render_filters() {
    let options = [];
    let filters = this.get_filters();
    for (let filter of _.keys(filters)) {
      options.push({
        label: _.startCase(filter),
        value: filter,
        disabled: true,
        filter: null
      });

      if (filters[filter].options.length > 0) {
        for (let option of filters[filter].options) {
          option._filter_ = filter;
          options.push(option);
        }
      }
      if (this.state.filter_options[filter]) {
        for (let option of this.state.filter_options[filter]) {
          option._filter_ = filter;
          options.push(option);
        }
      }
    }

    return (
      <div style={{ marginLeft: "10px", width: "100%" }}>
        <Select
          name="filter-form"
          multi={true}
          onChange={this._handle_filter_change.bind(this)}
          closeOnSelect={true}
          value={this.state.filter_values}
          removeSelected={true}
          placeholder={"Select a filter"}
          options={options}
        />
      </div>
    );
  }
  sort_by(sort_fields) {
    return this.state.queryset ? this.state.queryset : [];
  }

  _sort_handler(field) {
    return event => {
      let index = _.findIndex(this.sort_fields, item =>
        item.hasOwnProperty(field)
      );
      if (index >= 0) {
        if (this.sort_fields[index][field] == "asc") {
          this.sort_fields[index][field] = "desc";
        } else {
          this.sort_fields = _.filter(
            this.sort_fields,
            item => !item.hasOwnProperty(field)
          );
        }
      } else {
        let temp = {};
        temp[field] = "asc";
        this.sort_fields.push(temp);
      }

      this.sort_by(this.sort_fields);
      this.forceUpdate();
    };
  }
  /**
   * This method is an event handler that listens to when all objects of the queryset
   * displayed within a single display page are selected
   */

  _select_all(event) {
    if (this._all_selected) {
      this.setState({ selected_objects: new Set([], this.is_object_equal) });
    } else {
      this.setState({
        selected_objects: new Set(this.state.queryset, this.is_object_equal)
      });
    }
    this._all_selected = !this._all_selected;
  }
  /**
   * This method is an event handler that listens when a single objects of the queryset
   * displayed is selected
   */

  _select_one(object) {
    return event => {
      if (this.state.selected_objects.contains(object)) {
        this.state.selected_objects.remove(object);

        this.setState({ selected_objects: this.state.selected_objects });
      } else {
        this.state.selected_objects.add(object);
        this.setState({ selected_objects: this.state.selected_objects });
      }
    };
  }

  /**
   * Generate the table body for the list display page
   *
   * @return  An array of table rows <tr/>
   */

  _get_table_body() {
    return this._get_ordered_queryset().map((object, i) => {
      return (
        <tr key={"row-" + i}>
          <td>
            {" "}
            <input
              type="checkbox"
              id={i + "_checkbox"}
              onChange={this._select_one(object)}
              checked={this.state.selected_objects.contains(object)}
            />{" "}
            <label htmlFor={i + "_checkbox"}>&nbsp;</label>{" "}
          </td>
          {this.get_list_display().map(item => {
            return (
              <td key={item}>
                {" "}
                {this.get_list_display_links().find(a => {
                  return item == a;
                })
                  ? this._create_object_link(
                      object,
                      this._display_field(object, item)
                    )
                  : this._display_field(object, item)}{" "}
              </td>
            );
          })}
        </tr>
      );
    });
  }
  /**
   * Changes the state property "loading" to true. The state property can be used to show a
   * progress indicator.
   */

  show_progress() {
    this.setState({ loading: true });
  }
  /**
   * Changes the state property "loading" to false. This method  can be used to hide a
   * progress indicator by inspecting the "loading" property of the state object.
   */

  hide_progress() {
    this.setState({ loading: false });
  }
  /**
   * The default progress indicator. Can be overriden.
   * @return A progress indicator component
   */

  render_progress(loading) {
    if (loading) {
      return (
        <div className="fetch-progress">
          <progress />
        </div>
      );
    }
  }
  /**
   * An event listener that listens to the search event. This method calls search method which
   * implements a custom search. The method uses the "live_search" property to implement live
   * search or not.
   * @private
   * @param {object} event The search onChange event
   */

  _handle_search(event) {
    let term = event.target.value;

    if (term) {
      let key = event.which || event.keyCode;

      if (this.get_live_search() || key === 13) {
        let queryset = this.search(term, this.state.queryset);
        this.setState({ queryset: queryset, total: queryset.length });
      }
    } else {
      this.setState({
        queryset: this.get_queryset(
          this.state.page_number,
          this.get_list_per_page(),
          this.state.queryset
        ),
        total: this.get_queryset(
          this.state.page_number,
          this.list_per_page,
          this.state.queryset
        ).length
      });
    }
  }
  /**
   * Renders the search component
   *
   * @return A search input field
   */

  render_search_field() {
    return (
      <div>
        <input
          name="search"
          type="text"
          className="ra-search-field"
          placeholder="Search"
          onChange={this._handle_search}
          onKeyUp={this._handle_search}
        />
      </div>
    );
  }
  /**
   * Renders the add object button. Checks to see if permission is given by has_add_permission
   *
   * @return An add button component
   */

  render_add_button() {
    if (this.has_add_permission()) {
      return (
        <button
          className={"ra-add-button"}
          onClick={this._object_link_clicked(null)}
        >
          {" "}
          Add {_.startCase(this.name)}
        </button>
      );
    }
  }
  /**
   * Renders the table in the display page. This calls _get_table_header and _get_table_body
   *
   * @return An a table displaying the state queryset set objects
   */

  render_table() {
    return (
      <table className="table">
        <thead>
          <tr key="header">
            <th>
              <input
                type="checkbox"
                id="all_boxes"
                onChange={this._select_all}
              />{" "}
              <label htmlFor="all_boxes">&nbsp;</label>
            </th>
            {this._get_table_header()}
          </tr>
        </thead>
        <tbody>{this._get_table_body()}</tbody>
      </table>
    );
  }
  /**
   * An event listener that listens to actions selected.
   *
   *@param {object} event -  the DOM on-change event
   */

  action_selected(event) {
    let action = event.target.value;

    console.log(this.state.selected_objects.getItems());
    this.get_actions()[action](this.state.selected_objects.getItems());
    this.get_queryset(this.state.page_number, this.get_list_per_page());
  }
  /**
   * An event listener that listens when a page is  selected.
   *
   *@param {number} page -  the page number selected
   */

  selectPage(page) {
    return event => {
      this.setState({ page_number: page.page }, () => {
        this.setState({
          queryset: this.get_queryset(
            this.state.page_number,
            this.get_list_per_page(),
            this.state.queryset
          )
        });
      });
      event.preventDefault();
    };
  }
  /**
   * An event listener that listens when the next page is  selected.
   *
   */

  nextPage() {
    this.setState({
      page_number: Math.min(this.state.page_number + 1, this.state.total)
    });
  }
  /**
   * An event listener that listens when the previous page is  selected.
   *
   */

  prevPage() {
    this.setState({ page_number: Math.max(this.state.page_number - 1, 1) });
  }
  /**
   * Renders the pagination UI
   *
   * @return A component that renders the pagination controls
   */

  render_pagination() {
    let pages = [];
    if (this.state.total) {
      let numpages = Math.ceil(this.state.total / this.get_list_per_page());
      let pages_in_pagination =
        numpages < this.pages_in_pagination
          ? numpages
          : this.pages_in_pagination;

      if (this.state.page_number == 1) {
        for (let i = 0; i < pages_in_pagination; i++) {
          pages.push(i + 1);
        }
      } else if (this.state.page_number == numpages) {
        for (let i = 0; i < pages_in_pagination; i++) {
          pages.push(numpages - i);
        }
      } else {
        for (let i = 0; i < pages_in_pagination; i++) {
          pages.push(this.state.page_number + i);
        }
      }
      /*
		for(var i=0;i<numpages;i++)
	    {
		pages.push(i+1);

	    }*/
    }

    return (
      <div className="pull-right">
        <span className="summary">
          {this.get_list_per_page() * (this.state.page_number - 1) + 1}-
          {Math.min(
            this.get_list_per_page() * (this.state.page_number - 1) +
              this.get_list_per_page(),
            this.state.total
          )}{" "}
          of {this.state.total}{" "}
        </span>

        <nav aria-label="Page navigation">
          <ul className="pagination">
            <li key={"left"} className="page-item">
              <a
                href="#"
                aria-label="Previous"
                onClick={this.prevPage.bind(this)}
                className="page-link"
              >
                <span aria-hidden="true">&laquo;</span>
              </a>
            </li>

            {pages.map(page => {
              return (
                <li key={page} className="page-item">
                  <a
                    href="#"
                    className="page-link"
                    onClick={this.selectPage({ page })}
                  >
                    {page}
                  </a>
                </li>
              );
            })}
            <li className="page-item">
              <a
                href="#"
                key={"right"}
                onClick={this.nextPage.bind(this)}
                aria-label="Next"
                className="page-link"
              >
                <span aria-hidden="true">&raquo;</span>
              </a>
            </li>
          </ul>
        </nav>
      </div>
    );
  }
  /**
   * Renders the actions select component
   *
   * @return A component that renders a select input for all actions in the list display page
   */

  render_actions() {
    return (
      <select
        className="ra-action-button"
        onChange={this.action_selected.bind(this)}
        defaultValue=""
      >
        <option key="key" value="" disabled={true}>
          Choose an action
        </option>
        {_.keys(this.get_actions()).map(action => {
          return (
            <option key={action} value={action}>
              {" "}
              {_.startCase(action)}
            </option>
          );
        })}
      </select>
    );
  }
  /**
   * Renders the back button component in the add/change view
   * @ignore
   * @return A component that renders a back button
   */

  render_back_button() {
    return (
      <div>
        <button
          className={"ra-back-button"}
          onClick={() => {
            this.setState({ display_type: display_type.list, object: null });
            this.get_queryset(
              this.state.page_number,
              this.get_list_per_page(),
              this.state.queryset
            );
          }}
        >
          {" "}
          Back{" "}
        </button>
      </div>
    );
  }
  /**
   * Renders the admin interface component
   *
   * @return A component that renders the admin interface
   */
  render_list_view() {
    return (
      <div>
        {this.render_add_button()}
        {this.render_below_add_button()}
        {this.render_search_field()}
        {this.render_below_search_field()}
        {this.render_actions()}
        {this.render_below_actions()}
        {this.render_filters()}
        {this.render_below_filters()}

        {this.render_table()}
        {this.render_below_table()}
        {this.render_progress(this.state.loading)}
        {this.render_below_progress()}

        {this.render_pagination()}
      </div>
    );
  }
  render_below_add_button() {}
  render_below_search_field() {}

  render_below_actions() {}

  render_below_filters() {}

  render_below_table() {}

  render_below_progress() {}

  render_change_view() {
    return (
      <div>
        {this.render_progress(this.state.loading)}
        {this.render_back_button()}
        {this.get_form(this.state.object)}
      </div>
    );
  }
  render_above_change_view() {}
  render_below_change_view() {}
  render_above_list_view() {}
  render_below_list_view() {}
  render_list_page() {
    return (
      <div>
        {this.render_above_list_view()}
        {this.render_list_view()}
        {this.render_below_list_view()}
        {this.render_change_modal()}
      </div>
    );
  }

  render_change_modal() {
    return (
      <Modal
        isOpen={this.state.display_type != display_type.list}
        contentLabel={this.name || ""}
        parentSelector={() => document.querySelector("#react-crud-admin")}
        styles={customStyles}
      >
        {this.render_above_change_view()}
        {this.render_change_view()}
        {this.render_below_change_view()}
      </Modal>
    );
  }
  render_permission_denied() {
    return (
      <div>
        {" "}
        <h1> Permission Denied </h1>
      </div>
    );
  }
  render() {
    if (!this.has_module_permission()) {
      return this.render_permission_denied();
    }

    return <div id="react-crud-admin">{this.render_list_page()}</div>;
  }
  show_list_view() {
    this._display_will_change();
    this.setState({ display_type: display_type.list }, this._display_changed);
  }
}

export default Admin;