A jQuery plugin providing an admin panel for any REST backend. Supports nested panels.
JavaScript
Switch branches/tags
Nothing to show
Permalink
Failed to load latest commit information.
web/js
README.md New jraRefreshedList event is triggered on the container whenever the… Oct 5, 2012

README.md

jquery-rest-admin

A jQuery plugin providing an admin panel for any REST backend. Supports nested panels.

Here's an example with text, checkbox and single select controls. This example manages all of the 'category' objects in the system by talking to a REST controller on the back end. As long as that REST controller follows the REST conventions and responds with a JSON representation of the object or array of objects, it will just work.

(Of course, since this is a browser-side solution, your REST controller must check permissions and validate everything sent to it for possible malicious or out-of-bounds data, no matter how much browser-side validation has been done.)

Note that we pass in an array of JSONified "type" objects as the choices for one of the single select dropdowns. We can do that as long as we specify what the choiceLabel and choiceValue property names are. Another single select dropdown just has label and value properties for each option. These are the default values for choiceLabel and choiceValue.

$(function() {
  var types = [ { name: "Free", id: 1 }, { name: "Jobs", id: 2 } ];
  $('.categories').restAdmin({
    url: '/admin/categories',
    schema: [ 
      { 
        name: "name", 
        type: "text", 
        label: "Name",
        unique: true,
        required: true
      },
      { 
        name: "posters",
        type: "select",
        label: "Who Can Post?",
        choices: [ { label: "Everyone", value: "everyone" }, { label: "Admins", value: "admins" }],
        required: true
      },
      { 
        name: "type",
        type: "select",
        label: "Type",
        choices: types,
        choiceLabel: 'name',
        choiceValue: 'id',
        required: true
      },
      {
        name: "paid_option",
        type: "checkbox",
        label: "Paid"
      }
    ] 
  });
});

Here's another example with another admin nested in the first, allowing every department to have an editable list of aliases. By default the nested admin does not carry out its own REST actions.

There actually is a REST URL convention for nested objects:

/admin/departments/5/aliases

But this would not work for adding aliases to a new department that has not been saved yet, and it adds more wait time to the system. So instead, the complete collection of objects created with the nested admin (the aliases for the department) is submitted as a single array parameter when the parent object (the department) is saved.

$(function() {
  // Preloading the data is optional, it'll go get it via REST if you don't
  var items = [ { "name": "Biology", "id": 1 }, "name": "Chemistry", "id" 2 } ];
  $('.departments').restAdmin({
    data: items, 
    url: '/admin/departments', 
    sortable: true,
    container: '.departments', 
    schema: [ 
      { 
        name: "name", 
        type: "text", 
        label: "Name",
        required: true,
        unique: true
      },
      {
        name: "aliases",
        type: "admin",
        label: "Aliases",
        labelColumn: "name",
        options: {
          sortable: false,
          schema: [
            {
              name: "name",
              type: "text",
              label: "Name",
              required: true,
              unique: true
            }
          ]
        }
      }
    ] 
  });
});

Again, note that by default nested admins don't do their own REST requests. Instead the entire thing is serialized as an array parameter in the POST or PUT request for the parent object.

By default, jquery-rest-admin looks for a primary key in the id column of each object. You can change this by specifying an alternate column name with the "id" option.

What Does My Backend REST Controller Have To Do?

By default jquery-rest-admin retrieves, creates, updates and deletes objects via the REST URL that you specify. If your url option is set to /admin/categories, then the following methods and URLs will be accessed as needed:

GET /admin/categories should return a JSON array containing JSON objects representing every item. Items will be presented in the order returned (TODO: support browser side sorting). At some point offset and limit query parameters for server side pagination will be supported.

POST /admin/categories creates a new object by submitting a standard POST request with a value for each editable property of the object. Note that the request looks just like a normal POST form submission. If there are nested admins, by default their entire contents serialize as an array parameter in the parent admin's POST submission. It should return a JSON object representing the saved object on success. A checkbox that is checked submits the value 1. A checkbox that is not checked submits an empty string. This is a big improvement over the ambiguous "field not present at all" behavior of standard HTML checkboxes. It's very handy in both PHP and JavaScript, because both languages consider 1 true and an empty string false. Our apologies to Ruby developers who may have to explicitly check for an empty string, but you can't submit false or nil via a traditional urlencoded form, so there's not a lot more we can do to help.

PUT /admin/categories/5 updates the object with the id 5. Otherwise it is identical to POST. It should return a JSON object representing the saved object on success.

DELETE /admin/categories/5 deletes the object with the id 5. It should return a JSON object representing the deleted object on success.

PUT /admin/categories/rank submits a single array parameter called order, containing a list of the IDs of all items, in the order in which the user has just manually sorted them. This will never happen if options.sortable is not true. The response does not matter as long as the status code is successful.

Browser-Side Validation

We do a little. Soon we'll do more. Right now you can set required: true or unique: true for a column. If you set required: true that column can't be left blank. If you set unique: true, a new or updated object is not permitted to have the same value for that column as another object. Keep in mind this is based only on what is known to the admin.

You can also set deleteValidator, which should be a function that takes three parameters:

deleteValidator: function(datum, schema, name) { ... }

The first argument is the object to be deleted. The second argument is the schema object (options.schema). The third argument is the name of the column being validated.

If your deleteValidator returns true, the deletion is allowed to proceed. If the deleteValidator returns false, the deletion is blocked.

Overriding The Storage Method

If you don't like the default REST storage implementation, you can override the following:

options.loadData should accept an options argument and retrieve all of the objects from the server (GET /admin/categories). Invoke options.success with the array of objects if all goes well. Invoke options.error if all does not go well.

options.create should accept a datum (an object to create) and an options argument. Invoke options.success with the returned object if all goes well. Invoke options.error if all does not go well.

options.update should accept a datum (an object to update) and an options argument. Invoke options.success with the updated object if all goes well. Invoke options.error if all does not go well.

options.remove should accept a datum (an object to remove) and an options argument. Invoke options.success with the removed object if all goes well. Invoke options.error if all does not go well.

options.rank should accept an order parameter (an array of IDs of objects, in the desired sorting order) and an options argument. Invoke options.success if all goes well. Invoke options.error if all does not go well.

"What if I just want to update an array of objects on the browser side and I don't care about storage in a backend somewhere?" Just set options.local to true and make sure you pass in the initial array of objects as options.data. Your options.data array will be updated in place, so that you can inspect it at any time and find the latest edits are present.

Adding and Customizing Types

Right now the available column types are:

text, textarea, richtext, checkbox, select (single selection), readOnly (a text field that cannot be edited), and admin.

The richtext type is currently coded to rely on CKEditor. If you use it, you are responsible for loading ckeditor.js before you instantiate jqueryRestAdmin. The default toolbar is used (yes, it would be nice to pass on ckeditor configuration options, including the location of the custom configuration file - send us a pull request).

Of course this is not enough for everyone (TODO: add multiple hurryupquick, based on aMultipleSelect).

Fortunately you can add your own types. This is what the text type would look like if it were not built in:

var options = { ... the rest of the options I'm planning to pass to restAdmin... };
options.types.text = { 
  listText: function(column, val) {
    return val;
  },
  control: function(column, val) {
    var e = $('<input type="text" data-role="control" />');
    e.val(val);
    return e;
  },
  defaultValue: ''
});

The listText method must return a reasonable text representation of the current value of the control, for display in the list view (TODO: make displaying columns in the list view optional). The first argument is a column object, allowing you access to any properties that were set on this column. That is, if the schema includes a column like this:

{ name: 'first_name', required: true }

Then the column object has name and required properties that can be accessed from listText.

The control method returns a jQuery element which displays an editing control for your type. The column object is the first parameter and the current value is the second.

If your type has an HTML element which can be interrogated with the regular jQuery val() function to get its current value, just make sure your control method applies a data-role="control" attribute to it so that jquery-rest-admin can locate that element. Alternatively, you can set selfUpdating: true, in which case your control is expected to directly update val. This is the right approach if the value of your column is a modifiable object. This feature is currently used for nested admins, but you may find it useful elsewhere.

An alternative to selfUpdating: true is to define a getValue function for your type. This function receives the column definition as its first argument and the jQuery element for the control as second argument, and should return the appropriate value to submit. See the built-in implementation of the checkbox type for a good example, included because val() is not useful for checkboxes.

Finally, defaultValue establishes the default value for columns of this type if there is no default value for the column.

Note that you can override any part of the standard types' implementation via the options object, as well as adding new types.

Events

If your control does have an element that can be queried via val() but needs a nudge to update it when a save operation takes place, just bind the jraUpdate jQuery event on your control.

If your control needs to do part of its initialization after it is absolutely positively really added to the DOM of the page, bind the jraAdded jQuery event on your control.

If your application wishes to be informed whenever the list view is repopulated (which happens initially and whenever an edit is saved), bind the jraRefreshedList jQuery event on the container. Note that this event is posted once when the admin is initially populated (i.e. on the initial load of the page), so if you are thinking of triggering a page refresh on this event you should ignore the first one.

TODO

You can override a number of things not documented here. There are lots of things you can't override yet, in particular the markup, although it is pretty basic bootstrap-friendly markup and doesn't need a lot of overriding. We need a way to propagate server-side validation errors into the UI rather than just mysteriously ignoring a request to save or delete. TODO: document all that; implement more great stuff.