# src/admin.js

    import React from 'react';
    import _ from 'lodash';
    import Set from './set.js';
    import Select from 'react-select';
    import 'react-select/dist/react-select.css';
    const uuidv1 = require('uuid/v1');

    export const display_type = {
        list : "list",
        change : "change"
    };

    /** 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.queryset=[]
    	this._all_selected=false
    	this.register_listener()
    	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.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.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.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  },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.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>
    	    	    </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.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.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.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.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.list_per_page*(this.state.page_number-1)+1 }-{Math.min(this.list_per_page*(this.state.page_number-1)+this.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.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.list_per_page,this.state.queryset); }}> Back </button></div>
    	return null;

        }
        /**
         * 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.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()}
    	       </div>

        }
        render_change_page()
        {
    	return <div>
    	        {this.render_above_change_view()}
            	{this.render_change_view()}
    	        {this.render_below_change_view()}
    	       </div>

        }
        render()
        {
    	if(!this.has_module_permission())
    	{
    	    return <div> <h1> Permission Denied </h1></div>
    	}

    	if(this.state.display_type == display_type.list)
    	{
    	    return this.render_list_page();
    	}
    	else
    	{
    	    //Important: the next two lines are for URL navigation and handling the browser back button
    	    this._change_uuid=uuidv1();
    	    history.pushState({}, "Change View", window.location.hash+"/change/"+this._change_uuid);

    	    return this.render_change_page();
    	}

        }
        show_list_view()
        {
    	this._display_will_change()
    	this.setState({display_type : display_type.list},this._display_changed)

        }

        register_listener()
        {
    	this._hash_change_listener=(event)=> {

    	    if(this && this.state.display_type!=display_type.list && !_.endsWith(window.location.hash,this._change_uuid))
    	    {
    		this.show_list_view()
    	    }

    	}
    	window.addEventListener("hashchange",this._hash_change_listener );

        }

        componentWillUnmount()
        {
    	if(this._hash_change_listener){
    	    window.removeEventListener("hashchange",this._hash_change_listener,true );}
        }

    }

    export default Admin;