{"_id":"@deli/crudl","_rev":"18-a51dfa5f2299c8aa306f550b0d604a52","name":"@deli/crudl","time":{"modified":"2022-06-12T17:00:09.566Z","created":"2018-01-28T20:50:52.427Z","0.4.0":"2018-01-28T20:50:52.427Z","0.4.1":"2018-01-28T21:00:28.440Z","0.4.2":"2018-01-28T22:22:33.380Z","0.4.11":"2018-01-29T00:29:04.573Z","0.4.12":"2018-01-29T03:46:36.836Z","0.4.13":"2018-01-29T03:56:25.643Z","0.4.5":"2018-01-30T00:29:58.643Z"},"maintainers":[{"email":"george@oddcastles.com","name":"okg"}],"dist-tags":{"latest":"0.4.5"},"versions":{"0.4.5":{"name":"@deli/crudl","version":"0.4.5","description":"A backend agnostic REST and GraphQL based admin interface","main":"./lib/index.js","scripts":{"build":"npm run build:lib","build:lib":"babel src --out-dir lib","build:ui":"gulp build-ui","build:umd":"gulp build","build:umd:min":"gulp build-min","clean":"rimraf dist lib","prepublish":"npm run clean && npm run build","test":"jest"},"repository":{"type":"git","url":"git+https://github.com/crudlio/crudl.git"},"keywords":["admin","interface","CMS","REST","GraphQL"],"author":{"name":"vonautomatisch"},"license":"MIT","bugs":{"url":"https://github.com/crudlio/crudl.git"},"homepage":"http://crudl.io","files":["README.md","LICENSE","lib","dist"],"jest":{"browser":true,"verbose":true,"rootDir":"src","moduleFileExtensions":["","js","jsx","json"]},"browser":{"joi":"joi-browser"},"dependencies":{"@deli/redux-form":"6.2.0","axios":"^0.15.2","bluebird":"^3.4.6","classnames":"^2.2.5","core-decorators":"^0.15.0","history":"^4.7.2","joi":"^9.2.0","joi-browser":"^9.1.0","lodash":"^4.16.4","node-uuid":"^1.4.7","react-intl":"^2.1.5","react-redux":"^5.0.6","react-router":"^3.2.0","react-router-config":"^1.0.0-beta.4","react-router-redux":"^4.0.8","redux":"^3.6.2","redux-devtools":"^3.3.1","redux-devtools-dock-monitor":"^1.1.1","redux-devtools-log-monitor":"^1.1.0","redux-localstorage":"^1.0.0-rc5","redux-localstorage-filter":"^0.1.1","redux-mock-store":"^1.2.1","revalidate":"^1.0.0","semver":"^5.3.0","uuid":"^2.0.3"},"devDependencies":{"babel-cli":"^6.22.2","babel-eslint":"^7.1.0","babel-jest":"^16.0.0","babel-plugin-react-intl":"^2.2.0","babel-plugin-syntax-class-properties":"^6.13.0","babel-plugin-transform-class-properties":"^6.18.0","babel-plugin-transform-decorators-legacy":"^1.3.4","babel-plugin-transform-object-rest-spread":"^6.16.0","babel-preset-es2015":"^6.18.0","babel-preset-react":"^6.16.0","babel-preset-stage-2":"^6.5.0","babel-relay-plugin":"^0.8.1","babelify":"^7.3.0","browserify":"^13.1.1","enzyme":"^2.5.1","eslint":"^3.10.2","eslint-config-airbnb":"^13.0.0","eslint-plugin-import":"^2.2.0","eslint-plugin-jsx-a11y":"^2.2.3","eslint-plugin-react":"^6.7.1","eslint-stats":"^0.1.4","gulp":"^3.9.1","gulp-autoprefixer":"^3.1.1","gulp-concat-util":"^0.5.5","gulp-rename":"^1.2.2","gulp-sass":"^2.3.2","gulp-sourcemaps":"^2.2.0","gulp-uglify":"^2.0.0","gulp-util":"^3.0.7","gulp-watch":"^4.3.10","jest":"^18.1.0","lodash.assign":"^4.2.0","loose-envify":"^1.2.0","node-notifier":"^4.6.1","prop-types":"^15.6.0","react":"^16.2.0","react-dom":"^16.2.0","rimraf":"^2.5.4","sinon":"^1.17.6","vinyl-buffer":"^1.0.0","vinyl-source-stream":"^1.1.0","watchify":"^3.7.0"},"peerDependencies":{"react":"^16.2.0","react-dom":"^16.2.0"},"contributors":[{"name":"Patrick Kranzlmueller","email":"patrick@vonautomatisch.at"},{"name":"Axel Swoboda","email":"axel@vonautomatisch.at"},{"name":"Vaclav Mikolasek","email":"vaclav@vonautomatisch.at"}],"gitHead":"010989ee865c0ca2eaf87324540550a9619f18ca","_id":"@deli/crudl@0.4.5","_shasum":"8e6ccb186b86f6784c8724b3cc3433fa66a0894b","_from":".","_npmVersion":"4.6.1","_nodeVersion":"8.9.4","_npmUser":{"name":"okg","email":"george@oddcastles.com"},"maintainers":[{"email":"george@oddcastles.com","name":"okg"}],"dist":{"shasum":"8e6ccb186b86f6784c8724b3cc3433fa66a0894b","tarball":"https://registry.npmjs.org/@deli/crudl/-/crudl-0.4.5.tgz","integrity":"sha512-BY2BM+2RLtTAsf6isKV4LU42fACMIvsJzfaOZWtIr/h4hxMGdewWRtRl5q4EsibvScEF2Rbug6mCmqog5l/mlQ==","signatures":[{"keyid":"SHA256:jl3bwswu80PjjokCgh0o2w5c2U4LhQAE57gj9cz1kzA","sig":"MEUCIQDRGO0TIMSM1wpiuOeMPgNPsRKJFohJcODABC8Tqi8tWwIgA/kSPKjs3bc3TfPHKZIiVPbF/LciNOchyLObSwTd4xA="}]},"_npmOperationalInternal":{"host":"s3://npm-registry-packages","tmp":"tmp/crudl-0.4.5.tgz_1517272197328_0.9127456182613969"}}},"readme":"> Note: I am not the original author of this library. This project was forked in order to update dependencies to React@^16.0.0 as I am not sure if the project will be maintained in the near future. Below you'll find the unmodified readme from the original repo for reference.\n\n# crudl\n\nCRUDL is a React application for rapidly building an admin interface based on your API. You just need to define the endpoints and a visual representation in order to get a full-blown UI for managing your data.\n\n## TOC\n\n* [Architecture](#architecture)\n* [Options](#options)\n* [Admin](#admin)\n  * [Attributes and properties](#attributes-and-properties)\n* [Connectors](#connectors)\n  * [Requests](#requests)\n  * [Data](#data)\n  * [Errors](#errors)\n* [Views](#views)\n  * [Actions](#actions)\n  * [Promise functions](#promise-functions)\n  * [Normalize and denormalize functions](#normalize-and-denormalize-functions)\n  * [Paths](#paths)\n* [List View](#list-view)\n  * [The `list` action](#the-list-action)\n  * [Bulk Actions](#bulk-actions)\n  * [Pagination](#pagination)\n* [Change View](#change-view)\n  * [The `get` action](#the-get-action)\n  * [The `save` action](#the-save-action)\n  * [The `delete` action](#the-delete-action)\n  * [Tabs](#tabs)\n* [Add View](#add-view)\n  * [The `add` action](#the-add-action)\n* [Fieldsets](#fieldsets)\n* [Fields](#fields)\n  * [onChange](#onchange)\n  * [getValue](#getvalue)\n  * [lazy](#lazy)\n  * [Custom attributes](#custom-attributes)\n* [Permissions](#permissions)\n* [Messages](#messages)\n* [Credits & Links](#credits--links)\n\n## Architecture\n\nThe CRUDL architecture (depicted below) consists of three logical layers. The connectors, views, and the react-redux frontend. We use React and Redux for the frontend, which consists of different views such as _list_, _add_, and _change_ view. The purpose of the connectors layer is to provide the views with a unified access to different APIs like REST or GraphQL. You configure the connectors, the fields, and the views by providing an [admin](#admin).\n\n```\n+-----------------------+\n|     React / Redux     |\n+-----------------------+\n|         Views         |\n+-----------------------+\n  ↓         ↑         ↑         CRUDL\nrequest   data     errors\n  ↓         ↑         ↑\n+-----------------------+       ------------\n|       Connectors      |       CONNECTORS (standalone NPM packages)\n+-----------------------+       ------------\n            ↕\n         ~~~~~~~\n           API                  BACKEND\n         ~~~~~~~\n```\n\n## Admin\n\nThe purpose of the admin is to provide CRUDL with the necessary information about the connectors and the views.\nThe admin is an object with the following attributes and properties:\n\n```js\nconst admin = {\n    title,              // Title of the CRUDL instance (a string or a react element property)\n    views,              // a dictionary of views\n    auth: {\n        login,          // Login view descriptor\n        logout,         // Logout view descriptor\n    },\n    custom: {\n        dashboard,      // The index page of the CRUDL instance (a string or a react element property)\n        pageNotFound,   // The admin of the 404 page\n        menu,           // The custom navigation\n    },\n    options: {\n        debug,          // Include DevTools (default false)\n        basePath,       // The basePath of the front end (default  '/crudl/')\n        baseURL,        // The baseURL of the API backend (default  '/api/')\n        rootElementId,  // Where to place the root react element (default 'crudl-root')\n    }\n    messages,           // An object of custom messages\n    crudlVersion,       // The required crudl version in the semver format (e.g., \"^0.3.0\")\n    id,                 // The id of the admin. This id is stored (together with other info) locally in the\n                        // localStorage of the browser. If the admin id and the locally stored id do not match,\n                        // the stored information will not be used. That means, for example, that by changing\n                        // the admin id, you can enforce a logout of all users.\n}\n```\n\nThe provided admin will be validated (using [Joi](https://github.com/hapijs/joi)) and all its attributes and properties are checked against the admin's schema.\n\n> ### Attributes and properties\n>\n> We distinguish between attributes and properties. An attribute is a value of a certain type (such as string, boolean, function, an object, etc.), whereas property can also be a function that returns such a value. In other words, with property you can also provide the getter method. For example, the title of the CRUDL instance is a string (or react element) property. So you can define it as\n\n```js\ntitle: 'Welcome to CRUDL'`\n```\n\nor as\n\n```js\ntitle: () => `Welcome to CRUDL. Today is ${getDayName()}\n```\n\nor even as:\n\n```js\ntitle: () => <span>Welcome to <strong>CRUDL</strong>. Today is {getDayName()}</span>,\n```\n\n## Options\n\nIn `admin.options` you may specify some general CRUDL settings\n\n```js\n{\n    debug: false,                   // Include DevTools?\n    basePath: '/crudl/',            // The basePath of the front end\n    baseURL: '/api/',               // The baseURL of the API (backend)\n    rootElementId: 'crudl-root',    // Where to place the root react element\n}\n```\n\nAssuming we deploy CRUDL on www.mydomain.com, we'll have CRUDL running on `www.mydomain.com/crudl/...` and the ajax requests of the connectors will be directed at `www.mydomain.com/api/...`.\n\n## Connectors\n\nConnectors provide CRUDL with a unified view of the backend API. Connectors are a separate [package](https://github.com/crudlio/crudl-connectors-base) which can be also used independently from CRUDL.\n\n### Requests\n\nA request object contains all the information necessary to execute one of the CRUD methods on a connector.\nIt is an object with the following attributes:\n\n```js\n{\n    data,           // Context dependent: in a change view, the data contains the form values\n    params,         // Connectors may require parameters to do their job, these are stored here\n    filters,        // The requested filters\n    sorting,        // The requested sorting\n    page,           // The requested page\n    headers,        // The http headers (e.g. the auth token)\n}\n```\n\n### Data\n\nWhen a connector successfully executes a request it resolves to response data:\n\n```js\nusersConnector.read(req.filter('name', 'joe')).then(allJoes => {\n  // do something will all Joes\n});\n```\n\nList views require data to be in an array form `[ item1, item2, ... ]`. Where `item` is an object. Pagination information may be included as a parameter of the array:\n\n```js\nresult = [ item1, item2, ... ],\nresult.pagination = {\n    type: 'numbered',\n    allPages: [1, 2],\n    currentPage: 1,\n}\n```\n\nChange and add views require the data as an object, e.g.\n\n```js\n{\n    id: '3'\n    username: 'Jane',\n    email: 'jane@crudl.io'\n}\n```\n\n### Errors\n\nIt is the responsibility of the connectors to throw the right errors. CRUDL distinguishes three kinds of errors:\n\n* Validation error: The submitted form is not correct.\n\n  ```js\n  {\n      validationError: true,\n      errors: {\n          title: 'Title is required',\n          _errors: 'Either category or tag is required',\n      }\n  }\n  ```\n\n  Non field errors have the special attribute key `_error` (we use the same format error as [redux-form](https://github.com/erikras/redux-form)).\n\n* Authorization error: The user is not authorized. When this error is thrown, CRUDL redirects the user to the login view.\n\n  ```js\n  {\n      authorizationError: true,\n  }\n  ```\n\n* Default error: When something else goes wrong.\n\nIf any of the thrown errors contains an attribute `message`, this message will be displayed as a notification to the user.\n\n## Views\n\nThe attribute `admin.views` is a dictionary of the form:\n\n```js\n{\n    name1: {\n        listView,       // required\n        changeView,     // required\n        addView,        // optional\n    },\n    name2: {\n        listView,\n        changeView,\n        addView,\n    },\n    ...\n\n}\n```\n\nBefore we go into details about the views, let's define some common elements of the view:\n\n### Paths\n\n> Note on paths and urls. In order to distinguish between backend URLs and the frontend URLs, we call the later _paths_. That means, connectors (ajax call) access URLs and views are displayed at paths.\n\nA path can be defined as a simple (`'users'`) or parametrized (`'users/:id'`) string.\nThe parametrized version of the path definition is used only in change views and is not applicable to the list or add views. In order to resolve the parametrized change view path, the corresponding list item is used as the reference. The parameters of the current path are exported in the variable `crudl.path`.\n\n### Actions\n\nEach view must define its `actions`, which is an object [property](#attributes-and-properties). The attributes of the actions property are the particular actions.\n\nAn action is a function that takes a request as its argument and returns a _promise_. This promise either resolves to [data](#data) or throws an [error](#errors). Typically, action use some [connectors](https://github.com/crudlio/crudl-connectors-base) to do their job. For example, a typical list view defines an action like this:\n\n```js\nconst users = createDRFConnector('api/users/'); // using Django Rest Framework connectors\nlistView.actions = {\n  list: req => users.read(req), // or just 'list: users.read'\n};\n```\n\nA typical _save_ action of a change view looks for example like this:\n\n```js\nconst users = createDRFConnector('api/users/:id/');\n(changeView.path = 'users/:id'),\n  (changeView.actions = {\n    save: req => user(crudl.path.id).save(req),\n  });\n```\n\n### Normalize and denormalize functions\n\nThe functions `normalize` and `denormalize` are used to prepare, manipulate, annotate etc. the data for the frontend and for the backend. The normalization function prepares the data for the frontend (before they are displayed) and the denormalization function prepares to data for the backend (before they are passed to the connectors). The general form is `(data) => data` for views and `(value, allValues) => value` for [fields](#fields).\n\n## List View\n\nA list view is defined like this:\n\n```js\n{\n    // Required:\n    path,             // The path of this view e.g. 'users' relative to options.basePath\n    title,            // A string - title of this view (shown in navigation) e.g. 'Users'\n    fields,           // An array of list view fields (see below)\n    actions: {\n        list,         // The list action (see below)\n    },\n\n    // Optional:\n    filters: {\n        fields,       // An array of fields (see below)\n        denormalize,  // The denormalize function for the filters form\n    }\n    bulkActions,      // See bellow\n    permissions: {\n        list,         // either true or false. Default value is true\n    }\n    normalize,        // The normalize function of the form (listItems) => listItems (see below)\n    paginationComponent, // A function of the form (pagination) => ReactComponent\n}\n```\n\n* `filters.fields`: See [fields](#fields) for details.\n\n* `normalize`: a function of the form `listItems => listItems`\n\n### The `list` Action\n\nThe `list` action must either resolve to an array `[ item1, item2, ..., itemN ]` or throw an error. The items must be objects and the values of their attributes will be displayed in the list view fields. The array may optionally have a `pagination` attribute (see [Pagination](#pagination)). The `request` parameter is provided by the list view and it has the pertinent attributes `filters`, `page`, `sorting` and `headers` accordingly set. Fro example:\n\n```js\nlistView.actions.list = req => users.read(req);\n\n// In the list view at the path 'users/':\nlistView.actions\n  .get(crudl.createRequest().filter('is_staff', true))\n  .then(results => {\n    // [ { id: 1, username: 'admin' }, { id: 3, username: 'joe' }, ... ]\n  })\n  .catch(error => {\n    // { message: \"Unknown filter field 'is_staff'\" }\n  });\n```\n\n### Bulk Actions\n\nCrudl supports bulk actions that are executed on one or more selected list view items. Bulk actions are defined like this:\n\n```js\nlistView.bulkActions=  {\n    actionName: {\n        description: 'What the action does',\n        modalConfirm: {...} // Require modal dialog for confirmation (Optional)\n        before: (selection) => {...} // Do something with the selection before the action\n        action: (selection) => {...} // Do the bulk action\n        after: (selection) => {...}, // Do something with the results afterwards\n    },\n    // more bulk actions...\n}\n```\n\nAn example of a delete bulk action using a modal confirmation:\n\n```js\nlistView.bulkActions.delete = {\n    description: 'Delete tags',\n    modalConfirm: {\n        message: \"All the selected items will be deleted. This action cannot be reversed!\",\n        modalType: 'modal-delete',\n        labelConfirm: \"Delete All\",\n    },\n    action: (selection) => Promise.all(selection.map(item => tag(item.id).delete(crudl.req())))\n        .then(() => crudl.successMessage(`All items (${selection.length}) were deleted`))\n    },\n},\n```\n\nThe _before_ and _after_ actions take the current selection as argument and return a React component which will be displayed in an overlay window. This component will receive two handlers as props: `onProceed` and `onCancel`.\n\nAn example of a _Change Section_ action:\n\n```js\nlistView.bulkActions.changeSection = {\n    description: 'Change Section',\n    // Create a submission form to select a section\n    // onProceed and onCancel are handlers provided by the list view\n    before: createSelectSectionForm,\n    // The action itself\n    action: selection => Promise.all(selection.map(\n        item => category(item.id).update(crudl.req(item)) // category is a connector\n    )).then(() => crudl.successMessage('Successfully changed the sections')),\n},\n```\n\nUsing the crudl utility function `createForm()`, the function `createSelectSectionForm` may for example look like this:\n\n```js\nconst createSelectSectionForm = selection => ({ onProceed, onCancel }) => (\n  <div>\n    {crudl.createForm({\n      id: 'select-section',\n      title: 'Select Section',\n      fields: [\n        {\n          name: 'section',\n          label: 'Section',\n          field: 'Select',\n          lazy: () => options('sections', 'id', 'name').read(crudl.req()),\n        },\n      ],\n      onSubmit: values => onProceed(selection.map(s => Object.assign({}, s, { section: values.section }))),\n      onCancel,\n    })}\n  </div>\n);\n```\n\nNotice that the react component will obtain two props `onProceed()` and `onCancel()` which you can use to control the progression of the action.\n\n### Pagination\n\nA list view can display paginated data. In order to do so, the `list(req)` action must resolve to an array with an extra attribute `pagination` which provides the necessary pagination information. Two pagination types are currently supported:\n\n* **Numbered** pagination: Each page has a cursor (typically a number, and can be accessed directly. Pages are numbered from 1 to N. The `pagination` attribute is of the form\n\n  ```js\n  {\n      type: 'numbered',   // Required\n      allPages,           // Required\n      currentPage,        // Required\n      resultsTotal,       // Optional\n      filteredTotal,      // Optional\n  }\n  ```\n\n  where `allPages` is an array of page cursors. A page cursor can be anything. `allPages[i-1]` must provide a page cursor for the ith page. The currentPage is the page cursor of the currently displayed page. The corresponding page cursor of the current page is `allPages[currentPage-1]`. The total number of results can be optionally provided as `resultsTotal`. The total number of _filtered_ results can be optionally provided as `filteredTotal`.\n\n* **continuous** pagination: Results are displayed on one page and more are loaded if required. The `pagination` attribute has the form:\n  ```js\n  {\n      next,           // Required\n      resultsTotal,   // Optional\n      filteredTotal,  // Optional\n  }\n  ```\n  where `next` is a page cursor that must be truthy if there exist a next page, otherwise it MUST be falsy. The resultsTotal is optional and it gives the number of the total available results. The total number of _filtered_ results can be optionally provided as `filteredTotal`.\n\nWhen a user request a new page (or more results) the list view generate a new request to the connector layer. This request has an attribute `page` and its value is one of `allPages` (numbered pagination) or the value of `next` (continuous pagination).\n\n> If the `listView.paginationComponent` function is defined, then the value of the `pagination` attribute is passed to this function, which in turn must return a react component. See [Pagination.jsx](src/components/Pagination.jsx) for the details.\n\n## Change View\n\n```js\n{\n    // Required\n    path,               // Parametrized path definition e.g. 'users/:id/'\n    title,              // A string e.g. 'User'\n    actions: {\n        get,            // E.g. (req) => user(crudl.path.id).read(req)\n        save,           // E.g. (req) => user(crudl.path.id).save(req)\n        delete,         // E.g. (req) => user(crudl.path.id).delete(req)\n    },\n    fields,             // A list of fields\n    fieldsets,          // A list of fieldsets\n\n    // Optional\n    tabs,               // A list of tabs\n    tabtitle,           // The title of the first tab\n    normalize,          // The normalization function (dataToShow) => dataToShow\n    denormalize,        // The denormalization function (dataToSend) => dataToSend\n    validate,           // Frontend validation function\n    permissions: {\n        get: <boolean>,     // Does the user have a view permission?\n        save: <boolean>,    // Does the user have a change permission?\n        delete: <boolean>,  // Does the user have a delete permission?\n    },\n}\n```\n\nEither `fields` or `fieldsets`, but not both, must be specified. The attribute `validate` is a [redux-form](https://github.com/erikras/redux-form) validation function.\n\n### The `get` Action\n\nThe get action resolves to an object or rejects with an error. For example:\n\n```js\nchangeView.actions.get = req => user(crudl.path.id).read(req);\n\n// In the change view for the path 'users/3/':\nchangeView.actions\n  .get(crudl.createRequest())\n  .then(result => {\n    // { id: 3, username: 'joe', email: 'joe@crudl.io' }\n  })\n  .catch(error => {\n    // { authorizationError: true, message: \"You have been logged out!\" }\n  });\n```\n\n### The `save` Action\n\nThe save action should update the resource and resolve to the new values. For example:\n\n```js\nchangeView.actions.save = req => user(crudl.path.id).update(req);\n\n// In the change view for the path 'users/3/':\nchangeView.actions\n  .save(crudl.createRequest({ email: 'joe.doe@crudl.io' }))\n  .then(result => {\n    // { id: 3, username: 'joe', email: 'joe.doe@crudl.io' }\n  })\n  .catch(error => {\n    // { validationError: true, errors: { email: 'The email address is already registered' } }\n  });\n```\n\n### The `delete` action\n\nThe delete action deletes the resource and returns a promise. The value of the resolved promise is irrelevant.\nFor example:\n\n```js\nchangeView.actions.delete = req => user(crudl.path.id).delete(req);\n\n// In the change view for the path 'users/3/':\nchangeView.actions\n  .delete(crudl.createRequest())\n  .then(result => {\n    // 'User joe was deleted.'\n  })\n  .catch(error => {\n    // { message: \"You're not permitted to delete a user\" }\n  });\n```\n\n### Tabs\n\nTabs allow you to display and manipulate resource relations. For example, the following tab descriptor displays a list of links associated with the current blog entry.\n\n```js\nchangeView.tabs = [\n  {\n    title: 'Links',\n    actions: {\n      list: req => links.read(req.filter('entry', crudl.path.id)), // Filter results by the current blog entry\n      add: req => links.create(req),\n      save: req => link(req.data.id).update(req),\n      delete: req => link(req.data.id).delete(req),\n    },\n    getItemTitle: data => `${data.url} (${data.title})`, // Define the item title (Optional)\n    fields: [\n      {\n        name: 'url',\n        label: 'URL',\n        field: 'URL',\n        link: true,\n      },\n      {\n        name: 'title',\n        label: 'Title',\n        field: 'String',\n      },\n      {\n        name: 'id', // Needed in order to make update and delete requests\n        hidden: true, // Don't show this one\n      },\n      {\n        name: 'entry', // The foreign key field\n        hidden: true, // Don't show this one\n        initialValue: () => crudl.context('id'), // initialValue is used when adding a new link\n      },\n    ],\n    validate(data) {\n      // Check the data\n      return data;\n    },\n    normalize(data) {\n      // Prepare data for the frontend\n      return data;\n    },\n    denormalize(data) {\n      // Prepare data for the backend\n      return data;\n    },\n  },\n];\n```\n\n* Required attributes are: `title` and `actions`. The rest is optional.\n* The actions `list`, `add`, `save` and `delete` follow the same logic as the corresponding actions of list, change and add views.\n* `getItemTitle: (data) => <string>` defines the displayed title of the item form. If it is not provided, then the value of the first field is used (in this case it would be the URL value).\n* It's typical for the tab views to make use of hidden fields to include the related object's id in the form data.\n\n## Add View\n\nThe add view defines almost the same set of attributes and properties as the change view. It is often possible to reuse parts of the change view.\n\n```js\n{\n    // Required\n    path,               // A path definition\n    title,              // A string. e.g. 'Add new user'\n    actions: {\n        add,\n    },\n    permissions: {\n        add: <boolean>, // Does the user have a create permission?\n    },\n    fields,             // A list of fields\n    fieldsets,          // A list of fieldsets\n\n    // Optional\n    validate,           // Frontend validation function\n    denormalize,        // Note: add views don't have a normalize function\n}\n```\n\n### The `add` action\n\nThe add action should create a new resource and resolve to the new values. For example:\n\n```js\naddView.actions.add = req => users.create(req);\n\n// In the add view at the path 'users/new':\naddView.actions\n  .add(crudl.createRequest({ username: 'jane' }))\n  .then(result => {\n    // { id: 4, username: 'jane', email: '' }\n  })\n  .catch(error => {\n    // { validationError: true, errors: { email: 'Email adress is required' } }\n  });\n```\n\n## Fieldsets\n\nWith fieldsets, you are able to group fields with the change/addView.\n\n```js\n{\n    // Required\n    fields,                 // Array of fields\n\n    // Optional properties\n    title,                  // string property\n    hidden,                 // boolean property e.g. hidden: () => !isOwner()\n    description,            // string or react element property\n    expanded,               // boolean property\n\n    // Misc optional\n    onChange,               // onChange (see below)\n}\n```\n\n## Fields\n\nWith the fields, you describe the behavior of a single element with the changeView and/or addView. All the attributes of the field descriptor will be passed as props to the field component. The field descriptor can contain further custom attributes which are as well passed as props to the field component.\n\n```js\n{\n    // Required attributes\n    name,                   // string property\n    field,                  // either a string  (i.e. a name a field component) or\n                            // directly a react component. It is not required only when hidden == true\n                            // This attribute cannot be obtained asynchronously\n\n    // Optional attributes\n    getValue,               // A function of the form `(data) => fieldValue`. Default: `(data) => data[name]`\n    label,                  // string property (by default equal to the value of name)\n    readOnly,               // boolean property\n    required,               // boolean property\n    disabled,               // boolean property\n    hidden,                 // boolean property\n    initialValue,           // Initial value in an add view\n    validate,               // a function (value, allFieldsValues) => error || undefined\n    normalize,              // a function (valueFromBackend) => valueToFrontend\n    denormalize,            // a function (valueFromFrontend) => valueToBackend\n    onChange,               // onChange specification (see bellow)\n    add,                    // add relation specification (see bellow)\n    edit,                   // edit relation specification (see bellow)\n    lazy,                   // A function returning a promise (see bellow)\n\n    // further custom attributes and props\n}\n```\n\n### getValue\n\nThe value of the field is by default `data[name]`, where `name` is the required name attribute of the field descriptor and `data` is the response data from an API call. You can customize this behavior by providing your own `getValue` function of the form `(data) => fieldValue`. For example, suppose the returned data is\n\n```js\n{\n    username: 'joe'\n    contact: {\n        email: 'joe@github.com'\n        address: '...',\n    }\n}\n```\n\nand you want to describe an `email` field:\n\n```js\n{\n    name: 'email',\n    field: 'TextField',\n    getValue: data => data.contact.email,\n}\n```\n\n### onChange\n\nWith onChange, you are able to define dependencies between one or more fields. For example, you might have a field Country and a field State. When changing the field Country, the options for field State should be populated. In order to achieve this, you use onChange with State, listening to updates in Country and (re)populate the available options depending on the selected Country.\n\n```js\n{\n    // Required\n    in,                     // a string or an array of strings (field names)\n\n    // Optional\n    setProps,               // An object or a promise function\n    setValue,               // a plain value or a promise function\n    setInitialValue,        // a plain valuer or a promise function\n}\n```\n\n### lazy\n\nBy defining the `lazy` function, you may provide some attributes of the descriptor asynchronously. The lazy function takes zero arguments and must return a promise which resolves to an object (i.e. a partial descriptor). You **cannot** provide the attributes `name` and `field` asynchronously.\n\n**Example:** A Select field component has a prop `options` which is an array of objects with attributes `value` and `label`. You can provide these options _synchronously_ like this:\n\n```js\n{\n    name: 'rating',\n    label: 'Service Rating',\n    field: 'Select',\n    options: [{value: 0, label: 'Bad'}, {value: 1, label: 'Good'}, {value: 2, label: 'Excellent'}]\n},\n```\n\nOr you can provide these options _asynchronously_ using the lazy function:\n\n```js\n{\n    name: 'rating',\n    label: 'Service Rating',\n    field: 'Select',\n    lazy: () => crudl.connectors.ratings.read(crudl.req()).then(response => ({\n        options: response.data,\n    })),\n},\n```\n\nNote that all the descriptor attributes will be passed as props to the field component. This is also true for asynchronously provided attributes.\n\n### Add and Edit relations\n\nA field containing a foreign key may define add and edit relations. The add descriptor looks like this:\n\n```js\n{\n    name: 'section',\n    label: 'Section',\n    field: 'Select',\n    lazy: () => options('sections', 'id', 'name').read(crudl.req()),\n    add: {\n        title: 'New section',\n        actions: {\n            add: req => sections.create(req).then(data => data.id),\n        },\n        fields: [\n            {\n                name: 'name',\n                label: 'Name',\n                field: 'String',\n                required: true\n            },\n            {\n                name: 'slug',\n                label: 'Slug',\n                field: 'String',\n                required: true,\n            },\n        ],\n    },\n}\n```\n\nThe `add` action of the add relation MUST return the new value for the field in the original form (the _section_ field in this example).\n\nThe edit descriptor is quite similar:\n\n```js\n{\n    name: 'section',\n    label: 'Section',\n    field: 'Select',\n    lazy: () => options('sections', 'id', 'name').read(crudl.req()),\n    edit: {\n            title: 'Edit Section',\n            actions: {\n                get: (req) => section(crudl.context('section')).read(req),\n                save: (req) => section(crudl.context('section')).update(req),\n            },\n            fields: [\n                {\n                    name: 'name',\n                    label: 'Name',\n                    field: 'String',\n                    required: true\n                },\n                {\n                    name: 'slug',\n                    label: 'Slug',\n                    field: 'String',\n                    required: true,\n                },\n            ],\n        },\n}\n```\n\nIn contrast to the `add` actions of the add relation, the `save` action IS NOT required to resolve the the value of the originating field. Note that you can access the current form data via `crudl.context()` function.\n\n### Custom attributes\n\nYou can provide any number of further custom attributes which will then be passed as props to the field component. Note however that the following props are already passed to the field components and cannot be overwritten:\n\n* `dispatch`\n* `input`\n* `meta`\n* `registerFilterField`\n* `onAdd`\n* `onEdit`\n\n## Permissions\n\nEach view may define its permissions. Permissions are defined on a per-action basis. A change view, for example, can define `get`, `save`, and `delete` actions, so it can specify corresponding `get`, `save`, and `delete` permissions like this:\n\n```js\nchangeView.permissions = {\n  get: true, // A user can view the values\n  save: true, // A user may save changes\n  delete: false, // A user cannot delete the resource\n};\n```\n\nThe permission key of a view is a _property_. That means you can define a getter and assign permissions dynamically. For example:\n\n```js\nchangeView.permissions = {\n  delete: () => crudl.auth.user == crudl.context('owner'), // Only the owner of the resource can delete it\n};\n```\n\n## Messages\n\nWe use [react-intl](https://github.com/yahoo/react-intl) in order to provide for custom messages and translations. Examples of some custom messages:\n\n```js\nadmin.messages = {\n  'changeView.button.delete': 'Löschen',\n  'changeView.button.saveAndContinue': 'Speichern und weiter bearbeiten',\n  'changeView.button.save': 'Speichern',\n  'modal.labelCancel.default': 'Abbrechen',\n  'login.button': 'Anmelden',\n  'logout.affirmation': 'Tchüß!',\n  'logout.loginLink': 'Nochmal einloggen?',\n  'logout.button': 'Abmelden',\n  pageNotFound: 'Die gewünschte Seite wurde nicht gefunden!',\n  // ...more messages\n};\n```\n\nYou will find all configurable messages in `src/messages/*.js`.\n\n## Credits & Links\n\nCRUDL is written and maintained by vonautomatisch (Patrick Kranzlmüller, Axel Swoboda).\n\n* http://crudl.io\n* https://twitter.com/crudlio\n* http://vonautomatisch.at\n","readmeFilename":"README.md","description":"A backend agnostic REST and GraphQL based admin interface","homepage":"http://crudl.io","keywords":["admin","interface","CMS","REST","GraphQL"],"repository":{"type":"git","url":"git+https://github.com/crudlio/crudl.git"},"contributors":[{"name":"Patrick Kranzlmueller","email":"patrick@vonautomatisch.at"},{"name":"Axel Swoboda","email":"axel@vonautomatisch.at"},{"name":"Vaclav Mikolasek","email":"vaclav@vonautomatisch.at"}],"author":{"name":"vonautomatisch"},"bugs":{"url":"https://github.com/crudlio/crudl.git"},"license":"MIT"}