Building a Tasks app
This tutorial will guide you through the basic steps of building a Task management app in Weavy. We will cover things like Apps, Content types, Controllers and Views.
The Task app will mainly be a client side app built with vue.js, and all communication with the Weavy backend will be made through API endpoints that we create.
What are we going to build?
In the end of this tutorial we should have a basic task app demonstrating the concepts of building an app. There will also be a link to a fully functional task management app with all kind of interesting stuff not covered here.
In that app you should be able to add, edit and remove tasks as well as assigning a task to a user in Weavy and also setting a due date. You will also learn how to create a notification when assigning a user to a task.
Create a Task item
The first thing we are going to do is to create a content type for Task items.
First create a new file called TaskItem.cs
in the Areas\Apps\Models
folder. The contents of the file should look something like this:
[Serializable]
[Guid("F16EFF39-3BD7-4FB6-8DBF-F8FE88BBF3EB")]
[Content(Icon = "checkbox-marked-outline", Name = "Task item", Description = "A task item.", Parents = new Type[] { typeof(TasksApp) })]
public class TaskItem : Content {
}
Next, let's add a property called Completed
to the TaskItem
class. This property will hold a value indicating whether a task is completed or not.
[Serializable]
[Guid("F16EFF39-3BD7-4FB6-8DBF-F8FE88BBF3EB")]
[Content(Icon = "checkbox-marked-outline", Name = "Task item", Description = "A task item.", Parents = new Type[] { typeof(TasksApp) })]
public class TaskItem : Content {
/// <summary>
/// If the task is completed or not
/// </summary>
public bool Completed { get; set; }
}
Create the Tasks app
Now it's time to create the class representing the Tasks App. The class should derive from the App
base class and have the
[Serializable]
and
[Guid]
attributes.
We will also annotate the class with the [App]
attribute allowing us to set metadata such as
Name
, Description
,
Icon
, AllowMultiple
and
Content
.
By setting AllowMultiple
to false
it will only be possible to add one instance of the TasksApp
to a Space, and with the Content
property we can limit the types of content items that can be created in the app (here we only want to allow the TaskItem
content type that we just added).
Create a new file called TasksApp.cs
in the Models
folder. The contents of the file should look something like this:
/// <summary>
/// A task management app
/// </summary>
[Serializable]
[Guid("F1C835B0-E2A7-4CF3-8900-FE95B6504145")]
[App(Icon = "checkbox-marked-outline", Name = "Tasks", Description = "A task management app.", AllowMultiple = false, Content = new Type[] { typeof(TaskItem) })]
public class TasksApp : App {
}
So far we have created an App named TasksApp
and a content type named TaskItem
. We should now be able to compile the solution and check that
our app and content type shows up in Weavy.
Hit F5 in Visual Studio, and navigate to a Space when your browser opens. In the Space click Add tab and voilà, there's our Task Management App ;)
Adding the TasksApp
to the Space will do... absolutely nothing right now. Weavy will just render the app with the default Controller and View which is empty.
We could add a custom View to render some content, but we want to take this example a step further and use our own Controller.
Before we move on and add the Controller and View, we should add a property to the TasksApp
that will hold all the tasks that we create:
/// <summary>
/// A task management app
/// </summary>
[Serializable]
[Guid("F1C835B0-E2A7-4CF3-8900-FE95B6504145")]
[App(Icon = "checkbox-marked-outline", Name = "Tasks", Description = "A task management app.", AllowMultiple = true, Content = new Type[] { typeof(TaskItem) })]
public class TasksApp : App {
/// <summary>
/// All the tasks in the task list
/// </summary>
public SearchResult<Content, ContentQuery> Tasks { get; set; }
}
Add a Controller and View
By adding a Controller that inherits from AppController<TApp>
we can take full controll over the rendering flow of an App in Weavy.
The Controller is just like any MVC Controller where we can add the Actions we need.
Create a new file named TasksController.cs
in the Areas\Apps\Controllers
folder. The contents of the file should look something like this:
/// <summary>
/// Controller for the <see cref="TasksApp" />.
/// </summary>
[RoutePrefix("apps/{id:int}/F1C835B0-E2A7-4CF3-8900-FE95B6504145")]
public class TasksController : AppController<TasksApp> {
}
We are going to override the Weavy.Web.Controllers.AppController`1.Get
method so that we can get all the tasks connected to the app before returning them to the View.
/// <summary>
/// Controller for the <see cref="TasksApp" />.
/// </summary>
[RoutePrefix("apps/{id:int}/F1C835B0-E2A7-4CF3-8900-FE95B6504145")]
public class TasksController : AppController<TasksApp> {
/// <summary>
/// Get action for the app
/// </summary>
/// <param name="app">The app to display.</param>
/// <param name="query">An object with query parameters for search, paging etc.</param>
public override ActionResult Get(TasksApp app, Query query) {
app.Tasks = ContentService.Search<TaskItem>(new ContentQuery<TaskItem>(query) {
AppId = app.Id,
Depth = 1,
OrderBy = "CreatedAt ASC", Count = true
});
return View(app);
}
}
In the Get
method, we will have access to the current App through the app parameter passed to the method from the base class.
In the code above, we set the Tasks
property of the TasksApp
to the result of ContentService.Search<TaskItem>()
which gets all the tasks for the app. The app is then used as the model passed to the View.
Now it's time to create the View. Add a file called Get.cshtml
in the Areas\Apps\Views\Tasks
folder with the following code:
@model Weavy.Areas.Apps.Models.TasksApp
<div class="container">
<h1>@Model.Name</h1>
@if (!Model.Tasks.Any()) {
<em>Sorry, no tasks here yet!</em>
}
<ul>
@foreach (var task in Model.Tasks) {
<li>@task.Name</li>
}
</ul>
</div>
Hit F5 in Visual Studio and try out the app so far. You should now be able to add a Task app to a Space and have it rendered through the Controller and View we just created.
Create tasks
At this step we can get the tasks from the app and render it in our View. I would be nice to be able to create some tasks though... ;)
We could implement this in a couple of different ways. As an MVC Action or an API Action on a Controller.
We are going to use the latter, an API action endpoint on our TasksController
.
Add the following code to your TasksController
:
/// <summary>
/// Add a new task to the app.
/// </summary>
/// <param name="id">The id of the app</param>
/// <param name="model"></param>
/// <returns></returns>
[HttpPost]
[Route("tasks")]
public JsonResult InsertTask(int id, TaskIn model) {
var app = AppService.Get<TasksApp>(id);
var task = new TaskItem {
Name = model.Name
};
var inserted = ContentService.Insert<TaskItem>(task, app);
return Json(inserted);
}
In the code above, we first get the current app. Then we create a new TaskItem
and add it to the app with ContentService.Insert()
.
The action above expects a TaskIn
model which contains the properties we want to set.
Create a new class in the Areas\Apps\Models
folder called TaskIn.cs
and add the properties below:
/// <summary>
/// Representing a task from a xhr request
/// </summary>
public class TaskIn {
/// <summary>
/// The id of the task
/// </summary>
public int Id { get; set; }
/// <summary>
/// The name of the task
/// </summary>
public string Name { get; set; }
/// <summary>
/// If the task is completed or not
/// </summary>
public bool Completed { get; set; }
}
Update the Get.cshtml
file to include a textbox and button for creating a new task as well as the ajax script code to call the endpoint.
We will also introduce Vue.js which will help us manage the tasks in a very simple way.
This is of course not needed and you can use whatever code and/or javascript framework you want in a App View.
The Get.cshtml
view should now look something like this:
@model Weavy.Areas.Apps.Models.TasksApp
<div class="container" id="task-app-container">
<h1>@Model.Name</h1>
<div class="input-group mb-3">
<input type="text" class="form-control" placeholder="Create a new task here..." v-model="taskName">
<div class="input-group-append">
<button class="btn btn-outline-secondary" type="button" id="button-create" @click="createTask">Add task</button>
</div>
</div>
<em v-if="!tasks.length">Sorry, no tasks here yet!</em>
<ul>
<li v-for="task in tasks">{{task.name}}</li>
</ul>
</div>
<script src="https://cdn.jsdelivr.net/npm/vue@2.5.17/dist/vue.js" data-turbolinks-track="reload"></script>
<script>
$(function () {
var appId = @Model.Id;
var appGuid = '@Model.AppGuid';
var vm = new Vue({
el: '#task-app-container',
data: {
tasks: @Html.ToJson(Model.Tasks),
taskName: ''
},
methods: {
createTask: function () {
var taskApp = this;
var name = taskApp.taskName;
$.ajax({
url: weavy.url.resolve('/apps/' + appId + '/' + appGuid + '/tasks'),
data: JSON.stringify({ name: name }),
method: 'POST',
contentType: 'application/json'
}).then(function (taskResponse) {
taskApp.tasks.push(taskResponse);
taskApp.newTask = '';
});
}
}
});
});
</script>
Test the app
Hit F5 once again to launch Weavy. Try the new Task app by adding it to a Space and create some tasks.
Add built-in features
The Content
base class in Weavy is intentionally kept very thin and small.
But you can easily extend your content type with some built-in features by implementing interfaces for the functionality you need.
For example, if you want to let the end user to star the task item, you would implement the IStarrable
interface on your TaskItem
class.
[Serializable]
[Guid("F16EFF39-3BD7-4FB6-8DBF-F8FE88BBF3EB")]
[Content(Icon = "checkbox-marked-outline", Name = "Task item", Description = "A task item.", Parents = new Type[] { typeof(TasksApp) })]
public class TaskItem : Content, IStarrable {
/// <summary>
/// Gets the ids of all people that starred this task.
/// </summary>
public IEnumerable<int> StarredByIds { get; set; }
}
Starring a Content
item is simple, by adding @Html.StarToggle(Model)
to your View
Weavy will render a button allowing you to star/unstar the content item.
Other interfaces available to extend your content item are ILikeable
, ICommentable
,
IEmbeddable
, IDraftable
, IFollowable
, IHasAttachments
,
IHasEmbeds
, ILockable
, IPinnable
, ISortable
,
ITaggable
, ITrashable
, IVersionable
Stylesheets
The best place to place custom stylesheets is the Areas\Apps\Styles
folder.
Weavy interally uses Sass for it's styles
but it is also possible to use standard css if you prefer that.
For our task app we can create a new file called tasks.scss
where we can put our stylesheet rules.
The name is not important, but it's a good convention to name it like the app.
Weavy has a built in bundler for stylesheets and scripts which you can access with the @Html.ThemeStyle()
helper method. This helper will make sure your scss stylesheet is compiled and included in the page.
To include the scss file in our Get.cshtml
View we can add the following code:
@section styles {
@Html.ThemeStyle("~/areas/apps/styles/tasks.scss")
}
Next steps
The next steps would be to add more API endpoints to handle Edit, ToggleComplete, Delete and so on. A fully functional example is available in our weavy-examples repo on GitHub. The code in the repo includes all the functionality from this tutorial and also has code for setting a due date on a task, assigning a task to a user, commenting a task, sending notifications and more. The app also has a ready to use UI that you can use or modify as you like.