2
0
mirror of https://github.com/frappe/books.git synced 2024-11-08 23:00:56 +00:00
This commit is contained in:
Rushabh Mehta 2018-01-10 18:19:52 +05:30
parent 234eb9dea1
commit 0fa2c70133
19 changed files with 690 additions and 309 deletions

318
README.md
View File

@ -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

View File

@ -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'

View File

@ -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)) {

View File

@ -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);
}
}
}
}

21
frappe/docs/README.md Normal file
View File

@ -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)

49
frappe/docs/backends.md Normal file
View File

@ -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});
```

60
frappe/docs/client.md Normal file
View File

@ -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'});
```

View File

@ -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 `<a href="#edit/todo/${data.name}">${data.subject}</a>`;
}
}
// 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`

42
frappe/docs/controls.md Normal file
View File

@ -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 (`<input>`)
## Text
Long text input (`<textarea>`)
## Select
Select a single value from a list of options (`<select>`)

65
frappe/docs/document.md Normal file
View File

@ -0,0 +1,65 @@
# Managing Documents
Frappe.js 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
// 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
// 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
// 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
// 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
// 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();
```
### Extending Documents
Each model can be extended by adding events in the [controller](controllers.md) class.

63
frappe/docs/forms.md Normal file
View File

@ -0,0 +1,63 @@
# Forms
Forms are automatically created from the model (DocType)
Form objects create the controls and also handler insert and update.
Note: A single Form object can handle multiple documents.
### Example
```js
const Page = require('frappe-core/frappe/client/view/page').Page;
const Form = require('frappe-core/frappe/client/view/form').Form;
edit_page = new Page('Edit To Do');
router.add('/edit/todo/:name', edit_page);
edit_page.form = new Form({
doctype: 'ToDo',
parent: edit_page.body
});
```
## Creating
To create a new Form, you need to pass the model (DocType) and `parent` element.
Controls will be created for all the `fields` of the model that is passed along with a `Submit` button
## Editing
To setup a form for editing, you can bind a document by calling the `use` method.
```js
edit_page.on('show', async (params) => {
let doc = await frappe.get_doc('ToDo', params.name);
edit_page.form.use(doc);
})
```
## New Document
To setup a form for a new document, just create a new document with the Frappe.js document helpers, and `use` it with paramter `is_new` = true
```js
// setup todo new
frappe.router.add('new/todo', async (params) => {
// new document
app.doc = await frappe.get_doc({doctype: 'ToDo'});
// set a random name
app.doc.set_name();
// show the page
app.edit_page.show();
// is_new=true
app.edit_page.form.use(app.doc, true);
});
```

32
frappe/docs/lists.md Normal file
View File

@ -0,0 +1,32 @@
# Lists
A list object handles object listing and paging, for a standard model.
### Example
```js
const Page = require('frappe-core/frappe/client/view/page').Page;
const ListView = require('frappe-core/frappe/client/view/list').ListView;
// create a new page
let todo_list = new Page('ToDo List');
// init a new list
todo_list.list = new ListView({
doctype: 'ToDo',
parent: this.todo_list.body
});
todo_list.on('show', () => {
// refresh on show
todo_list.list.run();
})
```
## Creating a new List
You can create a new list object by passing the `DocType` and the parent element of the list
## Refreshing
To reload the list, call the `run` method

12
frappe/docs/metadata.md Normal file
View File

@ -0,0 +1,12 @@
# Metadata
Metadata are first class objects in Frappe.js. You can get a metadata object by `frappe.get_meta`. All objects from the `models` folders of all modules are loaded.
### Example
```js
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);
```

41
frappe/docs/models.md Normal file
View File

@ -0,0 +1,41 @@
# Declaring Models
Models are declared by adding a `.json` model file in the `models/doctype` folder of the module/app.
Note: A model is called `DocType` in Frappe.js
### Example
```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
}
]
}
```

41
frappe/docs/page.md Normal file
View File

@ -0,0 +1,41 @@
# Page
A page is a basic container that fills up the `main` area of the Frappe.js SPA.
### Example
```js
const Page = require('frappe-core/frappe/client/view/page').Page;
let todo_list = new Page('ToDo List');
// make the current page active
todo_list.show();
```
## Structure
The page has the following elements
1. Body
## Events
You can `show` a page or `hide` a page.
## Bind Events
- `show` when a page is shown
- `hide` when a page is hidden
```js
let todo_list = new Page('ToDo List');
// run the refresh when it is shown
todo_list.on('show', () => todo_list.list.run());
```
## Current Page
The current page is maintained in `frappe.router.current_page` and is set the the lastest shown page.

79
frappe/docs/rest.md Normal file
View File

@ -0,0 +1,79 @@
# 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"
}
]
```

52
frappe/docs/router.md Normal file
View File

@ -0,0 +1,52 @@
# Client-side Routing
The Frappe.js client comes in with a built in router, that is handles via hashing (example `#route`)
## Add a new route
You can add a new route by calling `frappe.router.add`
Dynamic routes can be added by declaring each parameter as `:param` in the route string (similar to express.js)
### Example
```js
const Page = require('frappe-core/frappe/client/view/page').Page;
let todo_list = new Page('ToDo List');
// make the current page active
todo_list.show();
```
```js
// to do list
frappe.router.add('default', () => {
app.todo_list.show();
app.todo_list.list.run();
});
// setup todo form
frappe.router.add('edit/todo/:name', async (params) => {
app.doc = await frappe.get_doc('ToDo', params.name);
app.edit_page.show();
app.edit_page.form.use(app.doc);
});
// setup todo new
frappe.router.add('new/todo', async (params) => {
app.doc = await frappe.get_doc({doctype: 'ToDo'});
app.doc.set_name();
app.edit_page.show();
app.edit_page.form.use(app.doc, true);
});
```
## Show a route
To set a route, you can call `frappe.router.show(route_name)`
```js
frappe.router.show(window.location.hash);
```

20
frappe/docs/server.md Normal file
View File

@ -0,0 +1,20 @@
# Server
The framework comes bundles with an `express.js` web server with pre-built backends, [REST API](rest.md) and ORM.
### Example
```js
const server = require('frappe-core/frappe/server');
server.start({
backend: 'sqllite',
connection_params: {db_path: 'test.db'},
static: './',
port: 8000
});
```
By starting a server, Frappe will automatically handle REST calls for all the declared models.
Database migration (syncing the tables based on models) is also done at the time of server start.

9
frappe/docs/testing.md Normal file
View File

@ -0,0 +1,9 @@
# Tests
All tests are in the `tests` folder and are run using `mocha`. To run tests
### Calling
```sh
npm run test
```