Task management is one of the most required features for business. While there are many solutions for this problem there is no silver bullet yet because of each business field has each own specifics. Development of a bespoke application to meet specific requirements in a such circumstance isn’t rare. AllcountJS allows you to build custom task management applications from scratch pretty fast. Let’s consider an example: how to build such application step by step.

If you unfamiliar with AllcountJS you probably would like to see our Getting Started guide first. While AllcountJS allows to use all power of Node.js in this article we will work only with App Config code. In order to run demo code you should do npm install allcountjs-cli then allcountjs init or simply run code from our demo page here and you’ll get something like

Model

Let’s start with model declaration. Probably most important thing in task management is to have task object (obviously) and some status flow. Let’s define our model in main.js :

A.app({ appName: "Task manager", onlyAuthenticated: true, menuItems: [ { name: "Planning", entityTypeId: "Task" }, { name: "Statuses", entityTypeId: "Status" } ], entities: function(Fields) { return { Task: { fields: { summary: Fields.textarea("Summary").required(), dueDate: Fields.date("Due Date").required(), status: Fields.fixedReference("Status", "Status") } }, Status: { fields: { name: Fields.text("Name").required(), order: Fields.integer("Order").required() }, referenceName: "name" } } } });

Such App Config produces working application that has two entities: Task and Status. Task object has summary and due date fields along with the status reference. Status entity has a name field used as a reference name while showing combo box and order field that will be used later to define order of statuses. As you can see required fields are marked with .required() . It means entity couldn’t be created or saved until these fields are filled. Also you could note onlyAuthenticated flag which designates app couldn’t be accessed without authentication.

Order of statuses

We could define our status priorities just by adding sorting: [['order', 1]], to the Status entity type:

A.app({ appName: "Task manager", onlyAuthenticated: true, menuItems: [ { name: "Planning", entityTypeId: "Task" }, { name: "Statuses", entityTypeId: "Status" } ], entities: function(Fields) { return { Task: { fields: { summary: Fields.textarea("Summary").required(), dueDate: Fields.date("Due Date").required(), status: Fields.fixedReference("Status", "Status") } }, Status: { fields: { name: Fields.text("Name").required(), order: Fields.integer("Order").required() }, sorting: [['order', 1]], referenceName: "name" } } } });

Such declaration instructs AllcountJS to sort Status entity by order field in ascending order.

Board view

The one of the most powerful AllcountJS features is a Views concept. View is an Entity too but as a storage it uses another entity. So there is an opportunity to create many multiple behaviors and visualizations for same data source. Let’s create board view for our Task entity by adding following property

views: { TaskBoard: { customView: "board" } }

And create new menu item to open the board:

{ name: "Board", entityTypeId: "TaskBoard", icon: "bars" }

Also we should create custom view board we reference ( board.jade )

extends project/card-board block panelBody .panel-body h4 {{item.summary}} p {{item.dueDate | date}}

AllcountJS uses jade template language by default. Code in board.jade defines template for custom view of our board. It defines panelBody block that describes how the card will look like. We should get following result for main.js

A.app({ appName: "Task manager", onlyAuthenticated: true, menuItems: [ { name: "Planning", entityTypeId: "Task" }, { name: "Board", entityTypeId: "TaskBoard", icon: "bars" }, { name: "Statuses", entityTypeId: "Status" } ], entities: function(Fields) { return { Task: { fields: { summary: Fields.textarea("Summary").required(), dueDate: Fields.date("Due Date").required(), status: Fields.fixedReference("Status", "Status") }, views: { TaskBoard: { customView: "board" } } }, Status: { fields: { name: Fields.text("Name").required(), order: Fields.integer("Order").required() }, sorting: [['order', 1]], referenceName: "name" } } } });

If you run this code sample you’ll see Kanban board with status where you can move cards between statuses. Order of statuses is defined by order field because of sorting.

Polishing

To polish things you probably want to add icons for menu items and the app. You could do it by simply referring to Font Awesome icons. Let’s add appIcon and icons for menu and we’ll get final version for the app.

A.app({ appName: "Task manager", appIcon: "book", onlyAuthenticated: true, menuItems: [ { name: "Planning", entityTypeId: "Task", icon: "tasks" }, { name: "Board", entityTypeId: "TaskBoard", icon: "bars" }, { name: "Statuses", entityTypeId: "Status", icon: "sort" } ], entities: function(Fields) { return { Task: { fields: { summary: Fields.textarea("Summary").required(), dueDate: Fields.date("Due Date").required(), status: Fields.fixedReference("Status", "Status") }, views: { TaskBoard: { customView: "board" } } }, Status: { fields: { name: Fields.text("Name").required(), order: Fields.integer("Order").required() }, sorting: [['order', 1]], referenceName: "name" } } } });

You could try to run final result in the our Demo Gallery.

Please feel free to ask questions in comments below and discuss this post in our Gitter channel. We very appreciate that!