From 0fa2c70133861fec20431e201ceeb9d878b813e4 Mon Sep 17 00:00:00 2001 From: Rushabh Mehta Date: Wed, 10 Jan 2018 18:19:52 +0530 Subject: [PATCH] docs --- README.md | 318 ++------------------------------- frappe/backends/rest_client.js | 3 +- frappe/client/view/form.js | 4 + frappe/client/view/page.js | 23 ++- frappe/docs/README.md | 21 +++ frappe/docs/backends.md | 49 +++++ frappe/docs/client.md | 60 +++++++ frappe/docs/controllers.md | 65 +++++++ frappe/docs/controls.md | 42 +++++ frappe/docs/document.md | 65 +++++++ frappe/docs/forms.md | 63 +++++++ frappe/docs/lists.md | 32 ++++ frappe/docs/metadata.md | 12 ++ frappe/docs/models.md | 41 +++++ frappe/docs/page.md | 41 +++++ frappe/docs/rest.md | 79 ++++++++ frappe/docs/router.md | 52 ++++++ frappe/docs/server.md | 20 +++ frappe/docs/testing.md | 9 + 19 files changed, 690 insertions(+), 309 deletions(-) create mode 100644 frappe/docs/README.md create mode 100644 frappe/docs/backends.md create mode 100644 frappe/docs/client.md create mode 100644 frappe/docs/controllers.md create mode 100644 frappe/docs/controls.md create mode 100644 frappe/docs/document.md create mode 100644 frappe/docs/forms.md create mode 100644 frappe/docs/lists.md create mode 100644 frappe/docs/metadata.md create mode 100644 frappe/docs/models.md create mode 100644 frappe/docs/page.md create mode 100644 frappe/docs/rest.md create mode 100644 frappe/docs/router.md create mode 100644 frappe/docs/server.md create mode 100644 frappe/docs/testing.md diff --git a/README.md b/README.md index 7fd83280..8faf6745 100644 --- a/README.md +++ b/README.md @@ -1,313 +1,19 @@ -# Frappe Core +# Frappe.JS -Core libs for Frappe Framework JS +Frappe.js is a meta-data driven framework that enables rapid application development of Node.js and Electron based applications. -## Declaring Models +## Features -Models are declared by adding a `.json` model file in the `models/doctype` folder of the module/app. +- Rapid Application Development +- Single Page App +- Forms and Controls +- REST-API +- Database backends -```json -{ - "autoname": "hash", - "name": "ToDo", - "doctype": "DocType", - "issingle": 0, - "fields": [ - { - "fieldname": "subject", - "label": "Subject", - "fieldtype": "Data", - "reqd": 1 - }, - { - "fieldname": "description", - "label": "Description", - "fieldtype": "Text" - }, - { - "fieldname": "status", - "label": "Status", - "fieldtype": "Select", - "options": [ - "Open", - "Closed" - ], - "default": "Open", - "reqd": 1 - } - ] -} -``` +## Documentation -## Setup / Migrate +[Read the full docs](docs/README.md) -```js -const frappe = require('frappe-core'); -await frappe.init(); -await frappe.init_db('sqlite', {db_path: 'test.db'}); - -// sync all schema from `models` folders in all apps -await frappe.migrate(); -``` - -## Managing Documents - -Frappe Object-Relational-Mapper (ORM) helps you manage (create, read, update, delete) documents based on the DocTypes declared. - -Documents are sub-classed from the `frappe.document.Document` class. - -All document write methods are asynchronous and return javascript Promise objects. - -### Initialize - -Documents are initialized with the `frappe.get_doc` method. If `doctype` and `name` are passed as parameters, then the document is fetched from the backend. If a simple object is passed, then object properties are set in the document. - -```js -const frappe = require('frappe-core'); -await frappe.init(); -await frappe.init_db('sqlite', {db_path: 'test.db'}); - -// make a new todo -let todo = await frappe.get_doc({doctype: 'ToDo', subject: 'something'}); -``` - -### Create - -You can insert a document in the backend with the `insert` method. - -```js -const frappe = require('frappe-core'); -await frappe.init(); -await frappe.init_db('sqlite', {db_path: 'test.db'}); - -// make a new todo -let todo = await frappe.get_doc({doctype: 'ToDo', subject: 'something'}); -await todo.insert(); -``` - -### Read - -You can read a document from the backend with the `frappe.get_doc` method - -```js -const frappe = require('frappe-core'); -await frappe.init(); -await frappe.init_db('sqlite', {db_path: 'test.db'}); - -// get all open todos -let todos = await frappe.db.get_all({doctype:'ToDo', fields:['name'], filters: {status: "Open"}); -let first_todo = await frappe.get_doc('ToDo', toods[0].name); -``` - -### Update - -The `update` method updates a document. - -```js -const frappe = require('frappe-core'); -await frappe.init(); -await frappe.init_db('sqlite', {db_path: 'test.db'}); - -// get all open todos -let todos = await frappe.db.get_all({doctype:'ToDo', fields:['name'], filters: {status: "Open"}); -let first_todo = await frappe.get_doc('ToDo', toods[0].name); - -first_todo.status = 'Closed'; -await first_todo.update(); -``` - -### Delete - -The `delete` method deletes a document. - -```js -const frappe = require('frappe-core'); -await frappe.init(); -await frappe.init_db('sqlite', {db_path: 'test.db'}); - -// get all open todos -let todos = await frappe.db.get_all({doctype:'ToDo', fields:['name'], filters: {status: "Open"}); -let first_todo = await frappe.get_doc('ToDo', toods[0].name); - -await first_todo.delete(); -``` - -## Metadata - -Metadata are first class objects in Frappe. You can get a metadata object by `frappe.get_meta`. All objects from the `models` folders of all modules are loaded. - -```js -const frappe = require('frappe-core'); -await frappe.init(); -await frappe.init_db('sqlite', {db_path: 'test.db'}); - -let todo_meta = frappe.get_meta('ToDo'); - -// get all fields of type "Data" -let data_fields = todo_meta.fields.map(d => d.fieldtype=='Data' ? d : null); -``` - -## Controllers - -You can write event handlers in controllers, by declaring a `.js` file in the `models/doctype/` folder along with the model file. - -The name of the class must be the slugged name of the DocType - -To add a standard handler, you must bind all handlers in `setup` method. - -```js -const frappe = require('frappe-core'); - -class todo extends frappe.document.Document { - setup() { - this.add_handler('validate'); - } - - validate() { - // set default status as "Open" if not set - if (!this.status) { - this.status = 'Open'; - } - } -} - -module.exports = { todo: todo }; -``` - -### Controller Events - -Standard events on which you can bind handlers are - -- `before_insert` -- `before_update` -- `validate` (called before any write) -- `after_insert`, -- `after_update` (called after any write) -- `before_submit` -- `after_submit` -- `before_cancel` -- `after_cancel` -- `before_delete` -- `after_delete` - -## Database - -You can also directly write SQL with `frappe.db.sql` - -```js -const frappe = require('frappe-core'); -await frappe.init(); -await frappe.init_db('sqlite', {db_path: 'test.db'}); - -all_todos = frappe.db.sql('select name from todo'); -``` - -## REST API - -You can directly access documents at `/api/resource/:doctype` - -### Create - -- URL: `/api/resource/:doctype` -- Method: `POST` -- Data: document properties - -**Example:** - -- URL: `/api/resource/todo` -- Method: `POST` - -Data: - -```json -{ - "subject": "test", - "description": "test description" -} -``` - -### Read - -- URL: `/api/resource/:doctype/:name` -- Method: `GET` - -**Example:** - -- URL: `/api/resource/todo/uig7d1v12` - -Reponse: - -```json -{ - "name": "uig7d1v12", - "owner": "guest", - "modified_by": "guest", - "creation": "2018-01-01T12:08:19.482Z", - "modified": "2018-01-01T12:08:19.482Z", - "docstatus": 0, - "subject": "test 1", - "description": "description 1", - "status": "Open" -} -``` - -### List - -- URL: `/api/resource/:doctype/` -- Method: `GET` -- Params (optional) - - `start`: Page start - - `limit`: Page limit - -**Example:** - -- URL: `/api/resource/todo` - -Response: - -```json -[ - { - "name": "r4qxyki0i6", - "subject": "test 1" - }, - { - "name": "efywwvtwcp", - "subject": "test 1" - }, - { - "name": "9ioz05urgp", - "subject": "test 1" - } -] -``` - -## REST Client - -Frappe comes with a built in REST client so you can also use REST as a database backend with the frappe API - -### Create, Read, Update, Delete - -You can manage documents, using the same Document API as if it were a local database - -```js -await frappe.init(); -await frappe.init_db('rest', {server: 'localhost:8000'}); - -let doc = await frappe.get_doc({doctype:'ToDo', subject:'test rest insert 1'}); -await doc.insert(); - -doc.subject = 'subject changed'; -await doc.update(); - -let data = await frappe.db.get_all({doctype:'ToDo'}); -``` - -## Tests - -All tests are in the `tests` folder and are run using `mocha`. To run tests - -```sh -npm run test -``` +## License +MIT \ No newline at end of file diff --git a/frappe/backends/rest_client.js b/frappe/backends/rest_client.js index 9863970b..e72b4483 100644 --- a/frappe/backends/rest_client.js +++ b/frappe/backends/rest_client.js @@ -2,13 +2,12 @@ const frappe = require('frappe-core'); const path = require('path'); class RESTClient { - constructor({server, protocol='http', fetch}) { + constructor({server, protocol='http'}) { this.server = server; this.protocol = protocol; this.init_type_map(); - frappe.fetch = fetch; this.json_headers = { 'Accept': 'application/json', 'Content-Type': 'application/json' diff --git a/frappe/client/view/form.js b/frappe/client/view/form.js index ff829472..4f9ff2a7 100644 --- a/frappe/client/view/form.js +++ b/frappe/client/view/form.js @@ -11,9 +11,13 @@ class Form { this.controls_list = []; this.meta = frappe.get_meta(this.doctype); + this.make(); } make() { + if (this.body || !this.parent) { + return; + } this.body = frappe.ui.add('form', null, this.parent); for(let df of this.meta.fields) { if (controls.get_control_class(df.fieldtype)) { diff --git a/frappe/client/view/page.js b/frappe/client/view/page.js index ea962693..eb3719a8 100644 --- a/frappe/client/view/page.js +++ b/frappe/client/view/page.js @@ -2,22 +2,43 @@ const frappe = require('frappe-core'); class Page { constructor(title) { + this.handlers = {}; this.title = title; this.make(); } + make() { this.body = frappe.ui.add('div', 'page hide', frappe.main); } + hide() { frappe.ui.add_class(this.body, 'hide'); + + this.trigger('hide'); } - show() { + + show(params) { if (frappe.router.current_page) { frappe.router.current_page.hide(); } frappe.ui.remove_class(this.body, 'hide'); frappe.router.current_page = this; document.title = this.title; + + this.trigger('show', params); + } + + on(event, fn) { + if (!this.handlers[event]) this.handlers.event = []; + this.handlers[event].push(fn); + } + + trigger(event, params) { + if (this.handlers[event]) { + for (let handler of this.handlers[event]) { + handler(params); + } + } } } diff --git a/frappe/docs/README.md b/frappe/docs/README.md new file mode 100644 index 00000000..65eafe15 --- /dev/null +++ b/frappe/docs/README.md @@ -0,0 +1,21 @@ +# Frappe.js + +Frappe.js is a meta-data driven framework that enables rapid application development of Node.js and Electron based applications. + +## Contents + +- Models and Documents + - [Declaring Models](models.md) + - [Controllers](controllers.md) + - [Metadata](metadata.md) + - [Managing Documents](documents.md) +- [Server](server.md) + - [REST API](rest.md) +- [Client](client.md) + - [Routing](routing.md) + - [Page](page.md) + - [Lists](lists.md) + - [Forms](forms.md) + - [Controls](controls.md) +- [Backends](backends.md) +- [Testing](testing.md) \ No newline at end of file diff --git a/frappe/docs/backends.md b/frappe/docs/backends.md new file mode 100644 index 00000000..d0c1cdb5 --- /dev/null +++ b/frappe/docs/backends.md @@ -0,0 +1,49 @@ +# Backends + +Frappe.js comes with built-in backends for data storage. These can be client-side or server-side + +- SQLite +- REST + +There can be only one backend at a time that can be accessed by the `frappe.db` property. + +## API + +The backend will implement the following `async` methods + +- `get_doc` +- `get_all` +- `get_value` +- `insert` +- `update` + +## sqlite Backend + +Connection paramter required for the sqlite backend is the path of the file + +```js +sqllite = require('frappe-core/frappe/backends/sqlite'); + +frappe.db = await new sqlite.Database({db_path: db_path}) +``` + +### SQL Queries + +You can also directly write SQL with `frappe.db.sql` + +```js +all_todos = frappe.db.sql('select name from todo'); +``` + +## REST Backend + +For the client, the backend is the REST API that executes calls with web-requests. + +Before using, you must initialize the `frappe.fetch` property with `window.fetch` or `node-fetch` + +```js +const Database = require('frappe-core/frappe/backends/rest_client').Database; + +frappe.fetch = window.fetch.bind(); +frappe.db = await new Database({server: server}); +``` \ No newline at end of file diff --git a/frappe/docs/client.md b/frappe/docs/client.md new file mode 100644 index 00000000..4018f0bb --- /dev/null +++ b/frappe/docs/client.md @@ -0,0 +1,60 @@ +# Client + +Frappe.js comes with built in single-page-application (SPA) with routing, views, list and form objects. + +In building the client, you can use the REST API to query data, and use the models and controllers declared in your module. + +You can use the same document API in the client as in the server, the only difference being that the data will be fetched via REST API in the background. + +## Routing + +- [Router](router.md) + +## Views + +- [Page](page.md) +- [List](list.md) +- [Form](form.md) + +## Starting + +You can setup your client by setting up the server and then importing your controllers with `require` + +### Example + +```js +const client = require('frappe-core/frappe/client'); + +client.start({ + server: 'localhost:8000', + container: document.querySelector('.container'), +}).then(() => { + const todo = require('frappe-core/frappe/models/doctype/todo/todo.js'); + frappe.init_controller('todo', todo); + + // .... +}); +``` + +## REST Client + +Frappe.js comes with a built in REST client so you can also use REST as a database backend with the Frappe.js API + +### Create, Read, Update, Delete + +You can manage documents, using the same Document API as if it were a local database + +### Example + +```js +await frappe.init(); +await frappe.init_db('rest', {server: 'localhost:8000'}); + +let doc = await frappe.get_doc({doctype:'ToDo', subject:'test rest insert 1'}); +await doc.insert(); + +doc.subject = 'subject changed'; +await doc.update(); + +let data = await frappe.db.get_all({doctype:'ToDo'}); +``` diff --git a/frappe/docs/controllers.md b/frappe/docs/controllers.md new file mode 100644 index 00000000..71f59efd --- /dev/null +++ b/frappe/docs/controllers.md @@ -0,0 +1,65 @@ +# Controllers + +In Frappe.js you can extend the metadata class as well as the document class for a particular DocType. The `meta` class contains actions that are done on a group of objects and a document represents a single object. So properties and actions related to the group will be part of the `meta` class + +You can write event handlers in controllers, by declaring a `.js` file in the `models/doctype/` folder along with the model file. + +### Naming + +1. The name of the controller class must be the slugged name of the DocType (example `todo`) +2. The name of the `meta` class must be the name of the controller class prefixed by `meta_` (example `meta_todo`) + +To add a standard handler, you must bind all handlers in `setup` method. + +### Example + +```js +const frappe = require('frappe-core'); + +// extend the meta class +class todo_meta extends frappe.meta.Meta { + setup_meta() { + Object.assign(this, require('./todo.json')); + this.name = 'ToDo'; + this.list_options.fields = ['name', 'subject', 'status', 'description']; + } + + get_row_html(data) { + return `${data.subject}`; + } + +} + +// extend the document and add event handlers +class todo extends frappe.document.Document { + setup() { + this.add_handler('validate'); + } + validate() { + if (!this.status) { + this.status = 'Open'; + } + } +} + +module.exports = { + todo: todo, + todo_meta: todo_meta +}; +``` + +### Controller Events + +Standard events on which you can bind handlers are + +- `before_insert` +- `before_update` +- `validate` (called before any write) +- `after_insert`, +- `after_update` (called after any write) +- `before_submit` +- `after_submit` +- `before_cancel` +- `after_cancel` +- `before_delete` +- `after_delete` diff --git a/frappe/docs/controls.md b/frappe/docs/controls.md new file mode 100644 index 00000000..fc41464e --- /dev/null +++ b/frappe/docs/controls.md @@ -0,0 +1,42 @@ +# Controls + +Frappe.js comes in with built-in controls for various types of inputs + +## Creating + +A new control can be created with `control.make_control` method. + +```js +const controls = require('./controls'); + +let control = controls.make_control({ + fieldname: 'test', + fieldtype: 'Data', + label: 'Test Control' +}, body); +``` + +## Structure + +The control has the following structure of HTML Elements + +- `form_group` + - `label` + - `input` + - `description` + +## Types + +Type of control is defined by the `fieldtype` property. + +### Data + +Short text input (``) + +## Text + +Long text input (`