Chrome Extension de “ToDo” Integrado ao Trello

Uma extensão para controlar suas tarefas é muito útil, principalmente se você puder recuperar os dados mesmo se estiver em outro computador e sem muito trabalho.

O trello é um site onde podemos criar boards para as tarefas e organizar as atividades.

Neste post eu mostro como criar uma chrome extension que utiliza a API do trello para armazenar as tarefas que você poderá acessar em qualquer computador onde tenha a extensão instalada utilizando sua conta do trello.

Como sempre, o código está disponível no meu GitHub.

Entendendo a API

A documentação da API do trello está disponível no endereço https://trello.com/docs/index.html e está em fase beta no momento que escrevo este post.

A API é RESTful e isso é uma boa notícia, pois já sabemos trabalhar com RESTful e mesmo se a documentação não é muito completa, conseguimos fazer os acessos apenas por deduções (como é o caso desta API).

Obtendo a Chave de Acesso

Para acessar o trello a partir da nossa aplicação, precisamos de uma chave específica. Esta chave de 32 caracteres é a nossa autorização para utilizar a API através da nossa aplicação.

Para gerar a chave, é necessário estar logado no trello e depois acessar a URL https://trello.com/1/appKey/generate.

A imagem abaixo mostra a tela com a chave de acesso. Essa chave é a que foi gerada a partir da minha conta, portanto, é necessário que você gere uma com sua conta para que sua aplicação não seja impedida de acessar caso eu renove as minhas chaves.

Captura de Tela 2015-04-13 às 21.35.39

Configurações da Extensão

Toda extensão do chrome necessita um arquivo de manifesto onde contém algumas informações para o chrome iniciar a aplicação.

Dentre as configurações contidas no arquivo de manifesto, as permissões são bastante comuns e igualmente importantes.

Para esse projeto, precisamos de permissões de acesso às abas e armazenamento. Essas permissões são “activeTab”, “tabs” e “storage”, além (é claro) de acesso à URL da API https://*.trello.com/*.

Abaixo listo o arquivo manifest.json que fica localizado na raíz da app.

{
  "manifest_version": 2,
  "name": "ToDo Trello Chrome Extension",
  "description": "Controlador de tarefas integrado ao trello",
  "version": "1.0",
  "browser_action": {
    "default_icon": "images/icon128.png",
    "default_popup": "popup.html"
  },
  "permissions": [
    "activeTab",
    "storage",
    "tabs",
    "https://*.trello.com/*"
  ],
  "background": {
    "scripts": ["js/background.js"],
    "persistent": true
  }
}

Estrutura dos Arquivos

Para este projeto eu decidi incluir alguma coisa mais elaborada no visual.

Embora não seja meu ponto forte e nem o objetivo deste post, é sempre mais interessante quando trabalhamos com uma interface mais agradável.

Eu utilizei o bootstrap para a interface, pois é uma biblioteca muito fácil de utilizar e tem um resultado satisfatório, o objetivo é ter uma interface como a imagem abaixo.

Captura de Tela 2015-04-22 às 09.48.44

Os arquivos estão organizados por pastas de acordo com seu tipo, como podemos ver na imagem abaixo.

Captura de Tela 2015-04-22 às 09.49.32

 

Fluxo de Autenticação

Para conseguir acessar os dados do usuário precisamos obter o token de acesso.

Este token é necessário para que a API saiba que o usuário autorizou a nossa aplicação para obter e modificar dados da conta dele no trello.

Enquanto não temos o token de acesso, não conseguimos ler os dados do usuário e por isso não temos tarefas para apresentar na tela.

Neste momento devemos apresentar uma tela para que o usuário possa solicitar a autorização para a nossa aplicação.

Abaixo está a tela inicial da aplicação.

Captura de Tela 2015-04-22 às 09.57.49

Quando o usuário clicar no botão Autorizar, será aberta uma aba com uma tela informando que a nossa aplicação está solicitando uma autorização para acessar a conta dele.

A imagem abaixo mostra a tela de autorização.

Captura de Tela 2015-04-13 às 22.45.19

É importante lembrar que para o tipo de permissão que precisamos o trello gera um token de validade de apenas 1 dia.

Quando o usuário for acessar a extensão após 1 dia, a extensão deverá solicitar nova autorização.

Quando o usuário clicar em Allow com a intenção de autorizar a aplicação, o trello faz um redirecionamento para uma URL que a aplicação informou.

No nosso caso, não temos uma hospedagem para redirecionar o acesso, portanto eu defini no parâmetro return_url o nome da nossa app.

Foi criado um listener, no arquivo background.js, para identificar que ocorreu a autorização e armazenar o token de acesso que é enviado pelo trello através da URL de callback.

Abaixo listo o arquivo background.js

const CALLBACK_URL = "https://trello.com/1/token/ToDoChromeExtension#token=";

function setValue(key, value) {

    var obj = {};
    obj[key] = value;

    console.log("set", obj);

    chrome.storage.sync.set(obj, function () {
        console.log("ok");
    });
}

function getValue(key, callback) {
    chrome.storage.sync.get(key, function (value) {
        callback(value);
    });
}

chrome.tabs.onUpdated.addListener(function (tabId, changeInfo, tab) {
    getValue("tabId", function (value) {
        if (value.tabId === tabId) {
            if (changeInfo.status === "complete" && tab.url.indexOf(CALLBACK_URL) > -1) {

                var token = tab.url.replace(CALLBACK_URL, "");
                setValue("token", token);

                chrome.tabs.remove(tabId, function () {
                });
            }

        }
    });
});

Criando o Quadro de Tarefas

A extensão identifica que o usuário está autenticado e solicita os boards do usuário para a API.

O código abaixo mostra a função loadBoard que tenta encontrar o board cujo nome é o nome da nossa extensão, identificado aqui pela constante APP_NAME que foi definida no início do arquivo popup.js.

Na primeira execução, não vamos encontrar o board com o nome da nossa app; quando isso ocorrer, faremos um post na API para criar um board com o nome que desejamos.

const BOARDS_URL = “https://trello.com/1/members/my/boards?key=%5BKEY%5D&token=%5BTOKEN%5D”;

const BOARD_CREATE_URL = "https://trello.com/1/boards?key=[KEY]&token=[TOKEN]&name=[NAME]";

...

function loadBoard() {
    var self = this;
    getJSON(BOARDS_URL.replace('[KEY]', API_KEY).replace('[TOKEN]', token), function (data) {
        if (data === "expired token") {
            logoff();
            return;
        }

        self.appBoard = null;

        data.some(function (board) {
            if (board.name === APP_NAME && !board.closed) {
                console.log(board);
                self.appBoard = board;
                return true;
            }
            return false;
        });

        if (!appBoard) {
            post(BOARD_CREATE_URL
                .replace('[KEY]', API_KEY)
                .replace('[TOKEN]', self.token)
                .replace('[NAME]', APP_NAME), function (data) {
                self.appBoard = data;

                loadLists();
            });
        } else {
            loadLists();
        }
    }, function (jqxhr, textStatus, error) {
        if (jqxhr.status === 401) {
            logoff();
        }
    });
}

 

Após encontrar ou criar um board, fazemos uma busca nas listas, que são as colunas onde os cards ficam agrupados no trello.

Por padrão, quando o board é criado, existem 3 listas: To Do, Doing e Done.

O que faremos agora é identificar as listas para obter os cards (que são as nossas tarefas) e também para poder mover o card para a lista done quando a tarefa for concluída.

 

const LISTS_URL = "https://api.trello.com/1/boards/[BOARD_ID]/lists?cards=open&card_fields=name&fields=name&key=[KEY]&token=[TOKEN]";

...

function loadLists() {
    var self = this;

    getJSON(LISTS_URL
        .replace('[KEY]', API_KEY)
        .replace('[TOKEN]', self.token)
        .replace('[BOARD_ID]', self.appBoard.id), function (data) {

        clearList();

        data.forEach(function (list) {
            var done = false;

            if (list.name === "To Do") {
                self.toDoList = list;
            } else if (list.name === "Done") {
                self.doneList = list;
                done = true;
            }

            if (list.cards) {
                list.cards.forEach(function (card) {
                    showCard(card, done);
                });
            }
        });

        showCard({id: "new", name: ""}, false);
    });
}

Listando as Tarefas

Os cards são encontrados no JSON de retorno da lista, como podemos ver abaixo.

[
    {
        "id": "552e5c1ed5d312eefbce7ba4",
        "name": "To Do",
        "cards": [
            {
                "id": "552fa15a89d33bc16d0f8c57",
                "name": "Finish the ToDo chrome extension"
            },
            {
                "id": "552fb535d6a799698c93960b",
                "name": "English class"
            },
            {
                "id": "552fb53ac338f55f0b6ec2ad",
                "name": "Blog Post"
            },
            {
                "id": "552fb55af1b07169347244fa",
                "name": "Prototype"
            },
            {
                "id": "552fb566a2c694b19ab7b9b2",
                "name": "Meeting"
            }
        ]
    },
    {
        "id": "552e5c1ed5d312eefbce7ba6",
        "name": "Done",
        "cards": []
    }
]

Para cada card encontrado, será chamada a função showCard que será responsável por incluir ele na interface de forma dinâmica.

Também será chamada mais uma vez para incluir uma linha de tarefa vazia, que será apresentada ao final da lista para que possamos digitar a tarefa que desejamos criar.

Para a função são passados 2 parâmetros, sendo o primeiro o card e o segundo true/false para indicar se a tarefa já está concluída.

function showCard(card, done) {
    var template = $("#template").clone().attr('id', 'card_' + card.id).removeClass("hide");

    $("input[type=text]", template)
        .val(card.name)
        .on('change', function (e) {
            saveCard($(this));
        });

    $("input[type=checkbox]", template)
        .on('change', function (e) {
            moveCard(this);
        });

    if (card.id === 'new') {
        $("input[type=checkbox]", template).attr('disabled', 'disabled');
    }

    if (done) {
        $("input[type=checkbox]", template).attr('checked', 'checked');
    }

    $("#todo-container").append(template);
}

Criar, Alterar e Excluir uma Tarefa

Para criar uma tarefa (ou card), o usuário deve digitar um texto no último campo que está vazio e quando sair do campo, apertar tab ou enter, a extensão enviará um POST para a API para criar o card.

Para alterar uma tarefa, o usuário deve alterar o texto da tarefa que deseja modificar e sair do campo; a extensão irá realizar um PUT na API alterando o texto do card.

Para excluir, o usuário deverá limpar o campo e sair do mesmo. Quando isso ocorrer será enviado um DELETE para a API solicitando a exclusão.

Abaixo segue a função saveCard que é responsável por identificar qual tipo de ação será realizada.

const CARD_CREATE_URL = "https://trello.com/1/cards?key=[KEY]&token=[TOKEN]&idList=[LIST_ID]&name=";
const CARD_UPDATE_URL = "https://trello.com/1/cards/[CARD_ID]?key=[KEY]&token=[TOKEN]&name=";
const CARD_DELETE_URL = "https://trello.com/1/cards/[CARD_ID]?key=[KEY]&token=[TOKEN]";
...
function saveCard(input) {
    var cardId = input.parent().parent().parent().attr('id').replace('card_', '');

    if (input.val().length === 0) {
        if (cardId !== 'new') {
            sendDelete(CARD_DELETE_URL
                .replace('[KEY]', API_KEY)
                .replace('[TOKEN]', self.token)
                .replace('[CARD_ID]', cardId), function () {
                console.log('deleted');
                loadLists();
            });
        }
    } else {
        if (cardId === 'new') {
            post(CARD_CREATE_URL
                .replace('[KEY]', API_KEY)
                .replace('[TOKEN]', self.token)
                .replace('[LIST_ID]', self.toDoList.id) + encodeURIComponent(input.val()), function () {
                console.log('created');

                loadLists();
            });
        } else {
            put(CARD_UPDATE_URL
                .replace('[KEY]', API_KEY)
                .replace('[TOKEN]', self.token)
                .replace('[CARD_ID]', cardId) + encodeURIComponent(input.val()), function () {
                console.log('updated');
            });
        }

    }
}

 

Marcando uma Tarefa como Concluída

A tarefa que for concluída, deverá ter o checkbox localizado à esquerda do texto marcado.

Para identificar a tarefa como concluída, devemos mover o card para a lista “Done”.

Isso pode ser feito enviando um PUT para a API informando o card e o id da nova lista.

Uma tarefa pode ser enviada novamente para a lista de “To Do”, indicando que ela não está concluída. Este processo é o mesmo que marcar como concluída, porém o a id da lista deverá ser o da lista “To Do”.

Abaixo está a função que move a lista.

const CARD_MOVE_URL = "https://trello.com/1/cards/[CARD_ID]?idList=[LIST_ID]&key=[KEY]&token=[TOKEN]";
...
function moveCard(checkbox) {
    var cardId = $(checkbox).parent().parent().parent().parent().attr('id').replace('card_', '');

    put(CARD_MOVE_URL
        .replace('[KEY]', API_KEY)
        .replace('[TOKEN]', self.token)
        .replace('[CARD_ID]', cardId)
        .replace('[LIST_ID]', (checkbox.checked ? self.doneList.id : self.toDoList.id)), function () {
        console.log('moved');
        loadLists();
    });

}

 

Conclusão

Esta aplicação dá um certo trabalho, pois temos que tomar cuidado em armazenar o token do usuário para utilizar durante os requests.

Além disso, temos uma tela diferente para quando o usuário não está autenticado.

Não esqueça de pegar o projeto no meu GitHub e caso encontre alguma dificuldade em refazer o projeto pode deixar sua mensagem que eu terei prazer em responder.

 

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Create a free website or blog at WordPress.com.

Up ↑

%d bloggers like this: