Tutorial: desenvolvendo iMA em HTML 5 e JavaScript

O que é um iMA?

Um iMA (Módulo de Aprendizagem Interativa) é um módulos interativo voltados para o apoio ao ensino ou à aprendizagem, executados por meio de um navegador (eventualmente funcionando também como aplicativo). Desse modo, qualquer iMA pode ser incorporados aos ambientes para aprendizagem via Web, como é o caso do Moodle.
Funcionamento básico de iMA: Inicialmente o professor elabora uma atividade pelo iMA e a disponibiliza aos alunos; Depois, os estudantes resolvem o exercício proposto, utilizando o próprio iMA.
Boa parte dos iMA possuem mecanismo de avaliação automática, permitindo que, ao término da atividade, o aluno já saiba se a solução elaborada atende ao esperada pelo professor.
Para que a incorporação entre o iMA e o Moodle aconteça, é utilizado o "plugin" iTarefa. Observe que a utilização do iTarefa diminui a carga de trabalho para incorporar um artefato ao Moodle, pois isso pode ser feito sem a necessidade de um programador. Adicionalmente, um programador interessado em possibilitar o uso de sua ferramenta a partir do Moodle não precisará se preocupar com a integração do seu sistema com a plataforma, o que exige conhecimento de PHP.
Por outro lado, o iMA desenvolvido precisa realizar comunicação com o iTarefa, por meio de chamadas JavaScript, que serão detalhadas durante o tutorial.

Exemplos de iMA

Existem iMA desenvolvidos e que funcionam com todos os recursos disponibilizados pelo iTarefa:
  1. iGeom: Geometria Interativa
  2. iGeom: Geometria Interativa (versão JavaScript)
  3. iVProg: Programação Visual na Internet (versão JavaScript)
  4. iHanói: Problema das Torres de Hanói (versão JavaScript)

Integração do Moodle com o iTarefa e o iMA

A figura abaixo ilustra como se dá o processo de integração: o iMA está integrado ao iTarefa, que por sua vez, está integrado ao Moodle. Nessa imagem, podemos observar os três componentes apresentados no navegador do usuário. O trabalho de desenvolver um iMA se limita à integração com o iTarefa.


Fig. 1. Tela capturada com um iMA (com bordo verde), integrado ao iTarefa (bordo vermelho) dentro do Moodle.

Desenvolvendo um iMA

Neste tutorial, será apresentado o desenvolvimento de um iMA simplificado, chamado iArithmetic (aritmética interativa), que se destina a auxiliar o ensino de operações básicas de matemática.

A ideia do iArithmetic é que no momento de elaborar uma atividade nesse iMA, o professor escolha o tipo de operação a ser apresentada, a quantidade de parcelas, o número de desafios e o intervalo de valores. Quando a atividade for disponibilizada pelo iTarefa, a tela abaixo é apresentada ao aluno.
Note que no iArithmetic, cada parcela é representada por uma imagem, onde o aluno precisa associá-la a seu valor para resolver a operação.

Tela do professor para elaboração de atividade
Tela do aluno, para a resolução da atividade

Fig. 2. Na esquerda a interface do iMA para autoria do professor e na direita, sua interface para o aprendiz.

Vamos lá!

Para começar, criamos a seguinte estrutura de diretório para o projeto:
Em seguida, criamos o arquivo "integration-functions.js" no diretório "js". Neste arquivo, serão implementadas as funções necessárias à integração do iMA com o iTarefa.
As funções a serem implementadas em JavaScript no arquivo "js/integration-functions.js" são as seguintes:
Função Descrição
getParameterByName(name) Como o iMA será incluído na página HTML como um iframe, alguns parâmetros serão passados pelo iTarefa ao iMA, via URL. Sendo que este método será útil para ler os parâmetros informados (descritos abaixo).
getAnswer() Este método é invocado automaticamente pelo iTarefa, em dois diferentes momentos:
  • quando o professor está elaborando a atividade e a finaliza, clicando no botão "Salvar". Portanto, o retorno da função nesse caso devem ser os dados da atividade criada;
  • e quando o estudante está elaborando uma solução para o exercício e aciona o botão "Salvar", que para este caso, o retorno deve ser a resposta do estudante para a atividade.
Para ambos os casos, o retorno deste método será recebido pelo iTarefa e será armazenado no banco de dados.
getEvaluation() Este método deve estar presente nos iMA que possuam avaliador automático. Esta função é invocada pelo iTarefa, quando o aluno submete sua solução para avaliação. Portanto, toda a avaliação deve ser realizada quando o método getEvaluation() é chamado. O retorno deve ser um número real que signifique a nota atribuída para o aluno, entre 0.0 e 1.0. Esta nota será armazenada no banco de dados.
Antes de prosseguir, conheça os parâmetros que são repassados pelo iTarefa ao iMA. Estes parâmetros compõem a comunicação entre o iLM e o iTarefa.
Parâmetro Descrição
iLM_PARAM_ServerToGetAnswerURL
iLM_PARAM_SendAnswer Este parâmetro serve para distinguir que situação o iMA está sendo manuseado: se para a elaboração de uma atividade ou para resolver um exercício. Quando se trata do momento da criação da atividade, o valor é: true, enquanto que se é a situação de resolução do exercício, o valor é false.
iLM_PARAM_AssignmentURL
iLM_PARAM_Assignment Este parâmetro é informado quando o usuário está abrindo uma atividade interativa para resolvê-la. Tem como valor uma URL, que serve para acessar o exercício criado pelo professor.

Exemplo de valor: http://myschool.edu/moodle/mod/iassign/ilm_security.php?id=3&token=b3660dd4de0b0e9bb01fea6cc8f02ccd&view=1
Observe que o conteúdo do parâmetro token, presente na URL é acessível uma única vez, ou seja, quando o iMA acessá-la (via AJAX), obterá a informação a respeito da atividade, não podendo acessá-la novamente, pois a token será destruída por razões de segurança.
lang Este parâmetro informa ao iMA em que idioma o Moodle está sendo utilizado, permitindo a internacionalização. Exemplos de valores: en para inglês; pt para português.
Agora que conhecemos os métodos necessários para a comunicação entre o iTarefa e o nosso iMA, podemos implementá-los:

js/integration-functions.js


// Função para ler parâmetros informados pelo iTarefa via URL
// Apesar de não ser obrigatório, será muito útil para capturar os parâmetros
function getParameterByName (name) {
  var match = RegExp('[?&]' + name + '=([^&]*)').exec(window.location.search);
  return match ? decodeURIComponent(match[1].replace(/\+/g, ' ')) : null;
  }

// Criando um array com os parâmetros informados pelo iTarefa
// Observe que para cada parâmetro, é realizada a chamada do método getParameterByName, implementado acima
var iLMparameters = {
  iLM_PARAM_ServerToGetAnswerURL: getParameterByName("iLM_PARAM_ServerToGetAnswerURL"),
  iLM_PARAM_SendAnswer: getParameterByName("iLM_PARAM_SendAnswer"),
  iLM_PARAM_AssignmentURL: getParameterByName("iLM_PARAM_AssignmentURL"),
  iLM_PARAM_Assignment: getParameterByName("iLM_PARAM_Assignment"),
  lang: getParameterByName("lang")
  };

// Função chamada pelo iTarefa quando o professor finaliza a criação da atividade
// ou quando o aluno finaliza a resolução do exercício
// O retorno é um JSON com os dados do exercício ou da resolução
// Esse retorno será armazenado no banco de dados do Moodle, pelo iTarefa
function getAnswer () {
  // Se o parâmetro "iLM_PARAM_SendAnswer" for false,
  // então trata-se de resolução de atividade
  if (iLMparameters.iLM_PARAM_SendAnswer == 'false') {
    // Montar o retorno da resposta do aluno
    var respostas = Array();
    for (j = 0; j < desafios.length; j++) {
      respostas.push(parseInt($("#resposta_" + j).val()));
      }
    return '[{"respostas": ' + JSON.stringify(respostas) + '}, {"operacao": "' + operacao + '"}, {"parcelas": ' + JSON.stringify(parcelas) + '}, {"desenhos": ' + JSON.stringify(desenhos) + ' }, '
           + '{"desafios": ' + JSON.stringify(desafios) + '}]';
  } else {
    return JSON.stringify($('form[name="elaborar"]').serializeArray());
    }
  }

// Função chamada pelo iTarefa para receber a nota do aluno na atividade
// O retorno é um valor entre 0.0 e 1.0
function getEvaluation () {
  if (iLMparameters.iLM_PARAM_SendAnswer == 'false') {
    // Calcula a nota do aluno:

    // Como a nota vai de 0 a 1, calculamos um valor unitário para cada acerto:
    var valor_cada_acerto = 1 / desafios.length;

    // Agora, verificamos o total de acertos do aluno:
    var acertos = 0;
    for (i = 0; i < desafios.length; i++) {
      var temp = parcelas[desafios[i][0]];
      for (j = 1; j < desafios[i].length; j++) {
        switch (operacao) {
          case "ADI": temp += parcelas[desafios[i][j]]; break;
          case "SUB": temp -= parcelas[desafios[i][j]]; break;
          case "MUL": temp *= parcelas[desafios[i][j]]; break;
          case "DIV": temp /= parcelas[desafios[i][j]]; break;
          }
        }
      var resposta = parseInt($("#resposta_" + i).val().trim());            
      if (resposta == temp) {
        acertos ++;
        }
      }
    // Cálculo da nota: o produto do valor de cada desafio pelo número de acertos:
    var nota = valor_cada_acerto * acertos;

    // A chamada do método abaixo é obrigatória!
    // Observe que a chamada parte do iLM para o iTarefa
    parent.getEvaluationCallback(nota);
    }
  }


// Função para que o iMA leia os dados da atividade fornecidos pelo iTarefa
function getiLMContent () {
  // O parâmetro "iLM_PARAM_Assignment" fornece o URL do endereço que deve ser
  // requisitado via AJAX para a captura dos dados da atividade
  $.get(iLMparameters.iLM_PARAM_Assignment, function (d) {
    // Uma vez que os dados foram recebidos, o método "organizaAtividade" é chamado.
    // Observe que esse método faz parte do arquivo js/iarithmetic-functions.js
    organizaAtividade(JSON.parse(d));
    });
  }

// Adicionamos a diretiva .ready(), para que quando a página HTML estiver carregada,
// seja verificado qual a visualização deve ser apresentada: se a área de construção
// de atividade ou de resolução. E no caso de ser resolução, os dados da atividade
// precisam ser consultados, pelo método implementado acima, o getiLMContent()
$(document).ready(function () {
  // Se iLM_PARAM_SendAnswer for false, então trata-se de resolução de atividade,
  // portanto, a "DIV" de resolução é liberada
  if (iLMparameters.iLM_PARAM_SendAnswer == 'false') {
    $('.resolucao').css("display","block");
    getiLMContent();
  } else {
    // Caso não esteja em modo de resolução de atividade, a visualização no momento
    // é para a elaboração de atividade:
    $('.elaboracao').css("display","block");
    }
  });

Agora que o iMA está integrado ao iTarefa, vamos criar o arquivo "js/iarithmetic-functions.js", onde são implementadas as funções relacionadas ao comportamento do iArithmetic propriamente.

js/iarithmetic-functions.js


// Nesse arquivo, implementamos as funções relacionadas ao funcionamento do iArithmetic
// voltadas principalmente ao seu funcionamento e regras

var parcelas;
var desenhos;
var desafios;
var operacao;

// Recebe os dados que estão armazenados no Moodle
// No caso de o aluno ainda não ter submetido nenhuma resolução, apenas os dados relacionados
// à atividade são carregados. Enquanto que caso haja alguma resposta enviada ao exercício,
// os dados da resposta são recebidos. 
// Note que este funcionamento foi definido no método getAnswer() do arquivo js/integration-functions.js
function organizaAtividade (dados) {
    // Se o primeiro campo dos dados não for "name", trata-se apenas da atividade, sem uma resolução enviada previamente:
    if (dados[0].name != null) {
      // Para deixar o código mais organizado, o método "organizaAtividadeSemResolucao" fica responsável por tratar
      // de montar a atividade na tela
      organizaAtividadeSemResolucao(dados);
    } else { // Caso entre nesse "else", os dados recebidos incluem informações sobre uma tentativa do aluno
      // O método apresentaRespostas apresenta a atividade que foi proposta ao aluno e as respostas enviadas
      apresentaRespostas(dados);
      }
  }

// Apresenta a atividade na tela do usuário
function organizaAtividadeSemResolucao (dados) {
  operacao = dados[0].value;
  // Atribuindo valores aleatórios para as parcelas:
  parcelas = Array();
  for (i = 0; i < dados[1].value;) {
    var temp = Math.floor((Math.random() * dados[4].value) + dados[3].value);

    // Verificar se o número não é repetido:
    var ja_existe = false;
    for (j = 0; j < i; j++) {
      if (parcelas[j] == temp) {
        ja_existe = true;
        }
      }
    // Se não for repetido, adiciona e incrementa o i
    if (ja_existe == false) {
      parcelas.push(temp);
      i ++;
      }
    }

  // Escolhendo os desenhos que representarão cada valor:
  desenhos = Array();
  for (i = 0; i < dados[1].value;) {
    var temp = Math.floor((Math.random() * 10) + 1);

    // Verificar se o número não é repetido:
    var ja_existe = false;
    for (j = 0; j < i; j++) {
      if (desenhos[j] == temp) {
        ja_existe = true;
        }
      }
    // Se não for repetido, adiciona e incrementa o i
    if (ja_existe == false) {
      desenhos.push(temp);
      console.log(temp);
      i ++;
      }
    }

  // Gerando os desafios:
  desafios = Array();
  for (i = 0; i < dados[2].value; i++) {
    var temp = Array();
    for (j = 0; j < dados[1].value - 1; j ++) {
      temp.push(Math.floor((Math.random() * (dados[1].value)) + 0));
      }
    desafios.push(temp);
    }
  
  // Agora, é gerado o HTML da atividade:
  var elemento = "<center><table>";

  // Primeiro: apresentando os valores e seus desenhos:
  elemento += "<tr class='parcelas'><td><div>";
  for (i = 0; i < dados[1].value; i++) {
    elemento += "<img width='100px' src='img/"  + desenhos[i] + ".png'> = " + parcelas[i] + "";
    }
  elemento += "</div></td></tr>";

  // Segundo: apresentando os desafios:
  for (i = 0; i < dados[2].value; i++) {
    elemento += "<tr class='desafios'><td><div>";

    var d = desafios[i];
    for (j = 0; j < dados[1].value - 1; j++) {
      elemento += "<img width='100px' src='img/" 
              + desenhos[d[j]] + ".png'> ";
      if (j == dados[1].value - 2) continue;
      var op = dados[0].value;
      switch(op) {
        case "ADI": elemento += " + ";
          break;
        case "SUB": elemento += " - ";
          break;
        case "MUL": elemento += " x ";
          break;
        case "DIV": elemento += " / ";
          break;
        }
      }
    elemento += " = <input type=text size=2 id='resposta_"+i+"' /></div></td></tr>";
    }

  elemento += "</table>"; 
  $(".resolucao").append(elemento);
  }


// Quando o aluno já tiver submetido a resposta, este método é chamado para apresentar
// a resolução enviada, tanto para o estudante quanto para o professor
function apresentaRespostas (dados) {
  t_respostas = dados[0].respostas;
  t_operacao = dados[1].operacao;
  t_parcelas = dados[2].parcelas;
  t_desenhos = dados[3].desenhos;
  t_desafios = dados[4].desafios;

  // Agora, é gerado o HTML da atividade:
  var elemento = "<center><table>";

  // Primeiro: apresentando os valores e seus desenhos:
  elemento += "<tr class='parcelas'><td><div>";
  for (i = 0; i < t_parcelas.length; i++) {
    elemento += "<img width='100px' src='img/" + t_desenhos[i] + ".png'> = " + t_parcelas[i] + "";
    }
  elemento += "</div></td></tr>";

  // Segundo: apresentando os desafios:
  for (i = 0; i < t_desafios.length; i++) {
    elemento += "<tr class='desafios'><td><div>";

    var d = t_desafios[i];
    for (j = 0; j < t_parcelas.length - 1; j++) {
      elemento += "<img width='100px' src='img/" + t_desenhos[d[j]] + ".png'> ";
      if (j == t_parcelas.length - 2) continue;
      switch(t_operacao) {
        case "ADI": elemento += " + "; break;
        case "SUB": elemento += " - "; break;
        case "MUL": elemento += " x "; break;
        case "DIV": elemento += " / "; break;
        }
      }
    elemento += " = <input type=text size=2 id='resposta_"+i+"' value='"+t_respostas[i]+"' disabled='true' /></div></td></tr>";
    }

  elemento += "</table>"; 
  $(".resolucao").append(elemento);
  }

Agora que as funções JavaScript foram implementadas, incorporamos os arquivos dentro da página HTML. Para tanto, crie o arquivo "main.html" na raiz do projeto. É importante notar que esse arquivo "main.html" será a página incluída no iFrame pelo iTarefa. Portanto é a parte principal do iMA, onde tudo deve acontecer.

main.html


<!DOCTYPE html>
<html>
<head>
 <meta charset="UTF-8">
 <script src="js/jquery-3.3.1.min.js"></script>
 <!-- Incluindo o arquivo JavaScript com as funções de integração: -->
 <script src="js/integration-functions.js"></script>
 <!-- Incluindo o arquivo JavaScript com as funções do iArithmetic: -->
 <script src="js/iarithmetic-functions.js"></script>

 <link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>

 <!-- Div com a área para resolução da atividade -->
 <!-- Só é visualizado se o parâmetro iLM_PARAM_SendAnswer for "false" -->
 <div class="resolucao" style="display: none;">
  
 </div>

 <!-- Div com o formulário para elaboração de atividade -->
 <!-- Só é visualizado se o parâmetro iLM_PARAM_SendAnswer for "true" -->
 <div class="elaboracao" style="display: none;">
  <form name="elaborar">
  <div>Elaboração de atividade aritmética</div>
  <div>
   Operação: 
   <select name="operacao">
    <option value="ADI">Adição</option>
    <option value="SUB">Subtração</option>
    <option value="MUL">Multiplicação</option>
    <option value="DIV">Divisão</option>
   </select>
  </div>
  <div>
   Total de parcelas por desafio: 
   <select name="parcelas">
    <option>2</option>
    <option>3</option>
    <option>4</option>
    <option>5</option>
    <option>6</option>
    <option>7</option>
    <option>8</option>
    <option>9</option>
    <option>10</option>
   </select>
  </div>
  <div>
   Total de desafios: 
   <select name="desafios">
    <option>1</option>
    <option>2</option>
    <option>3</option>
    <option>4</option>
    <option>5</option>
   </select>
  </div>
  <div>
   Intervalo de valores:
   de <input type="text" size="2" name="de"> até <input type="text" size="2" name="ate">
  </div>
  </form>
 </div>

</body>
</html>

As imagens que foram utilizadas no iMA e que devem ser inseridas no diretório "img" estão dentro do pacote completo do exemplo, no link abaixo:

Para testar uma versão HTML/JavaScript sem o servidor Moodle, a clique aqui.