• Non ci sono risultati.

In questo capitolo vengono discusse e spiegate nel dettaglio le personalizzazioni principali che sono state necessarie per poter predisporre l’utilizzo della webapp così come è stata presentata nel capitolo precedente, avvalendosi anche di estratti del codice stesso implementato. Viene inoltre trattata la parte relativa alla migrazione dei dati di audit dalla vecchia alla nuova architettura della base di dati, spiegando anche qui mediante l’ausilio di estratti del codice il suo funzionamento in maniera dettagliata.

Possiamo distinguere tra due tipi di personalizzazioni, quelle a carattere estetico, nate per fornire un aspetto grafico che si confacesse all’applicazione, e quelle funzionali nate dalla necessità di sopperire ad alcune limitazioni del BMF3. In entrambi i casi è stato necessario andare a lavorare in javascript, sennonché talvolta si è reso necessario implementare delle modifiche anche a livello HTML e più raramente CSS.

Ricordiamo che javascript è un linguaggio di scripting orientato agli oggetti e agli eventi, i programmi (script) scritti in tale linguaggio contengono le istruzioni necessarie per il funzionamento della logica di business che, come visto nel paragrafo 3.1 del capitolo 3, implementa la logica applicativa che rende operativa un’applicazione gestendo dunque le informazioni scambiate tra la sorgente dei dati (base di dati) da una parte e l’interfaccia utente dall’altra. Mentre il linguaggio HTML (HyperText Markup Language) è un linguaggio di markup con la funzione di gestire la struttura logica, la formattazione e l’impaginazione dei documenti ipertestuali disponibili sul web e dunque implementa la logica di presentazione, coadiuvato dal linguaggio CSS (Cascading Style Sheets) che definisce la rappresentazione e gli stili grafici della pagina web. La compilazione da parte del web browser del documento HTML genera il cosiddetto albero DOM (Document Object Model), che individua l’insieme degli elementi (tag) costituenti la pagina web. Gli script Javascript possono essere utilizzati direttamente nella logica di presentazione venendo inseriti in un file HTML o in pagine JSP (JavaServer Pages) per essere successivamente richiamati nella logica di business.

70

Come spiegato nel sotto-paragrafo 3.4.2 del capitolo 3, il BMF3 mette a disposizione dell’programmatore la possibilità di creare degli script personalizzati che possono aggiungere delle funzionalità agli oggetti BMF. Tali script devono essere integrati all’interno di un file opportuno creato appositamente per gestire le personalizzazioni. Il file si chiama “customization.js”, è pertanto un file javascript e si trova al percorso “js/customization/customization.js”. Una volta modificato il file sarà necessario “esporre” il nuovo elemento creato in maniera tale che possa essere accessibile e andare a modificare il json di configurazione dell’oggetto BMF a cui si vuole applicare la personalizzazione settando la proprietà “customization” con il nome dell’elemento custom.

È possibile personalizzare diversi oggetti come ad esempio il filtro, l’oggetto inputForm e anche il report semplicemente estendendo gli oggetti originali all’interno del file di personalizzazione, così facendo il nuovo oggetto creato possiede le stesse funzionalità del padre che possono essere sovrascritte e se ne possono aggiungere delle nuove.

Le personalizzazioni principali che sono state fatte hanno interessato esclusivamente oggetti di tipo filtro e report, in particolare per personalizzare tutti gli oggetti di tipo filtro e tutti gli oggetti di tipo report che sono stati creati per realizzare la webapp non è stato necessario realizzare una personalizzazione per ognuno di loro bensì solamente due, una per tutti gli oggetti filtro e una per tutti quelli report. Ho pertanto creato delle personalizzazioni il cui comportamento varia in maniera autonoma in base alla configurazione dell’oggetto richiamato. In questo modo ho cercato di ridurre il più possibile il numero di script creandone pochi in grado di adattarsi dinamicamente alla configurazione della pagina.

Gli script principali sono pertanto due, uno che estende l’oggetto filtro originale (filtro.js) del BMF che prende il nome di filtroAudit, e uno che estende l’oggetto report originale (dati.js) che prende il nome di datiAudit. A partire da quest’ultimo ho generato un’ulteriore script per gestire la funzionalità di creazione del pdf a cui ho dato il nome di datiAuditPdf, tale script anziché estendere l’oggetto report originale estende direttamente datiAudit, ne conserva perciò il comportamento ma implementa anche quello utile alla stampa del pdf.

71

5.1 - Personalizzazione JavaScript: filtroAudit

Per ogni oggetto filtro creato ho dunque aggiunto al json di configurazione il riferimento al mio oggetto javascript “filtroAudit”, inserendolo all’interno della proprietà “customization”. Dunque ogni volta che un filtro è richiamato viene istanziato un nuovo oggetto filtroAudit di cui si effettua il rendering e tutti i campi del filtro definiti nel json di configurazione vengono generati dinamicamente e mostrati nel DOM della pagina.

Ad ogni oggetto filtro del BMF è associato un template scritto in HTML che definisce l’aspetto che questo deve avere nella pagina quando viene effettuato il rendering, ossia in cui vengono specificati gli elementi del DOM che costituiranno il filtro nella maschera, pertanto quasi tutti gli elementi risultano essere statici a meno di alcune parti che vengono create in maniera dinamica in base al json di configurazione, nello specifico in base alle proprietà che vengono disposte all’interno del campo “fields”, vettore contenente gli oggetti js coi dettagli sui campi che costituiscono il filtro, come spiegato nel sotto-paragrafo 3.4.2.

Per l’oggetto filtroAudit ho creato un nuovo template custom che è pressoché uguale a quello originale a meno di alcune piccole differenze che vedremo più avanti. Per utilizzare il template personalizzato è stato sufficiente valorizzare la proprietà “template” dell’oggetto filtroAudit con il riferimento al file HTML custom creato.

5.1.1 - sessionStorage

Una delle più importanti differenze con il filtro originale che il filtro custom filtroAudit implementa è la capacità di memorizzare il contenuto dei suoi campi in sessione, in questo modo se si accede ad una pagina che implementa filtroAudit, questo andrà a leggere la sessione andando a valorizzare quei campi del filtro per cui sono presenti dei dati.

Lo scopo principale di questa funzione è quello di sopperire ad una limitazione del framework che pulisce i dati in sessione ogniqualvolta si viene reindirizzati ad una nuova pagina contente un filtro, inoltre permette di facilitare il lavoro dell’utente che può inserire i dati una sola volta senza doverli più reinserire durante la navigazione da una pagina a un’altra.

Nello specifico i dati vengono salvati sfruttando una Web API di HTML5 installata su tutti i web browser moderni denominata sessionStorage. Viene infatti messo a disposizione uno spazio nella memoria del web browser che può essere usato per memorizzare informazioni sotto forma di file JSON, che altro non sono che delle stringhe di testo con un formato standard. I

72

dati persistono all’interno della memoria finché la sessione non è chiusa, ad esempio chiudendo il browser. L’API mette a disposizione diverse funzioni che permettono di salvare e rimuovere oggetti in sessione o semplicemente di ripulire l’intero Storage.

events: function() {

return _.extend({}, Filtro.filtroItemView.prototype.events, { 'blur .filter-item':'beforeSaveParam',

'click #clear_id': 'clearVisibleParams', 'click .filter-item-refresh': 'clearParam', 'click input[data-numgroup]': 'enableFields'

}); }

In questo frammento di codice vediamo tutti gli event handler associati ai diversi eventi che possono coinvolgere gli elementi del DOM. Il salvataggio dei parametri avviene grazie a un event handler che resta in attesa dell’evento di blur di un qualunque campo del filtro (individuati dalla classe “filter-item”), dunque quando questi perdono il focus, perché clicco su un altro elemento del DOM oppure perché avvio la ricerca, in automatico viene lanciata la funzione beforeSaveParam in cui sono svolte alcune azioni preliminari, facendo distinzione tra campi di input (editabili o select) e campi di selezione data (datepicker), e successivamente viene lanciata la funzione saveParam in cui il parametro viene salvato dal browser nello Storage.

saveParam: function(fieldId, fieldVal) { console.log('saveParam');

var params = JSON.parse(sessionStorage.getItem('params'));

if(params !== null) {

if(fieldVal !== "" || !_.isNull(fieldVal)) { params[fieldId] = fieldVal;

} else {

delete params[fieldId]; }

} else {

var params = {};

params[fieldId] = fieldVal; }

if(_.isEmpty(params))

return null;

// Salvo in sessionStorage il json di params se non è vuoto sessionStorage.setItem('params', JSON.stringify(params));

return params; }

Come vediamo dal frammento di codice la variabile contenente i parametri viene convertita in un oggetto javascript tramite la funzione JSON.parse quando si legge il contenuto dello Storage, poiché vi possono essere salvati solo stringhe, e riconvertiti in stringa tramite la funzione JSON.stringify prima di essere salvati.

Salvati i parametri nello Storage, successivamente viene lanciata la funzione saveParamsInSession per salvarli anche nella sessione dell’application server tramite una

73

chiamata ad un servizio web (web service), la chiamata è asincrona (ajax) pertanto non blocca in alcun modo la pagina web.

$.ajax({

type: "POST",

data: [],

dataType: Global.dataType,// json

contentType:"application/json; charset=utf-8",

url: Global.serverDomain + Global.cxt + "/ws/config/result.bmf?"+ qrstring,

success: function(){

console.log('Params saved in session'); },

failure: function(){

console.log('Saving failed'); }

});

Dove $.ajax è una funzione messa a disposizione dalla libreria jQuery che permette in maniera semplificata di avviare una comunicazione AJAX con il server, senza dover istanziare manualmente l’oggetto XMLHttpRequest per aprire il canale di comunicazione. Nella proprietà url vediamo la chiamata al service result.bmf mentre qrstring è la stringa contenente i parametri che vogliamo salvare in sessione, questi verranno passati in ingresso a una funziona java che li elaborerà. Una volta che vengono salvati i parametri persistono all’interno della memoria del browser e vengono richiamati tramite la funzione loadParams ogni qualvolta si accede ad un filtroAudit, così se esistono dati in sessione che interessano i campi del filtro questi ultimi vengono valorizzati.

Se, invece, si volesse ripulire la sessione dai parametri attualmente esistenti oppure si volesse eliminare il parametro relativo ad uno specifico campo, vengono messi a disposizione due event handler capaci di gestire la pulizia dello Storage. In particolar modo nel template HTML custom che ho assegnato al filtroAudit, diversamente da quello originale, sono presenti due tipologie di tasti in più. Un pulsante denominato “Svuota campi” accanto al pulsante di ricerca del filtro e altri pulsanti posti accanto ad ogni campo. Il primo ha la funzione di ripulire tutti i campi presenti nel filtro che sono stati valorizzati, andando naturalmente ha eliminare anche i parametri relativi ai medesimi campi dalla sessione dello Storage e dalla sessione del BMF. Mentre il secondo ripulisce solo il campo accanto al pulsante con cui si va a interagire eliminando solo quel parametro dalla sessione.

Per poter implementare questi tasti ho dovuto lavorare sia lato HTML che lato javascript. Nel template HTML ho dovuto applicare staticamente il tasto “Svuota campi” accanto a quello standard di ricerca come vediamo dal codice qui sotto.

74

<button id="clear_id" class="btn btn-primary pull-right" type="button" style="margin-left:1%">

Svuota campi <i class="glyphicon glyphicon-remove"></i> </button>

<button id="search_id" class="btn btn-primary pull-right" type="submit">

{{#if buttonText}} {{ buttonText }} {{ else }}

Cerca

{{/if}} <i class="glyphicon glyphicon-search"></i> </button>

Più complesso è stato applicare un tasto accanto ad ogni campo del filtro in quanto questi non sono statici bensì vengono costruiti dinamicamente in fase di creazione del filtro sulla base del json di configurazione. Il codice HTML dei campi viene pertanto compilato in runtime dal browser stesso andando ad utilizzare le funzionalità messe a disposizione dalla libreria Handlebars. Questa libreria permette di utilizzare una serie di funzioni direttamente all’interno del file HTML mediante l’ausilio di espressioni contenute tra doppie o triple parentesi graffe ({{ expression }}), è possibile inoltre registrare i cosiddetti “helpers” che altro non sono che degli script javascript che possono essere richiamati direttamente dal HTML e a cui è possibile passare dei parametri in modo da costruire dinamicamente il template.

<div class="row">

{{filtroAudit this}}

</div>

<div class="row">

{{filtroRadioBtnAudit this}}

</div>

Dal codice si possono vedere due funzioni, filtroAudit e filtroRadioBtnAudit, a cui entrambi viene passato il parametro this che riferisce al json di configurazione dell’oggetto. Queste funzioni sono implementate all’interno di un helpers custom creato da me, filtroaudithelpers.js. Originariamente la rappresentazione degli oggetti BMF è demandata agli helpers registrati nel BMF tuttavia per poter integrare tali personalizzazioni è stato necessario creare un helpers personalizzato. La funzione filtroAudit è quella responsabile della creazione dei campi del filtro compresi i pulsanti utili a svuotare il campo ed eliminare il parametro in sessione, nello specifico la funzione field_render genera la stringa contenente il codice HTML per il rendering dei campi.

filtroAudit = function( filter, options ) { var res = "";

_.each(filter.fields, function(field, idx) { res += field_render(field, idx); });

return new hb.SafeString(res); };

75 5.1.2 - Radio Button

Un’altra personalizzazione implementata nel filtroAudit è quella relativa al radio button che abbiamo visto essere presente più volte in filtri diversi. Utilizzato nei campi di tipo data è stato pensato per permette all’utente di scegliere se filtrare i risultati immettendo una data precisa oppure un periodo di tempo semplicemente tramite la selezione del radio button che abilita un campo piuttosto che un altro.

Figura 29: campo personalizzato per l’inserimento della data di accesso alla CCE e della data azione, con l’ausilio del radio button si può decidere se immettere la data precisa oppure un intervallo di date

Per applicarla è stato necessario, così come per i pulsanti di refresh dei campi visto in precedenza, un event handler che gestisca l’evento di click del radio button abilitando e disabilitando i campi opportuni, una funzione di controllo in fase di creazione del filtro che verifica se sono presenti in sessione dati relativi ai campi data eventualmente caricandoli e infine una funziona apposita nel file filtroaudithelpers.js che gestisca il rendering dei suddetti campi a livello HTML.

Anzitutto quando avviene il rendering del filtro viene lanciata una funzione chiamata checkForRadioBtn che ha lo scopo di controllare se esistono dati in sessione da caricare valorizzando eventualmente i campi data coinvolti. Per fare questo è stato necessario applicare agli elementi di input dei campi una classe personalizzata chiamata radioBtnAudit con la funzione di individuare facilmente nel DOM gli elementi, inoltre sono state aggiunte delle proprietà nel file json di configurazione che vengono utilizzate per il corretto rendering.

checkForRadioBtn: function(){

console.log('check:for:radioBtn');

if($('.bmf3-filter').hasClass('hide')) return false; var arr = [],

name = '';

$('input[data-numgroup]').each(function(){ if($(this).attr('name') != name){ name = $(this).attr('name'); arr.push(name);

} });

var radiobtn$ = $('input[data-numgroup]').parent(); var isChecked;

_.each(arr, function(name){ isChecked = false;

radiobtn$.find('[name='+name+']').each(function(){ elem = this;

$(this).parent().find('.radioBtnAudit').each(function(){ if($(this).val() !=''){

$(elem).prop('checked', true); isChecked = true;

} });

76 });

});

this.enableFields(); }

Nella funzione checkForRadioBtn individuo tutti i campi correlati ai radio button e seleziono (checked) il radio button corrispondente quando i campi non sono vuoti, ossia se son stati caricati dati dalla sessione tramite la funzione loadParams. Se invece non sono presenti dati in sessione seleziono il primo radio button. Successivamente lancio la funzione enableFields che ha invece il compito di disabilitare i campi che non sono selezionati in maniera da renderli non editabili e cliccabili dall’utente.

enableFields: function(ev){

$formGroup = $('input[data-numgroup]:not(:checked)').parent(); if(_.isUndefined(ev)){

$formGroup.find('.radioBtnAudit').prop('disabled', true);

$formGroup.find('.input-group-btn button').removeClass('btn-default').addClass('disabled'); $formGroup.find('.radioBtnAudit').val('').trigger('blur');

} else {

// Abilito i campi checked

$target = $(ev.currentTarget).parent();

$target.find('.input-group-btn, .radioBtnAudit').prop('disabled', false);

$target.find('.input-group-btn').find('button').addClass('btn- default').removeClass('disabled');

// Disabilito i campi not:checked

$formGroup.find('.input-group-btn, .radioBtnAudit').prop('disabled', true); $formGroup.find('.radioBtnAudit').each(function(){

$(this).val('').trigger('blur'); });

$formGroup.find('.input-group-btn').find('button').removeClass('btn-default').addClass('disabled'); }

}

Una proprietà aggiunta al json è numGroup che individua l’insieme dei campi associati ad uno specifico elemento radio button, prendendo a riferimento la figura 29 abbiamo che “data accesso” ha “numGroup” settato a 1 mentre “data accesso dal” e “data accesso al” hanno “numGroup” uguale a 2. Poiché nella stessa maschera è possibile avere più campi data (accesso, azioni ect) su cui filtrare ho aggiunto anche un’altra proprietà al json denominata radioBtnName con cui vado a valorizzare l’attributo name dell’elemento input radio button in modo da discriminare gruppi di radio button distinti, sempre prendendo la figura 29 come riferimento abbiamo infatti che “data accesso”, “data accesso dal” e “data accesso al” hanno “radioBtnName” uguale a “data_accesso” mentre “data azione”, “data azione dal” e “data azione al” hanno “radioBtnName” uguale a “data_azione”.

Per effettuare il rendering di questi campi è stata utilizzata la funzione custom denominata filtroAuditRadioBtn implementata nel helpers custom del filtro.

filtroRadioBtnAudit = function(filter, options) { var res = '';

var fieldsRadioBtn = {}; var index = {};

_.each(filter.fields, function(field, idx) { if (field !== undefined && field !== null) {

if(field.class == 'radioBtnAudit' && field.radioBtnName !== undefined) { if(fieldsRadioBtn[field.numGroup] !== undefined) {

77 field.index = idx;

fieldsRadioBtn[field.numGroup].push(field); } else {

field.index = idx;

fieldsRadioBtn[field.numGroup] = [field]; }

} } });

var radioName = undefined;

res += '<div class="form-group col-md-6">'; _.each(fieldsRadioBtn, function(objs, key, list){

if(radioName === undefined) radioName = objs[0].radioBtnName; if(radioName != objs[0].radioBtnName) {

radioName = objs[0].radioBtnName; res += '</div>';

res += '<div class="form-group col-md-6">';

res += filtroRadioBtn_render(objs, key, objs[0].radioBtnName); } else

res += filtroRadioBtn_render(objs, key, objs[0].radioBtnName); });

res += '</div>';

return new hb.SafeString(res); };

La funzione nella fase preliminare individua i campi del vettore fields, contenente tutti i campi del filtro, che contengono la classe radioBtnAudit generando poi il codice HTML degli elementi da appendere al DOM del filtro tramite la funzione filtroRadioBtn_render e infine ritorna la stringa (res) contenente il codice HTML, marcandola come stringa sicura tramite la funzione Handlebars.SafeString.

5.1.3 - Sottotitoli custom

Una funzione che ho implementato è quella dei sottotitoli custom. Normalmente è possibile aggiungere nelle pagine del BMF dei sottotitoli semplici in cui è possibile mostrare, ad esempio, delle informazioni riepilogative estratte dai report navigati precedentemente, questi però sono sempre visibili e non possono essere richiamati all’occorrenza inoltre presentano una grafica che non permette di distinguere tra le informazioni primarie e secondarie. Dunque ho sviluppato dei nuovi sottotitoli che permettono di tenere sott’occhio le informazioni principali e danno modo, con un click, di accedere alle informazioni secondarie. Nello specifico questi sottotitoli custom sono stati utilizzati per rappresentare i dati del paziente e dell’operatore sanitario in esame. I sottotitoli vengono utilizzati pressoché in tutte le pagine e mostrano a video i dati anagrafici del paziente e dell’operatore tramite una grafica che enfatizza la loro importanza rispetto ai sottotitoli semplici, se presenti, e soprattutto forniscono la possibilità di accedere alle informazioni secondarie tramite l’apertura di una finestra di dialogo contenente per esempio nel caso del paziente oltre le informazioni anagrafiche anche informazioni su luogo e data di nascita, codice fiscale ect.

I dati dei sottotitoli vengono creati e salvati durante le fasi preliminari di scelta di paziente e operatore, in particolare cliccando su un elemento del DOM che si trova nel report che

78

possiede una classe custom appositamente creata denominata audit-subtitles. Il click su questa classe fa scattare un handler che memorizza in sessione, anche stavolta tramite l’ausilio della sessionStorage, i dati su paziente e/o operatore. Questa operazione di salvataggio è demandata all’oggetto datiAudit, personalizzazione dell’oggetto report del BMF.

Per mostrare i sottotitoli è necessario aggiungere all’url della pagina la stringa “auditSub=paziente$operatore”, in questo modo verranno caricati a video sia il sottotitolo contenente i dati del paziente che quello contente i dati dell’operatore, se si omette uno dei due verrà caricato solamente il sottotitolo desiderato. Pertanto quando un filtroAudit viene caricato, oltre ai controlli precedentemente visti sui radio button e parametri in sessione, viene fatto un controllo sui sottotitoli che si vuole inserire nella pagina.

checkForSubtitles: function(url) { console.log('check:for:subtitles');

// Elimino i sottotioli attualmente presenti $('#audit-subtitle').remove();

if(url.indexOf('auditSub') !=-1) {

var auditSub = url.slice(url.indexOf('auditSub')).split('&')[0]; var subtitles = auditSub.split('=')[1].split('$');

this.createSubtitles(subtitles); }

}

Una funzione, checkForSubtitles, viene lanciata al rendering del filtro che elimina dal DOM i sottotitoli precedentemente esistenti, se presenti, individua l’elenco dei sottotitoli che si vuole andare a rappresentare e infine passa l’elenco in ingresso alla funzione createSubtitles che provvede a crearli e appenderli nella sezione apposita del DOM.

var subtitles = JSON.parse(sessionStorage.getItem('subtitles'));

if(!_.isNull(subtitles)) {

var section = $('<section id="audit-subtitle"></section>'); $('#subtitle').before(section);

section.append(this.createModalDialog());

var div = $('<div class="list-group" style="padding-left:1%; padding-right:1%"></div>'); var blockquote;

_.each(titles, function(title){

var obj = subtitles[title.toLowerCase()]; if(obj !== undefined){

// Sottotitolo

blockquote = $('<blockquote class="" data-subtitle='+title.toLowerCase()+' data-toggle="modal" data- target="#audit-modal"></blockquote>');

blockquote.append($('<p class="lead text-primary">'+obj.label+'</p>')); blockquote.append($('<p class="text-info">'+obj.text+'</p>'));

div.append(blockquote); }

});

section.append(div); }

In questo frammento di codice vediamo il cuore di createSubtitles in cui vado a caricare dallo Storage l’oggetto javascript contenente i dati dei sottotitoli, se questi esistono e sono diversi da null, e creo una sezione apposita (con id=“audit-subtitle”) immediatamente prima di quella

79

dedicata ai sottotitoli semplici (id=“subtitle”) in cui appendo il blocco div contenente i sottotitoli (blockquote) mostrando all’utente il solo identificativo e i dati anagrafici.

Documenti correlati