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.

Before you begin, please make sure you have a Weavy site up and running and that you understand the fundamentals of Weavy

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 {
    
}
Since the TasksApp class isn't created yet, the Parents property above will generate a compilation error. Feel free to create an empty class named TasksApp now or wait to add the Parents property until we get that far.

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.