Questo è un esempio di come può essere usato
AngularJS 1.0 per costruire una
app all'interno di una pagina
SharePoint.
L'esempio visualizza all'interno di alcuni box, una serie di link presi da una lista SharePoint:
app links la lista da cui vengono presi i links è questa:
lista links Per realizzare questa app serve:
- una lista SharePoint chiamata SgartItLinks con i seguenti campi: Title (testo), ImageSrc (testo), OpenNewWindow (yes/no), Link (testo) e Category (choice)
- una document library chiamata HtmlJs un cui mettere il codice JavaScript e html
- la libreria AngularJS 1.0 scaricabile da qui angular.min.js da salvare in HtmlJs
- un file sgart-it-links.html con la app (vedi dopo)
- una content editor web part per che punta al file htmljs/sgart-it-links.html con il codice dalla app AngularJS
La app, contenuta nel file
htmljs/sgart-it-links.html, avrà tre blocchi principali:
- una view con l'html che rappresenta la app
- il codice JavaScript che farà funzionare la app (con la libreria angular.min.js)
- gli stili CSS per renderla più carina
La
view sarà questa:
<div data-ng-app="sgartItLinks" class="sgart-it-links ng-cloak">
<!-- view -->
<div data-ng-controller="LinksCtrl as ctrl">
<div class="link-form">
Categorie:
<select data-ng-model="ctrl.category" data-ng-options="v.i as v.d for v in ctrl.categoryAll" data-ng-change="ctrl.doSearch()">
</select>
<div class="buttons">
<button type="button" data-ng-repeat="item in ctrl.categoryAll" data-ng-click="ctrl.doSearch(item.i)" data-ng-bind="item.d"
data-ng-class="{'selected': ctrl.isCurrentCategory(item)}"></button>
</div>
</div>
<div class="items-empty" data-ng-show="ctrl.isEmpty()">No links</div>
<div class="sgart-it-updating" data-ng-show="isUpdating()">Wait...</div>
<ul class="results" data-ng-hide="ctrl.isEmpty()">
<li data-ng-repeat="item in ctrl.items">
<a data-ng-href="{{item.link}}" target="ctrl.getTarget(item)">
<div class="img-wrapper">
<img src="" data-ng-src="{{ctrl.getimageSrc(item)}}" data-ng-show="ctrl.isVisibleImage(item)">
</div>
<strong class="title-wrapper"><span data-ng-bind="item.title"></span></strong>
</a>
</li>
</ul>
</div>
</div>
<!-- angular js ver. 1.5.x -->
<script src="../htmljs/angular.min.js"></script>
<script type="text/javascript">
... vedi dopo ...
</script>
Notare gli attibuti
data-ng-* che servono a far funzionare
AngularJS.
Il prefisso data- è opzionale serve solo per rendere compatibile il codice con i validatori html, potevano essere scritti anche come ng-*
In particolare abbiamo:
- ng-app dichiara la app, tutto il contenuto all'interno sarà sotto il controllo di AngularJS
- ng-controller definisce il controller JavaScript che gestirà questo pezzo della view
- ng-repeat l'equivalente di un foreach C# serve per ciclare su un array JavaScript
- ng-model per realizzare il binding bidirezionale tra la view e il controller
- ng-bind per realizzare il binding unidirezionale tra la view e il controller
- ng-show per realizzare visualizzare o meno una parte della view in base a una proprietà booleana presente nel controller
- ng-options per realizzare il binding di un array con l'elemento html select
- ng-change per gestire l'evento onchange della select
- ng-click per gestire l'evento onclick
oltre a questi attibuti abbiamo le doppie parentesi graffe che sono l'equivalente di
ng-bing.
La parte di codice
JavaScript è la seguente:
//vedi anche http://www.sgart.it/IT/informatica/angularjs-come-funziona/post
/*
Usa la lista SharePoint .../Lists/SgartItLinks
con i seguenti campi:
- Title (string) required
- Link (string) required
- Category (chioce) required
- ImageSrc (string)
- OpenNewWindow (bool)
*/
(function () { // richiudo tutto in una enclosure per evitare conflitti di nomi
"use strict";
//costanti globali
var PARAMETERS = {
siteUrl: '', // es. '/sites/s1'
listName: 'SgartItLinks', //nome dalla lista
fields: {
title: 'Title',
link: 'Link',
category: 'CategoryValue', // suffisso Value aggiunto da SharePoint
imageSrc: 'ImageSrc',
openNewWindow: 'OpenNewWindow'
}
};
//dichiarazione app / inizializzo angular
var app = angular.module('sgartItLinks', []);
//*********************************************
// controller usato dalla view
// creo un CONTROLLER
// la variabile $scope viene "iniettata"" (inject) da angular
// la variabile linkFactory viene "iniettata"" (inject) da angular
app.controller('LinksCtrl', function($scope, $rootScope, linkFactory) {
var self = this;
// dichiaro le variabili e le aggancio allo scope da usare nella view
// conterrà tutti i link letti
self.items = [{ title: '', link: '', imageSrc: '', openNew: true, category: '' }];
// conterrà l'elenco delle categorie letto dal campo choice
self.categoryAll = [];
// la categoria selezionata
self.category = "";
//dichiaro le funzioni usate nella view
self.doSearch = function (id) { // esegue il seach sui link in base alla categoria
self.items = [];
if (typeof id === "undefined")
id = self.category;
else
self.category = id;
linkFactory.getLinks(id).then(function (result) {
self.items = result;
});
};
self.isEmpty = function () {
if($rootScope.isUpdating()) return false;
return self.items === null || self.items.length === 0;
};
self.getimageSrc = function (item) {
return item.imageSrc;
if (self.isVisibleImage())
return item.imageSrc;
else
return null;
};
self.isVisibleImage = function (item) {
if (typeof item === "undefined") return false;
if (typeof item.imageSrc === "undefined") return false;
return !(item.imageSrc === null || item.imageSrc === "");
};
self.isCurrentCategory = function (item) {
return item.i === self.category;
};
self.getTarget = function (item) {
if (item.openNew === true)
return "_blank";
else
return "";
};
// inizializzo il controller
function init() {
self.categoryAll = [];
self.category = "";
self.items = [];
linkFactory.getCategories().then(function (result) {
self.categoryAll = result;
self.doSearch();
});
};
init();
});
//*********************************************
// factory per l'accesso ai dati tramite il client object model di SharePoint
// inietto il factory $http per le chiamate ajax
// inietto stateFactory per gestire lo stato di update/loading
app.factory("linkFactory", function($http, stateFactory) {
//variabili globali
var dbApiBase = PARAMETERS.siteUrl;
// le funzioni esposte dal factory
return {
'getLinks': _getLinks,
'getCategories': _getCategories
}
function _getCategories() {
stateFactory.updating();
return $http.get(dbApiBase + "/_vti_bin/listdata.svc/" + PARAMETERS.listName + "()?$orderby=" + PARAMETERS.fields.category, {
cache: false,
params: {
t: new Date().getTime() //per evitare il caching su IE
}
}).then(successCallbackCategory, errorCallback);
};
function successCallbackCategory(response) {
var result = [{ i: '', d: 'All' }];
var items = response.data.d.results;
var prev = {};
//leggo le category associate ai link facendo un distinct
for (var i = 0; i < items.length; i++) {
var cat = items[i][PARAMETERS.fields.category];
if (typeof prev[cat] === 'undefined') {
result.push({ i: cat, d: cat });
prev[cat] = 1;
}
}
stateFactory.updated();
return result;
};
function _getLinks(category) {
stateFactory.updating();
var filter = "";
if (!(typeof category === 'undefined' || category === null || category === ''))
filter = "&$filter=" + PARAMETERS.fields.category + " eq '" + category + "'";
return $http.get(dbApiBase + "/_vti_bin/listdata.svc/" + PARAMETERS.listName + "()?$orderby=" + PARAMETERS.fields.title + filter, {
cache: false,
params: {
t: new Date().getTime() //per evitare il caching su IE
}
}).then(successCallbackLinks, errorCallback);
};
//{title:'', link:'', imageSrc:'', openNew:true, category:''}
function successCallbackLinks(response) {
var result = [];
var items = response.data.d.results;
for (var i = 0; i < items.length; i++) {
var item = items[i];
result.push({
title: item[PARAMETERS.fields.title],
link: item[PARAMETERS.fields.link],
imageSrc: item[PARAMETERS.fields.imageSrc],
openNew: item[PARAMETERS.fields.openNewWindow],
category: item[PARAMETERS.fields.category]
});
}
stateFactory.updated();
return result;
};
//gestione errori comune
function errorCallback(response) {
stateFactory.updated();
alert(response);
};
});
//*********************
// loading service per la gestione del feedback visuale all'utente durante le chiamate ajax
app.factory("stateFactory", function($rootScope) {
var updatingCount = 0;
//aggancio la funzione al root scope
//per averla sempre disponibile
$rootScope.isUpdating=function(){
return updatingCount !=0;
};
return {
'updating': _updating,
'updated': _updated,
'reset': _reset,
'isUpdating': $rootScope.isUpdating
};
function _updating(){
updatingCount++;
}
function _updated(){
if(updatingCount>0)
updatingCount--;
else if(updatingCount<0)
_reset();
}
function _reset(){
updatingCount=0;
}
});
})();
Possiamo individuare 3 blocchi principali con cui è costruita questa app
AngularJS:
- dichiarazione della app e inizializzazione di AngularJS ( angular.module()
- definizione del controller (app.controller - LinksCtrl)
- definizione del factory per l'accesso ai dati ( app.factory - linkFactory)
Attenzione se deve essere usato all'interno di
SharePoint 2010 è necessario modificare il meta tag
X-UA-Compatible da
IE=8 a
IE=9 o meglio ancora
IE=edge<meta http-equiv="X-UA-Compatible" content="IE=edge" />
altrimenti
AngularJS non funziona correttamente.
Il file completo può essere scaricato da qui
sgart-it-links.zipL'app può facilmente essere riadattata per girare in un abiente diverso da
SharePoint, ad esempio un sito
ASP.NET MVC che usa un
DB per persistere l'elenco dei links. In questo caso bisognerà solo modificare il factory per gestire le nuove API
Json esposte da
MVC in modo che i metodi del factory ritornino sempre gli stessi oggetti.