Pular para o conteúdo principal

Removendo botões de um curso no Blackboard Learn via injeção Javascript (JS)


Olá, leitores!
Este é o meu primeiro post sobre experiências agradáveis e desagradáveis quanto ao uso e/ou desenvolvimento de softwares em geral. Neste post (e provavelmente nos próximos), falarei sobre recursos, codificações e outros assuntos em um software no qual trabalho há certo tempo, isto é, o Blackboard Learn (Bb). Em linhas gerais, ele é um Ambiente Virtual de Aprendizagem utilizado por inúmeras instituições de ensino no mundo todo.
O Bb fornece às instituições inúmeros recursos, entre os quais, o de Gerenciamento de Curso. Ou seja, você pode de fato, construir um curso com inúmeros recursos de ensino-aprendizagem. Dentro desse cenário, o Bb permite a divisão de um curso, em áreas, utilizando botões como separadores. Em nosso curso exemplo, este é estruturado em cinco áreas, conforme ilustra a Figura 1:
Figura 1 - Exemplo de curso no Blackboard Learn
Fonte: O autor
Em nossa primeira empreitada gostaria de remover todos os botões e criá-los novamente. Bom, isto é razoavelmente simples, basta clicar no ícone , e sequencialmente nas opções “Excluir”, “Excluir conteúdo” e “Excluir”. No entanto, isto é, maçante quando a quantidade de botões é ligeiramente grande, isto porque o Blackboard requere quatro cliques (ações) pra exclusão de cada um dos botões e não existe um recurso do tipo “select all > delete”! Se considerarmos essa prática em vários cursos, isto se torna totalmente inviável. Daí nasce a necessidade de uma solução, ainda que paliativa.

Entendendo como funciona a exclusão de um botão no Bb

Diante do problema apresentado, é hora de aventurar-se, de tal modo, a descobrir como o Bb de fato trabalha com a exclusão destas áreas (botões). Questionamentos do tipo “Em qual linguagem foram escritas as funções de exclusão?”, “haveria APIs disponíveis pra solucionar o problema?”, “Itens nestas áreas seriam excluídos se os botões também o fossem?”, entre outros, devem ser respondidos.
Bom, ao simular o procedimento de exclusão, adjunto da ferramenta ‘Inspecionar Elemento’ do browser, conseguimos obter as seguintes informações:
  1. A primeira ação é um evento de clique acionado na tag a (botão ). Este exibe um menu de opções, que é gerenciado pelo framework Prototype.js;
  2. A segunda ação é a chamada de um método nativo no atributo href (tag a). Esta ação acontece ao clicarmos na opção “Excluir”. O método em questão é:
    javascript:theCourseMenu.deleteItem('toc_id');
  3. A terceira ação é, também, a chamada de um método nativo. Este, no entanto, no evento onclick (tag a). A ação acontece ao clicarmos na opção "Excluir conteúdo". O método é: javascript:theCourseMenu.deleteItemConfirmation('toc_id');
  4. A quarta (e última) ação é a chamada de um terceiro método nativo, também no evento onclick (tag a). Esta ação acontece ao clicarmos na opção “Excluir”. O método é: javascript:theCourseMenu.removeToc('toc_id').

Explorando as funções JS nativas

Antes de tudo, deixo claro que explanarei as funções utilizadas exclusivamente no procedimento de exclusão dos botões do Learn, tendo em vista que a quantidade de funções nativas utilizadas no ambiente é razoavelmente grande e assim sendo, foge ao nosso escopo. Além das funções nativas, há funções utilizadas em plugins externos, entre esses, destaca-se o Prototype.js. Este é utilizado no Bb para gerenciamento de eventos, requisições AJAX, entre outras coisas. Utilizando a versão 1.7 (Nov/2010), este pode ser localizado no diretório /javascript/prototype.js do Learn.
Caso desejem explorar o framework, consultem a documentação oficial, disponível em http://api.prototypejs.org/. Dicas e tutoriais também podem ser acedidos em http://prototypejs.org/learn/.
Focando essencialmente nas funções nativas, segue algumas particularidades:
  1. As três funções abordadas no procedimento de exclusão podem ser localizadas no mesmo arquivo JS, no diretório /javascript/ngui/coursemenu.js do Learn;
  2. As três funções recebem como parâmetro o mesmo identificador, isto é, o “toc_id”. Este nada mais é, que um identificador do próprio item;
  3. Duas das funções são chamadas no evento onclick, enquanto uma delas (a primeira) é chamada no atributo href da tag a.
Nota: A versão do Learn utilizada neste post é a 3000.1.5-rel.106+a770c2a, o que pode implicar em diferenças pontuais nas versões de plugins, localizações de diretórios, nomenclaturas, etc., apresentadas quando comparadas à outras versões.
A primeira função é descrita abaixo:
/**
* Entra no fluxo de trabalho de prompt inicial para a remoção do toc do nível superior (tradução nossa).
*/

deleteItem: function(toc_id) {
var viewUrl = this.appendMenuActionUrl('cmd=deleteContentArea&course_id=' + course_id + '&toc_id=' + toc_id);
this.DeleteItemBox = new lightbox.Lightbox({
focusOnClose: $('paletteItem:' + toc_id),
showCloseLink: false,
closeOnBodyClick: false,
dimensions:{
w: 400,
h: 280
},
ajax:{
url: viewUrl
}
});
this.DeleteItemBox.open();
},
Bom, a primeira coisa a analisarmos é o comentário que acompanha a função. No entanto, este não nos esclarece muita coisa. Desse modo, tentaremos analisar a codificação como um todo.
Inicialmente, na página do curso a variável theCourseMenu é criada e inicializada. Esta será utilizada como referência para as três funções utilizadas.
<script type="text/javascript">

var theCourseMenu = new courseMenu.CourseMenu('/webapps/blackboard/execute/doCourseMenuAction', '/webapps/blackboard/execute/getCourseMenuContextMenu');

</script>
Já a função em si, poderia ser resumida em:
  1. Cria-se uma variável viewURL. Esta por sua vez, armazena uma action composta pelo course_id + toc_id;
  2. Em seguida é instanciada uma janela Lightbox com os atributos focusOnClose, composta pela concatenação das Strings “paletteItem:” + "toc_id". Os atributos showCloseLink e closeOnBodyClick, referem-se à existência de um link pra fechar a janela, ou ao clique sobre a body (área externa à janela) pra fechamento da mesma, respectivamente. Nesses dois, os valores são falsos;
  3. w e h referem-se justamente a dimensão da janela, enquanto que a função ajax, adiciona à variável url a variável viewURL, para uma possível submissão;
  4. Por fim, com a função .open() a janela é exibida.
A segunda função é quase que idêntica à primeira, com exceção que ao invés do uso do this, é utilizado o atributo courseMenu. Esta pode ser visualizada abaixo:
/**
* Confirmação secundária para exclusão, incluindo relatório de crianças, se houver (tradução nossa).
*/

deleteItemConfirmation: function(toc_id) {
var viewUrl = this.appendMenuActionUrl('cmd=deleteContentArea&course_id=' + course_id + '&toc_id=' + toc_id);
courseMenu.DeleteItemBox = new lightbox.Lightbox({
focusOnClose: $('paletteItem:' + toc_id),
showCloseLink: false,
closeOnBodyClick: false,
dimensions:{
w: 400,
h: 280
},
ajax:{
url: viewUrl
}
});
courseMenu.DeleteItemBox.open();
return false;
},
Por fim, temos a terceira função, e esta, é a que de fato, exclui o botão.
/**
* Exclui um item de menu do curso para o ID do item (toc) fornecido
* Solicita ao usuário a confirmação antes de excluir
* Chama o servidor para remover o Toc
* A localização atual do documento é enviada como um parâmetro retUrl para permitir o recarregamento da página atual após a exclusão do item (tradução nossa).
*/

removeToc: function(toc_id){
var retUrl = escape(document.location.href);
new Ajax.Request(this.appendMenuActionUrl('cmd=removeToc&course_id=' + course_id + '&toc_id=' + toc_id + '&retUrl=' + retUrl + '&' + courseMenu.nonceKey + '=' + courseMenu.nonceValue), {
// a função onLoaded foi adicionada para fechar o DeleteItemBox uma vez que o pedido foi submetido, para que o usuário não clique no botão Excluir novamente (tradução nossa).
onLoaded: function(){
courseMenu.CourseMenu.prototype.closeConfirmation();
},
onSuccess: function(transport, json){
var result = transport.responseText.evalJSON(true);
if (result.success == "true"){
window.location = result.refreshUrl;
} else {
new page.InlineConfirmation("error", result.errorMessage, false);
}
}
});
},
Em linhas gerais, o script submete uma requisição AJAX ao back-end para remoção dos botões, utilizando a classe Ajax.Request. O método onSuccess, corresponde ao callback da função, ou seja, esta área é acionada quando o response é recebido, algo semelhante à comparação if (response.status == 200) em uma solicitação utilizando um objeto XMLHttpRequest. Outro ponto importante, é quanto ao uso do método onLoaded, este por sua vez, não funciona 100% em todos os browsers, o que de fato, compromete a execução do método.
Para uma melhor compreensão, consultem a seção “Ajax.Request” disponível em http://api.prototypejs.org/ajax/Ajax/Request/.

Mapeando os botões do curso

Obtivemos até aqui informações importantíssimas para prosseguirmos em nossa busca por uma solução. Pois bem, já identificamos qual método o Bb utiliza pra remover os botões, e qual parâmetro ele recebe pra que essa execução aconteça. Tendo isso em mente, basta identificarmos (dinamicamente) os botões que queremos excluir.
Indutivamente, buscaríamos obter o id dos botões, e este deveria corresponder ao mesmo valor do toc_id, já descrito, anteriormente. No entanto, esse valor não é imediatamente localizado.
Ao exploramos, utilizando novamente o recurso 'Inspecionar Elemento', obtemos uma informação importante: cada botão representa um item (nó) em uma lista não ordenada. Do ponto de vista de programação, seria algo como:
<ul id="myList">
<li id="myItem">
<a href="/area/button1">
<img src="edit.jpg" />
</a>
</li>
<li id="myItem">
<a href="/area/button1">
<img src="edit.jpg" />
</a>
</li>
<li id="myItem">
<a href="/area/button1">
<img src="edit.jpg" />
</a>
</li>
<li id="myItem">
<a href="/area/button1">
<img src="edit.jpg" />
</a>
</li>
<li id="myItem">
<a href="/area/button1">
<img src="edit.jpg" />
</a>
</li>
Seguindo esse raciocínio, obtemos em cada uma das li (do Learn) por meio do atributo id, o valor “paletteItem:_XXXXX_X”, onde o X representa valores numéricos. Percebe-se, também que _XXXXX_X, corresponde ao mesmo valor do toc_id. Com isso em mente, precisamos obter o id de cada uma das li, e utilizar uma função pra guardar apenas o valor que nos interessa.
É necessário identificar os botões, utilizando as tags li como referência. Desta forma, o mais sensato a fazer é mapear as li, de tal forma, a obter dinamicamente o seu respectivo valor de id. Como há inúmeras listas não ordenadas no Learn, utilizaremos o id da ul (lista) pra identificarmos assim, os nós corretos.
Em síntese isto é feito:
  1. Inicializando uma variável;
  2. Utilizando o objeto document.querySelector() pra identificar quaisquer elementos na página, seja por tag (<>), por class (.), por id (#), ou por outro filtro;
  3. Utilizando o seletor CSS, first-child para acessar o primeiro elemento de uma lista de elementos, e nth-child(n) para o segundo elemento em diante;
  4. Utilizando .id, pra identificar o id de um elemento.
E esse é o nosso código, utilizando o mapeamento [(ul > li(position)).id]:
var button1 = document.querySelector("#courseMenuPalette_contents > li:first-child").id;
var button2 = document.querySelector("#courseMenuPalette_contents > li:nth-child(2)").id;
Para uma melhor compreensão, explorem: querySelector() (w3schools, MDN), :first-child (w3schools, MDN) e :nth-child(n) (w3schools, MDN).

Tratando as strings

Bom, já temos mapeado o id das li. Agora precisamos converter esse valor, em um valor correspondente, ao valor do toc_id.
Isto é, razoavelmente simples, utilizando a função substr(start, legth). Em síntese, ela devolve uma String que comparada à outra, guarda os valores delimitados pelo start isto é, a posição (iniciada em zero) da onde os caracteres começam a ser contados para fazer parte da variável final.
Em nosso caso, para a variável paletteItem:_XXXXX_X, utilizamos como start, o valor 12. Isto corresponde, à posição identificada pelo caratere “_”. Já o segundo campo (length), poderia ser 10, que corresponde à quantidade de caracteres que desejo manter, contando desde o ponto start. No entanto, isto, não é tão funcional, caso desejássemos obter um valor superior (embora esse não seja o nosso caso), não seria possível. Por este motivo utilizaremos o valor undefined que corresponde à contagem ilimitada a partir de um valor start definido. Para uma melhor compreensão, acessem substr() (w3schools, MDN).
Esse seria nosso código exemplo:
var b1 = button1.substr(12, undefined);

Gerando o script de exclusão

Bom, depois de toda a exploração que realizamos o código abaixo, seria nosso código final, que em síntese é:
  1. Obtenção dos ids das li, por meio de mapeamento;
  2. Obtenção do valor dos ids após tratamento das strings;
  3. Execução do método de exclusão dos botões.
<button type="button" style="cursor: pointer" onclick="deleteButtons()">Delete buttons!</button>

<script type="text/javascript">

function deleteButtons() {

var button1 = document.querySelector("#courseMenuPalette_contents > li:first-child").id;
var button2 = document.querySelector("#courseMenuPalette_contents > li:nth-child(2)").id;
var button3 = document.querySelector("#courseMenuPalette_contents > li:nth-child(3)").id;
var button4 = document.querySelector("#courseMenuPalette_contents > li:nth-child(4)").id;
var button5 = document.querySelector("#courseMenuPalette_contents > li:nth-child(5)").id;

var b1 = button1.substr(12, undefined);
var b2 = button2.substr(12, undefined);
var b3 = button3.substr(12, undefined);
var b4 = button4.substr(12, undefined);
var b5 = button5.substr(12, undefined);

theCourseMenu.removeToc(b1);
theCourseMenu.removeToc(b2);
theCourseMenu.removeToc(b3);
theCourseMenu.removeToc(b4);
theCourseMenu.removeToc(b5);

}

</script>
Percebam que criei um button nesse código propositalmente. Este acionará toda a função que deve ser criada como um Item em uma Área do Curso (Criar conteúdo > Item). As figuras 2 e 3 abaixo, o antes e o depois desse procedimento.
Figura 2 – Item de exclusão criado no Learn
Fonte: O autor
Figura 3 – Exclusão realizada
Fonte: O autor
Nota: É importante frisar que a execução deste script inclui a exclusão dos itens contidos em cada uma das áreas de curso (botões).

Conclusão

Quero agradecer-lhes, queridos leitores, por acompanhar-me até aqui. No entanto, quero que sua interação seja a mais ativa possível, isto é, comente, compartilhe, elogie, critique e principalmente sinta-se à vontade. Gostaria também, de convidar os amigos, que tem interesse por programação, pra sugerir melhorias e/ou mesmo aplicá-las, como usos de estruturas de repetição, otimizações de forma geral, e até mesmo novas formas de chegar à solução que propus.
Como o caminho pra soluções deve ser contínuo, espero que em uma outra oportunidade, possa abordar a mesma solução utilizando linguagem server-side, utilizando a API para desenvolvimento de Building Blocks (B2) da Blackboard para o desenvolvimento de uma solução de exclusão de botões (ou outro item de curso) em cascata, isto é, recebendo um rol de cursos, realizando assim, a exclusão em massa, já que neste post a exclusão é curso, um a um.
Isso é tudo pessoal! Até a próxima!
Fábio Fernandes

Comentários

  1. Muito bom Fábio Fernandes, parabéns... Bastante instrutivo e intuitivo.

    ResponderExcluir

Postar um comentário