Stephen Radford - Sviluppare applicazioni web con AngularJs e

SVILUPPAREAPPLICAZIONIWEBCONANGULARJSEBOOTSTRAP
StephenRadford
©Apogeo-IF-IdeeeditorialiFeltrinellis.r.l.
SocioUnicoGiangiacomoFeltrinelliEditores.r.l.
ISBNedizionecartacea:9788850333424
Copyright©PacktPublishing2014.FirstpublishedintheEnglishlanguageunderthetitle
LearningWebDevelopmentwithBootstrapandAngularJS(9781783287550).
Ilpresentefilepuòessereusatoesclusivamenteperfinalitàdicaratterepersonale.Tuttii
contenutisonoprotettidallaLeggesuldirittod’autore.
Nomiemarchicitatineltestosonogeneralmentedepositatioregistratidallerispettive
caseproduttrici.
L’edizionecartaceaèinvenditanellemigliorilibrerie.
~
Sitoweb:www.apogeonline.com
ScoprilenovitàdiApogeosuFacebook
SeguicisuTwitter@apogeonline
Rimaniaggiornatoiscrivendotiallanostranewsletter
Introduzione
Durantelamiacarrierahosviluppatoprogettididiversedimensioni,dapiccolisiti
commercialiainterisocialnetwork.Eranotuttiaccomunatidallanecessitàdi
disporrediJavaScripteCSSbenstrutturati.
Questolibroaffrontaduefantasticiprogettiopensourcechederivanodaquesta
esigenza:BootstrapeAngularJS.
Gliargomentidellibro
IlCapitolo1,“Hello,{{name}}”,esaminaifondamentidiAngularJSeBootstrap
attraversolarealizzazionediunasempliceappHello,World.
IlCapitolo2,“SviluppareconAngularJSeBootstrap”,presental’appprincipale
chesvilupperemonelcorsodellibro,ilsistemadellegrigliediBootstrapealcuni
componentidiAngularJS.
IlCapitolo3,“Ifiltri”,illustraalcunideifiltriintegratiinAngularJSelacreazione
diunfiltro.
IlCapitolo4,“Routing”,sibasasulrouterintegratodiAngularJS,eillustra
l’utilizzodeipartialpercreareun’appmultiview.
IlCapitolo5,“Leviste”,esaminailsistemadellegrigliediBootstrape
approfondisceipartial.
IlCapitolo6,“CRUD”,spiegacome,dopoaverimpostatoleviste,possiamo
implementare,creare,leggere,aggiornareecancellarelefunzioni.
IlCapitolo7,“AngularStrap”,descriveilmoduloditerzeparticheconsentedi
utilizzareiplug-indiBootstraptramiteAngularJS.
IlCapitolo8,“Connessionealserver”,esaminaduesistemiufficialipercollegarsi
aunserver.
IlCapitolo9,“Itaskrunner”,spiegacomeminificaretuttiifileJSeLess
utilizzandoGruntegulp.
IlCapitolo10,“PersonalizzareBootstrap”,mostracomepersonalizzarefacilmente
BootstrapdopoaverimpostatoGrunt.js.
IlCapitolo11,“Validazione”,affrontaglistrumentidivalidazionesubitopronti
all’uso;liimplementeremoegestiremoglierroridelserver.
IlCapitolo12,“Strumentidellacommunity”,presentaalcunistrumenticreatidalla
communitydiAngularJS.
L’AppendiceA,“Personeeprogetti”,presentaalcunepersoneimportanti
nell’ambientediAngularJSeBootstrap,oltreaprogettirilevanti.
L’AppendiceB,“Incasodidubbio”,offrerisposteaglieventualidubbideilettori.
L’AppendiceC,“Risposteaiquiz”,comprendetuttelerispostealledomandedei
quizpresentinellibro.
Checosaoccorreperillibro
AngularJSeBootstrapnonhannodipendenzeeperquestolibrodovretesolo
disporrediunbrowserediuneditorditesto.ViconsiglioChromeeAtom.
Achisirivolgeillibro
Seviinteressalosviluppowebmoderno,sicuramenteconoscereteBootstrape
AngularJS.QuestolibrosirivolgealettoriconunminimodiesperienzainJavaScript
chedesideranoimpegnarsinellosviluppodiwebapp.
TuttaviaèassolutamentenecessarialaconoscenzadiJavaScript.Senonconoscete
ladifferenzatraunastringaeunoggetto,correteairipari.Ovviamente,seavetegià
utilizzatoAngularJSoBootstrapevoletesapernedipiù,visentireteavostroagio.
Convenzioni
Inquestolibrotroveretealcunistilidicaratterechedifferenzianodiversitipidi
informazioni.Diseguitoalcuniesempieunaspiegazionedellorosignificato.
Ilcodicechetrovereteneltesto,inomidelletabelledeldatabase,inomiele
estensionideifile,ipercorsi,gliURL,l’inputdegliutentieglihandlediTwitter
vengonopresentatiinmonospaziato.
Unframmentodicodiceèformattatonelseguentemodo:
<!DOCTYPEhtml>
<htmllang="en">
<head>
<metacharset="utf-8">
<title></title>
</head>
<body>
</body>
</html>
Gliinputeoutputdarigadicomandosipresentanonelmodoseguente:
open-a'GoogleChrome'--args-allow-file-access-from-files
Termininuovi,paroleimportanti,cartelleodirectoryedelementidell’interfaccia
sonoriportatiincorsivo.
NOTA
Isuggerimenti,gliavvertimentielenoteimportantiappaionoinquestomodo.
Scaricaifiledegliesempi
Sulsitodell’editoreoriginaleinglese,PacktPublishing,potetescaricareifiledegli
esempipresentatideltesto.Perfarloènecessarioregistrarsigratuitamente
all’indirizzohttps://www.packtpub.com/register.Quindiandatesullaschedadellibro
all’indirizzohttps://www.packtpub.com/web-development/learning-web-development-bootstrap-and(percomoditàinformaabbraviatahttp://bit.ly/packt-ab)efateclicsuCode
angularjs
Files.Perproblemididownloadpotetecontattarelanostraredazioneall’indirizzo
[email protected].
L’autore
StephenRadfordèunosviluppatorewebatuttotondonativodiBristol,chevive
nelcentrodiLeicester,inGranBretagna.Dopoaverfrequentatoall’universitàil
corsodiGraficaeComunicazioneVisiva,siètrasferitoinquestacittà,doveèstato
assuntodaunadellepiùimportantisocietàdimarketingonlinedelpaese.
Mentrelavoravaperalcuneagenzie,Stephenhasviluppatodiversiprogetti
collaterali,tracuiFTPloy,unSaaSconcepitoperrendereaccessibileatuttiil
deploymentcontinuo.Questoprogettosièclassificatotraifinalistinellacategoria
“SideProjectoftheYear”dei.NetAwards.
Attualmente,insiemeconilsuosocio,gestisceCocoon,unasocietàdisviluppo
webcherealizzaegestisceappcomeFTPloyeFormer.Cocoonlavoraancheastretto
contattoconungruppodistartupeaziendechetrasformanoideeinsitieapp.
Vorrei ringraziare quanti mi hanno sostenuto durante la stesura di questo libro. Prima di tutto, il mio
partner, Declan. Mi è stato di grande supporto e non potrei desiderare di avere accanto nessuna persona
miglioredilui.PaulMckayèstatoilprimoalqualehomostratoillibroemihaancheaiutatoconilmio
profilo biografico perché, stranamente, ho incontrato grandi difficoltà a scrivere sulla mia vita.
Ovviamente, voglio ringraziare i miei genitori. Mio padre ha atteso pazientemente la copia cartacea del
libro;sperocheorasiainbellamostrainsoggiorno.
Irevisori
TasosBekos,ingegnereinformatico,utilizzaletecnologiewebdapiùdidieci
anni.Halavoratocomesviluppatorefreelanceeconsulenteperalcunedellepiù
importantisocietàinternazionalifinanziarieeditelecomunicazioni.Attualmente
lavoracomesviluppatoreperZuluTrade,dovesiavvaledellepiùmodernetecnologie
front-end.ConosceafondoAngularJSedèunmembroattivodellacommunityopen
source,nellaqualeoperacomeprincipalecollaboratorealprogettoAngularUI
Bootstrap.Quandononscrivecodice,trascorreiltempoagiocareconisuoiduefigli.
JackHsuèunosviluppatorewebspecializzatoneglistrumentienelletecnologie
front-end.Èilprincipalesviluppatorefront-endpressoNulogy,dovemettelasua
conoscenzadiJavaScripteAngularJSalserviziodeicolleghi.PrimadiNulogy,ha
lavoratopernumeroseaziende,tracuiTheGlobe&Mail,OntarioInstituteofCancer
ResearcheWaveAccounting.Neltempoliberogiocaaivideogiochi,esploraidiversi
quartieridiTorontooviaggiaperilmondo.Sulsuoblogpersonaleèpossibile
leggerenumerosipostriguardantilaprogrammazione.
OleB.Michelsenlavoradapiùdi12anninellosviluppowebesièlaureatoin
informaticapressolaDIKU,all’universitàdiCopenhagen.Direcentesiè
specializzatonellosviluppoJavaScriptfront-end,concentrandosiinparticolaresu
WebRTCesuframeworkperappsinglepage.
JurgenVandeMoereènatonel1978edècresciutoaEvergem,inBelgio,coni
genitori,lasorellaeisuoianimalidomestici.A6annihainiziatoadaiutareilpadre,
proprietariodiunnegoziodiinformatica,adassemblareicomputerpericlienti.
Mentregliamicigiocavanoaivideogiochi,Jurgenpreferivascriverescripte
programmiperrisolvereiproblemicheeranocostrettiadaffrontareiclientidel
padre.DopoessersilaureatoinlatinoematematicapressoilcollegeSint-Lievensa
Gand,Jurgenhaproseguitolasuaformazionepressol’universitàdellastessacittà,
dovehafrequentatoinformatica.All’universitàilsuonomeutenteUnixera
“jvandemo,”ilnicknamecheusaancoraoggisuInternet.Nel1999hainiziatola
carrieraprofessionalepressoInfoworld.Dopoannididurolavorocomesviluppatore
enetworkengineer,nel2005enel2006haricopertoposizionimanageriali.
Sentendosiunosviluppatore,eavvertendolamancanzadiscriverecodice,nel2007
hadecisodiporrefineallacarrieradimanagerperdedicarsidinuovoallasuavera
passione:losviluppo.Daallorahastudiatoelavoratodacasa,inBelgio,dovevive
attualmenteconlasuafidanzata,suofiglioeisuoicani.Inunmondoinrapida
evoluzionediapplicazionidata-intensiveereal-time,siconcentrasulletecnologie
legateaJavaScriptebasatesuAngularJSeNode.js.Isuoinumerosicontributi
pubblicieprivatihannopermessodiavviaremoltepliciprogettidisuccessointuttoil
mondo.Seviserveunaconsulenzaperilvostroprogetto,potetescrivergli
all’[email protected](@jvandemo)oleggereil
suoblog(http://www.jvandemo.com).
Capitolo1
Hello,{{name}}
Ilmodomiglioreperimparareaprogrammareèscriverecodice,edèproprio
questociòchefaremo.Percomprenderequantoèfacileesseresubitooperativicon
BootstrapeAngularJS,realizzeremoun’applicazionemoltosemplicecheci
consentiràdidigitareunnomeevisualizzarlosullapaginaintemporeale.Scoprirete
cosìl’efficaciadelbindingdeidatibidirezionaleeillinguaggiopertemplate
integratodiAngular.UtilizzeremoBootstrapperattribuireunostileeunastruttura
all’app.
Primadiinstallareiframework,creeremolastrutturadellecartelleeilfile
index.htmlchesaràlabasedell’app.
Impostazione
Perrealizzarel’appconAngulareBootstrap,procediamoall’impostazione,che
consistenelcreareunapaginaHTMLeincluderealcunifile.Innanzituttocreateuna
nuovadirectory,chapter1,eapritelanelvostroeditor.Createalsuointernounnuovo
file,index.html,eimmettetequestocodiceboilerplate:
<!DOCTYPEhtml>
<htmllang=”en”>
<head>
<metacharset=”utf-8”>
<title></title>
</head>
<body>
</body>
</html>
SitrattadiunapaginaHTMLstandardconlaqualeopereremodopoaverintegrato
AngulareBootstrap.
Createduecartelleall’internodellacartellachapter1:cssejs.Lastruttura
completadellecartelledovrebbeesseresimileallaseguente.
InstallazionediAngularJSeBootstrap
InstallarequestiframeworkèsemplicecomeincluderefileCSSoJavaScriptnella
pagina.Lopotremmofareconuncontentdeliverynetwork(CDN)comeGoogle
CodeoMaxCDN,maperorarecupereremoifilemanualmente.Esaminiamoipassi
chedovresteconoscerequandointegrereteAngularJSeBootstrapnelprogetto.
InstallazionediBootstrap
Andateall’indirizzohttp://getbootstrap.comefateclicsulpulsanteDownload
Bootstrap.OtterreteunfileZIPconl’ultimaversionediBootstrapchecomprende
CSS,fontefileJavaScript.Leversioniprecedentiincludevanounadirectorydelle
immagini,chenellaVersione3diventaiconfonts.
Perquest’app,ciinteressaperorasoltantounfile:bootstrap.min.csspresentenella
directorycss.Ilfogliodistileforniscetuttalastrutturaequeglielementigraziosi,tra
cuipulsantiemessaggidiavviso,percuiBootstrapèconosciuto.Copiatelonella
directorycssdelprogettoeapriteilfileindex.htmlnell’editorditesto.
IntegrareBootstrapèfacilecomecollegareilfileCSScheabbiamoappena
copiato.Aggiungetequantosegueall’internodeltag<head>.Inseritequestotag
nell’elemento<head>dellapagina:
<linkrel=”stylesheet”href=”css/bootstrap.min.css”>
InstallazionediAngularJS
DopoaverintegratoBootstrapnellawebapp,procedeteainstallareAngular.
Visitateilsitohttps://angularjs.org/efateclicsulpulsanteDownload.Vedretealcune
opzioni;avoiservelaversionestabileminificata.
Copiateilfilecheavetescaricatonelladirectoryjsdelprogettoeapriteilfile
index.html.Angularpuòessereintegratonell’appcomequalsiasialtrofileJavaScript.
Èpreferibileincluderloneltag<head>dellapagina,altrimentialcunefunzioniacui
ricorreretenellibrononsarannoattive.Anchesenonènecessario,dovretecompiere
altripassiperintegrareancoradipiùAngularnelfileHTML.
Inseritequestotag<script>nell’<head>dellapagina.
<scriptsrc=”js/angular.min.js”></script>
Tuttofatto?Quasi.DobbiamodireadAngularcheintendiamoutilizzarlonell’app.
Angularrichiedequestobootstrap,eilframeworksemplificamoltissimoquesta
procedura.Dovetesemplicementeincludereunaltroattributoneltag<html>di
apertura:
<htmllang=”en”ng-app>
Eccofatto!OraAngularsachevogliamoutilizzarlo.
NOTA
Angular ci consente anche di far precedere questi attributi da data- (per esempio data-ng-app)
nelcasovolessimoscrivereHTML5valido.
UtilizzareAngularJS
AbbiamovistomoltateoriaallabasediAngular;ègiuntoilmomentodimetterla
inpratica.Dopoavercreatoun’appfunzionante,vedremocomeabbellirlacon
Bootstrap.
Riapriteilfileindex.html,maquestavoltaapriteloanchenelbrowsercosìdavedere
ciòsucuistatelavorando.Eccoachepuntosiamoarrivati:
<htmllang=”en”ng-app>
<head>
<metacharset=”utf-8”>
<linkrel=”stylesheet”href=”css/bootstrap.min.css”>
<title></title>
<scripttype=”text/javascript”src=”js/angular.min.js”></script>
</head>
<body>
</body>
</html>
AbbiamoimpostatoBootstrapeAngulareabbiamoinizializzatol’appcon
l’attributong-appneltagdiapertura<html>;orapassiamorapidamenteall’azione.
Realizzeremoun’appHello,Worldunpo’diversa.Invecedifarcomparirequesta
scritta,avremouncampoditestocheeffettueràilbindingdeidatieliripeterà
automaticamentenellavista;tuttoquestosenzascrivereunasolarigadiJavaScript.
Iniziamoadaggiungereiltag<h1>neltag<body>:
<h1>Hello,World</h1>
NelbrowservedretecheBootstraphaaggiornatolavisualizzazionepredefinita.Al
postodelfontTimesNewRomancomparel’Helveticaeimarginiditroppoattorno
albordosonostatieliminati.
Oradobbiamoincluderel’inputditestoespecificareancheilmodelloche
intendiamoutilizzare.Ilmodellopuòesserediqualsiasitipo,mainquestocasosarà
unastringachel’inputrestituirà:
<inputtype=”text”ng-model=”name”>
L’attributong-modeldichiarailbindingdelmodellosuquestoelemento,etuttociò
chedigiteremonelcampoditestosaràassociatoaessodaAngular.Ovviamentenon
verràvisualizzatocomepermagiasullapagina;dovremodirealframeworkdove
ripeterlo.Pervisualizzareilmodellosullapagina,èsufficienteracchiudereilnome
tradoppieparentesigraffe:
{{name}}
InseriteloalpostodiWorldneltag<h1>eaggiornatelapaginanelbrowser.Se
digitateilvostronomenelcampoditesto,vedretechevienevisualizzato
automaticamentenell’headerintemporeale.Angularlofaalpostovostrosenza
doverscrivereunasolarigadiJavaScript.
Unrisultatoottimo,masarebbeopportunoavereun’impostazionepredefinitache
evitidifarsembrarechenonfunzionaancoraprimacheunutentedigitiilsuonome.
Fortunatamentetuttociòcheècompresotraleparentesigraffevieneanalizzatocome
un’espressioneAngularJS,ecosìèpossibileverificareseilmodellohaunvalore;in
casocontrario,puòripetereWorld.InAngularquestaèun’espressione,edèsufficiente
aggiungereildoppiopipecomeinJS:
{{name||‘World’}}
NOTA
Angulardescriveinquestomodoun’espressione:“FrammentidicodicesimiliaJavaScriptchedi
solitosonopostiinbindingcome{{espressione}}.”
ÈopportunoricordarechesitrattadiJavaScript,edeccoperchédobbiamoinserire
levirgolettepersegnalarechesitrattadiunastringaenondelnomediunmodello.
Seprovasteacancellarle,vedrestecheAngularnonvisualizzadinuovonulla.Ciò
accadeperchéimodellinameeWorldnonsonodefiniti.
Questimodellipossonoesseredefinitidirettamenteall’internodell’HTML
utilizzando,comeabbiamovisto,unattributo,maèanchepossibileassegnareaessi
unvaloredauncontroller.AquestoscopocreateunnuovofileJS,controller.js,e
includetelonell’app:
<scripttype=”text/javascript”src=”js/controller.js”></script>
InseritelodopoaverinclusoAngularnellapaginaperevitareerrori.
IcontrollersonosemplicementefunzionicheAngularpuòutilizzare;
esaminiamoneuno:
functionAppCtrl($scope){
}
Quiabbiamodichiaratoilcontroller(insostanzaunasemplicefunzionedel
costruttoreJavaScript)einessoabbiamoinseritoloscope.Loscopeèciòacui
possiamoaccedereall’internodellavista.Suun’unicapaginapossonoesistere
molteplicicontrolleremoltepliciscope.SitrattainsostanzadiunoggettoJavaScript
deinostrimodelliedellenostrefunzioni,suiqualiAngularoperalasuamagia;per
esempio,loscopedellanostraapplicazioneperilmomentoapparecosì:
{
name:“Stephen”
}
Loscopecambiaasecondadiciòchesidigitanelcampoditesto.Aessosipuò
accederesiadallavistasiadalcontroller.
Dopoavercreatoilcontroller,dobbiamodireadAngularcheintendiamo
utilizzarlo.Perlanostraappciserveunsolocontroller;aggiungiamounsecondo
attributoaltag<html>:
ng-controller=”AppCtrl”
QuestoattributodiceadAngularchevogliamoutilizzarelafunzioneAppCtrlche
abbiamoappenacreatocomecontrollerperlapagina.Potremmoovviamente
aggiungerloaqualsiasielementosullapaginacompreso,sevolessimo,ilbody.
Perverificarechetuttofunzioni,specificheremounvaloreinizialeperilmodello.
Èfacilecomeimpostareunaproprietàsuqualsiasioggetto:
functionAppCtrl($scope){
$scope.name=“World”;
}
Seaggiornatel’appnelbrowser,vedretecheWorldèprecompilatocomevaloredel
modello.Questoèunottimoesempiodell’efficacebindingdeidatibidirezionaledi
Angular.CiconsentediutilizzaredatipredefinitidiunaAPIodiundatabaseepoi
modificarlidirettamentenellavistaprimadiriottenerlinelcontroller.
NOTA
Angular descrive il binding dei dati come “la sincronizzazione dei dati tra i componenti del
modello e della vista”. Così, se modifichiamo il valore di un modello nella vista o nel controller
JavaScript,tuttosiaggiornadiconseguenza.
Bootstrap
Dopoavercreatol’applicazioneHelloWorldeaververificatochetuttofunzioni
comeprevisto,èilmomentodiutilizzareBootstrapperaggiungerestileestruttura
allanostraapp.Peroral’appèmalallineatasullasinistraetuttosembratroppofitto;
rimediamoconunpo’discaffolding.Bootstrapoffreunottimosistemadigriglie
responsivemobilefirstchepossiamoutilizzareaggiungendoalcunidivealcune
classi.
Prima,però,racchiudiamoilcontenutoinuncontenitoreperfarsubitounpo’di
ordine.
NOTA
Ilconcettodimobilefirstconsistenelprogettare/sviluppareinnanzituttoperglischermipiùpiccoli
eaggiungereelementialdesigninvecedieliminarli.
<divclass=”container”>
<h1>Hello,{{name||‘World’}}</h1>
<inputtype=”text”ng-model=”name”>
</div>
Seridimensionatelafinestradelbrowser,dovresteiniziareaosservarelacapacità
diadattamentodelframeworkevederelafinestracomprimersi.
Puòessereunabuonaidearacchiuderloinquellochenellaterminologiadi
BootstrapèunJumbotron(nelleversioniprecedentisichiamavaHeroUnit).Farà
risaltaremoltodipiùiltitolo.Possiamoottenerequestorisultatoracchiudendoitag
<h1>e<input>inunnuovodivassociatoallaclassejumbotron:
<divclass=”container”>
<divclass=”jumbotron”>
<h1>Hello,{{name||‘World’}}</h1>
<inputtype=”text”ng-model=”name”>
</div>
</div>
Haunaspettodecisamentemigliore,mailcontenutoèancoratroppovicinoalla
partesuperioredellafinestradelbrowser.Possiamointrodurreunulteriore
miglioramentoconunheaderdipagina,ancheseilcampoditestomisembraancora
fuoriposto.Sistemiamoinnanzituttol’headerdipagina:
<divclass=”container”>
<divclass=”page-header”>
<h2>Chapter1<small>Hello,World</small></h2>
</div>
<divclass=”jumbotron”>
<h1>Hello,{{name||‘World’}}</h1>
<inputtype=”text”ng-model=”name”>
</div>
</div>
Hoinseritoilnumeroeiltitolodelcapitolo.Iltag<small>all’internodeltag<h2>
permettedidistinguereefficacementeilnumerodaltitolodelcapitolo.
Laclassepage-headeraggiungealtromargineepadding,oltreaunsottilebordo
inferiore.L’ultimoelementochepotremmomigliorareèlacaselladitesto.Bootstrap
offrealcuniottimistiliditestoevalelapenautilizzarli.Perprimacosadobbiamo
aggiungerelaclasseform-controlall’inputditesto.
Inquestomodolalarghezzaverràimpostataal100%ealcuniaspettistilistici
miglioreranno,comeibordiarrotondatieunbagliorenelmomentoincuil’elemento
ottieneilfocus:
<inputtype=”text”ng-model=”name”class=”form-control”>
Vamoltomeglio,masembraancoraunpo’piccolorispettoall’header.Bootstrap
offrealtredueclassicherimpicciolisconooingrandisconol’elemento,
rispettivamenteinput-lgeinput-sm.Inquestocaso,scegliamolaclasseinput-lgela
aggiungiamoall’input:
<inputtype=”text”ng-model=”name”class=”form-controlinput-lg”>
Dobbiamoancorarisolverelaquestionedellaspaziaturaperchéètroppoaridosso
deltag<h1>.Forseèunabuonaideaaggiungereun’etichetta,cosìl’utentecapiràche
devedigitarenellacasella.Bootstrapciconsentediprendereduepiccioniconuna
favaperchéincludeunmarginenell’etichetta:
<labelfor=”name”>EnterYourName</label>
<inputtype=”text”ng-model=”name”class=”form-controlinput-lg”id=”name”>
Quiz
1.
2.
3.
4.
ComevieneinizializzatoAngularsullapagina?
Checosasiusapervisualizzaresullapaginaunvaloredelmodello?
Achecosacorrispondel’acronimoMVC?
ComecreiamouncontrollerecomediciamoadAngularcheintendiamo
utilizzarlo?
5. InBootstrap3qualèilnuovonomediHeroUnit?
Riepilogo
Lanostraappèbellaefunzionapropriocomedovrebbe;riepiloghiamociòche
abbiamoimparatoinquestoprimocapitolo.
InnanzituttoabbiamovistocomeèfacileinstallareAngularJSeBootstrap
includendounsolofileJavaScripteununicofogliodistile.Abbiamoanche
osservatocomevieneinizializzataun’applicazioneAngulareabbiamoiniziatoa
realizzarelanostraprimaapp.
L’appHello,Worldcheabbiamocreato,seppurmoltosemplice,illustraalcune
caratteristichefondamentalidiAngular:
espressioni;
scope;
modelli;
bindingdeidatibidirezionale.
TuttoquestoèstatopossibilesenzascrivereunasolarigadiJavaScript;infattiil
controllercheabbiamocreatoservivasoloaillustrareilbindingbidirezionaleenon
erauncomponenteobbligatoriodell’app.
ConBootstrap,abbiamoutilizzatoalcunideinumerosicomponentidisponibili,tra
cuileclassijumbotronepage-headerperattribuireall’appunpo’distileesostanza.
Inoltreabbiamovistoinazioneilnuovodesignresponsivemobilefirstsenzadover
affollareilmarkupconclassioelementinonnecessari.
NelCapitolo2esamineremopiùneldettaglioalcunecaratteristichefondamentali
diAngularJSeBootstrapepresenteremoilprogettocheimplementeremonelcorso
dellibro.
Capitolo2
SviluppareconAngularJSe
Bootstrap
DopoavercreatoufficialmentelaprimaapputilizzandoAngularJSeBootstrap,è
giuntoilmomentodifareunsaltodiqualità.
Inquestolibroviserviretedientrambiiframeworkpersviluppareun’appdi
gestionedeicontatticontantodiricercatestuale,creazione,modificaecancellazione.
Esamineremounabasedicodicechepuòesseremantenutaesfrutteremotuttoil
potenzialedientrambiiframework.Iniziamoasviluppare!
Impostazione
Creiamorapidamenteunanuovadirectoryperl’appeimpostiamounastruttura
simileall’appHello,WorldcheabbiamosviluppatonelCapitolo1.
Peresempio,nellastrutturadicartellemostratanellaprossimafigura,abbiamo
inseritoledirectorynelladirectoryassetsperteneretuttoinordine.CopiateAngular
eBootstrap,comedescrittonelCapitolo1all’internodelledirectorypertinentie
createilfileindex.htmlnellaradice,chediventeràlabasedell’appdigestionedei
contatti.IlseguentesnippetèunasemplicepaginaHTMLconintegratiBootstrape
Angular.AbbiamoancheinizializzatoAngularsullapaginaconl’attributong-appnel
tag<html>.
Eccocomedovrebbeapparireinquestafase:
<!DOCTYPEhtml>
<htmllang=”en”ng-app>
<head>
<metacharset=”utf-8”>
<title>ContactsManager</title>
<linkrel=”stylesheet”href=”assets/css/bootstrap.min.css”>
<scripttype=”text/javascript”
src=”assets/js/angular.min.js”></script>
</head>
<body>
</body>
</html>
Scaffolding
Dopoaverimpostatoilfileelastrutturadellecartelledibase,possiamoiniziarea
effettuareloscaffoldingdell’appgrazieaBootstrap.Oltreaoffrireunaseriedi
componenti,comelanavigazioneeipulsanti,chepossiamoutilizzarenell’appdi
gestionedeicontatti,Bootstrapproponeancheunsistemadigriglieresponsivemolto
potentedicuisfrutteremoognipotenzialità.
Barradinavigazione
Avremobisognodiunabarradinavigazione(navbar)perpassaredaunavista
all’altra.Ovviamentesaràcollocataincimaalloschermo.
Osserviamolabarradinavigazionecompletaprimadiesaminarlapiùafondo:
<navclass=”navbarnavbar-default”role=”navigation”>
<divclass=”navbar-header”>
<buttontype=”button”class=”navbar-toggle”
data-toggle=”collapse”data-target=”#nav-toggle”>
<spanclass=”icon-bar”></span>
<spanclass=”icon-bar”></span>
<spanclass=”icon-bar”></span>
</button>
<aclass=”navbar-brand”href=”/”>ContactsManager</a>
</div>
<divclass=”collapsenavbar-collapse”id=”nav-toggle”>
<ulclass=”navnavbar-nav”>
<liclass=”active”><ahref=”/”>Browse</a></li>
<li><ahref=”/add”>AddContact</a></li>
</ul>
<formclass=”navbar-formnavbar-right”role=”search”>
<inputtype=”text”class=”form-control”
placeholder=”Search”>
</form>
</div>
</nav>
Questocodicepotrebbeintimorirvi,consideratocheèquellodiuncomponente
dellapaginamoltosemplice,masel’analizzateneldettaglio,noteretecheogni
elementoènecessario.
Iltag<nav>contienetuttociòcheèpresentenellabarradinavigazione.Alsuo
internositrovanoduesezioni:navbar-headerenavbar-collapse.Questielementisono
riservatiallanavigazionemobileecontrollanociòchevienemostratoonascostodal
pulsanteinterruttore.
L’attributodata-targetperilpulsantecorrispondedirettamenteall’attributoid
dell’elementonavbar-collapse;inquestomodoBootstrapsachecosadeveattivare.La
prossimaschermatariproducelabarradinavigazionesudispositivipiùgrandidiun
tablet.
Includeremolabarradinavigazionedirettamenteall’internodeltag<body>.In
questomodooccuperemotuttalalarghezzadellafinestradelbrowser.
Selaridimensionate,vedretecheBootstrapvisualizzal’headermobileconil
pulsanteinterruttoreperschermididimensioniinferioriai768px,ossiale
dimensionidelloschermodiuniPadconorientamentoverticale.Tuttaviasefateclic
sulpulsantepermuovervinellanavigazione,vedretechenonsuccedenulla.Ciò
accadeperchénonabbiamoinclusoinBootstrapilfileJavaScript,presentenelfile
ZIPcheabbiamoscaricatoinprecedenza.
Copiatelonelladirectoryjsdell’appefateviriferimentonelfileindex.html.Dovrete
includereanchejQuerynell’applicazione,poichéilJSdiBootstrapdipendeda
questo.
Potetetrovarel’ultimaversioneall’indirizzohttp://jquery.com/;ancoraunavolta
aggiungetelaalladirectoryeincludetelanellapaginaprimadibootstrap.js.Verificate
cheifileJavaScriptcompaianonelseguenteordine:
<scriptsrc=”assets/js/jquery.min.js”></script>
<scriptsrc=”assets/js/bootstrap.min.js”></script>
<scriptsrc=”assets/js/angular.min.js”></script>
Seriaggiornatelapaginadovresteriuscireafarclicsulpulsanteinterruttoreper
visualizzarelanavigazioneperdispositivimobili.
LegrigliediBootstrap
Ilsistemaagrigliacon12colonnediBootstrapèmoltopotenteecipermettedi
effettuareloscaffoldingdellanostraappresponsiveconpochissimielementi,
approfittandodelCSSmodulare.Lagrigliaècompostadarigheecolonnecheè
possibileadattareutilizzandounaseriediclassi.Primadiiniziare,dobbiamoinserire
uncontenitoreperlerighe,altrimentiilframeworknonsicomporteràcomeprevisto.
Èsufficienteunsemplicetag<div>dacollocaresottolabarradinavigazione:
<divclass=”container”></div>
Inquestomodolagrigliasaràcentrataeaggiungeremolaproprietàmax-widthper
disporretuttoperbene.
Esistonoquattroprefissiperleclassi,chedefinisconoilcomportamentodelle
colonne.Utilizzeremoperlopiùilprefissocol-sm-,checomprimelecolonneinmodo
cheappaianounasopral’altraquandoilcontenitorehaunalarghezzainferiorea750
px.
Lealtreclassisiriferisconotutteadiversedimensionidelloschermodei
dispositiviesicomportanoinmodosimile.Laseguentetabella,trattada
http://getbootstrap.com/,mostraledifferenzetralequattroclassi.
Cellulari
(<768px)
Comportamentodella
griglia
Sempre
orizzontale
Larghezzamassima
Nessuna
Tablet
(≥768px)
All’iniziocompressa,orizzontale
sopraibreakpoint
Desktop
(≥992px)
Desktop
(≥1200px)
delcontenitore
(automatico)
750px
970px
1170px
Prefissodelleclassi
.col-xs-
.col-sm-
.col-md-
.col-lg-
Larghezzamassima
dellacolonna
Automatica
60px
78px
95px
Offset
N/D
Sì
Ordinamentodelle
colonne
N/D
Sì
Creiamorapidamenteunlayoutaduecolonneconun’areadelcontenutoprincipale
eunabarralaterale.Siccomelagrigliaècostituitada12colonne,dovremofarsìche
l’areadelcontenutorientriinesse,altrimentiavremodellospaziovuoto.
Ottocolonneperl’areadelcontenutoequattroperlabarralateraledovrebbero
essereunasoluzioneottimale,macomeimplementarle?
All’internodelcontenitorecreiamounnuovotag<div>conlaclasserow.Possiamo
impostarequanterighedesideriamo;ciascunapuòcontenerealmassimododici
colonne:
<divclass=”container”>
<divclass=”row”>
</div>
</div>
Siccomevogliamochelecolonnevenganovisualizzatesudispositivimobili,
utilizzeremoilprefissocol-sm-.Lacreazionediunacolonnaèsemplice;èsufficiente
scegliereilprefissoopportunoeaggiungereilnumerodellecolonnechesidesidera
impostare.Osservateillayoutdibaseaduecolonne:
<divclass=”container”>
<divclass=”row”>
<divclass=”col-sm-8”>
Thisisourcontentarea
</div>
<divclass=”col-sm-4”>
Hereisoursidebar
</div>
</div>
</div>
Selovisualizzatesuunoschermodidimensionisuperioriaquellodiun
dispositivomobile,Bootstrapaggiungeràautomaticamente30pxdispaziotrale
colonne(15pxsuciascunlato).Tuttavia,talvoltavorreteinserirealtrospaziotrale
colonneedistanziarleunpo’.Bootstrappermettediottenerequestorisultato
aggiungendoun’altraclasseallacolonna.
Sceglietedinuovoilprefissoopportuno,maquestavoltaaggiungetelaparola
chiaveoffset:
<divclass=”col-sm-4col-sm-offset-1”></div>
Inquestocasoilnumeropresenteallafinecontrollailnumerodellecolonnesucui
siimpostal’offset.Lanuovaclasseaggiungeunulterioremargineasinistra.
NOTA
Ricordate: la somma delle colonne in una riga, compreso l’offset, non deve essere superiore a
12.
All’internodellecolonneèpossibilenidificarealtrerigheecolonnepercreareun
layoutpiùcomplesso:
<divclass=”container”>
<divclass=”row”>
<divclass=”col-sm-8”>
<divclass=”row”>
<divclass=”col-sm-6”>
<p>Loremipsumdolor…<p>
</div>
<divclass=”col-sm-6”>
<p>Classaptenttaciti…</p>
</div>
</div>
</div>
</div>
</div>
Cosìsicreerannoduecolonneall’internodelcontenitoreprincipalecheabbiamo
impostatoprima.Atitolodiesempio,abbiamoinseritoall’internodeltestofittizio.
Nelbrowservedretecheorasonopresentitrecolonne.Tuttavia,siccomelagriglia
ènidificata,possiamocreareunanuovarigaeunasolacolonna,trecolonneoquello
cherichiedeillayout.
Classihelper
Bootstrapincludealcuneclassihelperchepossiamoutilizzareperadattareil
layout.Ingeneresonofunzionalierispondonoaununicoscopo.Osserviamoalcuni
esempi.
Elementiflottanti
GlielementiflottantisonospessoessenzialipercreareunlayoutefficacesulWeb,
eBootstrapoffredueclassiperrenderemobiliglielementiasinistraoadestra:
<divclass=”pull-left”>...</div>
<divclass=”pull-right”>...</div>
Perutilizzareefficacementeglielementiflottanti,dobbiamoracchiuderliinuna
classeclearfix.Inquestomodoliisoleremo,eilflussodeldocumentoverrà
visualizzatocomeprevisto:
<divclass=”clearfix”>
<divclass=”pull-left”>...</div>
<divclass=”pull-right”>...</div>
</div>
Seleclassifloatsitrovanodirettamenteall’internodiunelementoconlaclasse
,glielementi“mobili”vengonoisolatiautomaticamentedaBootstrapenonè
row
necessarioapplicaremanualmentelaclasseclearfix.
Centrareglielementi
Insiemeconglielementiflottanti,spessoènecessariocentrareglielementia
livellodiblocco.Bootstrappermettedifarloconlaclassecenter-block:
<divclass=”center-block”>...</div>
Inquestomodosiimpostanoleproprietàdelmarginesinistroedestrosu
automatico,el’elementosaràcentrato.
Mostrareonascondere
PotrestevolermostrareonascondereglielementiconilCSS;Bootstrapoffredue
classiperottenerequestorisultato:
<divclass=”show”>...</div>
<divclass=”hidden”>...</div>
Èimportanteosservarechelaclasseimpostalaproprietàdisplaysublock;quindi
applicatelasoltantoaglielementialivellodibloccoenonaquelliinlineoinline-block.
Bootstrapincludeinoltrenumeroseclassiperpermettereaglielementidivenire
mostrationascostisuschermididimensionispecifiche.Leclassiutilizzanolestesse
dimensionipredefinitedellagrigliadiBootstrap.
Peresempio,ilcodiceseguentenasconderàunelementosuunoschermocon
dimensionispecifiche:
<divclass=”hidden-md”></div>
Nasconderàl’elementosudispositiviconschermididimensionimedie,malo
lasceràancoravisibilesudispositivimobili,tabletegrandidesktop.Pernascondere
unelementosupiùdispositivi,doveteutilizzarepiùclassi:
<divclass=”hidden-mdhidden-lg”></div>
Leclassivisiblehannouncomportamentooppostoemostranoglielementisu
schermididimensionispecifiche.Tuttavia,adifferenzadelleclassihidden,bisogna
impostareilvaloredisplay,chepuòessereblock,inlineoinline-block:
<divclass=”visible-md-block”></div>
<divclass=”visible-md-inline”></div>
<divclass=”visible-md-inline-block”></div>
Ovviamenteèpossibileutilizzarelediverseclassiinassociazione.Seperesempio
voletechecompaiaunelementoalivellodibloccosuunoschermopiccolo,chein
seguitodevediventareinline-block,dovreteutilizzareilcodiceseguente:
<divclass=”visible-sm-blockvisible-md-inline-block”></div>
Senonricordatelediversedimensionidelleclassi,consultateilparagrafo“Le
grigliediBootstrap”.
Utilizzareledirettive
SenzasaperloabbiamogiàutilizzatociòcheAngulardefiniscedirettive.Sitratta
insostanzadipotentifunzionichepossonoesserechiamatedaunattributoodalsuo
stessoelemento.Angularneincludemolte.Siacheintendiamoeseguireilciclodei
dati,gestireiclicoinviareiform,Angularvelocizzeràtuttequesteoperazioni.
AbbiamoutilizzatounadirettivaperinizializzareAngularsullapaginatramiteng,etutteledirettivecheesamineremoinquestocapitolovengonousateallostesso
app
modo:aggiungendounattributoaunelemento.
Primadiosservarelealtredirettiveintegrate,ènecessariocrearerapidamenteun
controller.Createunnuovofileechiamatelocontroller.js.Salvatelonelladirectoryjs
all’internodelprogettoeapritelonell’editor.
ComeabbiamovistonelCapitolo1,icontrollersonosemplicifunzionistandard
delcostruttoreJSnellequalièpossibileinserireiservizidiAngularcome$scope.Di
questefunzionivienecreataun’istanzaquandoAngularrileval’attributong-controller.
Inquestomodopossiamoaveremoltepliciistanzedellostessocontrollerall’interno
dell’applicazioneeriutilizzarebuonapartedelcodice.Questadichiarazionedi
funzioneètuttociòdicuiabbiamobisognoperilcontroller:
functionAppCtrl(){
}
Percomunicarealframeworkchequestoèilcontrollercheintendiamoutilizzare,
dobbiamoincluderlonellapaginadopocheAngularvienecaricato,eaggiungerela
direttivang-controlleraltagdiapertura<html>:
<htmlng-controller=”AppCtl”>
…
<scripttype=”text/javascript”
src=”assets/js/controller.js”></script>
ng-clickeng-mouseover
UnadelleoperazionifondamentalicheèpossibileeseguireconJavaScriptconsiste
nelgestireuneventoclic.Aquestoscoposiutilizzal’attributoonclicksuunelemento
ricorrendoajQueryoaunlistenerdieventi.InAngularricorreremoaunadirettiva.
Comeesempio,creeremounpulsantecheapriràunafinestradiavviso:
un’operazionesemplice.Iniziamoadaggiungereilpulsanteall’areadelcontenuto
creatainprecedenza:
<divclass=”col-sm-8”>
<button>ClickMe</button>
</div>
Selovisualizzatenelbrowser,vedreteunpulsanteHTMLstandard:finqui
nessunasorpresa.Primadiassociareladirettivaaquestoelemento,dobbiamocreare
unhandlernelcontroller.Sitrattadiunasemplicefunzioneall’internodelcontroller
associatoalloscope.Èmoltoimportanteassociarelafunzionealloscope,altrimenti
saràimpossibileaccedervidallavista:
functionAppCtl($scope){
$scope.clickHandler=function(){
window.alert(‘Clicked!’);
};
}
Comesappiamo,possiamoaveremoltepliciscopesuunapagina;questisono
oggettiaiqualiinAngularpossonoaccederelavistaeilcontroller.Perfareinmodo
cheilcontrollerviabbiaaccesso,abbiamoinseritoilservizio$scopenelcontroller.
QuestoserviziometteadisposizioneloscopecheAngularcreasull’elementoal
qualeabbiamoaggiuntol’attributong-controller.
Angularsibasamoltosull’inserimentodelledipendenze,cheforsepotrestenon
conoscere.Comeabbiamovisto,Angularèsuddivisoinmodulieservizi.Ciascunodi
questimodulieservizidipendel’unodall’altroel’inserimentodelledipendenze
consentelatrasparenzareferenziale.Duranteiltestdell’unità,possiamoanche
simularedeglioggetticheverrannoinseritiperconfermareirisultatideitest.
L’inserimentodelledipendenzecipermettedidireadAngulardaqualiservizi
dipendeilcontroller,eilframeworkliconvertiràpernoi.
Potetetrovareunaspiegazionedettagliatasull’inserimentodelledipendenzedi
AngularJSnelladocumentazioneufficialeall’indirizzo
https://docs.angularjs.org/guide/di.
Oral’handlerèimpostato;comeabbiamofattoinprecedenza,dobbiamo
aggiungereladirettivaalpulsante,comeattributoaggiuntivo.Questavolta
passeremoilnomedellafunzionecheintendiamoeseguire,cheinquestocasoè
clickHandler.Angularvaluteràtuttociòcheinseriremonelladirettivacomeespressione
diAngularJS;pertantodobbiamofareattenzioneadaggiungeredueparentesiche
indicanochestiamochiamandounafunzione:
<buttonng-click=”clickHandler()”>ClickMe</button>
Selovisualizzatenelbrowser,vedreteunafinestradiavvisoquandofareteclicsul
pulsante.Nonènecessarioincluderelavariabile$scopequandosichiamalafunzione
nellavista.Lefunzionielevariabiliallequalièpossibileaccederedallavista
rimangonoall’internodelloscopecorrenteodiqualsiasiscopeprecedente.
Sedesideratevisualizzarelafinestradiavvisoall’hover(cioèalpassaggio)invece
chealclic,èsufficientemodificareilnomedelladirettivainng-mouseover,poiché
entrambefunzionanonellostessomodo.
ng-init
Ladirettivang-initvalutaun’espressionesulloscopecorrenteepuòessere
utilizzatadasolaoinassociazioneconaltredirettive.Laprioritàdiesecuzioneè
massimarispettoadaltredirettive,perfareinmodochel’espressionevengavalutata
intempo.
Eccounsempliceesempiodelladirettivang-initinazione:
<divng-init=”test=‘Hello,World’”></div>
{{test}}
InquestomodocompariràsulloschermoHello,Worldquandol’applicazionesarà
caricatanelbrowser.Sopraabbiamoimpostatoilvaloredelmodelloditeste
utilizzatolasintassiconledoppieparentesigraffepervisualizzarlo.
ng-showeng-hide
Talvoltadovremocontrollarelavisualizzazioneprogrammatadiunelemento.Sia
ng-showsiang-hidepossonoesserecontrollatedalvalorerestituitodaunafunzioneoda
unmodello.
PossiamoavvalercidellafunzioneclickHandlercheabbiamocreatopermostrarein
azioneladirettivang-clickalfinedirenderevisibileonasconderel’elemento.A
questoscopocreeremounnuovomodelloealterneremoilvaloretraveroefalso.
Creiamol’elementochemostreremoenasconderemo.Immetteteilseguentecodice
sottoilpulsante:
<divng-hide=”isHidden”>
Clickthebuttonabovetotoggle.
</div>
Ilvalorenell’attributong-hideèilnostromodello.Siccomeècompresonelloscope,
possiamofacilmentemodificarlonelcontroller:
$scope.clickHandler=function(){
$scope.isHidden=!$scope.isHidden;
};
Quistiamoinvertendoilvaloredelmodello,cheasuavoltarendevisibileomeno
il<div>.
Seloapritenelbrowser,vedretechel’elementoènascostoperimpostazione
predefinita.Esistonoalcunisistemipergestirequestoaspetto.Potremmoimpostareil
valoredi$scope.hiddensutruenelcontroller,oppureimpostareilvaloredihiddensutrue
utilizzandoladirettivang-init.Altrimentièpossibilericorrerealladirettivang-showche
hauncomportamentocontrariorispettoang-hideerenderàvisibileunelementoseil
valorediunmodelloèimpostatosutrue.
NOTA
Verificate che Angular sia caricato nell’header, altrimenti ng-hide e ng-show non funzioneranno
correttamente.QuestoperchéAngularutilizzalesueclassipernascondereglielementiequeste
devonoesserecaricatealrenderingdellapagina.
ng-if
Angularincludeancheunadirettivang-ifchesicomportainmodosimileang-showe
.Tuttaviang-ifeliminal’elementodalDOM,mentreng-showeng-hiderendono
ng-hide
visibiliomenoglielementi.
Osserviamorapidamentecomeèpossibileutilizzareng-ifnelcodiceprecedente:
<divng-if=”isHidden”>
Clickthebuttonabovetotoggle.
</div>
Sevolessimoinvertireilsignificatodell’istruzione,sarebbesufficienteaggiungere
unpuntoesclamativoprimadell’espressione:
<divng-if=”!isHidden”>
Clickthebuttonabovetotoggle.
</div>
ng-repeat
Benpresto,durantelosviluppodiunawebapp,avretebisognodirappresentareun
arraydiitem.Peresempio,nell’appdigestionedeicontatti,potrebbeessereun
elencodicontatti,oqualsiasialtracosa.Angularconsentediraggiungerequesto
scopoconladirettivang-repeat.
Vediamounesempiodialcunidatichepotresteincontrare.Sitrattadiunarraydi
oggetticonmoltepliciproprietàalsuointerno.Pervisualizzareidati,dovremo
riuscireadaccedereaogniproprietà.ng-repeatserveaquestoscopo.
Eccoilcontrollerconunarraydioggettidelcontattoassegnatialmodellodei
contatti:
functionAppCtrl($scope){
$scope.contacts=[
{
name:‘JohnDoe’,
phone:‘01234567890’,
email:‘[email protected]’
},
{
name:‘KaranBromwich’,
phone:‘09876543210’,
email:‘[email protected]’
}
];
}
Inquestocasoabbiamosoloduecontatti,macomepoteteimmaginare,un’API
potrebbeservirnecentinaiachenonsarebbepossibilegestiresenzang-repeat.
Aggiungeteunarraydicontattialcontrollereassegnateloa$scope.contacts.Apriteil
fileindex.htmlpercreareuntag<ul>.Ripeteremounavocepresenteinquestoelenco
nonordinato,quindiquestoèl’elementoalqualedobbiamoaggiungereladirettiva:
<ul>
<ling-repeat=”contactincontacts”></li>
</ul>
SesapetecomefunzionanoicicliinPHPoRuby,visentireteavostroagio.Create
unavariabileallaqualepoteraccedereall’internodell’elementocorrentecheentra
nelciclo.Lavariabiledopolaparolachiaveinfariferimentoalmodellocheabbiamo
creatosu$scopenelcontroller.Cosìèpossibileaccedereaqualsiasiproprietà
impostatasull’oggetto,eogniiterazioneoitemripetutiacquisisconounnuovoscope.
Possiamovisualizzarlisullapaginautilizzandolasintassidelledoppieparentesi
graffediAngular,comeabbiamovistonelCapitolo1:
<ul>
<ling-repeat=”contactincontacts”>
{{contact.name}}
</li>
</ul>
Cosìverràvisualizzato,comeprevisto,ilnomeall’internodellavocedell’elencoe
saràpossibileaccederefacilmenteaqualsiasiproprietàdell’oggettodelcontatto
facendoriferimentoaessotramitelasintassiapuntostandard.
ng-class
Spessocapitadivolermodificareoaggiungereunaclasseaunelemento.Aquesto
scopopossiamoricorrerealladirettivang-class,perdefinireunaclassedaaggiungere
odaeliminareinbasealvalorediunmodello.
Esistonoduesistemiperutilizzareng-class.Nellasuaformapiùsemplice,Angular
applicheràilvaloredelmodellocomeclasseCSSall’elemento:
<divng-class=”exampleClass”></div>
Seilmodelloalqualefacciamoriferimentoèindefinitoofalso,Angularnon
applicheràlaclasse.Questosistemaèottimoperlesingoleclassi,masevolessimo
averemaggiorecontrollooapplicarepiùclassiaunsoloelemento?Provatequesto
codice:
<divng-class=”{className:model,class2:model2}”></div>
Inquestocasol’espressioneèunpo’diversa.Disponiamodiuninsiemedinomidi
classieilmodelloconilqualeconfrontarlo.Seilmodellorestituiscetrue,laclasse
verràaggiuntaall’elemento.
Osserviamoloinazione.Utilizzeremolecaselledicontrolloconl’attributong-model
cheabbiamogiàesaminatonelCapitolo1perapplicarealcuneclassiaunparagrafo:
<png-class=”{‘text-center’:center,‘text-danger’:error}”>
Loremipsumdolorsitamet
</p>
HoaggiuntodueclassiBootstraptext-centeretext-danger.Questeesaminanoun
paiodimodelli,chepossiamomodificarerapidamenteconalcunecaselledi
controllo:
<label><inputtype=”checkbox”ng-model=”center”>textcenter</label>
<label><inputtype=”checkbox”ng-model=”error”>textdanger</label>
NOTA
Levirgolettesemplicicheracchiudonoinomidelleclassinell’espressionesononecessariesolo
quandosiutilizzanoitrattini(-),altrimentiAngulargeneraunerrore.
Quandoquestecaselledicontrollosonospuntate,leclassiopportunevengono
applicateall’elemento.
ng-style
Inmodosimileang-class,questadirettivaciconsentediassegnaredinamicamente
unostileaunelementoconAngular.Atitolodiesempiocreeremounaterzacasella
dicontrollocheapplicheràaltristiliall’elementoparagrafo.
Ladirettivang-styleutilizzaunoggettoJavaScriptstandard,eleparolechiave
sarannolaproprietàcheintendiamomodificare(peresempiocoloreesfondo).
Questosipuòapplicaredaunmodelloounvalorerestituitodaunafunzione.
Esaminiamocomeassociarloaunafunzionecheverificheràunmodello.Poi
possiamoaggiungerloallacaselladicontrollopercambiareglistili.
Apriteilfilecontroller.jsecreateunanuovafunzioneassociataalloscope.Quila
chiameremostyleDemo:
$scope.styleDemo=function(){
if(!$scope.styler){
return;
}
return{
background:‘red’,
fontWeight:‘bold’
};
};
All’internodellafunzionedobbiamoverificareilvalorediunmodello;inquesto
esempiosichiamastyler.Seèfalso,nonrestituirànulla,altrimentirestituiràun
oggettoconleproprietàCSS.AbbiamoutilizzatofontWeightinvecedifont-weight
nell’oggettorestituito.EntrambivannobeneeAngularapplicheràautomaticamente
lasintassicamelcasenellacorrettaproprietàCSS.Ricordatechequandosiutilizzano
itrattininelleparolechiavedeglioggettiJavaScript,questevannoracchiusetra
virgolette.
Questomodellosaràassociatoaunacaselladicontrollo,comeabbiamofattocon
ng-class:
<label><inputtype=”checkbox”ng-model=”styler”>ng-style</label>
L’ultimacosachedobbiamofareèaggiungereladirettivang-styleall’elementodel
paragrafo:
<p..ng-style=”styleDemo()”>
Loremipsumdolorsitamet
</p>
Angularèingradodirichiamarequestafunzioneognivoltachecambialoscope.
Ciòsignificachenonappenailvaloredelmodellocambiadafalseatrue,verranno
applicatiglistilieviceversa.
ng-cloak
L’ultimadirettivacheesamineremoèng-cloak.Quandoutilizzateitemplatedi
AngularinunapaginaHTML,vengonovisualizzatetemporaneamenteledoppie
parentesigraffeprimacheAngularJSabbiafinitodicaricareecompilaretuttosulla
pagina.Perovviareaquestocomportamento,dobbiamonascondere
temporaneamenteiltemplateprimacheterminiilrendering.
Angularconsentedifarloconladirettivang-cloak,cheimpostaunulteriorestile
sull’elementomentrevienecaricato,ovverodisplay:none!important;.
NOTA
Per evitare il flashing durante il caricamento del contenuto, è importante che Angular venga
caricatonellasezioneheaddellapaginaHTML.
Quiz
1. Checosaaggiungiamoincimaallapaginaperpoterpassaredaunavista
all’altra?
2. QuantecolonnecomprendeilsistemaagrigliadiBootstrap?
3. Checos’èunadirettivaecomevieneutilizzatalamaggiorpartediesse?
4. Qualedirettivadobbiamoutilizzarepereseguireilciclodeidati?
Riepilogo
Inquestocapitoloabbiamoaffrontatomoltiargomenti;primadiproseguireconil
prossimo,riepiloghiamoli.
Bootstrapciconsentedicrearerapidamenteunanavigazioneresponsive.
DobbiamoincludereilfileJavaScriptpresentenellaversionediBootstrapche
abbiamoscaricatoperrenderedisponibileilpulsanteinterruttoreperlanavigazione
mobile.
Abbiamoancheesaminatoilpotentesistemaagrigliaresponsiveinclusoin
Bootstrapecreatounsemplicelayoutaduecolonne.Durantequestaoperazione
abbiamoanalizzatoiquattrodiversiprefissidelleclassiperlecolonneenidificatola
griglia.Peradattareillayoutabbiamoscopertoalcuneclassihelperinclusenel
frameworkcheciconsentonodirenderemobili,centrareenascondereglielementi.
AbbiamoesaminatoindettaglioledirettiveintegrateinAngular,lefunzionicheè
possibileutilizzaredallavista.Primadiosservarleinazione,abbiamocreatoun
controller,ossiaunafunzioneincuipossiamopassareiservizidiAngularsfruttando
l’inserimentodelledipendenze.
Ledirettivecheabbiamodescrittosarannofondamentalimanmanoche
svilupperemol’appdigestionedeicontattinelcorsodellibro.Direttivecomeng-click
eng-mouseoversonoinsostanzanuovisistemipergestireglieventi,operazioneche
avetesicuramentesvoltoconjQueryovanillaJavaScript,madirettivecomeng-repeat
rappresenterannoforseunmodotuttonuovodilavorare,cheintrodurràunalogica
nellavistapereseguireilciclodeidatievisualizzarlisullapagina.
Abbiamoancheanalizzatoledirettivechevalutanoimodellinelloscopee
compionodiverseazionisullabasedeilorovalori.ng-showeng-hidemostranoo
nascondonounelementosullabasedelvalorediunmodello.Loabbiamovistoanche
conng-class,checihapermessodiaggiungeredelleclassiaglielementisullabasedei
valorideimodelli.
Capitolo3
Ifiltri
Nelcapitoloprecedenteabbiamoesaminatounodeicomponentifondamentalidi
AngularJS:ledirettive.Comemoltiframework,Angularoffrealtriparadigmicheci
aiutanoasviluppareleapp.Ifiltripermettonodimanipolareeordinarefacilmentei
datidallavistaodalcontroller,ecomeperledirettive,neesistonoalcuniefficacigià
integrati.
Sipossonoapplicareinmolticasieinquestocapitoloneesamineremoalcuni.Per
esempio,èpossibilemanipolareunastringa,convertendola,localizzandolao
troncandola.IfiltriconsentonoanchedioperareconaltritipiJavaScript,comearray
eoggetti.Forsevicapiteràdiimpostareunaricercaperfiltrareunsetdidatidicui
aveteeseguitoilciclousandong-repeat.Tuttoquestoèpossibileconifiltri.
Primadiosservarealcunifiltriinclusi,esamineremocomeèpossibileapplicareun
filtrodallavista.
Applicareunfiltrodallavista
Èpossibileapplicareifiltridirettamentealleespressioniall’internodeitemplate.
Ricordatecheun’espressioneètuttociòcheècompresoall’internodellasintassicon
doppieparentesigraffeodiunadirettiva:
{{expression|filter}}
Èfacileservirsidiunfiltro;èsufficienteaggiungereilsimbolopipe(|)seguitodal
nomedelfiltrocheintendiamoapplicareall’espressione.Possiamoprocedereallo
stessomodoperapplicarepiùfiltriaunasolaespressione.Èpossibileconcatenarne
piùdiunoeapplicarliinsequenza.Nell’esempioseguente,filter2verràapplicato
all’outputdelfilter1ecosìvia:
{{expression|filter1|filter2|filter3}}
Alcunifiltripossonoaveredegliargomenti,chesipossonoapplicarericorrendoa
unasintassisimile:
{{expression|filter:argument1:argument2}}
InquestocapitoloillustreremoalcunifiltriinclusiinAngulardirettamentedalla
vistautilizzandolasintassicheabbiamoesaminato.Vedremocomeapplicaregli
stessifiltridalcontrollerecomecrearneuno.
Currencyenumber
Ilprimofiltrocheanalizzeremoformattainumeriinvaluta.Nellaversione
localizzatabritannica-americana,aggiungealpostogiustounavirgolapersepararele
migliaiadaidecimali.Lifaancheprecederedalsimboloopportuno:
{{12345|currency}}
Ilsimbolodellavalutadipenderàdallaversionelocalizzata.Seutilizziamoquella
britannica-americana,perimpostazionepredefinita,Angularanteponeilsimbolodel
dollaro($),mapossiamopassareilsimbolodanoisceltocomeargomento:
{{12345|currency:’£’}}
Èimportantericordarsidiracchiuderlotravirgolette,comesefosseunastringa.
Angularincludeancheunsecondofiltroperformattareinumeri,checioffre
maggiorecontrollo;ciconsentedispecificareilnumerodicifredecimaliacui
vogliamoarrotondareilnumero:
{{12345.225|number:2}}
L’outputdiquestofiltrosarà12,345.23.Comepoteteosservare,ilnumeroèstato
arrotondatoaduedecimaliedèstataaggiuntaunavirgolapersepararelemigliaia.
Lowercaseeuppercase
QuestiduefiltrisonoforseipiùsempliciinclusiinAngular.Convertonolastringa
inminuscoloomaiuscolo:
{{‘Stephen’|lowercase}}
{{‘Declan’|uppercase}}
L’outputdiquestifiltrièilseguente:
stephen
DECLAN
limitTo
Inalcunicasipotrestevolerlimitareilnumerodicaratteridiunastringaodiun
array;questorisultatosipuòottenerefacilmenteinAngularJSutilizzandoilfiltro
limitTo:
{{‘Loremipsumdolorsitamet’|limitTo:15}}
Questofiltroaccettaunsoloargomento,cheèilnumerodicaratterialquale
dovrebbelimitarsil’input.Inquestocasoabbiamolimitatoicaratteridiunastringa,
mapotrebbetrattarsidiunarrayinunadirettivang-repeat,peresempio:
<divng-repeat=”array|limitTo:2”></div>
Date
QuandosioperaconidatidaunaAPI,spessoladatavienefornitacomeoraUNIX
ocometimestampcompleto.Nonèmoltocomodo,mafortunatamenteAngular
includeunsistemasempliceperformattareledateconunfiltro:
{{expression|date:format}}
Questofiltroaccettaunargomento:format.Peresempio,seabbiamountimestampe
intendiamoottenerel’anno,potremmoraggiungerefacilmentequestorisultatocon
l’espressioneseguente:
{{725508723000|date:’yyyy’}}
Possiamocombinarloconl’inputgiornomeseeavremofacilmenteunastringadi
datastandard:
{{725508723000|date:’dd/MM/yyyy’}}
Eccounelencodialcunideglielementipiùutilidacuipuòesserecostituitala
stringaformat.UnelencocompletosipuòtrovaresulsitodiAngularJS.
Elemento
Output
Esempio
yyyy
Annoinquattrocifre
2013
yy
Annoinduecifre
13
MMMM
Meseperesteso
Dicembre
MMM
Meseabbreviato
Dic
MM
Meseprecedutodazero
01
M
Meseinnumero
1
dd
Giornoprecedutodazero
01
d
Giornoinnumero
1
EEEE
Giornodellasettimana
Lunedì
EEE
Giornoabbreviatodellasettimana
Lun
HH
Oranelformato24oreprecedutadazero
01
H
Oranelformato24ore
1
hh
Oranelformato12oreprecedutadazero
01
h
Oranelformato12ore
1
mm
Minutoprecedutodazero
05
m
Minuto
5
ss
Secondoprecedutodazero
09
s
Secondo
9
a
AM/PM
AMoPM
Z
Fusoorario
+0100
ww(solo1.3+)
Settimanadell’annoprecedutadazero
03
w(solo1.3+)
Settimanadell’anno
3
Esistonoinoltrealcuniformatipredefinitichepossiamoutilizzare;esaminiamone
uno:
{{725508723000|date:’medium’}}
Laparolachiavemediumèsolounodeiformatididefaultchequestofiltroriconosce,
egeneraDec28,19922:12:03AM.
Eccounelencocompletodeiformatipredefinitiaccettatidalfiltrodate.
Parolachiave
medium
Equivalente
MMMd,yh:mm:ssa
Esempio
Sep3,201012:05:08pm
short
M/d/yyh:mma
9/3/1012:05pm
fullDate
EEEE,MMMMd,y
Friday,September3,2010
longDate
MMMMd,y
September3,2010
mediumDate
MMMd,y
Sep3,2010
shortDate
M/d/yy
9/3/10
mediumTime
h:mm:ssa
12:05:08pm
shortTime
h:mma
12:05pm
Possiamoancheincluderevaloriletteralinellastringadelformato;peresempio:
{{725508723000|date:”h‘inthemorning’”}}
Ivaloriletteralidevonoessereracchiusitravirgolettesemplici.Dobbiamoquindi
cambiarelevirgolettesemplicicheracchiudonol’argomentoconlevirgolettedoppie.
Sevolesteincluderenellastringaunasolavirgoletta,dovresteutilizzaredue
virgolettesemplici:
{{725508723000|date:”h‘o’’clock’”}}
Filter
Questofiltro,conunnomechepuògenerareconfusione,consentediselezionare
facilmenteunsetsecondariodiiteminunarray.All’internodellavistaèpossibile
utilizzarlocombinatoconladirettivang-repeatcheabbiamoillustratonelcapitolo
precedente.
Conessopossiamosviluppareunostrumentodiricercapotentechefiltreràl’array.
Esaminiamol’esempiong-repeatdelCapitolo2:
<ul>
<ling-repeat=”contactincontacts”>
{{contact.name}}-{{contact.phone}}
</li>
</ul>
Primadiaggiungereilfiltro,dobbiamoaggiungerel’oggettopatternchesarà
utilizzatoperlaselezionedall’array.Puòessereunmodello,unastringa,unoggetto
patternounafunzione.Mentrecreiamounaricerca,aggiungiamounmodelloaun
inputditesto:
<inputtype=”text”ng-model=”search”>
Nonrestacheassociareilmodelloalladirettivang-repeat.Lopotetefarecomecon
qualsiasialtrofiltro:inseriteunpipeseguitodalnomedelfiltro.Inquestocaso
dobbiamoancheaggiungereunargomentocheindicaalfiltroqualemodello,stringa,
oggettoofunzioneintendiamoutilizzare:
<ling-repeat=”contactincontacts|filter:search”>
Cosìpossiamoservircidelcampoditestochecreiamopereffettuarequalsiasi
ricercanell’array,checomprendenomi,numeriditelefonoeindirizzie-mail.
Tuttavia,checosasuccedeseintendiamorestringerelaricercasoltantoallaproprietà
namedeinostrioggetti?Èsufficientecambiareilmodello:
<inputtype=”text”ng-model=”search.name”>
Èimportantechelasciamoilnomedelmodelloding-repeatsusearch,altrimentiil
filtrononsilimiteràallaproprietàdesiderata.
Inalternativapotremmoutilizzarelasintassiseguenteperladirettivang-repeatper
limitareilfiltroaproprietàspecifiche.Inquestocasopossiamolasciareilnomedel
modellosusearch:
<ling-repeat=”contactincontacts|filter:{‘name’:search}”>
orderBy
Oltreafiltrarel’oggettoall’internodelladirettivang-repeat,possiamoanche
ordinarlo.Èun’ottimasoluzioneseidatifornitidaunarraynonsonogiàordinatio
senonesisteun’opzioneperfarlo.
Attualmentel’oggettoètuttoconfusoenonpresentaalcunordineapparente.
Osserviamocomepossiamoordinarlopernome:
<ling-repeat=”contactincontacts|filter:search|
orderBy:’name’”>
Ilprimoargomentochepossiamopassareèunastringaconilnomedellaproprietà
inbaseallaqualeintendiamoordinarel’array.Sevolessimofiltraresecondoil
numeroditelefonool’indirizzoe-mail,potremmopassarequestivalori.
Possiamoanchepassareunsecondoargomentoattraversounbooleanoche
controllaseilfiltrodeveinvertirel’ordineomeno:
<ling-repeat=”..|orderBy:’name’:true”>
JSON
L’ultimofiltroinclusoservesoprattuttoperildebugging.Trasformeràqualsiasi
oggettoJavaScriptinunastringaJSONperlavisualizzazionesullapagina.
Prendiamol’arraydicontatticheabbiamoutilizzatonelcapitoloprecedenteper
mostrareinazioneng-repeateapplichiamoilfiltrojson:
{{contacts|json}}
Ciòchesegueèl’outputdellavista:
[
{
“name”:”JohnDoe”,
“phone”:”01234567890”,
“email”:”[email protected]”
},
{
“name”:”KaranBromwich”,
“phone”:”09876543210”,
“email”:”[email protected]”
},
{
“name”:”DeclanProud”,
“phone”:”2341234231”,
“email”:”[email protected]”
},
{
“name”:”PaulMcKay”,
“phone”:”912345678”,
“email”:”[email protected]”
}
]
Comepotetevedere,èunasemplicerappresentazioneJSONdell’arraydioggetti
creatoinprecedenza.
ApplicareifiltridaJavaScript
TalvoltavorreteapplicareunfiltroutilizzandoJavaScript,disolitodalcontroller;è
importantecapirecomepossiamoottenerequestorisultato,tramitedueopzioni.
Possiamoinserireilservizio$filternelcontrollereutilizzarequalsiasifiltro
inclusonell’applicazione.Inalternativapossiamoinserireilfiltrocomesuoservizio
dedicatoeutilizzarlodasolo.Entrambiimetodisonoperfettamentevalidiespettaa
voisceglierequellochepreferite.
Esaminiamoliricorrendoinnanzituttoalservizio$filter.Consideriamoilfiltrojson
cheabbiamoappenavistoeutilizziamoconsole.logsullostessoarray.Inseriamoil
servizionelcontroller:
functionAppCtl($scope,$filter){
…
}
Ottimo!Orapossiamoutilizzarlocosìcomefacciamocon$scope.Aquestofineè
sufficientechiamarlocomeunafunzioneepassareilnomedelfiltrochedesideriamo
usare,chenelnostrocasoèjson:
$filter(‘json’);
Lastringarestituisceilfiltrostesso;possiamovederlonell’outputseapplichiamo
direttamentelasintassiconsole.log.Ciòsignificachepossiamochiamare
immediatamentelafunzioneaggiungendounasecondaseriediparentesisubitodopo:
$filter(‘json’)($scope.contacts);
Comesappiamo,ilfiltrojsonnonaccettaargomenti.Tuttaviailprimoargomentodi
tuttelefunzionideifiltrièinrealtàl’input.Nonlovediamoquandolechiamiamo
dallavistapoichéAngularoperalasuamagiadietrolequintepersemplificarele
cose.
Seracchiudetel’espressioneprecedenteinunaconsole.log,vedretechel’outputè
identicoaquellonellavistautilizzandolostessofiltro:
Inalternativa,senonvoletericorrerealservizio$filter,poteteinserireognifiltro
separatamentecomeunservizio.NelpatternvengonochiamatifilternameFilter.Peril
nostroesempio,dobbiamoinserirejsonFilter:
functionAppCtl($scope,jsonFilter){
…
}
Puòessereutilizzatoallostessomododellafunzionerestituitadalservizio$filter,
cosìdapermettercidipassarel’oggettodafiltrare:
jsonFilter($scope.contacts);
Orachesappiamocomeutilizzareifiltridalcontroller,vediamocomecrearneuno.
Sviluppareunfiltro
Comeabbiamovisto,ilfiltrolimitToèutilissimopertroncarestringheditesto.Ho
sempresentitolanecessitàdiunfiltrocheaggiungessedeipuntinidisospensionenel
casoincuiunastringasuperasseillimite.
FortunatamenteAngularciconsentediestendereifiltriinclusiesvilupparneuno
nuovo.
Moduli
Aquestoscopodobbiamocreareciòchevienechiamatomodulo.Essociconsente
diassociareunfiltroeutilizzarlonellevisteoneicontroller.Ladocumentazionedi
AngularJSspiegaperfettamentecos’èunmodulo:
Sipuòpensareaunmodulocomeauncontenitoreperlediversepartidiun’app:controller,servizi,filtri,
direttiveecosìvia.
Maperchédovremmoutilizzarneuno?Cisonounpaiodimotivi.Laragionepiù
importantechegiustificalaloroesistenzaèlafacilitàconcuièpossibilecreare
codiceriutilizzabile.
Immaginatedistarlavorandosuunapiattaformadiblogging.Potrestesviluppare
unmodulodestinatoaunmediabrowseroaunmediauploader.Conterrebbeuna
seriedicontroller,serviziefiltri,tuttiinsieme.Sevolesteutilizzarequestomedia
browserinunaltroprogetto,potrestesemplicementecopiareilmodulo.
Esistonoaltrimotiviperutilizzareimoduli.Sesicollaudal’unità,èsufficienteche
itestcarichinosoloimodulipertinentiperassicurarelarapiditàdell’operazione,eil
codicediventapiùsemplicedacomprendereeseguireperchéognicomponenteèben
inseritotraglialtrielementi.
Creareunmodulo
Èmoltofacilecreareunmodulo;ciconsentediampliareilcoreperchéAngular
nonammetteifiltripersonalizzatisenzalacreazionediunmodulo.Inquestocaso
realizziamounnuovomodulochiamatocontactsMgr.Ilsecondoargomentoèun
semplicearrayvuoto.Possonoesserciquantimodulidesideriamoelipossiamo
includerecomedipendenze,maperilmomentoquestololasceremovuoto:
angular.module(‘contactsMgr’,[]);
Tuttaviaènecessarioinserireunapiccolamodificanelmodoincuivieneaggiunto
ilcontroller.Attualmenteèsolounafunzione,maènecessarioaggiungerloalmodulo
perchéAngularlopossarecuperare:
angular.module(‘contactsMgr’,[])
.controller(‘AppCtl’,function($scope,jsonFilter){
…
});
Possiamoconcatenareicontrollernelmodulo.Avretenotatocheènecessario
utilizzareilmetodocontroller.Ilprimoargomentoèilnomedelcontroller,mentreil
secondoèlafunzionedicallbackconiserviziinseriti.
Seoracaricatel’app,vedretechenullafunzionacomeprevistoenonvienetrovata
lafunzionedelcontroller.QuestoperchénonabbiamoindicatoadAngularquale
modulodesideriamoutilizzare.Ènecessarioaggiungereilnomedelmoduloalla
direttivang-app:
<htmllang=”en”ng-app=”contactsMgr”ng-controller=”AppCtl”>
Unavoltainserito,tuttodovrebbeiniziareafunzionareperilversogiusto.Ora
stiamoutilizzandoilmoduloappenacreato.
Creareunfiltro
Dopoavercreatoilmoduloeaverlovistoinazione,possiamolavoraresulfiltro
limitTomigliorandolo.Convienepensareinnanzituttoalleoperazionichevorremo
svolgesse.Possiamosuddividerelefunzioniinpochibrevipassi.
Consideral’inputconunsoloargomentoperillimite.
Verificalalunghezzadell’inputrispettoallimite.
Sel’inputèmaggioredellimite,troncalastringaeaggiungedeipuntinidi
sospensione.
Altrimentirestituiscel’input.
Operandoconimodelli,lacreazionediunfiltrorisultamoltosimileallacreazione
diuncontroller:
.filter(‘truncate’,function(){
});
Propriocomeabbiamofattoquandoabbiamospostatoilcontrollersuunnuovo
modulo,utilizziamounnuovometodocheaccettadueargomenti:ilnomedelfiltroe
unafunzionedicallback.Comevistoquandoabbiamoapplicatoifiltridalcontroller,
quandosichiamaunfiltro,essorestituisceunasecondafunzione,chedobbiamo
aggiungerequi:
.filter(‘truncate’,function(){
returnfunction(){
};
})
Abbiamoanchescopertocheilprimoargomentodiunfiltroèsemprel’inputoi
datichefiltreremo.All’internodiquestafunzionepossiamoancheincludere
argomentiaggiuntivi.Nelcasoditruncate,occorreunargomentoperindicarealfiltro
aquanticaratteridovrebbelimitarelastringa:
.filter(‘truncate’,function(){
returnfunction(input,limit){
};
})
Lacostruzionedelfiltroècompletaeorapossiamoutilizzarlonellostessomodo
deglialtrifiltriesaminatiinprecedenza.Ovviamentenonabbiamoimpostatoalcuna
logicaenonvienerestituitonulladallafunzionefiltro;pertantononverrà
visualizzatonullasullapagina.
Oradobbiamoverificarelalunghezza,troncarelastringaeaggiungeredeipuntini
disospensione.Sipossonoottenerequestirisultatiinunastringagraziea
un’istruzioneternaria:
return(input.length>limit)?input.substr(0,limit)+’…’:
input;
Verifichiamolalunghezzadellastringa,eseèsuperioreallimite,latagliamoe
aggiungiamodeipuntinidisospensione.Senonsoddisfalanostracondizione,verrà
restituitol’inputoriginario.QuestoèimportanteperchéAngularnonvisualizzerà
nullasenonvienerestituitonulladalfiltro.
Assembliamoiltuttoedesaminiamolafunzionecompleta:
.filter(‘truncate’,function(){
returnfunction(input,limit){
return(input.length>limit)?input.substr(0,limit)+’…’
:input;
};
})
PossiamoutilizzareilnuovofiltronellostessomododelfiltrolimitTointegrato;
sostituiamoloeosserviamoilrisultato:
{{‘Loremipsumdolorsitamet’|truncate:15}}
Comeprevisto,oral’outputincludedeipuntinidisospensione,mentrein
precedenzalastringaeratroncatadopoillimite.
Quiz
1.
2.
3.
4.
5.
Comesipuòapplicareunfiltrodallavista?
Inchemodopassiamogliargomentialfiltrodallavista?
Qualefiltrodovremmoutilizzarepercreareunalivesearch?
Comepossiamoutilizzareunfiltrodalcontroller?
Checosadobbiamocreareprimadisviluppareunfiltro?
Riepilogo
Oradovresteconoscerelafunzioneelagrandeutilitàdeifiltri,mariepiloghiamo
tuttociòcheabbiamoaffrontatoinquestocapitolo.
Abbiamoiniziatoaosservarecomevieneapplicatounfiltrodirettamentedalla
vistautilizzandolasintassicheprevedel’usodelpipeelaseparazionedegli
argomenticonduepunti.Dopoaveranalizzatolebasi,abbiamoesaminatoinumerosi
filtriinclusi.
Alcunisonofondamentalienonrichiedonoalcunargomento,macenesonoanche
dipiùavanzaticheconsentonodiordinareofiltrareunarraydioggetti.
Oltreadapplicareifiltridallavista,abbiamoancheconsideratoiduemetodiper
filtraredalcontroller.Possiamoutilizzareilservizioincluso$filterodecideredi
inserireifiltriseparatamente.
InfineabbiamovistocomeestendereAngularpercreareunfiltroalfineditagliare
unastringaditesto.Primaperòabbiamovistocomecreareunmodulochecontenesse
ifiltrieicontroller.Dopoaverlorealizzato,siamoriuscitiacreareunfiltroea
utilizzarlonellostessomododiquelliintegrati.
AbbiamoaffrontatomoltiparadigmieconcettifondamentalidiAngular.Nel
prossimocapitolovedremocomeimpostareilsistemadiroutingpergestire
molteplicivisteecontrollerperl’appdigestionedeicontatti.
Capitolo4
Routing
Tuttelewebappsonocostituitedapiùpagineoviste,eAngularhatuttelecartein
regolapergestirequestoaspettoconilrouter(intesocomesistemadirouting,enon
comedispositivofisico).Forseconosceteilsistemadiroutingneiframeworklato
server,comeRubyonRailsoLaravel.Angularèinteramentelatoclient,etuttala
magiaaccadeall’internodelfileHTMLalqualepuntailbrowser.Inquestocapitolo
esamineremocomecreareroutestatichecontenentideiparametri.Individueremo
inoltrealcuneinsidienellequalipotresteimbattervi.
Primadiiniziareelenchiamoleroutecheoccorrerannoperl’appdigestionedei
contatti.
Index:saràlapaginaprincipale,cheelencheràtuttiicontattiinunatabella.
ViewContact:quipotremovisualizzareilcontattoneldettaglioemodificare
qualunqueinformazionecorrelata.
AddContact:includeràunformcheciconsentiràdiaggiungereuncontattoal
gestore.
Questesonoleroutefondamentali;vediamocomecrearle.
InstallarengRoute
FindaAngularJS1.2,ilroutersièpresentatocomeunmoduloseparatoedesterno
rispettoaicomponentifondamentalidiAngular.Ilfilechecioccorre,angular,puòesserescaricatodalsitowebdiAngularnellasezioneExtras
route.min.js
all’internodellafinestradidownload.
Dopoaverloscaricato,trascinatelonelladirectoryjsdelprogettoeincludetelo
nellapaginadopoAngularJS:
<scriptsrc=”assets/js/angular-route.min.js”></script>
Inoltreènecessariocomunicarealmodulocheintendiamoutilizzareilrouter;
dobbiamoquindiaggiungerloallalistadelledipendenzedelmodulo.Possonoessere
presentiquantedipendenzedesideriamo;peroraèsufficienteincluderengRoute:
angular.module(‘contactsMgr’,[‘ngRoute’])
Creareroutedibase
Comesappiamo,perconfigurareilrouterall’internodiAngularJS,ènecessarioun
modulo.NelCapitolo3neabbiamocreatounoalfinedisviluppareunfiltro
personalizzato.Possiamoutilizzarequestostessomoduloperleroute.
Leroutevengonocreatenelmetodoconfigdelmodulodell’applicazione:
angular.module(‘contactsMgr’,[‘ngRoute’])
.config(function($routeProvider){
})
Ilmetodoaccettaunafunzioneanonimaincuiinserireilservizio$routeProvider
necessario,chepresentasoltantoduemetodi:wheneotherwise.Peraggiungereuna
route,utilizziamoilmetodowhen,cheaccettadueparametri:ilpercorsodellastringae
leopzionidellarouteinquantooggetto:
angular.module(‘contactsMgr’,[‘ngRoute’])
.config(function($routeProvider){
$routeProvider.when(‘/’,{});
})
Nell’oggettodelleopzionidellaroutesonopresentidueproprietàcheci
interessano:controlleretemplateUrl.Laproprietàcontrollerchiamauncostruttore
esistenteonedefinisceunonuovotramiteunafunzioneanonima.La
controller
proprietàtemplateUrlconsentedidefinireilpercorsoaunfileHTMLcheospiterà
l’interomarkupdellavista.Inalternativapotremmodefinireiltemplatedirettamente
all’internodell’oggettoroute.Tuttavia,cosìfacendo,lasituazionesipotrebbeben
prestocomplicare;questasoluzioneèconsigliabilesolopertemplatecontenentiunao
duerighe.
Esaminiamolaroutechedefiniremoperlapaginaindex:
$routeProvider.when(‘/’,{
controller:‘indexCtl’,
templateUrl:‘assets/partials/index.html’
});
IlpercorsodeltemplateèrelativoalfileHTMLdibase,quindiincludeladirectory
assets.PossiamoprocedereecreareiltemplateHTML.InAngularquestielementi
vengonochiamatipartialeliutilizzeremopertutteleviste.
L’argomentocontrollerall’internodellarouteèfacoltativo,mal’abbiamoincluso
perchéciserviràperl’applicazione.Creiamoilcontrollercheciconsentedi
svilupparemodelliefunzionisoltantoperlavistaindex.
Nelfilecontroller.jspossiamoconcatenarloallafine:
.controller(‘indexCtrl’,function($scope){
});
Aggiungiamolasecondarouteconilmetodoconfig.Ospiteràilformper
aggiungereicontatti:
$routeProvider.when(‘/’,{
controller:‘indexCtl’,
templateUrl:‘assets/partials/index.html’
})
.when(‘/add-contact’,{
controller:‘addCtl’,
templateUrl:‘assets/partials/add.html’
});
Comenelcasodeicontroller,possiamoconcatenareleroute.Noncirestache
creareicontrollereipartialopportuni:
.controller(‘addCtl’,function($scope){
});
L’ultimaoperazionecheènecessariosvolgereprimacheAngularrendaoperativo
ilrouterconsistenell’includerenellapaginaladirettivang-view,cheinserisceilpartial
definitonellaroute:
<divclass=”container”>
<ng-view></ng-view>
</div>
NOTA
Èpossibileincludereng-viewsoltantounavoltaperpagina.
Sipuòincluderequestadirettivacomesuoelemento.Quiabbiamopreferito
includerlacomeunelementonelfileindex.htmlall’internodellaradice.Seèpresente
qualcosanelcontenitore,cancellateloesostituiteloconng-view.
Seapriteilprogettonelbrowser,vedretechelarouteèstataaggiuntaall’URLedè
precedutadalsimbolo#.PurtropposeusereteChrome,probabilmenteipartialnon
riuscirannoacaricarsi.Seapritelaconsole,vedreteunerroresimilealseguente:
CrossoriginrequestsareonlysupportedforHTTP.
Esistonoduesoluzioni.Possiamocaricareilcodicesuunserverweboppure,con
Chrome,possiamoavviareilbrowserutilizzandounflagperconsentirerichieste
cross-originperilprotocollofile://inambienteOSXoperc:/inambiente
Windows.
InOSX,eseguitequestocomandonelterminale:
open-a‘GoogleChrome’--args-allow-file-access-from-files
InaltrisistemibasatisuUnixeseguiteilseguente:
google-chrome--allow-file-access-from-files
InWindows,ènecessariomodificareilcollegamentosuldesktopperaggiungere
unflagallafinedelladestinazione:
C:\...\Application\chrome.exe--allow-file-access-from-files
SenonvoleteeseguireChromeconunflag,potetefargirarel’appdigestionedei
contattisuunserverweb.PotresteservirvidiquellointegratoinPythonoPHP,odi
un’appveraepropriacomeMAMPoWAMP.
Modificateladirectorydelprogettoedeseguiteilseguentecomandopertestare
l’applicazionesuunserverwebPython:
python-mSimpleHTTPServer8000
Poteteaccederealocalhost:8000nelbrowserpervisualizzarel’app.Inalternativa,se
preferiteeseguireunserverwebintegratoinPHP,potetefarlonelseguentemodo:
php-Slocalhost:8000
Routeconparametri
Dopoaverimpostatopiùroute,dobbiamocapirecomeincludereiparametrialloro
interno.Questoèunaspettoimportanteperconsentireuncertodinamismoall’interno
dell’appdigestionedeicontatti;peresempio,liutilizzeremopervisualizzareun
contattospecificofacendoriferimentoaunIDnumericooaunindice.
Èfacileinserirli;èsufficienteaggiungereunsegnapostoconduepuntiseguitidal
nomedelparametrocheintendiamocreare.Osserviamolaroutecherealizzeremoper
visualizzareilcontatto.Possiamodinuovoconcatenarlaalleroutegiàesistenti:
.when(‘contact/:id’,{
controller:‘contactCtl’,
templateUrl:‘assets/partials/contact.html’
});
Possiamoaggiungerefacilmentenelcontrollerquantiparametridesideriamo.Sarà
sufficienteinserireunservizioepotremoaccedereatuttiiparametridellaroutecome
oggetti:
.controller(‘contactCtl’,function($scope,$routeParams){
console.log($routeParams);
});
Seaccedetealocalhost:8000/#/contact/1eapritelaconsole,vedreteiparametridella
routeregistraticomeoggettoJS.
Ciòsignificachepossiamoaccedereaqualsiasiproprietàdell’oggettotramitela
sintassistandard:
$routeParams.id;
Routedifallback
L’ultimaroutedaconfigurareèquellacheverràvisualizzataquandononsitrova
nessunaroutecorrispondente.Potrestecreareunapagina404,mavediamocomeè
possibilereindirizzareunarouteinvecedivisualizzareuntemplate.
Percreareunaroutedifallback,utilizziamoilsecondometodochecioffreil
servizio$routeProvider:otherwise:
.otherwise({
redirectTo:‘/’
});
Inquestomodo,selarouterichiestanoncorrispondeanessunadiquelledefinite
nelrouter,Angularciriportaallapaginadell’index.
RoutinginHTML5oeliminazionedel
simbolo#
Oraabbiamoconfiguratotutteleroutefondamentaliepossiamoaccedereapartial
distintiperognuna.Èunottimorisultato,ancheseleroutecheseguonoilsimbolo#
nell’URLnoncipiacciono.Fortunatamenteesisteunsistemafacileperrimediare,
attivandociòcheAngulardefiniscehtml5Mode.
QuestamodalitàconsenteadAngulardisfruttareilpushStateneibrowsermoderni
fornendoalcontenutounfallbackperibrowserlegacy,comeIE8.
AbilitareHTML5Mode
Perconsentirequestanuovamodalità,riesaminiamoilmetodoconfig.Come
abbiamofattoinprecedenza,dobbiamoinserirviunservizio:
.config(function($routeProvider,$locationProvider){
...
$locationProvider.html5Mode(true);
})
Oraabbiamoinseritounsecondoservizio:$locationProvider,checiconsentedi
sfruttareilmetodohtml5Mode,cheaccettaunvalorebooleanoperattivarloo
disattivarlo.
Ilserviziooffreunsecondometodo,eanchesenonneapprofitteremonelcorso
dellosviluppodell’app,èopportunoconoscerlo.IlmetodohashPrefixpermettedi
aggiungerenell’URLunprefissodopoilsimbolo#.Peresempio,potremmo
aggiungereunpuntoesclamativoetrasformareilprefissoinunhashbang(#!):
$locationProvider.hashPrefix(‘!’);
Laprossimafiguramostral’URLdell’applicazioneesuddividel’indirizzonelle
diversesezionidellaroute.
Collegareleroute
Collegareleroutenonèdiversodacollegarelepaginesuunsitoweb.Utilizziamo
untagàncorae,invecedicollegarelapagina,colleghiamolaroute.
Peresempio,sevolessimocollegareilpulsanteAddContactnellabarradi
navigazione,dovremmoimmetterequestocodice:
<ahref=”/add-contact”>AddContact</a>
Alclicsullink,Angularvisualizzerebbeautomaticamenteilpartialcorrettoe
cambierebbeanchel’URL.Seavetedecisodinonutilizzarehtml5Mode,potete
comunqueeffettuareilcollegamentomedianteun’àncora,mal’attributohrefsarà
leggermentediverso,perchéènecessarioaggiungereilsimbolodelcancelletto:
<ahref=”#/add-contact”>AddContact</a>
Quiz
1.
2.
3.
4.
5.
Qualefileomodulodobbiamoincludereperconsentireilrouting?
Qualemetodoutilizziamopercreareleroute?
Checosadobbiamoinserirenelmetodoperriuscireacreareunaroute?
Inchemodocreiamounaroute?
Checosapossiamoutilizzarequandonessunaroutecorrispondealpercorso
corrente?
6. Inchemodopossiamoeliminareilsimbolo#nell’URL?
Riepilogo
Inquestocapitoloabbiamotrasformatol’applicazione,edaun’appconun’unica
paginasiamopassatiaunamultipaginaemultiroutenellaqualesvilupperemoil
nostrogestoredicontatti.Abbiamoiniziatoaprogettareleroutefondamentali
dell’appprimadiinstallareilmodulonecessario.
Abbiamovistocomeèpossibileutilizzarenelmoduloilmetodoconfigper
impostareleroute,eperfarloabbiamoinseritoilservizio$routeProvidereutilizzatoi
metodiwheneother.Cosìabbiamoimpostatoroutestaticheedinamichecontenenti
parametri.
Infineabbiamoanalizzatocomeeliminareilsimbolo#dall’URLmedianteil
diHTML5ecollegareentrambiitipidiroute.Nelprossimocapitolo
pushState
popoleremoipartialconilayoutchesvilupperemograzieaBootstrap.
Capitolo5
Leviste
NelCapitolo4abbiamovistocomeèpossibiletrasformarel’applicazioneinuna
webappconpiùrouteepiùviste.AbbiamosfruttatoilrouterdiAngulareimpostato
ipartialpertuttelevistefondamentali.Oraèilmomentodicrearelevistemediante
Bootstrapcosìdapoterpopolarel’appconidati.Analizziamounoperunoipartial.
PopolarelavistaIndex
LavistaIndexèciòchevienevisualizzatoquandoapriamol’app.Forseconviene
elencarequituttiicontatti;infattiavremobisognodiunrapidoaccessoalle
informazionimemorizzate.
Unatabellapuòessereun’opzioneefficace,maènecessariorifletteresuciòche
verràmemorizzatonelgestoredeicontatti.Eccounalistadeipossibiliitem:
Nome
Indirizzoe-mail
Numeroditelefono
Indirizzo
Sitoweb
Note
Gravatar(unserviziodiavatarriconosciutoalivellomondialesviluppatodai
creatoridiWordPress)
NonoccorrevisualizzaretuttequesteinformazioninellavistaIndex.Non
dimenticatechesaràanchepresentel’opzioneperscorrereicontattiequindiavremo
lapossibilitàdivisualizzarepiùinformazioni.
Unasceltaragionevolesembraquelladivisualizzarenome,indirizzoe-maile
numeroditelefonoinunatabellaconunlinksucuifareclic.
ApriteilpartialdellavistaIndex;sitrovainassets/partials/index.html.Perora
questofileèvuoto;aggiungeteall’iniziol’headerdellapagina:
<divclass=”page-header”>
<h1>AllContacts</h1>
</div>
Nondobbiamoracchiuderloinuncontenitorepoichéilpartialènidificatonelfile
principaledell’appindex.htmlsullarouteeviabbiamogiàinclusoilcontenitore:
<table>
<thead>
<tr>
<th>Name</th>
<th>EmailAddress</th>
<th>PhoneNumber</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
<tr>
<td>KaranBromwich</td>
<td>[email protected]</td>
<td>0123456734</td>
<td><ahref=”#”>View</a></td>
</tr>
<tr>
<td>DeclanProud</td>
<td>[email protected]</td>
<td>01234567890</td>
<td><ahref=”#”>View</a></td>
</tr>
</tbody>
</table>
Èunastrutturafunzionale,ancheselapaginanonhaunbell’aspetto.Comela
maggiorpartedeicomponenti,Bootstrappermettediapplicareglistilialletabelle,
ancheseènecessarioincludereunaclasseaggiuntivaperrenderlidisponibili.
AggiungetelaclassetablealtagdiaperturadellatabellaeBootstrapmetteràsubito
unpo’d’ordineaggiungendoibordinecessariefacendoinmodocheoccupil’intera
larghezza.
Sonopresentiinoltrealcuneclassisecondariecheèpossibileincludereper
vivacizzareunpo’latabella.
:aggiungeunbordoattornoatuttiilatidellatabellaedellecelle.
table-bordered
:aggiungeunosfondogrigioarighealternateperfacilitarelalettura.
table-striped
:modificalosfondodellarigaall’hover.
table-hover
:eliminaunapartedelpaddingsuperioreeinferioreinmododa
table-condensed
ridurrel’altezzadellatabella.
Oltreaquesteclassi,neesistonoaltrecheèpossibileapplicareallerigheoalle
celleechecoloranolosfondodellerighepercontestualizzarle.
:aggiungeallarigalostatohover.
active
:coloralosfondodiverde,perindicarecheun’azionehaavutosuccesso.
success
:attiral’attenzionesuunarigaosuunacellasegnalandoun’informazione.
info
:indicachepuòesserenecessariaun’azioneecoloralacelladigiallo.
warning
:richiamal’attenzionesuunerroreosuunproblema.
danger
Perilmomentoaggiungeremolaclassetable-striped,mastaavoisperimentarele
altreclassiintegrate.
Latabellainiziaadavereunbell’aspetto.Vedreteperòchesuunoschermodi
dimensionipiùpiccolerisultatagliataorizzontalmente.Perrimediareènecessario
racchiuderlainunaltroelementochecipermettadieffettuareloscorrimentosu
schermipiùpiccoli:
<divclass=”table-responsive”>
…
</div>
Orahaunaspettonettamentemigliorepoichéilcontenutononvienepiùtagliatoe
illayoutresponsivenonsiinterrompe.L’ultimaoperazionechefaremosullatabellaè
trasformareillinkdellavistainunpulsante.Bootstrapoffrenumerosistiliperi
pulsantidicuipossiamoavvalerci.
Tuttiipulsantisonocombinazionidelleclassirelative:
alpulsantepredefinito;
alcontesto;
alledimensioni.
Tutteinsiemeoffronoilcontrollochecioccorrealfinediscegliereilpulsante
giustoperl’occasionegiusta.Combiniamolepercreareunpulsantechebensiintegri
nellatabella:
<ahref=”#”class=”btnbtn-defaultbtn-xs”>View</a>
Laprimaclasseapplicaalcunistilipredefinitiaipulsanti;lasecondaassegnail
colore(inquestocaso,quellopredefinitoèilbianco)el’ultimadefiniscele
dimensioni.Inalternativapotremmoutilizzareunatraleclassidescrittenella
prossimatabellapermodificareilcoloredelpulsante.
Nomedellaclasse
Descrizione
btn-default
Pulsantebiancoconunbordogrigio
btn-primary
Pulsanteblu
btn-success
Pulsanteverde
btn-info
Pulsanteazzurro
btn-warning
Pulsantearancione
btn-danger
Pulsanterosso
btn-link
Pulsanteconlostilediunlink
Oltreaimpostareledimensionipredefinite,altretreclassilemodificano.Nel
codiceprecedenteabbiamogiàutilizzatobtn-xsperrimpiccioliremoltoilpulsante,
mapotremmoutilizzareanchebtn-smperfareinmodocheappaiaunpo’piùpiccolo
diquellopredefinitoobtn-lgperingrandirlo.
LavistaIndexècompletaedèprontaperesserepopolata.Nell’immagineseguente
osserviamoilrisultato.
PopolarelavistaAddContact
ÈchiarociòdicuiavremobisognonellavistaAddContact:unformcheci
consentadidigitareleinformazionirichieste.FortunatamenteBootstrapcioffre
moltocontrollonell’organizzazionedeicampi.Abbiamogiàelaboratoidatiche
memorizzeremo;orasitrattasolodiindividuarequalèiltipodicampomigliore:
Name:campoditesto
Emailaddress:campoe-mail
Phonenumber:campoperilnumeroditelefono
Address:areaditesto
Website:campoditesto
Notes:areaditesto
Gravatar:N/D
SiccomeGravatarusal’indirizzoe-mailperfornirel’immagine,quinondobbiamo
richiederealcunainformazioneaggiuntiva.Abbiamointuttoseicampi;quindidue
colonnesonosufficienti.
Innanzituttoapriamoilformeaggiungiamoalsuointernolecolonne.Abbiamogià
laclassedelcontenitore;cioccorronounanuovarigaeleduecolonne.Come
abbiamovistonelCapitolo2,ilsistemaagrigliadiBootstrapcontiene12colonne;
ricordiamoceloquandocreeremoillayout:
<form>
<divclass=”row”>
<divclass=”col-sm-6”>
</div>
<divclass=”col-sm-6”>
</div>
</div>
</form>
Comeabbiamofattoinprecedenza,utilizziamoilprefissocol-smperfareinmodo
chelecolonnesicomprimanosuitabletosuidispositivimobilipiùpiccoli.
Potremmoinserireleetichetteegliinputdirettamentenellecolonne,maperuna
spaziaturaottimale,ènecessarioracchiudereglielementiinuntagdivconlaclasse
:
form-group
<divclass=”form-group”>
<labelfor=”name”>Name</label>
<inputtype=”text”id=”name”>
</div>
PersfruttareglistilidiBootstrapnegliinput,aggiungiamolaclasseform-control.Se
aggiungessimolaclassecontrol-labelall’etichetta,applicherebbeunpo’dipadding
aggiuntivo:
<divclass=”form-group”>
<labelfor=”name”class=”control-label”>Name</label>
<inputtype=”text”id=”name”class=”form-control”>
</div>
Aggiungiamoall’internoglielementirestanti:ilnome,ilnumeroditelefonoe
l’indirizzonellacolonnadisinistrael’indirizzoe-mail,ilsitowebelenoteinquella
didestra.
Formorizzontali
Ilformpresentamoltobene.Senonvipiaccionoleetichetteinalto,potete
collocarleasinistraconunaleggeramodifica.Aggiungendolaclasseform-horizontalal
tagdiaperturadelform,leclassiform-groupsicomportanocomelerighediuna
griglia;pertantopossiamoapplicareleclassidellecolonneaglielementialloro
interno.Eccoilcodiced’esempio:
<divclass=”form-group”>
<labelfor=”name”class=”col-sm-4control-label”>Name</label>
<divclass=”col-sm-8”>
<inputtype=”text”id=”name”class=”form-control”>
</div>
</div>
Dopoaverinclusolaclasseform-horizontal,vedretecheèpossibileaggiungere
all’etichettaleclassiapplicabiliallecolonnediBootstrap.Siccomeform-control
impostalalarghezzasu100%,siadattaall’elementogenitorechedobbiamo
racchiudereinunelementoaggiuntivo.Abbiamoinclusoanchelaclassecontrol-label
equindil’etichettaècentrataverticalmente.
Ilformapparemoltomenodisordinatograzieallaclasseform-horizontal;
proseguiamoeracchiudiamotuttigliinputnell’elementoform-control.
Talvoltavorretefornireall’utentemaggioriinformazionidiquellestrettamente
necessarie.Lepoteteincluderesottol’inputcorrispondenteutilizzandountagspan
associatoallaclassehelp-block:
<divclass=”form-group”>
<labelfor=”notes”class=”col-sm-4control-label”>Notes</label>
<divclass=”col-sm-8”>
<textareaid=”notes”class=”form-control”></textarea>
<spanclass=”help-block”>Anyadditionalinformationaboutthe
contact.</span>
</div>
</div>
Noncirestacheaggiungereunpulsantediinvio.Nelleversioniprecedentidi
Bootstrap,eraracchiusoinunelementoconlaclasseform-actions.InBootstrap3,
invece,èsufficienteutilizzarelostessoform-groupcheabbiamousatofinora.Se
applicheretelostileform-horizontal,dovretedistanziarelecolonne:
<divclass=”form-group”>
<divclass=”col-sm-offset-4col-sm-8”>
<buttonclass=”btnbtn-primary”>AddContact</button>
</div>
</div>
Poichél’etichettaoccupaquattrocolonne,ènecessariostaccareilpulsantedello
stessospazioinmodochesiabenallineato.
Amepiacevailcontrastocheoffrivalavecchiaclasseform-actions.Fortunatamente
possiamoottenereunrisultatosimilericorrendoalcomponentewelldiBootstrap.In
questocasoabbiamospostatolaclasseform-groupchecontieneilpulsantesubmit
subitosottolarigagiàesistente(ricordatevicheform-horizontalfasìchetuttiigruppi
sicomportinocomerighe)eabbiamoaggiuntolaclassewell:
<divclass=”form-groupwell”>
<divclass=”col-sm-offset-2col-sm-10”>
<inputtype=”submit”class=”btnbtn-primary”value=”Add
Contact”>
</div>
</div>
Infine,percompletarelapagina,assegneremolostessoelementopage-headerche
abbiamoinclusonellavistaIndexelocollocheremosubitoall’iniziodelmarkup:
<divclass=”page-header”>
<h1>AddContact</h1>
</div>
Ilrisultatofinalesaràilseguente.
PopolarelavistaViewContact
L’ultimopartialchedobbiamopopolareèlaschermataincuiverràvisualizzatoil
contatto.Siamostatitentatidiinserirlocomeunform,macipiacel’ideadiaveredel
testostatico,chepossiamodecideredimodificareinalcuneparti.
DovremovisualizzarelestesseinformazionicheabbiamoimmessonellavistaAdd
Contact,oltrealgravatar.
Titoloegravatar
Innanzituttoincluderemounaclassepage-header.Ospiteràuntagh1conall’internoil
nomedelcontatto:
<divclass=”page-header”>
<h1>DeclanProud</h1>
</div>
Quiincluderemoancheilgravatar:vediamocome.Perorautilizzeremoalcune
immaginisegnapostoprovenientidahttp://placehold.it.Forsenonconoscetequesto
sito:quitrovatesegnapostoditutteledimensioni.Abbiamobisognodiun’immagine
50px×50px,cheinseriremomedianteilcodiceseguente:
<imgsrc=”http://placehold.it/50x50”>
Modificatenepureledimensioniavostropiacimento.Poteteinserirlasubitoprima
delnomedelcontattoneltagh1.Vogliamoancheaggiungerelaclasseimg-circle:
<divclass=”page-headerrow”>
<h1><imgsrc=”http://placehold.it/50x50”class=”img-circle”>
DeclanProud</h1>
</div>
Questaclasseèunadelletredisponibiliperapplicareunpo’distilealleimmagini;
aggiungeunarrotondamentodegliangoliparial50%percreareuncerchio.Èanche
disponibileimg-rounded,chearrotondagliangoli,oltreaimg-thumbnail,cheaggiungeun
belbordodoppio.
Laclasseform-horizontal
Siamofortunatiperchépossiamoriutilizzarebuonapartediquellocheabbiamo
fattonellavistaAddContact.Laclasseform-horizontalsicomporteràaltrettantobene
seèpresentedelcontenutostaticoalpostodeicampi.Questapaginadiventeràin
seguitolaschermatadiediting,oltreaesserelaschedadelcontatto;pertantoèutile
poterutilizzarelaclasseperentrambeleviste.
Questavoltaperòricorreremoauntagdivenonaunelementodelformper
racchiudereillayoutconduecolonne:
<divclass=”form-horizontal”>
<divclass=”row”>
<divclass=”col-sm-6”>
...
</div>
<divclass=”col-sm-6”>
...
</div>
</div>
</div>
Apartequestapiccolamodifica,illayoutèidentico.Abbiamolastessarigaelesei
colonnedellavistacreateinprecedenza.
Possiamoancheutilizzareleclassiform-groupoltrealleclassicontrol-label,checi
consentonodistrutturareefficacementel’etichettasenzausareunalistaounatabella,
comepossiamovederediseguito:
<divclass=”form-group”>
<labelfor=”name”class=”col-sm-4control-label”>Name</label>
<divclass=”col-sm-8”>
<p>DeclanProud</p>
</div>
</div>
Tuttaviaselocarichiamonelbrowser,vedretechel’etichettaeilcontattononsono
benallineati.Perrimediarepossiamoincludereun’altraclasseneltagparagraph:
<pclass=”form-control-static”>DeclanProud</p>
Aggiungetelaintuttiicampi.Abbiamoripresolostessolayoutprecedentecon
nome,numeroditelefonoeindirizzonellacolonnadisinistraeindirizzoe-mailesito
webenoteinquelladidestra.
Siamosoddisfattidiquestolayout.Tuttavia,quandoloridimensioniamo,sembra
cherimangamoltospaziobiancoadestra.Sarebbepreferibilemodificarelecolonne
principaliinmodochesianovisibiliinquestolayoutanchesudispositivimobili.
Nellarigasostituiamolaclassecol-sm-6conlaseguente:
<divclass=”col-xs-6”>
…
</div>
Inquestomodootterremoduecolonnesudispositivipiùpiccoliedeviteremoil
problemadellospaziobiancoineccesso.
Diseguitopoteteosservareilrisultato:
Quiz
1. Perchénonènecessarioincludereuncontenitoreneipartial?
2. Oltreatable,qualiclassièpossibileaggiungereaunatabellaperattribuireun
po’distile?
3. Comepossiamocreareungrandepulsanteazzurro?
4. Inchecosadevonoessereracchiusileetichetteegliinput?
5. Inchemodolaclasseform-horizontalmodificailform?
6. Checosautilizziamopervisualizzareunmessaggiodiaiutoaggiuntivoperun
campodelform?
7. Qualitreclassipossiamoapplicarealleimmaginiequalirisultatiproducono?
Riepilogo
Oraabbiamotrelayoutprincipalicompleti.Abbiamoottenutoquestorisultato
senzascrivereunasolarigadiCSSsfruttandoglistilieicomponentifondamentalidi
Bootstrap.
NellavistaIndexabbiamoesaminatocomeBootstrapincludestilipredefinitie
applicaleclassisecondarieperaggiungereunpo’distile.ÈunpatterncheBootstrap
utilizzasempre,el’abbiamovistoinazioneconipulsantiegliinput.
ConlavistaAddContactabbiamoimparatoqualisonoisistemimiglioriper
organizzareunformeabbiamostabilitodiapplicareform-horizontal.Lapotremo
riutilizzarequandocreeremolaschedadelcontatto.
Nelprossimocapitoloimpareremoacollegarelevisteadatidinamici.
UtilizzeremoAngularJSpercrearecontatti,passaredaunoall’altroevisualizzarli
nellatabella,modificarliocancellarli.
Capitolo6
CRUD
FinoraabbiamoesploratoiparadigmidiAngularesviluppatolastrutturadell’app
grazieaBootstrap.Inquestocapitolocibaseremosulleideeeiconcetticheabbiamo
esaminatonelcorsodellibroperrendereoperatival’app.
CRUDèl’acronimodiCreate,Read,UpdateeDelete(creare,leggere,aggiornare
ecancellare),tuttociòcheservepersviluppareun’appdigestionedeicontatti
efficiente.
Analizzeremoogniletteradiquestoacronimoevedremocomeèpossibilesfruttare
pienamenteAngular.
Read
Perilmomentoignoreremolapartecreativaepasseremosubitoallaletturadei
dati.Neutilizzeremodifittizisottoformadiunarraydioggettinelcontroller.
Nelprossimolistatovediamocomevengonoformattatieciòchedobbiamo
includere.
.controller(‘indexCtl’,function($scope){
$scope.contacts=[
{
name:‘StephenRadford’,
phone:‘0123456789’,
address:‘123,SomeStreet\nLeicester\nLE12AB’,
email:‘[email protected]’,
website:‘stephenradford.me’,
notes:‘’
},
{
name:‘DeclanProud’,
phone:‘91234859’,
address:‘234,SomeStreet\nLeicester\nLE12AB’,
email:‘[email protected]’,
website:‘declanproud.me’,
notes:‘Somenotesaboutthecontact.’
}
];
})
Siccomeabbiamoassociatol’arrayalloscope,possiamoaccedervidirettamente
all’internodellavista.Medianteladirettivang-repeatesaminatanelCapitolo2,è
possibileavviareilciclodell’arraypervisualizzareicontattinellatabella:
<trng-repeat=”contactincontacts”>
<td>{{contact.name}}</td>
<td>{{contact.email}}</td>
<td>{{contact.phone}}</td>
<td><ahref=”/contact/{{$index}}”class=”btnbtn-defaultbtnxs”>View</a></td>
</tr>
Questocodicehaunaspettomoltofamiliare.Abbiamoassociatoladirettiva
all’elementocheintendiamoripetere(inquestocasolarigadellatabella)eutilizzato
ledoppieparentesigraffepervisualizzareidatideicontatti.
Nellinkosserveretequalcosadileggermentediverso.Ladirettivang-repeatci
consentediottenerel’indicedell’oggettocorrenteutilizzando$index.Ricordereteche
laroutedelsingolocontattoaccettaunnumeroID.Utilizzeremol’indicedell’array
comeIDinmododapoteraccederefacilmentealcontattoquandoneavremo
bisogno.
Condividereidatitraleviste
Ciòcheabbiamorealizzatofinorapresentaunproblema.Anchesefunziona
perfettamenteperlavistaIndex,èdeltuttoinutilequandointendiamovisualizzareun
unicocontatto.Infattiilnostroarraydicontattiècontenutonelcontrollerdell’indice
epertantononpuòesserecondiviso.
Condividereidatimediante$rootScope
Sonodisponibiliduesistemipercondividereidatitraleviste:ilprimoè$rootScope.
Cosìcomeesisteunoscopeperognivista,l’applicazionestessanehauno,loscope
radice(rootscope),chefunzionaallostessomodo.
Perutilizzarloènecessarioinserireunnuovoserviziochesichiama$rootScope:
.controller(‘indexCtl’,function($scope,$rootScope){
$rootScope.contacts=[
{
name:‘StephenRadford’,
phone:‘0123456789’,
address:‘123,SomeStreet\nLeicester\nLE12AB’,
email:‘[email protected]’,
website:‘stephenradford.me’,
notes:‘’
},
{
name:‘DeclanProud’,
phone:‘91234859’,
address:‘234,SomeStreet\nLeicester\nLE12AB’,
email:‘[email protected]’,
website:‘declanproud.me’,
notes:‘Somenotesaboutthecontact.’
}
];
$scope.contacts=$rootScope.contacts;
})
Possiamoassociarenellostessomododeglielementiall’oggettoscoperadice.In
questocasol’abbiamoaggiuntoanchealloscopedellavista,maavremmopotuto
modificarefacilmentelavistaperaccederedirettamentealloscoperadice:
<trng-repeat=”contactin$root.contacts”>
Peraccederedallavistaaqualsiasielementonelloscoperadice,èsufficiente
anteporrealnomedelmodelloilprefisso$rootseguitodaunpunto.
Questometododicondivisionedeidatinonsfruttatuttiglistrumentichecioffre
Angularegeneramoltaconfusionenell’applicazione.
NOTA
Ilricorsoa $rootScope,esoprattuttoilsuoaccessodallavista,èunapraticasconsigliataepuò
ostacolare la gestione del progetto; per condividere i dati è preferibile utilizzare un controller a
livellodell’applicazioneounserviziopersonalizzato.
Creareunserviziopersonalizzato
Unasoluzionemigliorepercondividereidatitralevisteèsviluppareunsistema
personalizzato.Unservizioèinsostanzaunaclasseallaqualeèpossibileaccedere
dopoaverlainseritaneicontroller,cosìcomeabbiamovistocon$scope.
InAngularJSneesistonotretipi:.service(),.factory()e.value().Tuttisono
singleton,deidesignpatternchefannoinmodochediessiesistaunasolaistanzasu
unoggetto.Liesamineremotuttiprimadisvilupparneuno.
Value
Ilpiùessenzialedeitreèilmetodovalue.Aggiungiamoloalmodulo:
.value(‘demoService’,‘abc123’);
Comepossiamovedere,sitrattadiunserviziosemplicissimocheaccettadue
parametri:ilnomedelserviziocheintendiamocreareeilvalorechedovrebbeavere.
Questidatipossonoesserecondivisinell’applicazioneinserendolineicontroller:
.controller(‘indexCtl’,function($scope,demoService){
$scope.demo=demoService;
});
Èmoltosemplice,maoffreunmodorapidoefacilepercondividereidati.
Potremmovolercondividere,peresempio,unachiaveAPItramolteplicicontroller.Il
metodovaluesarebbelasoluzioneideale.
Factory
èsempliceedefficace,manonoffremoltefunzioni.Inveceilserviziofactory
Value
diAngularcipermettedichiamarealtriservizitramitelaDI(dependencyinjection),
econsenteanchel’inizializzazionedelservizioel’inizializzazionelazy.Riscriviamo
l’esempiocontenentevaluesostituendoloconfactory:
.factory(‘demoService’,functiondemoServiceFactory(){
return‘abc123’;
});
NOTA
Inquestocasoabbiamochiamatolafunzione[serviceName]Factory.Anchesenonènecessario,
èopportunofarloperchéconsenteundebuggingmoltopiùsemplicenell’analisidellostack.
Funzionerà,maperun’appcosìessenziale,èunsistemafintroppoelaboratoedè
preferibileutilizzarevalue.Comeabbiamoscoperto,factorypuòchiamarealtriservizi.
CreiamoneunaltroeinseriamoildemoServiceiniziale:
.factory(‘anotherService’,function[‘demoService’,
anotherServiceFactory(demoService){
returndemoService;
});
IlserviziofactorypuòanchemodificareilvalorefornitocidademoService,maviene
utilizzato,piùfrequentemente,perconnettersiaun’API.Comenelcasodivalue,
restituiscetipiJavaScript.Eccocomerestituisceunoggetto:
factory
.factory(‘anotherService’,function[‘demoService’,
anotherServiceFactory(demoService){
return{
connect:function(){
}
};
});
Ilmetodoconnectdefinitonell’oggettorestituitosaràdirettamenteaccessibile
quandoinseriamoilservizionelcontroller:
.controller(‘indexCtl’,function($scope,anotherService){
anotherService.connect();
});
Service
L’ultimotipodiserviziopresenteinAngularJSvienechiamatoservice(unnome
chegeneraconfusione,comeilfiltroFilter).Produceunsingletonalparidivaluee
,invocandoperòuncostruttoremediantel’operatorenew.Anchequestopuò
factory
generareconfusione;cerchiamodifarechiarezza.
NelseguentecasoèpresentelafunzionedelcostruttoreNotifiercheèunsemplice
aliasperilmetodowindow.alertdelbrowser:
functionNotifier(){
this.send=function(msg){
window.alert(msg);
}
}
Potremmoaggiungerladirettamentenelcontrollerechiamarlainquestomodo:
varnotifier=newNotifier();
notifier.send(‘Hello,World’);
Anchesefunzionerebbe,nonèlasoluzioneottimale.Esevolessimoutilizzare
nuovamentelafunzioneNotifierinunaltrocontroller?Comesappiamo,possiamo
condividerequestotipodielementiricorrendoaunserviziodiAngularJS.Con
factory,otterremmoqualcosadelgenere:
.factory(‘notifierService’,functionnotifierFactoryService(){
returnnewNotifier();
});
Nientemale,maquestoèproprioiltipodirisultatoperilqualeèstatoconcepito
service.Creaun’istanzaerestituisceunoggetto:
.service(‘notifierService’,Notifier);
Eccofatto!Angularprenderàilcostruttore,necreeràun’istanzaerestituirà
l’oggetto.Possiamoinserirlonelcontrollereutilizzarlocomeprevisto:
.controller(‘indexCtl’,function($scope,notifierService){
notifierService.send(‘Hello,World’);
});
Sviluppareunservizio
Potremmoutilizzareserviceofactorypercondividereicontattitraicontroller,ma
siccomenoncreiamoalcunaistanza,serviamocidifactory:
.factory(‘contacts’,functioncontactsFactory(){
})
Comeabbiamovisto,èsolol’oggettorestituitoacontenereimetodieleproprietà
pubbliche.Èpossibilesfruttarequestoaspettoincludendol’arraydicontatti
privatamente,consentendochesiarestituitoeaccessibilesoltantodametodipubblici:
varcontacts=[
…
];
Ilservizioincluderàduemetodiperleggereicontatti.Ilprimorestituiràl’intero
array,mentreilsecondounsolooggettocontattochedipendedall’indiceassegnatoa
esso.Restituiamol’oggettocontenentequestiduemetodievediamoinchecosa
consistono:
return{
get:function(){
returncontacts;
},
find:function(index){
returncontacts[index];
}
};
Entrambisonofunzionimolto“dibase”.Ilmetodogetrestituiscel’interoarray,
mentreilmetodofindaccettaunindicecherestituisceilcontattorichiesto.
Assembliamoiltuttoeosserviamoilrisultato:
.factory(‘contacts’,function(){
varcontacts=[
{
name:‘StephenRadford’,
phone:‘0123456789’,
address:‘123,SomeStreet\nLeicester\nLE12AB’,
email:‘[email protected]’,
website:‘stephenradford.me’,
notes:‘’
},
{
name:‘DeclanProud’,
phone:‘91234859’,
address:‘234,SomeStreet\nLeicester\nLE12AB’,
email:‘[email protected]’,
website:‘declanproud.me’,
notes:‘Somenotesaboutthecontact.’
}
];
return{
get:function(){
returncontacts;
},
find:function(index){
returncontacts[index];
}
};
})
Orapossiamoinserirequestoservizionelcontroller,cosìcomeabbiamofattocon
$scopee$rootScope:
.controller(‘indexCtl’,function($scope,contacts){
$scope.contacts=contacts.get();
})
Abbiamoanchepassatoalnuovometodoicontattinelloscope,equestohamesso
ordinenelcontroller.Seaggiungiamoconsole.logalserviziodicontatti,vedremoche
nonèpossibilevedereidatigrezzi,masoloiduemetodiaiqualiabbiamoavuto
accesso.
Utilizzareiparametridellaroute
Oracheilserviziofunzionaadovere,possiamoiniziareapopolarelavista
contenenteununicocontatto.Aquestoscopodovremoestrarrel’IDdallaroute.
NelCapitolo4abbiamoesaminatorapidamentecomeèpossibileaccedereai
parametridellaroute,maricapitoliamol’argomento.Innanzituttoènecessario
inserireunaltroservizionelcontrollerdell’unicocontatto:$routeParams.Questo
serviziorestituisceunoggettocontuttiiparametrinellaroutecomeproprietà
separate:
.controller(‘contactCtl’,function($scope,$routeParams,
contacts){
$scope.contact=contacts.find($routeParams.id);
});
Inquestocasoabbiamoaccessoalparametroidcheutilizziamopertrovareil
contattocorrettomedianteilserviziocheabbiamocreatoinprecedenza.Lopassiamo
allavistacreandounnuovomodellosulloscopechiamatocontact.
Estraiamotutteleinformazionipertinentinelpartialcontact.html.Ricordatechetutti
idatisonoproprietàdelcontattodelmodelloepossiamoaccederviinquestomodo:
{{contact.name}}
Funzionatuttotranneperalcunidettagli.Idatiriguardantil’indirizzodovrebbero
rispettareifineriga,ecipiacerebbericavaredinamicamentel’immaginedelgravatar.
Perottenerequestirisultatiènecessariocreareunfiltroeunadirettiva.
Creareunadirettivapersonalizzata
Abbiamosperimentatol’efficaciadelledirettiveefinoranonavevamoalcun
motivopersvilupparneunanuova.Maperincludereilgravatar,ènecessariocrearne
unapersonalizzata.
Cosìcomepericontroller,ifiltrieiservizi,lanuovadirettivadeveessere
associataalmoduloutilizzandoilmetodopertinente:
.directive(‘gravatar’,function(){
})
Comepericontroller,ifiltrieiservizi,ilmetododirectiverichiededueparametri.
Ilprimoèilnomedelladirettiva,ilsecondoèunafunzione.Unadirettivadeve
restituireunoggetto,eleproprietàdell’oggettorestituitodefinisconoil
comportamentodelladirettiva.
Laprimaproprietàcheimposteremoèrestrict;definisceilmodoincuièpossibile
utilizzareladirettiva.Abbiamogiàvistocomeèpossibileavvalersidellamaggior
partedelledirettivecomeattributioelementipersonalizzati,maAngularciconsente
anchediusarleinaltriduemodi.Èpossibileimpostarequestivaloriperlaproprietà
restrict.
:ladirettivapuòessereassociatasoloutilizzandounattributo,<divgravatar>
A
.
</div>
ladirettivapuòessereutilizzatacomeelementopersonalizzato,<gravatar>
E:
.
</gravatar>
:ladirettivapuòessereutilizzataaggiungendolacomeclasseall’elemento,<div
C
.
class=”gravatar”></div>
:consentel’esecuzionedelladirettivaattraversouncommentoHTML,<!--
M
.
directive:gravatar-->
NOTA
Perledirettiveèpreferibileutilizzareattributiedelementirispettoaclassiecommenti.
L’impostazionepredefinitadiAngularèlaprima,comeattributo.Possiamo
utilizzareperòlacombinazionedeivalorimenzionatipermettereapuntoilmodoin
cuiintendiamoutilizzareladirettiva.SelaimpostiamosuAEpotremochiamarla
tramiteunattributoounelementopersonalizzato:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’
}
})
Seènecessario,potremmoanchecreareuntemplateperladirettiva.Puòessere
inclusodirettamentenell’oggettoutilizzandolaproprietàtemplate,oppure,ricorrendo
allaproprietàtemplateUrl,possiamocaricaredall’URIspecificatounfileditemplate
esterno.Siccomequicreiamosoltantountagimg,possiamoaggiungerlodirettamente
nell’oggetto:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’,
template:‘<imgng-src=”{{img}}”class=”{{class}}”>’
}
})
Questotemplatesicomportacomeleviste.Abbiamoaggiuntoduesegnapostoper
l’URIdell’immagineoltrealleclassicheintendiamoincludere.
Perfarfunzionareiltutto,occorreassociareunafunzioneallaproprietàlink
nell’oggetto.Daquièpossibileaccederealloscope,all’elementoalqualeèassociata
ladirettivaeaqualsiasiattributodiquell’elemento:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’,
template:‘<imgsrc=”{{img}}”class=”{{class}}”>’,
link:function(scope,elem,attrs){
}
}
})
Perrecuperarel’immaginedelgravatar,sideveapplicarelafunzionehash
all’indirizzoe-maildelcontattotramitemd5.Purtroppononèunmetodonativodi
JavaScriptedènecessarioincludereunalibreriaseparata.Neabbiamoinclusauna
negliassetriguardantiquestocapitolo,chepotetescaricare;puòessereinclusacome
variabilesingle-line:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’,
template:‘<imgsrc=”{{img}}”class=”{{class}}”>’,
replace:true,
link:function(scope,elem,attrs){
varmd5=function(s){function
L(k,d){return(k<<d)|(k>>>(32-d))}functionK(G,k){var
I,d,F,H,x;F=(G&2147483648);H=(k&2147483648);I=(G&1073741824);d=(k&
1073741824);x=(G&1073741823)+(k&1073741823);if(I&d){return(x^21474
83648^F^H)}if(I|d){if(x&1073741824){return(x^3221225472^F^H)}else{
return(x^1073741824^F^H)}}else{return(x^F^H)}}function
r(d,F,k){return(d&F)|((~d)&k)}function
q(d,F,k){return(d&k)|(F&(~k))}function
p(d,F,k){return(d^F^k)}function
n(d,F,k){return(F^(d|(~k)))}function
u(G,F,aa,Z,k,H,I){G=K(G,K(K(r(F,aa,Z),k),I));return
K(L(G,H),F)}function
f(G,F,aa,Z,k,H,I){G=K(G,K(K(q(F,aa,Z),k),I));return
K(L(G,H),F)}function
D(G,F,aa,Z,k,H,I){G=K(G,K(K(p(F,aa,Z),k),I));return
K(L(G,H),F)}function
t(G,F,aa,Z,k,H,I){G=K(G,K(K(n(F,aa,Z),k),I));return
K(L(G,H),F)}functione(G){varZ;varF=G.length;varx=F+8;vark=(x(x%64))/64;varI=(k+1)*16;varaa=Array(I-1);vard=0;var
H=0;while(H<F){Z=(H(H%4))/4;d=(H%4)*8;aa[Z]=(aa[Z]|(G.charCodeAt(H)<<d));H++}Z=(H(H%4))/4;d=(H%4)*8;aa[Z]=aa[Z]|(128<<d);aa[I-2]=F<<3;aa[I1]=F>>>29;returnaa}functionB(x){var
k=””,F=””,G,d;for(d=0;d<=3;d++){G=(x>>>(d*8))&255;F=”0”+G.toString
(16);k=k+F.substr(F.length-2,2)}returnk}function
J(k){k=k.replace(/rn/g,”n”);vard=””;for(var
F=0;F<k.length;F++){var
x=k.charCodeAt(F);if(x<128){d+=String.fromCharCode(x)}else{if((x>1
27)&&(x<2048)){d+=String.fromCharCode((x>>6)|192);d+=String.fromCh
arCode((x&63)|128)}else{d+=String.fromCharCode((x>>12)|224);d+=Str
ing.fromCharCode(((x>>6)&63)|128);d+=String.fromCharCode((x&63)|12
8)}}}returnd}varC=Array();varP,h,E,v,g,Y,X,W,V;var
S=7,Q=12,N=17,M=22;varA=5,z=9,y=14,w=20;var
o=4,m=11,l=16,j=23;var
U=6,T=10,R=15,O=21;s=J(s);C=e(s);Y=1732584193;X=4023233417;W=25623
83102;V=271733878;for(P=0;P<C.length;P+=16){h=Y;E=X;v=W;g=V;Y=u(Y,
X,W,V,C[P+0],S,3614090360);V=u(V,Y,X,W,C[P+1],Q,3905402710);W=u(W,
V,Y,X,C[P+2],N,606105819);X=u(X,W,V,Y,C[P+3],M,3250441966);Y=u(Y,X
,W,V,C[P+4],S,4118548399);V=u(V,Y,X,W,C[P+5],Q,1200080426);W=u(W,V
,Y,X,C[P+6],N,2821735955);X=u(X,W,V,Y,C[P+7],M,4249261313);Y=u(Y,X
,W,V,C[P+8],S,1770035416);V=u(V,Y,X,W,C[P+9],Q,2336552879);W=u(W,V
,Y,X,C[P+10],N,4294925233);X=u(X,W,V,Y,C[P+11],M,2304563134);Y=u(Y
,X,W,V,C[P+12],S,1804603682);V=u(V,Y,X,W,C[P+13],Q,4254626195);W=u
(W,V,Y,X,C[P+14],N,2792965006);X=u(X,W,V,Y,C[P+15],M,1236535329);Y
=f(Y,X,W,V,C[P+1],A,4129170786);V=f(V,Y,X,W,C[P+6],z,3225465664);W
=f(W,V,Y,X,C[P+11],y,643717713);X=f(X,W,V,Y,C[P+0],w,3921069994);Y
=f(Y,X,W,V,C[P+5],A,3593408605);V=f(V,Y,X,W,C[P+10],z,38016083);W=
f(W,V,Y,X,C[P+15],y,3634488961);X=f(X,W,V,Y,C[P+4],w,3889429448);Y
=f(Y,X,W,V,C[P+9],A,568446438);V=f(V,Y,X,W,C[P+14],z,3275163606);W
=f(W,V,Y,X,C[P+3],y,4107603335);X=f(X,W,V,Y,C[P+8],w,1163531501);Y
=f(Y,X,W,V,C[P+13],A,2850285829);V=f(V,Y,X,W,C[P+2],z,4243563512);
W=f(W,V,Y,X,C[P+7],y,1735328473);X=f(X,W,V,Y,C[P+12],w,2368359562)
;Y=D(Y,X,W,V,C[P+5],o,4294588738);V=D(V,Y,X,W,C[P+8],m,2272392833)
;W=D(W,V,Y,X,C[P+11],l,1839030562);X=D(X,W,V,Y,C[P+14],j,425965774
0);Y=D(Y,X,W,V,C[P+1],o,2763975236);V=D(V,Y,X,W,C[P+4],m,127289335
3);W=D(W,V,Y,X,C[P+7],l,4139469664);X=D(X,W,V,Y,C[P+10],j,32002366
56);Y=D(Y,X,W,V,C[P+13],o,681279174);V=D(V,Y,X,W,C[P+0],m,39364300
74);W=D(W,V,Y,X,C[P+3],l,3572445317);X=D(X,W,V,Y,C[P+6],j,76029189
);Y=D(Y,X,W,V,C[P+9],o,3654602809);V=D(V,Y,X,W,C[P+12],m,387315146
1);W=D(W,V,Y,X,C[P+15],l,530742520);X=D(X,W,V,Y,C[P+2],j,329962864
5);Y=t(Y,X,W,V,C[P+0],U,4096336452);V=t(V,Y,X,W,C[P+7],T,112689141
5);W=t(W,V,Y,X,C[P+14],R,2878612391);X=t(X,W,V,Y,C[P+5],O,42375332
41);Y=t(Y,X,W,V,C[P+12],U,1700485571);V=t(V,Y,X,W,C[P+3],T,2399980
690);W=t(W,V,Y,X,C[P+10],R,4293915773);X=t(X,W,V,Y,C[P+1],O,224004
4497);Y=t(Y,X,W,V,C[P+8],U,1873313359);V=t(V,Y,X,W,C[P+15],T,42643
55552);W=t(W,V,Y,X,C[P+6],R,2734768916);X=t(X,W,V,Y,C[P+13],O,1309
151649);Y=t(Y,X,W,V,C[P+4],U,4149444226);V=t(V,Y,X,W,C[P+11],T,317
4756917);W=t(W,V,Y,X,C[P+2],R,718787259);X=t(X,W,V,Y,C[P+9],O,3951
481745);Y=K(Y,h);X=K(X,E);W=K(W,v);V=K(V,g)}var
i=B(Y)+B(X)+B(W)+B(V);returni.toLowerCase()};
}
}
})
Dopoaverinclusolafunzionemd5,potremoaccedereall’immaginedelcontattodal
gravatar.Passeremodueelementiallafunzionedellink.Ilprimosaràl’indirizzoemail,mentreilsecondoparametrofacoltativosaràcostituitodalledimensioni
dell’immaginecheintendiamorecuperare.
Gliattributipassatiallafunzionesonosemplicioggettiaiqualipossiamoaccedere.
Peresempio,seintendiamorecuperareilvaloredell’attributodell’indirizzoe-mail,
possiamoaccedervimedianteattrs.email.
Abbiamoanchedefinitolaproprietàreplace,chesostituiràl’elementoalquale
abbiamoassociatoladirettivaconiltemplatespecificato.Perimpostazione
predefinita,Angularaggiungeràiltemplatecomeelementofiglio.
Finiamorapidamenteesperimentiamolanuovadirettiva:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’,
template:‘<imgsrc=”{{img}}”class=”{{class}}”>’,
link:function(scope,elem,attrs){
varmd5=function(s){...};
varsize=(attrs.size)?attrs.size:64;
scope.img=‘http://gravatar.com/avatar/’+md5(attrs.email)+’?s=’+size;
scope.class=attrs.class;
}
}
})
Abbiamoutilizzatounoperatoreternarioperaverelapossibilitàdiimpostarele
dimensionidell’immagine.Abbiamoancheassociatoleclassiassegnateall’elemento,
oltreaunirel’URLdelgravataralloscope.
Ladirettivaèpronta.Proviamoautilizzarlaconunattributo:
<divgravataremail=”{{contact.email}}”size=”50”class=”imgcircle”></div>
Ottimo,sembrafunzionaretutto.Vienevisualizzatoilgravatarevieneinclusala
classecheproduceunabellaimmaginecircolare.Purtroppoessaèracchiusaneltag
divalqualeabbiamoassociatoladirettivaequindisispostasuunanuovariga.Ciò
accadeperchénonabbiamocomunicatoadAngularcheintendiamosostituire
l’elementoesistenteconiltemplatecompilato.Aquestoscopoimpostiamosutruela
proprietàreplacedell’oggettodelladirettiva:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’,
template:‘<imgsrc=”{{img}}”class=”{{class}}”>’,
replace:true,
link:function(scope,elem,attrs){
varmd5=function(s){…};
varsize=(attrs.size)?attrs.size:64;
scope.img=‘http://gravatar.com/avatar/’+md5(attrs.email)+’?s=’+size;
scope.class=attrs.class;
}
}
})
Seaggiorniamoilbrowser,vedremochealpostodell’immagineracchiusa
nell’elementooriginarioèpresentelanostraimmagine.Inalternativaavremmo
potutochiamareladirettivatramiteunelementopersonalizzato:
<gravataremail=”{{contact.email}}”size=”50”class=”imgcircle”></gravatar>
SenonservespecificatamenteilsupportoaIE8(nonpiùsupportatoinAngularJS
1.3+),ledirettivecheinseriscononuovielementitramitel’utilizzodiuntemplate
dovrebberovenirechiamatemedianteunelementopersonalizzato,comenell’esempio
precedente.
Ledirettivechemanipolanoelementiesistenti,peresempiochiamandounplug-in
jQuery,dovrebberovenirechiamatesoltantotramiteunattributo.
Laprossimafiguramostralanuovadirettivaquandovienevisualizzatoun
contatto.
Rispettareifineriga
Attualmenteicampiaddressenotesnonrispettanoifinerigaperchélenuoverighe
devonoessereconvertiteininterruzionidirigaHTML.FortunatamenteAngular
semplificaalmassimoquestaoperazionemedianteunfiltropersonalizzato.
ComeabbiamovistorealizzandounfiltronelCapitolo3,descriveremo
rapidamenteilfiltroparagraphcheènecessariocreareperconvertireilfineriga\nin
:
<br/>
.filter(‘paragraph’,function(){
returnfunction(input){
return(input)?input.replace(/\n/g,‘<br/>’):input;
};
})
Seaggiungiamoquestofiltroall’indirizzousandolasintassicheprevede
l’operatorepipe(|),noteremoqualcosadistrano.Leinterruzionidipaginavengono
convertiteinentitàHTMLevisualizzatesullapagina.Permotividisicurezza,
Angularesegueautomaticamentel’escapedelleentitàHTMLperimpedirelo
scriptingcross-site.
Siccomenonintendiamoovviamentevisualizzarle,dobbiamoutilizzareuna
direttivainclusaperlegareilmodelloallapagina.Ladirettivang-bind-htmlprodurrà
esattamentequestorisultato.Eccolainazioneneltagparagraph:
<pclass=”form-control-static”ng-bind-html=”contact.address|
paragraph”></p>
Manonfunzionaancora.Secontrolliamolaconsole,Angulargenerailseguente
errore:
Error:[$sce:unsafe]Attemptingtouseanunsafevalueinasafe
context.
InfattiAngularrichiedeilmodulongSanitizecheèpossibilescaricaredallasezione
Extrasall’indirizzohttps://angularjs.org/.Ilmodulofiltraiframmentidicodice
pericolosi,comegliscript,producendounoutputripulitoesicuro.
ProcuratevelodalsitodiAngular,aggiungeteloalladirectoryjseincludetelonel
fileprincipaleindex.html:
<scripttype=”text/javascript”src=”/assets/js/angularsanitize.min.js”></script>
Dopoaverinclusoilmodulonellapagina,ènecessarioinserirlocomedipendenza
delnostromodulo,cosìcomeabbiamofattoconngRoute:
angular.module(‘contactsMgr’,[‘ngRoute’,‘ngSanitize’])
Seaggiornatelapagina,vedretecheoral’indirizzooccupapiùrighe,come
previsto.
ImpostarelaricercaeaggiungerelapageClass
active
L’ultimoargomentodaaffrontarenell’ambitodelReadèlaricerca.Poichéla
ricercafiltreràlatabellanellavistaIndex,dobbiamoreindirizzarlaquandoiniziamoa
digitare.Inoltreilmodellocheutilizzeremodovràessereaccessibileovunque.
DobbiamoanchecapirecomeimpostarelapageClassactive.Almomentoèfissasu
Browsemasarebbebellorenderladinamica.
Entrambiquestiobiettivinonsipossonoraggiungereconng-view;ènecessario
creareuncontrollerperl’interaapplicazione.Questosignificachesaremoingradodi
accedereovunquealmodelloperlaricerca:
.controller(‘appCtl’,function($scope,$location){
});
Perreindirizzarelapaginaabbiamoinseritoilservizio$location.Essooffre
l’accessoalmetodopath,chepossiamosfruttareperrealizzareilnostrointento.A
differenzadeicontrollerdellaroute,ènecessariochiamarlosullapagina
aggiungendoloaltagdiaperturaHTML:
<htmllang=”en”ng-app=”contactsMgr”ng-controller=”appCtl”>
Laricerca
Oracheilcontrollerèinizializzato,possiamoprocedere.Ladirettivang-keyupsi
attiverànonappenainizieremoadigitarenellacaselladiricerca,reindirizzandocialla
vistaIndex.
Aggiungiamol’handleralcontroller:
.controller(‘appCtl’,function($scope,$location){
$scope.startSearch=function(){
$location.path(‘/’);
};
});
Sitrattadiunafunzioneabbastanzasempliceedifacilecomprensione.Quando
aggiungeremoladirettivaallacaselladiricerca,essamodificheràilpercorsocorrente
nellavistaIndex.Procediamoeimpostiamola.Senonl’avetegiàfatto,èilmomento
diassegnareancheunmodelloallacaselladiricerca:
<formclass=”navbar-formnavbar-right”role=”search”>
<inputtype=”text”class=”form-control”placeholder=”Search”ngmodel=”search”ng-keyup=”startSearch()”>
</form>
Oracheabbiamovistocomereindirizzare,èsufficientefiltrareng-repeatcome
abbiamofattoinprecedenza:
<trng-repeat=”contactincontacts|filter:search”>
Ricordatechesevoletelimitarloalnome,ènecessariomodificareilmodelnella
caselladiricercanelseguentemodo:
<inputtype=”text”class=”form-control”placeholder=”Search”ngmodel=”search.name”ng-keyup=”startSearch()”>
LapageClassactive
InfineènecessarioimpostarelapageClassactive.Dobbiamoverificareilpercorso
correnteeaggiungere,senecessario,unaclasse.Aquestoscopoutilizziamong-classe
unafunzionenelcontrollerdell’app.
Lafunzioneverificheràseilpercorsocorrenteèlostessodiquellocheèstato
passato:
$scope.pageClass=function(path){
return(path==$location.path())?‘active’:‘’;
};
Sec’ècorrispondenza,restituiràlaclasseactive,altrimentinulla.Aggiungiamolaa
entrambiglielementidellanavigazione:
<ling-class=”pageClass(‘/’)”><ahref=”/”>Browse</a></li>
<ling-class=”pageClass(‘/add-contact’)”><a
href=”/add-contact”>AddContact</a></li>
Dopoaveraggiuntoladirettivang-classall’elementodellalista,lapaginacorrente
mostratadallaclasseactivediventainteramentedinamicaecorretta.
Create
Finoraabbiamotrascuratolaprimaletteradell’acronimoCRUD,maoraèvenuto
ilmomentodifareunpassoindietroeimpostareilformdiaggiuntadeicontatti.
Dobbiamoinnanzituttoverificarechetuttigliinputdelformabbianoassociatoil
modelloopportuno:
<inputtype=”text”id=”name”class=”form-control”ngmodel=”contact.name”>
Poichéabbiamorealizzatounserviziopergestireicontatti,èragionevole
estenderloperriuscireacreareconessoicontatti.Definiamounmetodocreateche
portiilcontattonell’arrayeloaggiungaall’oggettodelservizio:
create:function(contact){
contacts.push(contact);
}
Orariflettiamosuciòchevogliamoaccadadopol’inviodelform.Intendiamo
inserireuncontattonell’arrayutilizzandoilmetodoappenacreatonelservizio,
fornireunfeedbackpositivosuquestaoperazioneeinfinecancellaretuttodalform:
$scope.submit=function(){
contacts.create($scope.contact);
$scope.contact=null;
$scope.added=true;
};
Conilprecedentecodicesiamoriuscitinelnostrointento:abbiamopassatoil
contattoalservizio,reimpostatoilmodellodelcontattoedefinitounmodelloche
possiamoverificareperfornireilfeedback.
Esistonoduesistemiperchiamarelafunzioneappenacreata.Potremmo
aggiungerlacomedirettivang-clickalpulsantesubmit,maforseèpiùsaggioe
accessibileaggiungerlaaunadirettivang-submitnelform:
<formclass=”form-horizontal”ng-submit=”submit()”>
Sitrattadiincluderelafinestradiavvisopercomunicareall’utentecheilcontattoè
statoaggiuntoconsuccesso.Vogliamocheperimpostazionepredefinitasianascosta,
cosìutilizzandong-showeosservandoilmodelaggiunto,possiamodeciderequando
visualizzarla:
<divclass=”alertalert-success”ng-show=”added”>
Thecontactwasaddedsuccessfully.
</div>
Update
Comericorderete,nonabbiamocreatounavistadestinataunicamenteallamodifica
deicontatti.Possiamosfruttarelostessopartialcheabbiamoutilizzatoper
aggiungereicontattioppurefarequalcosadipiùstimolanteconlavistacontenente
unsolocontattoecreareunadirettivapermodificareidati.
Èimprobabilechesianecessariomodificareinunavoltasolatuttiidatidiun
contatto;spessositratteràdicambiaresoltantoilnumeroditelefonool’indirizzoemail.LanostraideaèquelladivisualizzareiltestoafiancodiunpulsanteEdit.Al
clicsudiesso,potremomodificarequeldatodelcontatto.
Chiamiamoladirettivaeditableedeseguiamolacomeabbiamofattoinprecedenza:
.directive(‘editable’,function(){
return{
};
})
Èopportunolasciarelasceltadiincluderlacomeelementopersonalizzatooppure
utilizzarlamedianteunattributo.Perilmomentolautilizzeremosoltantocome
attributo,maforseinfuturopotrebberorichiederlaaltriprogetti:
.directive(‘editable’,function(){
return{
restrict:‘AE’,
templateUrl:‘/assets/partials/editable.html’
};
})
Questavoltanonsaràsufficienteunarigadimarkup,cosìabbiamodecisodi
utilizzarelaproprietàtemplateUrl.Sitrattasoltantodicreareilreferencepartial,come
abbiamofattoconleroute.
Scope
OltreadeciderediutilizzareunURLperiltemplate,studieremounanuova
proprietà:scope.Essacioffremaggiorecontrollosulloscopecheintendiamo
utilizzareconladirettiva.
Inquestocasoselaimpostiamocomehashsicreeràunnuovoscopeisolato.Non
ereditadalgenitore,equindinondovremopreoccuparcidellaletturaodellamodifica
accidentaledeidatinelloscopedellavista.Inquestocasoabbiamoaggiuntodue
valoriallafunzionehashdelloscope:
.directive(‘editable’,function(){
return{
restrict:‘AE’,
templateUrl:‘/assets/partials/editable.html’,
scope:{
value:‘=editable’,
field:‘@fieldType’
}
};
})
Lachiaveèilnomecheassegniamoalnuovoscope,mentreilvaloreèunattributo
dell’elemento.Notatecheabbiamoantepostoduediversiprefissiaivalori,che
pertantosicomporterannoinduemodimoltodiversi.
NOTA
RicordatechegliattributiseparatidauntrattinosonoconvertitiinCamelCasedaAngular.
Quandoilprefissocorrispondealsegno=,possiamolegareunmodellodelloscope
genitorealloscopedelladirettiva.Nondobbiamoquindiutilizzarelasintassi{{}}e
possiamoapprofittaredelbindingdeidatibidirezionale.
Neavremobisognoperchémodificheremoilvaloredelmodellocollegato
all’internodelladirettiva.
Seanteponiamoilsimbolo@,ladirettivautilizzeràilvaloreletteraledell’attributo.
Possiamoutilizzarelasintassi{{}}perpassareilvalorediunmodellooimmettere
unastringa.Quandoutilizziamoilsimbolo@nessunmodelloècollegato.
Controller
Abbiamovistoinprecedenzacomepossiamoutilizzareilmetodolinknella
direttiva,maneabbiamoancheunaltroadisposizione.Ilmetodocontrollerfunziona
esicomportapropriocomeuncontrollerdirettamenteassociatoalmodulo.Possiamo
inserirequalsiasiservizionecessarioetuttocisembreràmoltofamiliare.
Ladifferenzarispettoalinkconsistenell’ordinenelqualevieneelaborato.Il
metodocontrollervieneeseguitoprimachel’applicazioneabbiafinitodiessere
compilata,mentreilmetodolinkdopo.Dovremmoutilizzaresemprecontroller,a
menochenoncreiamounadirettivawrapperperunplug-injQueryoqualcosache
deveessereeseguitodopochetuttohafinitodicaricarsi.
Inprecedenzaabbiamoutilizzatolinkperladirettivadelgravatarpoichévolevamo
descrivereledifferenzetraidueapprocciall’internodelladirettiva.Aggiungiamoil
metodocontrolleralladirettiva.Inquestafasedovrebbepresentarsicosì:
.directive(‘editable’,function(){
return{
restrict:‘AE’,
templateUrl:‘/assets/partials/editable.html’,
scope:{
value:‘=editable’,
field:‘@fieldType’
},
controller:function($scope){
}
};
})
NOTA
Laproprietà controller nella direttiva si comporta come una sorta di API e consente alle altre
direttivedicomunicareleuneconlealtre,adifferenzadilink.
Assemblareiltutto
Abbiamoimpostatoloscopeeilcontroller.Oraèilmomentodipopolareilpartial
edefinirelafunzionalità.Visualizziamoilvaloredelmodelloeincludiamoil
pulsanteEditcheattiveràl’editor:
<spanng-bind-html=”value|paragraph”></span><buttonclass=”btn
btn-defaultbtn-xs”>Edit</button>
Comericorderete,abbiamoimpostatol’aliasdelnomedell’attributomodificabile
suvalue,edèproprioquestocheutilizzeremo.Dobbiamoinoltreprevederetipidi
campidiunarigaodipiùrighe;pertantoricorreremoang-bind-htmlealfiltroparagraph.
Sfrutteremong-showeng-hideperanalizzareunmodellonelloscopedelladirettiva.È
opportunoconsentireall’utentedicancellarelemodificheequindinon
modificheremoilvalorecheabbiamopassatodirettamente.Aggiungereilseguente
codicealcontrollercreaunnuovomodellochepossiamomodificare,oltreagenerare
qualcosacheng-showeng-hidepossonotenered’occhio:
$scope.editor={
showing:false,
value:$scope.value
};
Èpossibileutilizzareilmodelloeditor.showingpercreareduesezionineltemplate.
UnasezioneverràvisualizzataprimachefacciamoclicsuEdit,l’altradopo:
<divng-hide=”editor.showing”>
<spanng-bind-html=”value|paragraph”></span><buttonclass=”btn
btn-defaultbtn-xs”>Edit</button>
</div>
<divng-show=”editor.showing”>
</div>
Creiamolafunzionecheutilizzeremopermostrareonasconderel’editor,che
chiameremodang-click:
$scope.toggleEditor=function(){
$scope.editor.showing=!$scope.editor.showing;
};
OraagganciamotuttoalpulsanteEditnelpartial:
<spanng-bind-html=”value|paragraph”></span><buttonclass=”btn
btn-defaultbtn-xs”ng-click=”toggleEditor()”>Edit</button>
Ladirettivacipermetteràdiscegliereiltipodiinputnecessario(testo,e-mail,area
ditestoecosìvia)anchesepreferiamol’impostazionepredefinita,unacaselladi
testodiunariga,perchéèciòcheutilizzeremoconmaggiorefrequenza.Inquesto
casoabbiamooptatoperunoperatoreternarioperverificareseèstatoimpostatoun
valoreosesideveutilizzarel’impostazionepredefinita:
$scope.field=($scope.field)?$scope.field:‘text’;
Ladirettivang-ifèstataaggiuntadirecenteinAngularJS1.2.Adifferenzadingeng-hide,associaodissociaelementidalDocumentObjectModel(DOM)senon
show
soddisfanolacondizione;inquestocasoèperfettaperverificareiltipodicampo:
<divng-show=”editor.showing”>
<divng-if=”field==‘textarea’”>
<textareang-model=”editor.value”class=”formcontrol”></textarea>
</div>
<divng-if=”field!=‘textarea’”>
<inputtype=”{{field}}”ng-model=”editor.value”class=”formcontrol”>
</div>
</div>
Comepotetevedere,èsemplicedautilizzareeoffreuncontrollocompletosiache
intendiamoutilizzareun’areaditestoounelementodiinput.Abbiamoanche
popolatol’attributotypedeltaginputconilvalorecheèstatopassatoeinclusoin
entrambiglielementinelnuovomodello.
Noncirestacheinserireneltemplateduepulsantipersalvareocancellare.
Sarebbeopportunosepararliconunfilettoorizzontale:
<divng-hide=”editor.showing”>
<spanng-bind-html=”value|paragraph”></span><buttonclass=”btn
btn-defaultbtn-xs”ng-click=”toggleEditor()”>Edit</button>
</div>
<divng-show=”editor.showing”>
<divng-if=”field==‘textarea’”>
<textareang-model=”editor.value”class=”formcontrol”></textarea>
</div>
<divng-if=”field!=‘textarea’”>
<inputtype=”{{field}}”ng-model=”editor.value”class=”formcontrol”>
</div>
<hr>
<buttonclass=”btnbtn-successbtn-xs”ngclick=”save()”>Save</button>
<buttonclass=”btnbtn-defaultbtn-xs”ngclick=”toggleEditor()”>Cancel</button>
</div>
AbbiamoassociatoilpulsanteSaveaunanuovafunzionesavechedobbiamo
ancoracreare;ilpulsanteCancelutilizzalastessafunzionetoggleEditorprecedente.
Lafunzionesaveèsemplice;assegnailnuovomodellocheabbiamocreatoaquello
cheabbiamolegatoinprecedenzaalladirettivaechiamalafunzionetoggleEditorper
nasconderetutto:
$scope.save=function(){
$scope.value=$scope.editor.value;
$scope.toggleEditor();
};
Abbiamoterminatoladirettivamodificabile,macomepossiamoutilizzarla?Nel
partialcontact.htmlabbiamovisualizzatoinprecedenzatuttiimodelliall’internodei
tagparagraphricorrendoallasintassiconledoppieparentesigraffe.Oracheladirettiva
fatuttoquestoalpostonostro,possiamosostituireilcontenutodeltag<p>e
aggiungeredueattributi:
<pclass=”form-control-static”editable=”contact.email”fieldtype=”email”></p>
Dopoaversostituitotuttiimodelli,dovresteottenereuneditorvisivodifacile
utilizzoperognisezionedelcontatto.
Delete
Èpossibilecancellareicontattigrazie,nuovamente,alservizio.Creeremoun
metodofinalecheaccetteràunindicedell’arrayelocancellerà.Siccomedeleteèuna
parolachiavediJavaScript,utilizzeremodestroycomenomedelmetodo:
destroy:function(index){
contacts.splice(index,1);
}
Èunmetodomoltosemplice.Prendiamol’indiceeutilizziamoilmetodonativo
splicepercancellarlodall’array.Oradobbiamocreareunafunzionesulloscopedella
vistaindexingradodichiamarequestometododalservizio:
$scope.delete=function(index){
contacts.destroy(index);
};
Infineaggiungiamounpulsanteallacolonnadelleazionidellatabellaper
cancellarealclicilcontattovoluto:
<buttonclass=”btnbtn-dangerbtn-xs”ngclick=”delete($index)”>Delete</button>
Quiz
1. Indicateduemodiconiqualièpossibilecondividereidatitraleviste.
2. Qualisonoitretipidiservizidisponibili?
3. Checosaènecessarioincludereperutilizzareng-bind-html?
4. Qualèladifferenzatraimetodilinkecontrollerdiunadirettiva?
5. Quandosiutilizzaloscopeisolato,checosasignificanoiprefissi=e@?
6. Inchemodopossiamolimitareunadirettivaaunelementoeauncommento?
7. Perchéènecessariocreareuncontrollerpertuttal’applicazione?
8. Inchemodootteniamol’indicediunoggettodang-repeat?
Riepilogo
Inquestocapitoloabbiamoaffrontatomoltiargomentiedèunabuonaidea
riepilogarli.Agrandilineeabbiamotrasformatociòcheeranotemplatestaticiin
un’apppienamentefunzionantechepermetteilCRUD(Create,Read,Update,
Delete).
Nelfrattempoabbiamoscopertomoltoaltro.Abbiamoesaminatoilsistema
migliorepercondividereidatitralevistecreandounserviziopersonalizzatoper
gestireicontatti.Ilserviziocompletociconsente,ovunquenell’applicazione,di
recuperaretuttiicontatti,trovarneunosolo,aggiungerneunonuovoocancellarlo.
Oltreadefinireunserviziopersonalizzato,abbiamoanchecreatonuovedirettive.
Laprimacihapermessodivisualizzarel’immaginediungravatarbasatasuun
indirizzoe-mail.Abbiamoscopertoimoltepliciediversimodidiutilizzodiuna
direttiva,tramiteunattributooperfinouncommentoHTML.
Lasecondadirettivacheabbiamocreatoèunpo’piùcomplessa.Abbiamo
consideratoscopeisolatieladifferenzatraimetodilinkecontrollerdiunadirettiva;
inoltreabbiamosviluppatounottimosistemapermodificareidatidelcontatto.
NelcapitoloseguentevedremoinazionelalibreriaditerzepartiAngularStrapche
ciconsentediavvalercideiplug-indiBootstrapall’internodiAngular.
Capitolo7
AngularStrap
Abbiamodescrittol’elevatonumerodicomponentipresentiinBootstrap;ora
esamineremol’utilizzodeiplug-inJavaScriptdisponibili.Èpossibilecrearedelle
direttiveperciascunodiessi,malacommunitydiAngularforniscegiàunricco
modulochiamatoAngularStrap.
InquestocapitoloconsidereremoAngularStrap,iplug-incheBootstrapcipermette
diutilizzareeilmodoincuièpossibileinserirlinell’applicazione.
InstallareAngularStrap
InnanzituttodobbiamoscaricareAngularStrap.Poteteprocurarvelodalsito
http://mgcrea.github.io/angular-strap/.Fateclicsulpulsantedidownloadinaltoadestra
escaricatel’ultimaversionecomefileZIP.
Quest’ultimocontiene,tral’altro,tuttiimodulisingolisottoformadiuncomodo
fileminificato.Trovereteiduefilecheciservononelladirectorydist.Copiateangulareangular-strap.tpl.min.jsnelladirectoryjsdelprogetto.Includetelinelfile
strap.min.js
dellaradicedopoAngulareprimadelmodulodelprogetto:
index.html
<scripttype=”text/javascript”src=”/assets/js/angularstrap.min.js”></script>
<scripttype=”text/javascript”src=”/assets/js/angularstrap.tpl.min.js”></script>
Comeperognialtromodulo,ènecessarioinserirlonell’applicazione.La
dichiarazioneèsituatanellaprimarigadelcontroller.js;ilnomedelmodulo
AngularStrapèmgcrea.ngStrap.DiseguitoabbiamoaggiuntoAngularStrapcomeuna
dipendenzadelmodulocontactsMgr:
angular.module(‘contactsMgr’,[‘ngRoute’,‘ngSanitize’,
‘mgcrea.ngStrap’])
Manonèfinitaqui.AngularStrapdipendedalmodulongAnimatechedobbiamo
ancoraincluderenelprogetto.LopossiamoscaricarefacendoclicsullinkExtras
nellafinestramodaledidownloadall’indirizzohttps://angularjs.org/.
Aggiungetelaversioneminificataalladirectoryjsdelprogettoeincludetelaprima
diAngularStrap:
<scripttype=”text/javascript”src=”/assets/js/angularanimate.min.js”></script>
IlmodulongAnimatenondeveessereinseritonelprogettoamenochenonvogliate
usarloaldifuoridelledirettivediBootstrap.ConsentedisfruttareleanimazioniCSS
inmodulicomengShowengHideperottenereuneffettoditransizioneinvecedi
visualizzaresemplicementequalcosa.
Potremmoanchesvilupparedelleanimazionidautilizzareinsiemecon
AngularStrap,ancheseAngularMotionèilsuocompagnoperfetto.Sitrattadiun
semplicefogliodistilechecontieneanimazionipredefiniteprontedautilizzareconil
modulongAnimate.
Possiamoscaricarel’ultimaversionedalsitohttp://mgcrea.github.io/angular-motion/
facendoclicsulpulsantedidownloadinaltoadestra.AnchequestofileZIPcontiene
ifilesorgenteoltreallaversioneminificataprontaperlaproduzione,presentenella
directorydist.Copiatelanelprogettoeincludetelanellapaginacomefogliodistile
secondario:
<linkrel=”stylesheet”href=”/assets/css/angular-motion.min.css”>
Orapotremocreareeffettiditransizione,farscorrere,ridimensionareeruotarele
animazionifornitedaAngularMotioninsiemeconledirettiveAngularStrap.
Forsevisembreràstranochenonsianecessarioincludereloscriptdeiplug-indi
Bootstrap.C’èunmotivo:ledirettivecheabbiamoinclusoconAngularStrapnon
sonosemplicifunzioniwrappercheeseguonojQuery,mariscritturecompleteche
sfruttanoappienoAngular.
UtilizzareAngularStrap
DopoaverinstallatoAngularStrap,esaminiamoalcunisuoiplug-inecome
utilizzarli.
Primadiiniziare,impostiamorapidamenteunambientedemo.Duplichiamoilfile
index.htmlerinominiamolodemo.html.ModificheremoancheilcontrollerindemoCtrlelo
aggiungeremoalfilecontroller.js.Inquestomodoavremounospazioincuioperare.
Lafinestramodale
UnafinestramodaleèunparadigmaUImoltocomunenelleapp.Èunottimo
sistemapervisualizzarepocheinformazionisenzaindirizzarel’utenteaunanuova
pagina.
Puòesserechiamataalclicsuunpulsanteacuièapplicataladirettivabs-modal:
<buttonclass=”btnbtn-primary”bs-modal=”modal”>Show
Modal</button>
Ilvalorechevienepassatoèunmodellodelloscope;èunhashcontenentedue
valori:titleecontent.Eccoloall’internodelcontroller:
$scope.modal={
title:‘ModalTitle’,
content:‘Modalcontent’
};
Esistonoalcuneopzionidasfruttareconladirettivamodal;sonoapplicate
all’elementocomeattributiefatteprecederedadata-.Peresempio,seintendiamo
modificarel’animazione,possiamoscriverequestocodice:
<buttonclass=”btnbtn-primary”bs-modal=”modal”dataanimation=”am-fade-and-scale”>ShowModal</button>
cheutilizzeràl’animazionefade-and-scalediAngularMotion.Ricordatevidivisitare
ilsitodiAngularMotionperunalistacompletadelleanimazionidisponibili.
Latabellaseguente,trattadalsitodiAngularStrap,illustralalistadituttele
opzionidisponibiliperladirettivamodal.
Nome
Tipo
Impostazione
predefinita
Descrizione
animation
stringa
am-fade
Applicaun’animazioneCSS.
backdropAnimation
stringa
am-fade
Applicaun’animazioneCSSallosfondo.
placement
stringa
'top'
Posizionaladirettivamodal:inalto(top)/inbasso
(bottom)/alcentro(center).
title
stringa
''
Valorepredefinitodeltitolo.
content
stringa
''
Valorepredefinitodelcontenuto.
html
booleano
false
Sostituisceng-bindconng-bind-html.
backdrop
booleanoo
true
Includeunelementomodaledellosfondo.
Utilizzastaticperlosfondo,chenonchiudela
finestramodalealclic.
keyboard
booleano
true
Chiudelafinestramodalequandosipremeil
tastoEsc.
container
stringa/false false
Aggiungelafinestramodaleaunelemento
specifico.Esempio:container:'body'.
template
percorso
false
Sefornito,scavalcailtemplatepredefinito.
contentTemplate
percorso
false
Sefornito,recuperailpartialeloincludecome
contenutointerno.
'static'
Tooltip
Itooltipsonounottimosistemaperoffriresuggerimentieconsiglisenzaessere
invadenti.AngularStrapnesemplifical’inclusione;liattiviamoconilclic,l’hovero
ilfocus:
<buttonclass=”btnbtn-link”bs-tooltip=”tooltip”>what’s
this?</button>
Inquestocasoabbiamounpulsante(alqualeèstatoapplicatounostileinmododa
farlosembrareunlinkgraziealleclassidiBootstrap)eabbiamoinclusoladirettiva
bsTooltip.
Cosìcomeabbiamofattoconladirettivamodal,possiamopassareunmodelloalla
direttiva.Questavoltadobbiamoincluderesoltantolaproprietàtitlenell’oggetto:
$scope.tooltip={
title:‘TooltipTitle’
};
Perimpostazionepredefinitailtooltipcompariràall’hoversulpulsante,maè
possibilemodificarlafacilmenteutilizzandogliattributidatacheabbiamovistoin
precedenza:
<buttonclass=”btnbtn-link”bs-tooltip=”tooltip”datatrigger=”click”>what’sthis?</button>
Ladirettivaciconsenteanchedilegarlaauninputemostrareiltooltipalfocus.
Ancheilposizionamentopuòesseredefinitodall’attributodata:
<inputtype=”text”bs-tooltip=”tooltip”data-trigger=”focus”dataplacement=”right”>
Questocodicemostreràiltooltipadestraquandol’inputottieneilfocus.Di
seguitopotetevederelalistaditutteleopzionipresentenelladocumentazionedi
AngularStrap.
Nome
Tipo
Impostazione
predefinita
Descrizione
animation
stringa
am-fade
Applicaun’animazioneCSS.
placement
stringa
'top'
Posizionailtooltip:top/bottom/left/righto
qualsiasialtracombinazionecomebottom-left.
trigger
stringa
'hover'
Definiscecomesiattivailtooltip:
click/hover/focus.
title
stringa
''
Valorepredefinitodeltitolo.
html
booleano
false
Sostituisceng-bindconng-bind-html.
delay
numero/oggetto 0
Ritardalacomparsaolascomparsadeltooltip
(ms);nonsiapplicaaun’attivazionemanuale.Se
vieneindicatounnumero,ilritardosiapplicasia
ahidesiaashow.Lastrutturadell’oggettoèla
seguente:delay:{show:500,hide:100}.
container
stringa/false
false
Aggiungelafinestramodaleaunelemento
specifico.Esempio:container:'body'
template
percorso
false
Sefornito,scavalcailtemplatepredefinito.
contentTemplate
percorso
false
Sefornito,recuperailpartialeloincludecome
contenutoall’interno.
Popover
Ipopoversonounasortaditooltipestesoefornisconoun’areatitleecontent.
Similiaitooltip,sipossonoattivarealclic,all’hoveroalfocus:
<buttonclass=”btnbtn-primary”bs-popover=”popover”>Show
Popover</button>
Ilmodelloassociatoèidenticoperformatoaquelloutilizzatoperlafinestra
modalepoichécontieneleproprietàtitleecontent:
$scope.popover={
title:‘Title’,
content:‘Popovercontent’
};
Ovviamentetuttoèmodificabilemediantegliattributidata.Laprossimatabella
riportaunalistacompletadelleopzioni.
Nome
Tipo
Impostazione
predefinita
Descrizione
animation
stringa
am-fade
Applicaun’animazioneCSS.
placement
stringa
'top'
Posizionailtooltip:top/bottom/left/right,o
qualsiasialtracombinazionecomebottom-left.
trigger
stringa
'hover'
Definiscecomesiattivailtooltip:
click/hover/focus.
title
stringa
''
Valorepredefinitodeltitolo.
content
stringa
''
Valorepredefinitodelcontenuto.
html
booleano
false
Sostituisceng-bindconng-bind-html.
delay
numero/oggetto 0
Ritardalacomparsaolascomparsadeltooltip
(ms);nonsiapplicaaun’attivazionemanuale.Se
vieneindicatounnumero,ilritardosiapplicasia
ahidesiaashow.Lastrutturadell’oggettoèla
seguente:delay:{show:500,hide:100}
container
stringa/false
false
Aggiungelafinestramodaleaunelemento
specifico.Esempio:container:'body'.
template
percorso
false
Sefornito,scavalcailtemplatepredefinito.
contentTemplate
percorso
false
Sefornito,recuperailpartialeloincludecome
contenutoall’interno.
Alert
AbbiamogiàvistocomeèpossibileutilizzarelefinestrediBootstrapperoffrireun
feedbackagliutenti.AngularStrapciconsentedievidenziarle,applicareuneffettodi
transizioneopermettereagliutentidirimuoverle.Utilizziamoladirettivaalert
aggiungendol’attributobs-alertaunelemento:
<buttonclass=”btnbtn-primary”bs-alert=”alert”>Show
Alert</button>
L’oggettodelmodellodefiniscenonsoloiltitoloeilcontenutomaanchelaclasse
contextcheutilizzeremo.Puòesseresuccess,info,warningodangeremodificherà
opportunamenteilcoloredellosfondoedeltesto:
$scope.alert={
title:‘Title’,
content:‘Alertcontent’,
type:‘success’
};
Definiremoconprecisionedoveverràaggiuntalafinestradiavviso;possiamo
utilizzarel’attributodata-containerperdefinireunelementospecificoincui
desideriamovisualizzarla.Creiamounnuovoelementoincimaallapaginaperil
contenitore:
<divid=”alertContainer”></div>
Aggiungiamoloalpulsantemediantel’attributodata-container:
<buttonclass=”btnbtn-primary”bs-alert=”alert”
data-container=”#alertContainer”>ShowAlert</button>
Oraquandofaremoclicsulpulsante,incimaalloschermocompariràlafinestradi
avviso.SulsitodiAngularStrapèdisponibilelalistaseguenteditutteleopzioni
disponibili.
Nome
Tipo
Impostazione
predefinita
Descrizione
animation
stringa
am-fade
Applicaun’animazioneCSS.
placement
stringa
'top'
Posizionailtooltip:top/bottom/left/rightoqualsiasi
altracombinazionecomebottom-left.
title
stringa
''
Valorepredefinitodeltitolo.
content
stringa
''
Valorepredefinitodelcontenuto.
type
stringa
'info'
Valorepredefinitodeltipo.
keyboard
booleano
true
Chiudelafinestradiavvisoquandosipremeiltasto
Esc.
container
stringa/false false
Aggiungelafinestramodaleaunelementospecifico.
Esempio:container:'body'.
template
percorso
Sefornito,scavalcailtemplatepredefinito.
false
UtilizzareiservizidiAngularStrap
LamaggioranzadeimoduliinclusiinAngularStrapesponeancheiserviziper
l’applicazione.Possiamoutilizzarlipermostrareelementi,comefinestremodali,
finestrediavvisoepopoversenzaesserecostrettiaricorrerealledirettive.
Vediamocomeutilizzareilservizio$alertpermostrareunafinestradiavvisodal
controller.Ciserviremodelladirettivang-clicksuunpulsanteperavviarla.Creiamo
innanzituttounpulsanteeassociamoladirettivang-click:
<buttonclass=”btnbtn-success”ng-click=”showAlert()”>Alertvia
Service</button>
ImposteremorapidamentelafunzioneshowAlert()nelcontroller.Comeprimacosa
occorrecreareunafinestradiavvisoutilizzandoquestoservizio.Inseriamo$alertnel
controllerecreiamounanuovaistanzadiunafinestradiavvisoconilcodice
seguente:
controller(‘demoCtl’,function($scope,$alert){
varalert=$alert({
title:‘AlertTitle!’,
content:‘Here\’ssomecontent.’,
type:‘danger’,
container:‘#alertContainer’,
show:false
});
});
Ilcostruttoredelservizioaccettaunafunzionehashseguendolostessopattern
accettatodalladirettiva.Quipossiamoincluderequalsiasiopzione,comeil
contenitorealqualedesideriamoaggiungerelafinestradiavviso.Perimpostazione
predefinitalafinestradiavvisochevienecreatacompariràautomaticamente.Per
nasconderlaènecessarioincluderelaproprietàshoweimpostarlasufalse.
Infinenonrimanechedefinirel’handlershowAlert().L’istanzadellafinestradi
avvisocreatadalserviziocioffretremetodidicuipossiamoservirci:show(),hide()e
.Utilizziamoshow():
toggle()
$scope.showAlert=alert.show;
Sefacciamoclicsulnuovopulsante,lafinestradiavvisocompariràincimaalla
pagina(oovunqueabbiamoposizionatoilcontenitore)esicomporteràcome
previsto.
IntegrareAngularStrap
Dopoavervistocomeèpossibileutilizzaremoltiplug-in,ènecessariorenderli
operativievivacizzarel’appdigestionedeicontatti.Utilizzeremoiplug-intooltipe
perfornireagliutentisuggerimentiefeedback.
alert
SostituiamoinnanzituttoiltestodelsuggerimentosottolacasellaNotesnellavista
AddContactconuntooltip:
<textareaid=”notes”class=”form-control”ng-model=”contact.notes”
bs-tooltipdata-title=”Anyadditionalinformationaboutthe
contact.”data-trigger=”focus”
data-placement=”bottom”></textarea>
Invecedicreareunmodelloelegarloalladirettiva,hapiùsensosfruttare
l’attributodata-titledisponibile.Inquestocasoabbiamopreferitocollocarlosottoe
avviarloalfocus.
Potrebberoessereopportuneduefinestrediavviso.Unaèquellapreesistentedopo
l’aggiuntadiunnuovocontatto;l’altracomparedopoavercancellatouncontatto
nellavistaIndex.
Consideriamoprimalafinestradiavvisopreesistente.Sidevesostituirel’elemento
alertconilcontenitorecreatoinprecedenza:
<divid=”alertContainer”></div>
Possiamoinserireilservizio$alerteprepararel’istanzadellafinestradiavviso
primadivisualizzarlaall’internodellafunzionesubmit.Essaavràlaseguente
configurazione:
varalert=$alert({
title:‘Success!’,
content:‘Thecontactwasaddedsuccessfully.’,
type:‘success’,
container:‘#alertContainer’,
show:false
});
L’aggiungeremoall’alertContainercreatoinprecedenza.Inquestocasoilcontext
richiedeunmessaggiodisuccesso;pertantoabbiamoimpostatotypesusuccess.
Noncirestachemostrarelafinestradiavvisodopocheèstatocreatoconsuccesso
uncontatto:
$scope.submit=function(){
contacts.add($scope.contact);
$scope.contact=null;
alert.show();
};
Possiamoprocedereallostessomodoquandocancelliamouncontattoperoffrire
maggiorefeedbackall’utente.Comeprima,collochiamoilcontenitorenella
posizioneincuivorremmochecomparisserolefinestrediavvisonellavistaIndex:
<divid=”alertContainer”></div>
Dobbiamoinserireilservizio$alertnelcontroller:
.controller(‘indexCtrl’,function($scope,contacts,$alert){
OrapossiamoutilizzareilservizioappenainseritopercrearedeletionAlert:
vardeletionAlert=$alert({
title:‘Success!’,
content:‘Thecontactwasdeletedsuccessfully.’,
type:‘success’,
container:‘#alertContainer’,
show:false
});
Perconcludere,noncirestachefarcomparirelafinestradiavvisoquando
facciamoclicsulpulsantedelete:
$scope.delete=function(index){
contacts.destroy(index);
deletionAlert.show();
};
Eccocomedovrebbeesserel’output:
Quiz
1. DaqualemodulodipendeAngularStrap?
2. QualèilnomedelprogettochepossiamoutilizzareperleanimazioniCSS
predefinite?
3. Checosaènecessarioanteporreagliattributiperutilizzarlicomeopzioni
all’internodelledirettivediAngularStrap?
4. Qualisonoiquattrosistemiconiqualièpossibileattivareunpopoveroun
tooltip?
5. Qualisonoitremetodidisponibilipercreareun’istanzamedianteilserviziodi
alert?
Riepilogo
Inquestocapitoloabbiamovistocomeèfacileavvalersideinumerosimoduli
integratiinAngularStrap.AnchesenonpossonoutilizzaredirettamenteJavaScriptdi
Bootstrap,sonotutticomponentilegatiaquestoframeworkeoperanosenza
soluzionedicontinuitàall’internodell’applicazione.Abbiamoesaminatosoloalcuni
deiplug-indisponibilieilmodoincuièpossibileutilizzarlimedianteledirettive.
Talvoltaunadirettivanonèlasoluzionemigliore;inalternativaèpossibilericorrere
aiservizicompresiinAngularStrap.Nelcapitoloseguentevedremocomeèpossibile
connetterel’applicazionealserverperrecuperareememorizzareicontatti.
Capitolo8
Connessionealserver
Finoral’applicazioneèancorainteramentefront-endedè,pertanto,piuttosto
inutile.Ènecessariomemorizzareicontattiperpoterlirecuperareinseguito.A
questoscopociconnetteremoaunservercheospiteràun’APIRESTfulchegenera
JSON.
Angularoffrediversepossibilitàperconnettersialserver.Inquestocapitolone
esamineremoalcune,oltreaintrodurredellealternativechepotreteapprofondirein
seguito.
Nonvedremocomesvilupparel’aspettorelativoallatoserver,chenonrientra
negliambitidellanostratrattazione.Tuttaviaèpresentenellerisorsescaricabilidel
libro.
Eccogliargomenticheaffronteremo:
comeestrarreidatidalservermediante$http;
comeutilizzareedovetrovarengResource;
lealternativedellacommunitytracuiRestAngular;
integrazionenell’applicazionedellanuovaconnessioneconilserver.
Mettiamociall’opera.
Connettersicon$http
Angularincludegiàalcunimetodidibassolivelloperrecuperareeinviareidati.
Seaveteutilizzato$.ajax,$.posto$.getinjQuery,sareteavostroagio.
Comesapete,questimetodisonodisponibilisottoformadiunserviziocheè
possibileinserireneicontrolleroneiservizi.Diseguitopoteteosservareilservizio
$httpinseritonelcontroller:
.controller(‘indexCtrl’,function($scope,contacts,$alert,
$http){
})
Ilservizioincludealcunimetodichefunzionanocontuttiiverbidelprotocollo
REST.Imetodiseguentisonodisponibiliall’internodi$http.
:accettaunURLeunoggettoconfigfacoltativo.Esegueunarichiesta
$http.get()
HTTPGET.
:accettaunURLeunoggettoconfigfacoltativo.Esegueunarichiesta
$http.head()
HTTPHEAD.
:accettaunURL,unoggettodataeunoggettoconfigfacoltativo.
$http.post()
EsegueunarichiestaHTTPPOST.
:accettaunURL,unoggettodataeunoggettoconfigfacoltativo.
$http.put()
EsegueunarichiestaHTTPPUT.
:accettaunURLeunoggettoconfigfacoltativo.Esegueuna
$http.delete()
richiestaHTTPDELETE.
:accettaunURLeunoggettoconfigfacoltativo.Ilnomedella
$http.jsonp()
funzionedicallbackdovrebbeesserelastringaJSON_CALLBACK.
:accettaunURL,unoggettodataeunoggettoconfigfacoltativo.
$http.patch()
EsegueunarichiestaHTTPPUT.
Tuttiquestimetodisonoscorciatoieperlafunzioneprincipale$http(),cheaccetta
unargomento:unoggetto.Lefunzioniprimamenzionateimpostanoilverboe/oil
tipodicontenutocheintendiamorecuperare.
Peresempio,idueframmentidicodiceseguentisonoidentici,mailsecondoè
moltopiùleggibile:
$http({
method:‘GET’,
url:‘http://localhost:8000’
});
$http.get(‘http://localhost:8000’);
RecuperareidatièfacileeAngularbeneficiadeipatternPromisessviluppatida
Promises/A+eresipopolaridajQuery.Ilpatternciconsentedideterminare
facilmentesel’URLalqualeabbiamoavutoaccessoharestituitounarisposta
positivaohageneratounerrore.
Puòsembrarecomplesso,mainsostanzasitrattadiunaseriedimetodiche
possiamoconcatenarepercrearefacilmenteunapprocciotry/catchperlechiamate
asincrone.Seracchiudiamoconsole.login$http.get(),vedremotuttiimetodi
disponibilivisualizzatinellaconsole.
Tuttiimetodiaccettanoun’unicafunzionedicallback,conl’eccezionedithen,che
accettaduemetodi,unoperilsuccessodell’operazioneeunaltroperl’errore.
Vediamocomepossiamoutilizzarli.Nelcontrollerdell’index,sostituiamoallariga
contacts.get()ilseguentecodice:
$http.get(‘http://localhost:8000’)
.success(function(data){
$scope.contacts=data;
})
.error(function(){
window.alert(‘Therewasanerror!’);
});
Angularsioccuperàdelresto.Lafunzionedicallbackperilmetodosuccessviene
eseguitaquandovienerestituitouncodicedistatus2xx;altrimentivieneeseguitoun
errore.
Avremmopotutoabbreviareilcodiceprecedenteutilizzandoilmetodothenedue
funzionidicallback,nelseguentemodo:
$http.get(‘http://localhost:8000’)
.then(function(result){
$scope.contacts=result.data;
},function(){
window.alert(‘Therewasanerror!’);
});
Questofaperòrisparmiarepococodiceedèmenoleggibileperaltrisviluppatori
chepotrebberononconoscereAngularJS.Notatechedatanonèl’argomentopassato
allefunzionidicallbackall’internodelmetodothen;otteniamoinveceunoggetto
contenentedati,statoeheader.
Inviareidati
Comeilrecuperodeidati,illoroinviomediante$httpèmoltofacileesimile
all’implementazionedijQuery.Lafunzione$http.post()sicomportanellostesso
mododi$http.get()maaccettaunsecondoparametro:unhashcontenentetuttiidati
cheintendiamoinviarealserver:
$http.post(‘http://localhost:8000’,{
name:‘DeclanProud’,
email:‘[email protected]’,
...
});
Analogamente,ilmetodopostrestituisceancheunapromiseconglistessimetodi
esaminatiprima:
$http.post(‘http://localhost:8000’,{
name:‘DeclanProud’,
email:‘[email protected]’,
...
})
.success(function(){
...
})
.error(function(){
...
});
ConnettersiconngResource
Glihelperdellaconnessionedibassolivellocome$httpsonoottimiper
connessionisingole,madiventanobenprestoscomodiperlagestionediunintero
progetto.FortunatamenteAngularoffreunaltrosistemaconilqualeaccedereaidati
latoservergrazieaunmodulofacoltativochiamatongResource.
IncluderengResource
ComengRoute,ilmodulongResourcesitrovaallinkExtrasnellafinestramodaledi
downloadall’indirizzohttps://angularjs.org/.Scaricateloetrascinatelonelladirectory
jsdelprogetto.IncludetelodopoAngularnelfileradiceHTML:
<scripttype=””text/javascript””src=”/assets/js/angularresource.min.js”></script>
VerificatecheilmodulocontactsMgrsappiachengResourceèunadipendenza:
angular.module(‘contactsMgr’,[‘ngRoute’,‘ngSanitize’,
‘mgcrea.ngStrap’,‘ngResource’])
ConfigurarengResource
Ilmoduloesponeilservizio$resourcecheèpossibileinserireneicontrolleronei
servizi.Imetodiinclusisonodiunlivellopiùalto,einfattiutilizzano$httpper
interagireconilserver.Inseriamoilservizio$resourcenelserviziodigestionedei
contatticreatonelCapitolo7evediamocomeconnettersialserver:
.factory(‘Contact’,functionContactFactory($resource){
...
})
Ilservizioincludeunmetodocheutilizzeremoperimpostarelaconnessione.
Restituiscealcunefunzionisottoformadiunoggettoresource.Sarannoqueste
funzioniarecuperareeainviareidatialedalserver.
Vediamocomepossiamoservircidiquestometodosingoloeciòcherestituisce:
varResource=$resource(‘http://localhost:8000/contacts/:id’,
{id:‘@id’});
Ilcodicerestituiràl’oggettoseguente,consistenteinazionicheèpossibile
utilizzareperrecuperare,salvareocancellareidati:
{
‘get’:{method:’GET’},
‘save’:{method:’POST’},
‘query’:{method:’GET’,isArray:true},
‘remove’:{method:’DELETE’},
‘delete’:{method:’DELETE’}
};
Ilprimoparametroèlaradicedellarisorsasulserver.Peresempio,sestessimo
sviluppandounsistemadiblogging,avremmoalcunerisorsecomepost,tageautori.
Èanchepossibileaggiungeredeisegnaposto,comepotremmofaredurantela
creazionediunaroute.
Ilsecondoparametroèunhashcheincludeivaloripredefinitideisegnaposto.Seil
valorepredefinitodiunsegnapostopresentasseilprefissocontenenteilsimbolo@,
quelvaloresarebberecuperatodall’oggettodatachevienepassatoquandoaccediamo
alserver.
Possiamoanchepassareunterzoparametroperestendereleazionipredefiniteche
vengonorestituite.AggiungiamounmetodoupdatecheutilizzeràilverboPUTper
aggiornareuncontattoesistentesulserver:
varResource=$resource(‘http://localhost:8000/contacts/:id’,
{id:‘@id’},{
update:{method:‘PUT’}
});
Comepotetevedere,sitrattadiunoggettoJSstandardincuièpossibiledefinire
moltepliciazionipersonalizzate.Esistonoalcunielementicheèpossibileincludere
all’internodell’oggettoconfigurationassociatoall’azione,maprobabilmentesarà
necessarioimpostaresoltantomethodeisArray.Laproprietàmethodselezionaquale
verboHTTPènecessarioutilizzare(inquestocasoPUT),eisArrayèunbooleanoche
serveaindicareangResourceseilserverrestituiràununicoitemounarraydiitem.
Richiederealserver
SiamoriuscitiaconfigurarengResource;oradobbiamosoloattivarlo,operazione
moltofacile.Sitrattadiutilizzareunadelleazionirestituitedall’oggetto$resource.
Intendiamorecuperaretuttiicontatti:ilmetodoquerysembralasoluzioneideale.
UtilizzailmetodoGETelaproprietàisArrayvieneverificata:
.factory(‘Contact’,functionContactFactory($resource){
varResource=$resource(‘http://localhost:8000/:id’,{id:
‘@id’},{
update:{method:‘PUT’}
});
return{
get:function(){
returnResource.query();
},
...
};
})
Eccofatto!Nondobbiamopreoccuparcidell’unwrappingdellepromiseperché
questoaspettovienegestitoautomaticamentedangResource.Noncirestacherieseguire
lachiamata$httpinindexCtlalmetodoContact.get():
$scope.contacts=Contact.get();
Siccomenonutilizzeremopiùunarrayditipohard-code,nonpossiamoaccederea
singolicontattiservendocidiunindice.LamaggiorpartedelleAPIrestituisceunID
degliitemequestanonfaeccezione.Modifichiamo{{$index}}dang-repeatutilizzato
nellink,peravvalercidell’IDinvecechedeisingolicontatti;dovrebbetrovarsi
all’incircaariga23nelfilepartials/index.html:
<ahref=”/contact/{{contact.id}}”class=”btnbtn-defaultbtnxs”>View</a>
Ènecessariomodificareilmetodofindnelserviziodeicontattiperrecuperareun
unicocontattoinbaseall’IDaessoassegnato.Abbiamogiàimpostatolarisorsaper
ammettereunparametroid;noncirestachepopolarloquandoutilizzeremoilmetodo
dellarisorsa:
get
find:function(id){
returnResource.get({id:id});
},
Abbiamoanchemodificatoilnomedelparametrodaindexaidperrenderlopiù
leggibilesequalcunaltrodovesselavorareinseguitoalprogetto.
Inviarealserver
Questoèciòchevienerecuperatodalserver,mainchemodopossiamocreareun
nuovocontattooaggiornarneunoesistenteconngResource?Esaminiamoprimacome
crearneunoeosserviamocomengResourcegestiscequestoaspetto.Modificheremoil
metodocreatenelserviziodigestionedeicontattiperrestituireunanuovaistanza
dellarisorsa:
create:function(){
returnnewResource();
},
QuestodiventeràilmodellonellavistaAddContact.Sicomportacomeprevisto,
madàaccessoaunmetodo$saveperpassarloalserver.Chiamiamoilnuovometodo
eassegniamoloalmodellocontactall’internodiaddCtl:
create
$scope.contact=Contact.create();
Tuttodovrebbecomportarsicomeprevistoquandocarichiamolavista;oraè
necessariosostituirealmetodocontacts.set,ormainonpiùattivo,l’handlersubmitper
lanuovafunzione$saveoffertadallarisorsa:
$scope.submit=function(){
$scope.contact.$save();
$scope.contact=Contact.create();
alert.show();
};
Abbiamoanchemodificato$scope.contact,chenonvienepiùeliminatomarecupera
unanuovaistanzadellarisorsadalservizio.
Analogamentepossiamoutilizzarelastessaazioneupdatecreatainprecedenzaper
salvarelemodifichediuncontattoesistente.Aquestoscopoènecessarioservirsidi
uneventopersonalizzatoinmodocheilcontrollersappiaquandosalviamole
modificheall’internodelladirettivamodificabile.Glieventipersonalizzatisi
comportanopropriocomegliomologhinativiJavaScript,alparidiclickemouseover.È
possibilemettersiinascoltoedeseguiredelleazioniquandovengonoattivati.
Percreareuneventopersonalizzato,utilizziamoilmetodo$scope.$emit.Accettadue
parametri:ilnomedell’eventoel’arraydiparametricheintendiamopassareal
listener.Inquestocasononènecessariopassarealcunparametro;chiamiamo
semplicementel’eventosalvatoeinseriamolonellafunzione$scope.saveall’interno
delladirettivamodificabile:
$scope.$emit(‘saved’);
Ascoltareuneventoèaltrettantofacile:èsufficienteutilizzareilmetodo$scope.$on.
Essoaccettadueparametri:ilprimoèilnomedell’eventodaascoltare,ilsecondoè
lafunzionedell’handler.AggiungiamoilseguentecodicealcontrollercontactCtl:
$scope.$on(‘saved’,function(){
...
});
Seavessimopassatodeiparametriall’evento,sarebberostatiaccessibilicome
parametrinellistenerdeglieventieilprimosarebbesemprestatol’eventoJS.
Illistenereseguiràilmetodo$updatesulmodellocontact.Tuttavia,siccomel’evento
vieneemessoprimacheilmodelloabbiaconclusol’aggiornamento,dobbiamo
spingerloallafinedellostackodellacodacorrente.SeconosceteJavaScript,saprete
cheèpossibileutilizzaresetTimeout.Èpropriociòchefaremo,mainvecediservircidi
,opteremoperilserviziowrapperdiAngular:$timeout.
setTimeout
Dobbiamoinserirlonelcontroller:
.controller(‘contactCtrl’,function($scope,$routeParams,Contact,
$timeout){
...
})
PoisitrattasemplicementediutilizzarlocomesetTimeout:
$scope.$on(‘saved’,function(){
$timeout(function(){
$scope.contact.$update();
},0);
});
Orasemodifichereteuncontattoefareteclicsulpulsantesave,idativerranno
salvatisulserver.
Cancellareicontatti
Infine,noncirestacheimpostareilpulsantedelete.Modifichiamoilmetodo
all’internodelserviziodeicontatti:
destroy:function(id){
resource.delete({id:id});
}
Inquestocasochiamiamoilmetododeletesullarisorsa;volendoavremmopotuto
utilizzareremovechesvolgelastessaoperazione.Abbiamonuovamentemodificatoil
nomedelparametroindexperrenderlopiùleggibile.
Dobbiamoancoracompiereun’operazioneinquestaistanza:aggiornarela
funzionedeletenelcontrollerindexCtl.Analizziamoallorailmetodofinito:
$scope.delete=function(index){
Contact.destroy($scope.contacts[index].id);
$scope.contacts.splice(index,1);
alert.show();
};
Siccomedobbiamosiaeseguireilpingdelserversiarimuoverlodall’arraylocale,
ènecessariocontinuareautilizzareindex.NellachiamataContact.destroy()accediamo
alcontattoopportunoerecuperiamoilsuoID.Locancelliamodirettamentedall’array
localeutilizzandoilmetodoJSnativospliceperfareinmodochetuttosia
sincronizzato.
Gestionedeglierrori
Èpossibilegestireglierroricomefaremmocon$http.Tutteleazionicheabbiamo
esaminatoaccettanoduefunzionidicallback:unaperilsuccessoel’altraper
l’errore.Eccoilmetodogetconincluseentrambelefunzionidicallback:
returnResource.get({id:id},function(){
window.alert(‘Success!’);
},function(){
window.alert(‘Error!’);
});
Cosìpossiamoinformarel’utentecheesisteunproblemaoeseguirealtreazionise
necessario.Siccomelofacciamodaunservizio,èopportunoincluderedueparametri
perconsentirechequestefunzionidicallbackvenganoimpostatedalcontroller
quandovienechiamatoilmetodoinquestione:
find:function(id,success,error){
returnRgesource.get({id:id},success,error);
},
Sistemialternatividiconnessione
Abbiamogiàesaminatoalcunisistemiconiqualièpossibileconnettersialservere
configurarel’apppersfruttarengResource.Esistonoaltrimodulicheèpossibile
utilizzareperconnettersiaunserver;neconsidereremorapidamentedue.
RestAngular
RestAngularèunprogettodellacommunitycheoffreunservizioperconnettersi
adAPIRESTful,comengResource.Presentaalcunedifferenzesignificativecheè
opportunoconoscere.
L’aspettopiùimportantedatenerepresenteècheRestAngularutilizzapromise
propriocome$http.Pertantosiaccedeaunpatternperdeterminareseunachiamata
haavutosuccessoomeno,maquestosignificacheesistonopassiaggiuntivida
compiereechenonèpossibileassegnarlosemplicementeaunmodello.
Ancheseènecessarioscrivereunpo’piùdicodiceacausadell’utilizzodelle
promise,nondovretetrascrivereisegnapostoseguendoilpatternREST;RestAngular
lofaràalpostovostro.
PreferiamongResource.RestAngularrichiedealcunipassaggiinpiù,mentre
funzionaperfettamenteconinostriservizi.Tuttaviavalesemprelapena
ngResource
sperimentareciòchepotrebberivelarsiefficaceperleproprieesigenzeequindi
consigliamodiprovareancheRestAngular.
UtilizzareRestAngular
ÈpossibilescaricareRestAngulardalsitohttps://github.com/mgonto/restangulare
includerlocomequalunquealtromodulo.Vedremorapidamentecomeimpostarloein
chemodoèpossibileottenereunalistadeicontatti.
ApprezziamolacapacitàdiRestAngulardiimpostareunURLdibase.Èpossibile
definirlonelmetodoglobaleconfigdelmodulotramiteilservizioRestangularProvider:
.config(function($routeProvider,$locationProvider,
RestangularProvider){
RestangularProvider.setBaseUrl(‘http://localhost:8000/’);
})
Dopoaverimpostatol’URLdibase,possiamoutilizzareRestAngularnominando
larisorsaallaqualeintendiamoaccedereeunmetododiRestAngular:
Restangular.all(‘contacts’).then(function(contacts){
$scope.contacts=contacts;
});
Comepotetenotare,RestAngularsibasasulpatterndellepromiseutilizzatoda
$httpedènecessarioeseguirel’unwrappingperassegnarealmodelloidatirestituiti.
LeggeteladocumentazionediRestAngularsuGitHubperunalistacompletadei
metodicheèpossibileutilizzare.
Firebase
Firebaseèunserviziorelativamentenuovocheconsentedicrearefacilmente
un’applicazioneintemporealesenzascrivereunasolarigadicodicediback-end.
QuandosioperaconAngular,l’aziendaoffreun’utilelibreriadihelperper
sincronizzarefacilmenteidaticonilsuoservizio.
LadashboarddiFirebaseconsentedivisualizzareidatiinunastrutturaadalbero
comprimibilesimileaquellamostratasuccessivamente.
Dopoavercreatounaccountall’indirizzohttp://firebase.comeconfiguratol’app,è
giuntoilmomentodiimpostareAngularFire.DobbiamoincludereilclientFirebasee
AngularFire,chesipossonotrovareall’indirizzohttp://angularfire.com.
ÈmoltofacilerecuperareidatidaFirebase,considerandochetuttoavvienein
temporealeequalsiasimodificacompiutaaltrovesirifletteautomaticamente
nell’applicazione.Comelamaggiorpartedeimoduli,AngularFireesponeun
servizio,inquestocaso$firebase.Possiamoinserirloneicontroller,nelledirettiveo
neiservizi,nelseguentemodo:
.factory(‘Contact’,functionContactFactory($resource,$firebase){
...
})
ÈpossibileavviarelaconnessioneconFirebaseutilizzandoilsuoclientJS:
varcontacts=new
Firebase(“https://<yourbase>.firebaseio.com/contacts”);
IlservizioAngularFirefacilitailrecuperodeidatidaFirebase.Questoèciòche
potremmofareconilmetodogetdelnostroservizio:
get:function(){
return$firebase(contacts);
},
Ancheaggiungereicontattièmoltofacile.Dopoaverrecuperatoidati,otteniamo
l’accessoaimetodi$add,$removee$update:
$firebase(contacts).$add({
name:‘DeclanProud’,
...
});
SeapriteilpannellodicontrollodiFirebaseeaggiungetemanualmenteun
contatto,vedretechequestocompariràautomaticamentenellalistadeicontatti.
Ovviamenteèesageratoperun’appcomequestachegestisceicontatti,maapre
infinitepossibilitàperclientdichat,notificheealtroancorasenzaesserecostrettia
scrivereunasolarigadicodicediback-end.
Quiz
1. Chetipodioggettorestituisceilmetodo$http?
2. Inchemodoèpossibileottenereunarraydicontattieassegnarliaunmodello
con$http?
3. Checosasignificailsimbolo@inunaconfigurazionediparametripredefinita?
4. IndicateleduedifferenzeprincipalitrangResourceeRestAngular.
5. InchemodoFirebasecreal’applicazione?
Riepilogo
Inquestocapitoloabbiamotrasformatol’applicazionedaun’appfront-endche
utilizzavadatiditipohard-codeinunachesiinterfacciaconun’APIpermemorizzare
erecuperareleinformazioni.AbbiamoscopertocomeèflessibileAngular
esaminandoquattrodiversimetodiperconnettersiaunserver.
Iservizidibassolivellocome$httpsonoottimiinalcunicasi,maperlosviluppodi
un’applicazionecompleta,abbiamovistocheèopportunoutilizzarequalcosadipiù
raffinato.ModulicomengResourcemantengonolabasedicodicegestibilee
rispondentealprincipioDRY(Don’tRepeatYourself).
Nelprossimocapitoloapprofondiremoquestoconcettoanalizzandoduerunnerdi
codice:Gruntegulp.
Capitolo9
Itaskrunner
Ilprogettohaunbell’aspetto,manonèefficiente.Abbiamoinclusodiecifile
JavaScript,checomportanodiecirichiestedirete,senzaconsiderareilfogliodistile.
Ciòsignificachelapaginaimpiegheràpiùtempoacaricarsi.Alterminedel
caricamento,ilbrowserdovràrecuperareognifileJavaScriptecompilarlo.
Potremmoprenderemanualmentequestifileeconcatenarliinunosolo.Tuttavia
lavorandoalprogettoèprobabilechecontinueremoaeffettuarealtremodifiche,e
sarebbeirritanteripeterecontinuamentequestoprocesso.
Itaskrunnersonounottimosistemaperautomatizzareattivitànoiose.Non
dovremopiùconcatenareeminificaremanualmente,maavremoun’installazioneche
controlleràlemodificheneifileelicreeràautomaticamente.
Anchesenonliavetemaiutilizzati,probabilmenteavretesentitoparlareditask
runnercomeGruntegulp.Inquestocapitololimetteremoentrambiallaprovaper
concatenareeminificareifileJavaScriptinununicofile.
InstallareNodeeNPM
SiaGruntsiagulpsibasanosuNodeeilsuoNodePackageManager(NPM).Se
avetegiàinstallatoeconfiguratoNode,tralasciatequestoparagrafo.Esamineremo
l’installazionesuMac,mailprocessoèsimileperWindows.GliutentiLinux
dovrannocompilaredalfilesorgenteovisitare
https://github.com/joyent/node/wiki/Installing-Node.js-via-package-managerperinstallarlo
tramiteungestoredipacchetti.
Andateall’indirizzohttp://nodejs.org/download/escaricatel’installerappositoperla
piattaforma.Apritelo,accettateilcontrattodilicenzaecompletatelaprocedura.
Setuttosisvolgecomeprevisto,dovrestevedercomparireunmessaggiosimile:
Nodewasinstalledat
/usr/local/bin/node
npmwasinstalledat
/usr/local/bin/npm
Verificateche/usr/local/binsitroviin$PATH.Senonsietesicurichesitrovinel
vostropercorso,eseguitequestocomandodaterminale:
echo$PATH
Cercate/usr/local/bin.Senonlotrovate,aggiungetequantoseguea~/.bash_profile
oppure~/.zshrc:
exportPATH=/usr/local/bin:$PATH
AllafinedellaproceduraNodeeNPMsarannoinstallati.Potreteaccedereai
comandinodeenpm,evisaràpossibileinstallareGruntogulpnelprogetto.
NOTA
Potrebbe essere necessario riavviare la sessione del terminale per far funzionare tutto come
previsto.
UtilizzareGrunt
DopoaverinstallatoNode,sfruttereteGrunt.Laconfigurazioneavvieneintrefasi:
lostrumentodarigadicomando,l’installazionelocalediGruntnelprogettoela
configurazionedelGruntfile.
Installarel’interfacciaarigadicomando
Installarelacommand-lineinterface(CLI)èmoltofacilegrazieaNPM.È
sufficienteeseguirequantoseguenelterminale.Ilflag–gverificheràl’installazione
globalediGrunt:
npminstall-ggrunt-cli
Asecondadeipermessi,potresteeseguirlocomeroot.SusistemiOSXobasatisu
*nix,sitrattadieseguirloconilprefissosudo.InWindows,ènecessarioaprirelashell
dicomandocomeamministratori.
Alterminedell’installazione,ilcomandogruntsaràdisponibileeverràaggiuntoal
percorsodelsistema,epotràvenireeseguitodaqualsiasidirectory.
InstallareGrunt
PerutilizzareGruntnelprogettoènecessarioaggiungereduefile:package.jsone
.
Gruntfile.js
Ilfilepackage.jsonnonvieneutilizzatodaGrunt,madaNPM.Indicaalgestorequali
pacchettiservonoalprogettoquandoeseguiamol’installer.Gruntfile.jsèciòche
configuraGrunt.Forniscediverseindicazionialtaskrunner,daqualifileconsiderare
aqualiattivitàeseguire.
Creareunfilepackage.json
Creiamoilfilepackage.jsonaffinchéNPMsappiaqualifilerecuperare.Diseguitoil
fileJSONcompletato.IlNodePackageManagerciconsentiràdicrearlofacilmente
eseguendoilcomandonpminit:
{
“name”:“ContactsMgr”,
“version”:“1.0.0”,
“description”:“AsimplecontactsmanagerinAngularJS+
Bootstrap”,
“dependencies”:{
“grunt”:“~0.4.1”,
“grunt-contrib-uglify”:“~0.2.0”,
“grunt-contrib-watch”:“~0.5.3”
}
}
Comepotetenotare,sitrattadiunoggettoJSONstandardconalcuneproprietà
chiaveimpostate.Ilnome,laversioneeladescrizionesonorichiesti,mautilizzati
soltantoquandodistribuiamoilprogettosottoformadiunpacchettosuNPM.La
proprietàdependenciesèilpuntoincuiavvengonoprocessiinteressanti.
Gruntsitrovaincimaallalistadelledipendenze.Glialtripacchetticheabbiamo
inclusosvolgerannolapartepiùrilevantedellavoro.Ilpacchettogrunt-contrib-uglify
concateneràeminificheràifileJSel’ultimopacchettonellalistacontrolleràifile
allaricercadimodificheedeseguiràattivitàspecifiche.
NOTA
Èimportantericordarecheilnomedelpacchettonondevecontenerespaziocaratterispeciali.
CreareilfileGruntfile.js
Gruntfileèmoltoimportante.ConsideratelocomeilmanualediistruzionidiGrunt,
chesenzadiessononsaprebbechecosafare.Tuttalaconfigurazioneavviene
all’internodellaseguentefunzionewrapperdiGrunt:
module.exports=function(grunt){
};
NOTA
Èimportanteche Gruntfile.jsvengasalvatonellaradicedovesivuoleeseguireGruntecheil
nomedelfileiniziconlaGmaiuscola.
Lamaggiorpartedeiplug-inrichiederàl’utilizzodelmetodoinitConfigdiGrunt,ed
èciòcheutilizzeremoconilplug-inuglify:
module.exports=function(grunt){
grunt.initConfig({
...
});
};
All’internopossiamoconfigurareiplug-inutilizzandoilnomecomechiave.
Possiamoancheottenereleinformazionidirettamentedalfilepackage.json,comeil
nomedautilizzarenelleattività:
grunt.initConfig({
pkg:grunt.file.readJSON(‘package.json’)
});
QuestocodicecaricheràilfileJSONeloassegneràallachiavepkg,consentendocidi
accedereaqualsiasiinformazioneimpostatainprecedenzaavvalendocidellasintassi
standardperitemplate(<%=%>).
Quandoconfigureremol’attivitàuglify,imposteremodueproprietà:optionsebuild.
Laproprietàoptionsconsentedidefinireelementicomeibannercheintendiamo
includerenelfilecompilato,creareunamappadeifilesorgenteoseintendiamo
concatenareperildebugging.
Laproprietàbuildèiltargetepossiamoattribuirlequalsiasinomedesideriamo.Per
esempio,unapotrebbechiamarsideveun’altraproductioncondiverseopzioni.Può
accettareleproprietàsrcedest,checiconsentonodiimpostarequalifilevengono
inclusiequaligenerati.Èanchepossibiledefinireunaltrooggettooptions,utile
quandointendiamoutilizzaremolteplicitarget.Eccol’attivitàuglifyconl’hash
:
options
grunt.initConfig({
pkg:grunt.file.readJSON(‘package.json’),
uglify:{
options:{
banner:‘/*!<%=pkg.name%><%=
grunt.template.today(“yyyy-mm-dd”)%>*/\n’
}
}
});
Inquestocasoabbiamoinclusoilbannernell’oggettooptions.Siccomeilfile
èstatoconvertitoinunoggettoJS,èsufficienteutilizzarelasintassi
package.json
standardperaccederealnome.Gruntcomprendeancheduehelperdicuipossiamo
avvalerci.Vedretecheinquestocasoricaviamoladataodierna,maèanchepossibile
utilizzareilmetodogrunt.template.dateperformattareuntimestampJS.Puòessere
utilequandointendiamoincludereladatainunbanneroinunnomefile.
Oraimpostiamoiltarget.Laproprietàsrcpuòessereunastringaounarray.In
questocaso,siccomeutilizziamoalcunifileJS,dovremoservircidiunarray.La
proprietàdestèilpercorsorelativoalfilechevogliamocheGruntcreiper
impostazionepredefinita,mapuòancheesseremodificatomedianteilmetodo
grunt.file.setBase.Abbiamoaggiuntol’oggettobuildeimpostatoleproprietàsrcedest:
grunt.initConfig({
pkg:grunt.file.readJSON(‘package.json’),
uglify:{
options:{
banner:‘/*!<%=pkg.name%><%=
grunt.template.today(“yyyy-mm-dd”)%>*/\n’
},
build:{
src:[
‘assets/js/vendor/jquery.js’,
‘assets/js/vendor/bootstrap.js’,
‘assets/js/vendor/angular.js’,
‘assets/js/vendor/angular-animate.js’,
‘assets/js/vendor/angular-resource.js’,
‘assets/js/vendor/angular-route.js’,
‘assets/js/vendor/angular-sanitize.js’,
‘assets/js/vendor/angular-strap.js’,
‘assets/js/vendor/angular-strap.tpl.js’,
‘assets/js/controller.js’
],
dest:‘assets/js/build/<%=pkg.name%>.js’
}
}
});
Abbiamoutilizzatoilnomedelpacchettocomenomefileetuttiifileminificati
sonostatisostituiticonleversioninonminificate.Poichéminificheremotutto,è
necessarioutilizzareleversionidisviluppodeifileperevitareproblemidurantela
compilazione.
L’ordinedeifilenell’arraysrcèlostessoconcuisarannoinclusinelfiledi
destinazione.SiccomeabbiamobisognochejQuerysiainclusoprimadiAngulare
Angularprimadeimoduli,èimportanteimpostarlibene.
Ilplug-inècompilato,maGruntnonsacheintendiamoutilizzarel’attivitàuglify
cheabbiamoscaricatoinprecedenzadaNPM.Aquestoscopoènecessarioutilizzare
ilmetodoloadNpmTasks:
grunt.loadNpmTasks(‘grunt-contrib-uglify’);
Siinseriscenellafunzionemodule.exportsefainmodocheilGruntfilecompletosia
comeilseguente:
module.exports=function(grunt){
grunt.initConfig({
pkg:grunt.file.readJSON(‘package.json’),
uglify:{
options:{
banner:‘/*!<%=pkg.name%><%=
grunt.template.today(“yyyy-mm-dd”)%>*/\n’
},
build:{
src:[
‘assets/js/vendor/jquery.js’,
‘assets/js/vendor/bootstrap.js’,
‘assets/js/vendor/angular.js’,
‘assets/js/vendor/angular-animate.js’,
‘assets/js/vendor/angular-resource.js’,
‘assets/js/vendor/angular-route.js’,
‘assets/js/vendor/angular-sanitize.js’,
‘assets/js/vendor/angular-strap.js’,
‘assets/js/vendor/angular-strap.tpl.min.js’,
‘assets/js/controller.js’
],
dest:‘assets/js/build/<%=pkg.name%>.js’
}
}
});
grunt.loadNpmTasks(‘grunt-contrib-uglify’);
};
EseguireGrunt
Orapossiamoeseguirel’attivitàgruntuglifychegenereràilfileContactsMgr.js
completo.
Sesostituiteaidieciscriptilnuovofilenellaradiceindex.htmlecaricate
l’applicazione,noteretechenonfunzionanullaevedreteilseguenteerroredella
console:
Error:[$injector:unpr]Unknownprovider:a
Comepartedelprocessodiminificazione,inomidellevariabilivengonosostituiti
conleloroversioniabbreviate.SappiamocheAngularsibasamoltosulla
dependencyinjection,cheosservailnomedellavariabileperinserireilservizio
correttoneicontrollerenelledirettive.
FortunatamenteAngularoffreunasoluzionerapidaefacile.Sitrattadicambiarele
funzioninellequaliinseriamoiservizi,congliarraycontenentiinomideiserviziche
intendiamoinserireelafunzionecomesuoivalori.Eccocomeappareconfigdel
modulo:
.config([‘$routeProvider’,‘$locationProvider’,
function($routeProvider,$locationProvider){
...
}])
Finchélafunzioneèl’ultimanell’array,Angularesamineràlevariabilieutilizzerà
ilserviziocorrispondentedell’array.Gruntnonmodificheràilvaloredegliitem
nell’arraypoichésitrattadistringheenondinomidivariabili;èquindiimportante
chel’ordinenell’arraycorrispondaaciòchevieneinseritonellafunzione.
Èsufficienteaggiungerequestiwrapperperl’arraynelfilecontroller.jspoichéa
tuttelelibrerieeaimodulicheabbiamoutilizzatoègiàstatoapplicatoquesto
processo.
Ricordatecheancheicontrollernelledirettivedevonoutilizzarelanotazionedegli
array.
Impostarewatch
SiamoriuscitiaconfigurareGruntpercompilareifileefunzionabenissimo.
Tuttavianonsarebbepiùunprocessoautomaticosedovessimoeseguiregruntuglify
ognivoltacheintroduciamounamodificaneifileJavaScript.Gruntpuòtenere
d’occhiopernoiquestiaspettiedeseguireautomaticamentealcuneattivitàquando
apportiamodellemodificheaifile.
Aquestoscopoutilizziamoilpacchettogrunt-contrib-watchcheabbiamorecuperato
primadaNPM.Laconfigurazioneèmoltosempliceerichiedesolodueproprietà:
filesetasks:
watch:{
files:[
‘assets/js/*.js’
],
tasks:[‘uglify’]
},
Utilizziamol’asteriscocomecaratterejollyaffinchéGruntindividuiifile.jsnella
directoryassets/js.Possiamocollocarequanteattivitàdesideriamonell’arraytask,e
verrannoeseguiteinordine.
L’esecuzionedigruntwatchnelTerminalefaràinmodocheGruntcontinuiavenire
eseguitoinbackground.Nonappenaunfilevienemodificato,entrainazioneed
eseguel’attivitàuglify,concatenandoeminificandoifileJavaScript.
Crearel’attivitàpredefinita
Spessoavretebisognodieseguiremoltepliciattivitàinunavoltasola.Gruntvi
consentedifarloregistrandol’attività:
grunt.registerTask(‘default’,[‘uglify’]);
Ilprimoparametroèilnomedell’attività,mentreilsecondoèl’arraydelleattività
chedesideriamoeseguire.L’utilizzodellaparolachiavedefaultcomunicaaGruntche
questaèl’attivitàdaeseguirequandononnespecifichiamouna.Peresempio,
potremmoeseguirel’attivitàdefaultinunodeiduemodiseguenti:
gruntdefault
grunt
Utilizzaregulp
GulpèabbastanzanuovoeprendelemossedaGrunt.Acausadellasuavita
relativamentebreve,nonesistonomoltiplug-indisponibili.Tuttaviac’èuglifyeil
loronumeroèincontinuacrescita.Ilvantaggioèchegulpmiraasemplificarela
configurazioneeaeseguireleattivitàpiùvelocementediGrunt:spettaavoidecidere
qualedeidueèpiùadattoalvostroprogetto.
ComeGrunt,gulpècostituitodadueparti:lostrumentodarigadicomando
globaleel’installazionelocalecheincluderemonelprogetto.
Installaregulpglobalmente
ÈmoltofacileinstallaregulpglobalmentemedianteununicocomandoNPM:
npminstall-ggulp
Potrestedoverloeseguirecomeradiceutilizzandosudooattraversoilpromptdei
comandidiWindowscomeamministratori.
Alterminedell’installazione,ilcomandogulpsaràdisponibiledalterminale.
Installareledipendenzedigulp
PropriocomeconGrunt,ènecessariocreareunfilepackage.json,checonterràtutte
ledipendenzedelprogetto.Iniziamoainstallaregulp:
{
“name”:“ContactsMgr”,
“version”:“1.0.0”,
“description”:“AsimplecontactsmanagerinAngularJS+
Bootstrap”,
“dependencies”:{
“gulp”:“~3.6.0”
}
}
Possiamoaggiungeremanualmenteuglifyalfilepackage.json,oppurericorrerea
NPMperquestaoperazione:
npminstall--save-devgulp-uglify
Ilflagsave-devindicaaNPMchevogliamocheloaggiungaalfilepackage.json.In
alternativapotremmoutilizzareilflag--save,masiccomegulpvieneusatosoltantoin
fasedisviluppo,nonciservedurantelaproduzione.
Adifferenzadelplug-inuglifydiGrunt,nonconcateneràifileedovremoricorrere
aunaltroplug-inperquestoscopo:
npminstall--save-devgulp-concat
Impostaregulpfile
AdifferenzadiGrunt,ilfilegulpfilenondeveiniziareconlaletteraGmaiuscola,
ancheseospitalaconfigurazioneesicollocanellaradicedelprogetto.Seilconcetto
allabasedelfileèsimile,laconfigurazioneèmoltodiversa.
RicorderetecheGruntfilerichiedevaunafunzionewrapperperpoteraccedereatutti
imetodiGrunt.Congulpèunpo’differenteeognipacchettocheotterremograziea
NPMpotrebbeesserenecessarionelfile.Includetequantosegueall’iniziodel
gulpfile.
vargulp=require(‘gulp’);
varuglify=require(‘gulp-uglify’);
varconcat=require(‘gulp-concat’);
varpkg=require(‘./package.json’);
Possiamoancheincludereleinformazionidelfilepackage.jsonrichiedendolonello
stessomododiunpacchettoNPM.Icaratteri./all’inizio,indicanoaNodedi
guardarenellastessadirectorydelgulpfilequandoeseguiamogulp.
Inquestocasononesistealcunoggettodiconfigurazione,equestosemplificale
cose.Tuttoavvieneall’internodelleattività.Eccol’attivitàuglifycompleta:
gulp.task(‘uglify’,function(){
gulp.src(paths.js)
.pipe(concat(‘ContactsMgr.min.js’))
.pipe(uglify())
.pipe(gulp.dest(‘assets/js/build’));
});
Ilmetodogulp.taskaccettadueparametri:ilnomedell’attivitàeunafunzione
anonimachecontienetuttociòchefaràl’attività.
Abbiamoinclusounavariabileingulp.src.Cosìlapotremoutilizzareinseguitoe
offriràmaggioreflessibilitàsenzaesserecostrettiascriverlaognivolta:
varpaths={
js:[
‘assets/js/vendor/jquery.js’,
‘assets/js/vendor/bootstrap.js’,
‘assets/js/vendor/angular.js’,
‘assets/js/vendor/angular-animate.js’,
‘assets/js/vendor/angular-resource.js’,
‘assets/js/vendor/angular-route.js’,
‘assets/js/vendor/angular-sanitize.js’,
‘assets/js/vendor/angular-strap.js’,
‘assets/js/vendor/angular-strap.tpl.min.js’,
‘assets/js/controller.js’
]
};
Eccol’oggettopathsalqualefacciamoriferimento.Sitrattadellostessoarraydi
filecheabbiamoinclusoprimainGruntfile.
Gulputilizzaglioperatoripipeperelaborareidati.Tuttiipacchettiaiquali
abbiamofattoriferimentoall’iniziodelfilegulpfilesonofunzioni.Ilplug-inconcat
accettailnomedelfilecheintendiamogenerarecomeoutput.Abbiamorecuperatoil
nomedalfilepackage.jsoneaggiuntol’estensione.js.Ilplug-inuglifyoffreunnumero
diopzionichepossiamopassarecomeunhashJSperfacilitareildebugging,eil
metodogulp.destcheutilizziamociconsentediimmettereilnomedelladirectory
dell’output.
Dopoaverimpostatol’attività,potremmoeseguiregulpuglify,maprimaè
necessarioimpostareanchewatch.AdifferenzadiGrunt,nonènecessarioincludere
unulterioreplug-inperquestoscopo,poichésipresentacomeunaltrometododi
gulp:
gulp.task(‘watch’,function(){
gulp.watch(paths.js,[‘uglify’]);
});
L’impostazioneèmoltosemplice.Creiamounanuovaattivitàeutilizziamoil
metodogulp.watch,attraversandol’arraydifilecheintendiamoosservareepoil’array
diattivitàchevogliamoeseguirequandoquestifilecambiano.
Impostiamorapidamenteun’attivitàpredefinitapercompletareilfilegulpfile:
gulp.task(‘default’,[‘uglify’]);
Eccoilfilegulpfile.jscompletocheconfiguraefficacementeleattività:
vargulp=require(‘gulp’);
varuglify=require(‘gulp-uglify’);
varconcat=require(‘gulp-concat’);
varpkg=require(‘./package.json’);
varpaths={
js:[
‘assets/js/vendor/jquery.js’,
‘assets/js/vendor/bootstrap.js’,
‘assets/js/vendor/angular.js’,
‘assets/js/vendor/angular-animate.js’,
‘assets/js/vendor/angular-resource.js’,
‘assets/js/vendor/angular-route.js’,
‘assets/js/vendor/angular-sanitize.js’,
‘assets/js/vendor/angular-strap.js’,
‘assets/js/vendor/angular-strap.tpl.min.js’,
‘assets/js/controller.js’
]
};
gulp.task(‘uglify’,function(){
gulp.src(paths.js)
.pipe(concat(pkg.name+’.js’))
.pipe(uglify())
.pipe(gulp.dest(‘assets/js/build’));
});
gulp.task(‘watch’,function(){
gulp.watch(paths.js,[‘uglify’]);
});
gulp.task(‘default’,[‘uglify’]);
Orapossiamoeseguiregulpuglifyogulpnelterminaleperconcatenareeminificare
ifileJavaScript.Aparteunsingoloutilizzo,possiamoancheeseguiregulpwatchper
verificarel’esistenzadieventualimodificheedeseguireautomaticamentel’attività
uglify.
Riorganizzareilprogetto
Dopoaverimpostatoiltaskrunner,èilmomentodiriorganizzareilprogettoper
ottenereunabasedicodicepiùgestibile.Separeremoicontroller,ledirettive,ifiltrie
iserviziinfiledistintiperteneretuttoinordine.
Iniziamoaelaborareunanuovastrutturadidirectorycomeillustralaseguente
figura.
Abbiamospostatotuttofuoridelladirectoryassets/jsnelladirectoryappdella
radice.Quandopasseremoallaproduzione,nonintendiamodistribuireifilesorgente,
quindièunabuonaideatoglierlidalladirectoryassetsdoverimarrannoifile
compressi.
Lanuovadirectoryappèstatastrutturatainmodoleggermentediverso.Cisonotre
cartellecomponents,vendoreviewseunfilemodule.js.Icomponentisonoitem
condivisi,quindinelcasodell’app,collochiamoquiledirettiveeiservizi.Lacartella
vendorcontienetuttiifileJSditerzepartimentrelacartellaviewstuttiiprincipali
controllerperleviste.
Dopoavercreatolenuovedirectory,possiamoiniziareaseparareicontrollerin
diversifileperincluderlinelladirectoryviews.Devonoancoraessereassociatial
moduloeaquestoscopopossiamoutilizzareladichiarazione
angular.module(‘contactsMgr’)all’iniziodelfile.
Ecco,peresempio,ilcontrollercontactCtl;nominatelocontact.jseinseritelonella
cartellaviews:
angular.module(‘contactsMgr’).controller(‘contactCtl’,[‘$scope’,
‘$routeParams’,‘contacts’,‘$timeout’,function($scope,
$routeParams,contacts,$timeout){
$scope.contact=contacts.find($routeParams.id);
$scope.$on(‘saved’,function(){
$timeout(function(){
$scope.contact.$update();
},0);
});
}]);
Orachetuttiicontrollersonostatiseparati,passiamoalledirettive.Lesposteremo
nellacartellacomponentsconlanuovadirectoryappdellaradice.Cosìcomeconi
controller,ènecessarioverificarechesianoancoraassociatealmodulo.
Copiateognidirettivainfileseparatieassegnateaessel’estensione.directive.js.
Peresempio,ilgravatarsitroverànellacartellacomponentsesarà
gravatar.directive.js,mentreladirettivamodificabilesaràeditable.directive.js.
Siccomeifiltrisonoanch’essicomponenticondivisi,possiamocollocarliinsieme
conledirettiveenominarliallostessomodo.Posizionateiduefiltriinfileseparati:
truncate.filter.jsenewLine.filter.js.L’ultimocomponenteèilserviziocontactsche
utilizziamoperconnettercialserver.Createunnuovofilecontacts.service.jse
copiatelo.
Dopoaversistematovisteecomponenti,dobbiamoverificarecheilnuovofile
module.jscontengaciòchedovrebbe,ossiailmodulo.Copiateilcontenutodelfile
dalladirectoryassets/jsnelfilemodule.js.Dopoaverspostatotuttiifile,
contactsMgr.js
cancellateilcontenutodelladirectoryjs.Quiaggiungeremoinseguitounfile
interamenteminificato.
DobbiamoconfigurareGrunt/gulpaffinchéosservilenuovedirectoryegeneri
l’ouputnelladirectoryjsall’internodellacartellaassets.Ipercorsiaggiornatinel
Gruntfileoifilegulpfiledovrebberoessereiseguenti:
‘app/vendor/jquery.js’,
‘app/vendor/bootstrap.js’,
‘app/vendor/angular.js’,
‘app/vendor/angular-animate.js’,
‘app/vendor/angular-resource.js’,
‘app/vendor/angular-route.js’,
‘app/vendor/angular-sanitize.js’,
‘app/vendor/angular-strap.js’,
‘app/vendor/angular-strap.tpl.js’,
‘app/module.js’,
‘app/components/**/*.js’,
‘app/views/**/*.js’
Angularsioccupadellagestionedelledipendenze,maciòchecontaèl’ordine.
Carichiamojquery.jsprimadiangular.jsaffinchéAngularsappiachevogliamo
utilizzarequestoenoniljqLiteincluso.
TuttiimodulivendornecessitanodiAngularchedeveessereinclusoprimadiessi.
Inoltreicomponentielevisterichiedonoilcaricamentodelmodulo,altrimentinon
sapràachecosaassociarsi.
Primadieseguireiltaskrunnerdesiderato,modificateladestinazioneda
assets/js/buildadassets/js.
Eseguiteiltaskrunnerpercompilarel’applicazionecheaveteriorganizzato.Infine
modificateilfilealqualefateriferimentoinindex.htmloracheladestinazioneè
cambiata:
<scripttype=”text/javascript”
src=”assets/js/ContactsMgr.js”></script>
Apriteilbrowserpercontrollarechetuttoappaiacomeprevisto.Setuttosi
comportasecondoipiani,ilgestoredeicontattidovrebbefunzionareallaperfezione.
NOTA
Senonfunzionacomedovrebbe,controllatelaconsole.Probabilmenteavetetralasciatoqualcosa
ol’aveteinclusonell’ordinesbagliato.
Quiz
1. SuqualeambientesibasanosiaGruntsiagulp?
2. Perchéserveunfilepackage.json?
3. Qualeplug-invieneutilizzatoperminificareifile?
4. ChecosaènecessariofareaifileAngularprimadiminificarli?
Riepilogo
Inquestocapitoloabbiamoesaminatoduestrumentimoltoefficaciesimili.Ci
hannoconsentitononsolodiridurrenotevolmenteilnumerodellerichiesteHTTP,
maanchediriorganizzarecompletamentel’app.
SiaGruntsiagulpottengonolostessorisultato,edèunaquestionestrettamente
personalescegliereiltaskrunnerdautilizzare.Riteniamogulppiùrapidoefacileda
configurare,maindubbiamenteGruntvantaunnumeromaggiorediplug-inedèuno
strumentodisponibiledapiùtempoepiùcollaudato.
Nelcapitoloseguentevedremocomeutilizzarequestiduetaskrunnerperprendere
ifileLesscheutilizzaBootstrapecompilarliinunaversionepersonalizzata.
Capitolo10
PersonalizzareBootstrap
Finoral’applicazioneèpiuttostoconvenzionale.Siamoriuscitiasfruttareappieno
Bootstrap,mal’aspettopredefinitononhanulladinuovo.Bootstrapèprogettatoper
esserepersonalizzatoeutilizzaLessoilpreprocessoreCSSSASSpervelocizzaree
semplificarequestaoperazione.
Nelcorsodiquestocapitolostudieremocomeèpossibilecompilareilsorgente
LessdiBootstrapprimadipersonalizzarel’aspettoaffinchél’applicazionesia
veramenteunanostracreazione.Affronteremoiseguentiargomenti.
IfondamentidiLess.
PersonalizzareBootstrap.
CompilareLessconGruntogulp.
ImpostareLiveReload.
ItemidiBootstrap.
CompilareLessconGruntogulp
Primadiiniziareapersonalizzarel’app,èunabuonaideascoprirecome
trasformarenumerosifileLessinununicofogliodistile.Abbiamogiàvistocome
impostareGruntegulpperconcatenareeminificareifileJavaScript;ora
utilizzeremoquestistessitaskrunnerpercompilareLess.
Scaricareilsorgente
Innanzituttoprocuriamocil’ultimaversionediBootstrapeinseriamoifileLessnel
progetto.Visitiamohttp://getbootstrap.com/efacciamoclicsuDownloadBootstrap.
Comparirannotreopzioni:Bootstrap,SourcecodeeSass.Scegliamol’opzione
SourcecodeperchéincludeifileLesscheèpossibilepersonalizzareecompilare.
Comepotetenotare,ilsorgentediBootstrapèdiecivoltepiùgrandedellaversione
minificata.Pertenereinordineiltutto,copiateladirectorylessdalmaterialeche
avetescaricatonellacartellaassetsall’internodelprogetto.Ladirectorycontienei40
filelesschecostituisconoglistilidiBootstrap.
CompilareconGrunt
Comeabbiamovisto,Gruntèunefficientetaskrunner.Possiamoavvalercidella
minificazionedeiJavaScriptperautomatizzarelacompilazionediLessconCSS.A
questoscopoGruntricorreaunplug-incheèpossibilerecuperaredaNPM.
Includiamolonelpackage.jsondelprogettoedeseguiamonpminstalldalterminale.
TuttaviaèmoltopiùsempliceeseguireununicocomandoelasciarecheNPM
aggiungaladipendenzanelfilepackage.jsondelprogetto,nelmodoseguente:
npminstallgrunt-contrib-less--save-dev
Dopoaverinstallatoilplug-in,possiamoconfigurarlonelfileGrunt.Tuttoavviene
nuovamentenell’oggettoconfig.Imposteremoduetarget:unoperlosviluppoeun
altroperlaproduzione.Cosìpotremodefinireleopzioniperogniscenario.Per
esempio,perlaproduzionepotremmovolerminificareilCSS,maquestaoperazione
nonèsempreauspicabileinfasedisviluppo.
Seguelaconfigurazionecompletaperl’attivitàless:
less:{
dev:{
files:{
‘assets/css/bootstrap.css’:
‘assets/less/bootstrap.less’
}
},
production:{
options:{
cleancss:true
},
files:{
‘assets/css/bootstrap.css’:
‘assets/less/bootstrap.less’
}
}
}
Neltargetdevnonabbiamoimpostatoopzioni,maproductionpresentailflagcleancss
impostatosutrueperridurreledimensionideifileminificandol’output.L’oggetto
usalasintassiabbreviataperleproprietàsrcedestcheabbiamoesaminato
files
configurandoGrunteutilizzandouglifyperifileJavaScript.
NondimenticatedicaricareanchequestomodulodaNPM,altrimentiGruntnon
riusciràadaccedereall’attivitàless.InseritelonelwrapperdiGrunt:
grunt.loadNpmTasks(‘grunt-contrib-less’);
Orasaràpossibilecompilareglistili.Possiamoeseguirequantoseguenel
terminalepersvolgerel’attività:
gruntless
Questoperòproduceuneffettononvoluto,poichél’attivitàvieneeseguitasu
entrambiitargetunodopol’altro.
Possiamolimitarlaaunsolotargetspecificandoilnomedeltargetdopoidue
punti:
gruntless:dev
ImpostareWatcheLiveReload
Ovviamenteilprincipioallabasedell’utilizzodiuntaskrunnercomeGruntogulp
èautomatizzaretuttiquestiprocessi.Èpossibileutilizzareilplug-inwatchcome
abbiamofattonelCapitolo9pereseguireun’attivitàquandounfilecambia.Questo
cipermetteanchediutilizzarelivereloadsullapaginaconl’ausiliodiunplug-indel
browser.
Laconfigurazioneèfacilepoichéabbiamogiàinstallatoilplug-in.Ecco
l’impostazionecorrenteperl’attivitàwatch:
watch:{
files:[
‘assets/js/*.js’
],
tasks:[‘uglify’]
}
Potremmoaggiungereladirectorylessall’arrayfilesel’attivitàlessall’arraytasks.
Inquestomodoentrambeleattivitàverrannoeseguitequandocambieràunfile.jso
;tuttavianonèquestoilnostroobiettivo.Separandoleinduetarget,avremo
.less
maggiorecontrollosulleattivitàchesarannoeseguite:
watch:{
js:{
files:[
‘assets/js/*.js’
],
tasks:[‘uglify’]
},
less:{
files:[
‘assets/less/*.less’
],
tasks:[‘less:dev’]
}
}
Perquantoriguardalaconfigurazione,noncambianulla.Abbiamosoloseparatoi
duetipidifilepereseguirerispettivamenteuglifyeleattivitàless:dev.
Ilplug-inhaancheunaltroassonellamanica.Fungedaserverperilplug-in
LiveReloadpermolteplicibrowser.Perutilizzarloconl’app,ènecessarioincludereun
ulterioretagscriptnellapagina:
<scriptsrc=”http://localhost:35729/livereload.js”></script>
Inalternativaesisteun’estensioneperChrome,FirefoxeSafari,chepuòessere
scaricatadahttp://livereload.com/.Dopoaverlainstallata,nelbrowsersaràpossibile
effettuareilpingdelserverLiveReloadognivoltachesiverificanodellemodifiche.
ImpostareGruntpereseguireilpingdelnuovoplug-indelbrowserèmoltofacile;
èsufficienteimpostarelaproprietàlivereloadsutrueinunoggettooptions.
Aggiungiamolarapidamentealtargetless:
less:{
files:[
‘assets/less/*.less’
],
tasks:[‘less:dev’],
options:{
livereload:true
}
}
SeapriteilbrowsereattivateLiveReload,vedretechelapaginasiricaricaogni
qualvoltasieffettuanodellemodificheaifileless.Ottimo,manonsarebbepreferibile
sesiaggiornassesoloilCSSenonl’interapagina?Secambiaunfile,Grunt
ricaricheràl’interapagina.Perottenerequestorisultato,èpossibileaggiungereun
secondotargetallaconfigurazionecheverificalapresenzadimodifichenelfile
bootstrap.css:
css:{
files:[
‘assets/css/bootstrap.css’
],
options:{
livereload:true
}
}
SedisattiviamoLiveReloadneltargetless,ilbrowserriceveràunnuovofileCSSe
nonricaricheràpiùlapagina.
Compilarecongulp
Oraesaminiamol’altrotaskrunner:gulp.PropriocomeGrunt,gulputilizzaun
altroplug-inperconsentirelacompilazionediLess.Sarànecessarioincludereun
secondoplug-inperLiveReload,poichénonègiàinclusoinGrunt.
Installiamoeconfiguriamoilplug-inless.Possiamofarlodarigadicomando
eseguendo:
npminstallgulp-less--save-dev
Cosìloinstalleremonelprogettoeloincluderemoanchenelfilepackage.jsonperun
usofuturo.Perutilizzarlonelgulpfile,ricorreremoalmetodorequirediNodeper
includereilpacchetto.Inseriteloall’iniziodelgulpfile:
varless=require(‘gulp-less’);
Creiamounanuovaattivitàchiamatalesspergestiretuttalacompilazione,
servendocidelplug-incheabbiamoappenaincluso:
gulp.task(‘less’,function(){
});
ComeabbiamofattoconJavaScript,utilizzeremoilmetodosrcdigulpcondue
operatoripipeperottenereilrisultatodesiderato.Consideriamol’attivitàcompletaed
esaminiamolaneldettaglio:
gulp.task(‘less’,function(){
gulp.src(‘assets/less/bootstrap.less’)
.pipe(less({
filename:‘bootstrap.css’
}))
.pipe(gulp.dest(‘assets/css’));
});
Inquestocasoabbiamoinclusounsolofile;[email protected]
plug-inLessaccettaqualsiasiparametroaccettatodalcompilatoreLessufficiale.In
questaconfigurazioneimpostiamounnomefile,maperimpostazionepredefinita
utilizzeràilnomefiledelsorgente.
Infineciserviremodelmetodogulp.destperesportareilfilecompilatonella
directoryCSS.Questipiperappresentanoipassichel’attivitàesegueinordineedè
possibileaggiungernefacilmentealtrioriordinarliinfuturosefossenecessario.
OraGulpèconfiguratoperutilizzareilplug-inLessedèprontopercompilaregli
stilimedianteilcomandolessdigulp.
ImpostareWatcheLiveReload
Ovviamentemancheremmol’obiettivodell’automazionesedovessimoeseguire
questocomandomanualmente.Abbiamogiàscopertochewatchèintegratoingulp;
includerelessnell’attualeconfigurazioneconsistenell’aggiungeresoloun’altrariga
dicodice:
gulp.task(‘watch’,function(){
gulp.watch(paths.js,[‘uglify’]);
gulp.watch(paths.less,[‘less’])
});
Facciamoriferimentoaunanuovaproprietàall’internodell’oggettopaths.
Aggiungiamolaaffinchégulpsappiadovesitrovanoifilechestiamoconsiderando:
less:‘assets/less/*.less’
UtilizziamoilcarattereasteriscoperfareriferimentoaognisingolofileLess;così
gulppuòvedereesattamentequandoqualcosaècambiato.
ImpostareLiveReloadrichiedepiùlavoroperchécomportal’installazionediun
altroplug-in.RecuperiamolodaNPMconilseguentecomando:
npminstall--save-devgulp-livereload
Dopoaverloinstallato,fateriferimentoalplug-inall’iniziodelgulpfile:
varlivereload=require(‘gulp-livereload’);
LafunzionerestituitaèilservizioLiveReloadelopossiamoutilizzareperindicare
all’estensionedelbrowserqualifilesonocambiati.Perconfigurarlocorrettamenteè
necessariosvolgeredueoperazioninell’attivitàwatch.Innanzituttofacciamo
riferimentoalserverLiveReloadacuipossiamopassareifilechangedaunevento
attivatodalmetodogulp.watch:
gulp.task(‘watch’,function(){
varserver=livereload();
gulp.watch(paths.js,[‘uglify’]);
gulp.watch(paths.less,[‘less’]).on(‘change’,function(file){
server.changed(file.path);
});
});
Abbiamoassegnatolafunzionelivereload()allavariabileservereaggiuntoun
listenerperl’eventochangealwatcher.Dopochel’eventoverificaunoggettofile,è
possibilepassareilpercorsodelfilealserver.
ComeconGrunt,ènecessarioaffrontarelaquestionedelricaricamentodel
browserquandocambiaunfilenonCSS.Possiamorisolverlaaggiungendounterzo
watcher.Diseguitol’attivitàcompletataconquestaintegrazione:
gulp.task(‘watch’,function(){
varserver=livereload();
gulp.watch(paths.js,[‘uglify’]);
gulp.watch(paths.less,[‘less’]);
gulp.watch(‘assets/css/bootstrap.css’).on(‘change’,
function(file){
server.changed(file.path);
});
});
NOTA
Non dimenticate di sostituire nel file index.html, il file CSS minificato con quello appena
compilato.
Less
PercomprenderemegliociòcheoffreLess,introduciamoneifondamentiper
capirnelanaturaelasintassiallabasedelpreprocessore.
Osserveremoquattrotralesueprincipalicaratteristiche:importazione,mixin,
regoleannidateevariabili.Unalistacompletaèdisponibilesulsito
http://lesscss.org/features/.
IlvantaggiodiLessèchesenonvoleteutilizzarenessunasintassiofunzione
nuova,nonsaretecostrettiafarlo.QualsiasiCSSvalidoèancheLessvalido.
Importazionedifile
ComeperCSS,inLesspossiamoincludereunfileall’internodiunaltro.Inoltre
seguelastessasintassidiCSS:
@import“file.less”
Tuttavia,adifferenzadiCSS,cheeffettuaun’ulteriorerichiestaHTTPperilfileal
qualesifariferimento,Lessuniràilfilequandoècompilato.Seapritebootstrap.less,
vedretetuttiifileLessnecessariaiqualivienefattoriferimento.
NOTA
ConleversionipiùrecentidiLesspotetetralasciare.lessquandoincludeteifileecompilate.
Variabili
BootstraputilizzalevariabiliLessperconsentircidimodificareicolorieifont
deglielementivariable.Possiamocambiarlirapidamenteaprendoilfilevariables.less.
Unavariabilevienedefinitadalsimbolo@seguitodalsuonome,peresempio:
@brand-primary:#428bca;
Sitrattadisempliciriferimentidautilizzareneglistili.Possiamochiamarela
variabilefacendoviriferimentoall’internodelleproprietà:
color:@brand-primary;
Durantelacompilazione,Lesssostituiràaquestiriferimentiilcoloredefinitoin
precedenza.Bootstraputilizzaquestevariabiliintuttiisuoielementi;pertantoè
possibilemodificarerapidamentecoloriefontinquestofile.
Regoleannidate
ForseunodeipatternpiùosticidiCSSèl’assegnazionedeglistiliaglielementi
figlio.Invecediannidarel’elementofiglioall’internodell’elementogenitore,è
necessarioscrivereunaregolaseparata.Eccounesempio:
div{
background:#ccc;
}
diva{
color:#000;
}
diva:hover{
color:#fff;
}
ConLess,possiamoannidarequesteregoleperteneretuttopiùinordine:
div{
background:#ccc;
a{
color:#000;
&:hover{
color:#fff;
}
}
}
Comepotetenotarenell’esempioprecedente,èpossibileannidarepseudoclassi
utilizzandolasintassi&.Sitrattadiunriferimentoallaregolariguardanteilgenitore.
Nellostessomodoèpossibileanchedefinireunaclassesecondaria.Peresempio,
eccounpulsanteconduestilipericoloriarancioneeblu:
button{
color:#fff;
&.orange{
background:orange;
}
&.blue{
background:blue;
}
}
Mixin
Unmixinconsentediincludereglistilidaun’altraregola.Accettaancheargomenti
cheèpossibilepassareperotteneremaggioreflessibilità.Vediamounesempio:
.border-radius(@radius:5px){
border-radius:@radius;
}
Possiamoutilizzarequestomixinneglistili:
button{
.border-radius;
}
Ilvalorepredefinitocheabbiamoimpostatoverràutilizzatoautomaticamente.
Tuttaviapossiamoscavalcarlofacilmenteracchiudendolotraparentesi:
button{
.border-radius(15px);
}
PersonalizzareglistilidiBootstrap
PoichéutilizziamoilsorgentediBootstrap,possiamoanalizzareepersonalizzare
qualsiasifile.LessestendeCSSecioffrenuovefunzionisfruttatedaBootstrap.
Caratteritipografici
BootstraputilizzaHelvetica,forseilfontpiùpopolarealmondo.Perdare
all’applicazioneunpo’dicarattere,vediamocomeèpossibilesostituireaquestoun
altrofontprovenientedallalibreriaGoogleFonts,visitabileall’indirizzo
https://www.google.com/fonts.Dateun’occhiataetrovatequellochepiùvipiace.Perora
utilizzeremoRoboto,uncaratterebastone.
AggiungeteilfontallaraccoltaeselezionateglistiliLight,NormaleBold,come
nellafigurariportataallapaginasuccessiva.
Copiatelariga@importeincludetelaall’iniziodelfilebootstrap.less,cosìavrete
accessoallafamigliadifontRobotoneivostristili.CercateTypographynelfile
.Lasezioneiniziaall’incircaariga38.Modificheremolavariabile@font-
variables.less
cheBootstraputilizzaperimpostazionepredefinitacomebase:
family-sans-serif
@font-family-sans-serif:Roboto,“HelveticaNeue”,Helvetica,
Arial,sans-serif;
Potremmoanchecambiareladimensionedelcarattere,maperoranoneffettuiamo
alcunamodifica;cisembratuttograziosoedequilibrato.
navbar
Cercatenavbarinvariables.less;lasezionedovrebbeiniziareall’incircaariga324.
Effettueremoalcunemodificheperrenderlomenoanonimo.Iniziamoacambiareil
grigioscialboinuncolorepiùstimolante,comeunbelblumetallizzato:
@navbar-default-bg:#667591;
Cambieremoancheilcoloredeltestoedeilinkaffinchésiintoninoconilcolore
piùscurodellosfondo:
@navbar-default-color:#fff;
@navbar-default-link-color:#fff;
@navbar-default-link-hover-color:#ccc;
@navbar-default-link-active-color:#fff;
Perquantoriguardalabarradinavigazionemobile,ènecessariomodificareanche
ilcoloredelpulsanteinterruttore:
@navbar-default-toggle-hover-bg:darken(@navbar-defaultbg,15%);
@navbar-default-toggle-icon-bar-bg:#fff;
@navbar-default-toggle-border-color:#fff;
Lessincludediversefunzionihelpercheèpossibileutilizzarepermodificarei
colori,tracuisaturate,desaturateefade.LeduefunzionidiBootstrappiùutilizzate
sonodarkenelighten.Schiarisconooscurisconopercentualmenteuncolore;sono
idealiperglistatihover.Inquestocasoabbiamooptatoperlavariabiledarkene
l’abbiamopassataperlosfondodellabarradinavigazione.
Infinemodifichiamol’altezzaedeliminiamol’arrotondamentodegliangoliper
ottenereunaspettopiùordinato:
@navbar-height:60px;
@navbar-border-radius:0;
Form
IformdiBootstrapsonoforseglielementipiùriconoscibili.Effettuiamoalcune
modificheperrenderlileggermentediversi.Modificheremovariables.lesseunpaiodi
altrifile.Innanzituttoinvariables.lesseliminiamol’arrotondamentodegliangolidagli
input:
@input-border-radius:0;
Possiamoanchemodificareilcoloredelbordoel’ombradelfocus:
@input-border-focus:#667591;
Forsel’ombraèunpo’eccessiva,mapossiamoeliminarlafacilmentemodificando
unmixindiBootstrap.Inpassatotuttiimixindelframeworkeranocontenuti
nell’unicofile:mixins.less,maquestaimpostazioneèrecentementecambiataeimixin
sonostatisuddivisiinfileseparati.
Nelladirectorymixins,apriteforms.lessecercate.form-control-focus.Èilmixinche
assegnaglistilialfocusperglielementidelform;poteteosservarlonellerighedi
codiceseguenti:
.form-control-focus(@color:@input-border-focus){
@color-rgba:rgba(red(@color),green(@color),blue(@color),.6);
&:focus{
border-color:@color;
outline:0;
.box-shadow(~”inset01px1pxrgba(0,0,0,.075),008px
@{color-rgba}”);
}
}
Ilmixinmodificailcoloredelbordo,eliminailcontornopredefinitodelbrowsere
aggiungeilboxconombra.Perilmomentotrasformiamoquest’ultimoinun
commento:
//.box-shadow(~”inset01px1pxrgba(0,0,0,.075),008px@{colorrgba}”);
Lessciconsentediutilizzareicommenti//neifoglidistile,chenonsaranno
riprodottinell’output.Seinvecericorriamoaicommenti(/**/)CSSstandard,si
rifletterannonell’output.
NellavistaAddContactutilizzeremoancheunwell,cheavràunaspettostrano
vistocheicampidiinputdell’utenteorasonoprividiangoliarrotondati.Potremmo
rimuoverel’arrotondamentodatuttiglielementiimpostandosu0lavariabile@border,maperoraapriamoilfilewells.less.Nellaclassewellbase,impostiamosu
radius-base
0border-radiuspereliminaregliangoliarrotondati.
Pulsanti
Bootstrapincludemolticoloriedimensioniperipulsanti.Possiamoeffettuarele
modifichenelfilevariables.TroveretelasezionecercandoButtons;dovrebbeessere
all’incircaallariga140.Bootstraputilizzaglistessicoloripermoltisuoicomponenti;
lasezioneriguardanteicoloriiniziaintornoallariga6.
@brand-primary:#428bca;
@brand-success:#5cb85c;
@brand-info:#5bc0de;
@brand-warning:#f0ad4e;
@brand-danger:#d9534f;
Modifichiamobrand-primaryaffinchésiintoniallosfondodellabarradinavigazione:
@brand-primary:#667591;
Bootstraputilizzalafunzionedarkenperapplicareilcoloredelbordodelpulsantee
icoloridellostatohover.
Èpossibilepersonalizzareogniaspettodeglistili,maperilmomentonon
cambieremonulla.IlContactsManagerapparemenoanonimoesciattograzieai
nuovicoloriecaratteripiùvivaci.Alterminedellapersonalizzazione,l’applicazione
dovrebbeavereilseguenteaspetto.
ItemidiBootstrap
UncambiamentorilevantediBootstrap3èstatal’eliminazionedituttiglistili
visivi.InfattiBootstrapintendeessereunframeworksucuisviluppareenon
semplicementedautilizzareconl’aspettopredefinito.
Tuttaviaglistiliesistonoancora:sonostatispostatiinunfileseparato,inclusonel
sorgente.Ilfiletheme.lessreintroducelesfumaturepresentiinBootstrap2;è
sufficienteimportarlonelfileLessprincipale.
Apritebootstrap.lesseaggiungetequantosegueallafinedelfileperincludereil
tema:
@import“theme.less”;
Pervedereunesempiorelativoall’utilizzodeltemadiBootstrap,dateun’occhiata
agetbootstrap.com/examples/theme.
DovetrovarealtritemidiBootstrap
EsistonoalcunisitiwebchepropongonotemidiBootstrap.Offronounsistema
rapidoeveloceperaggiungereunpo’dicarattereall’aspettostandarddelframework.
Consultateisitiseguentisedesideratesperimentareunodiquestitemi:
http://www.blacktie.co/
https://wrapbootstrap.com/
http://startbootstrap.com/
http://bootswatch.com/
Quiz
1. QualisonoalcunedellecaratteristicheprincipalicheaggiungeLess?
2. Inchemodoèpossibilefareriferimentoaunapseudoclasseinunaregola
annidata?
3. Comeèpossibilemodificareilfont?
4. Acosaserveilfiletheme.less?
Riepilogo
NelCapitolo9abbiamoesaminatoitaskrunnerperconcatenareeminificareifile
JavaScriptinununicofile.Inquestocapitoloabbiamoosservatocomeèpossibile
estenderlialsorgenteLessdiBootstrapecompilarlonelCSS.
Ciòconsentedipersonalizzarel’aspettodiBootstraputilizzandol’estensionedi
LessnelCSS:regoleannidate,variabiliemixin.L’applicazionehaacquisitouncerto
carattereeabbiamoanchevistocomeèpossibilereintrodurreunpo’dellostilevisivo
provenientedaBootstrap2.
NelprossimocapitolodescriveremolavalidazioneinAngularJSelasua
integrazionenell’app.
Capitolo11
Validazione
Tuttofunzionabeneehaunbell’aspetto,maalmomentononesistealcuntipodi
validazioneperiformoglierrorichepotrebberoessererinviatidalserver.Inquesto
capitoloesamineremocomeoperalavalidazioneinAngularJSecomeèpossibile
combinarlaconglistilidiBootstrapperfornireunfeedbackall’utente.
Osserveremoinoltrecomeèpossibileespandereleregoleintegratecreandoun
validatorpersonalizzatosullabasedeiconcetticheabbiamoappresoneicapitoli
precedenti.
Validazionedeiform
UnadellecaratteristichenascostediAngularJSèlavalidazionenativa.Sono
disponibiliunavalidazionedibasedeitipidiinputHTML5piùcomuni,oltrea
direttivepersonalizzatecomerequired,patterneminlength,solopercitarnealcune.
Vedremocomeaggiungerleall’applicazioneedestenderelavalidazioneintegratacon
unvalidatorpersonalizzato.
PerutilizzarelavalidazionediAngularJS,ènecessarioaggiungerequalcosaaltag
diaperturaform:
<formname=”addForm”novalidateclass=”form-horizontal”ngsubmit=”submit()”>
Larigadicodiceprecedentecontieneiltagformpresentenelpartialadd.html.
Abbiamoaggiuntonameoltreaunattributonovalidate.L’attributonameassegnaun
oggettoalloscopecorrente;pertantopossiamoaccedervidallavistaedalcontroller.
L’attributonovalidatedisattivalavalidazionenativadelbrowser.L’abbiamofatto
perchélagestiremonoieintendiamoevitarechel’impostazionepredefinita
rappresentiunostacoloogeneririsultatinonvoluti.
Angularvalideràautomaticamentel’inputdell’indirizzoe-mail,efaràlostessose
cambiamoilcampodelsitowebdatestoaURL.Possiamoaggiungererapidamente
l’attributonecessarioaqualsiasiinputcheconsideriamoobbligatorio:
<inputtype=”text”id=”name”class=”form-control”ngmodel=”contact.name”required>
Inalternativapossiamoutilizzareng-required.Imposteràsutruel’attributorichiesto
delbrowserseanchel’espressionediAngularJSètrue.Peresempio,almomentodel
pagamento,potrestevoleraggiungereunacaselladicontrolloperpermettere
all’utentedidigitareunindirizzodiversoperlaspedizioneelafatturazione.Neicasi
incuilacasellaèselezionata,èpossibileimpostareicampisurequirednelmodo
seguente:
<label><inputtype=”checkbox”ng-model=”shippingAddress”>Sendthisto
anotheraddress</label>
<divng-show=”shippingAddress”>
<inputtype=”text”ng-required=”shippingAddress”>
</div>
Siccomenonabbiamobisognodinessunacondizionenell’applicazione,
manteniamol’attributorequiredpredefinito.Aggiungiamolorapidamentealnome,al
numeroditelefonoeall’indirizzoe-mailpoichésonoquestiicampidelcontatto
richiestipiùfrequentemente.
Oraènecessarioimpedirealformdiinviareidatiquandononècompletamente
validato.Possiamofarloall’internodelcontrolleroppuredisattivandoilpulsante
submit.
Aggiungendounnomealform,AngularJShacreatounnuovomodello,checi
offrel’accessodirettoaessoall’internodellavista.Èleggermentediversodaquello
checreeremmodisolitoperchéracchiudemolteproprietàperverificarelavalidità
nonsolodelform,maanchedieventualielementispecificidanoiindicati.Vediamo
rapidamentecomedisattivareilpulsantesubmit:
<inputtype=”submit”class=”btnbtn-primary”value=”AddContact”
ng-disabled=”addForm.$invalid”>
Possiamoutilizzareilnuovomodelloinunadirettiva.InquestocasositrattadingecomunichiamoadAngularJSdidisattivareilpulsanteseilformnonè
disabled
valido.Inalternativaavremmopotutoverificarelapresenzadierrori:
<inputtype=”submit”class=”btnbtn-primary”value=”AddContact”
ng-disabled=”addForm.$error.required||addForm.$error.email”>
Laproprietà$errordelmodelloèunhashconidiversitipidierrorigeneratidal
form.Possonoessereerroridivalidazionedegliindirizzie-mail,dicorrispondenza
deipatternodicampimancanti.Ovviamentelaverificadiognitipocomporta
maggioripossibilitàdisbagliareerichiedemoltopiùcodice.Tuttaviaèpiùprolissoe
questotalvoltafavoriscelachiarezza.
Inoltreabbiamoaccessoa$dirtyea$pristine.Questidueflagriconosconose
l’utentehadigitatoqualcosanelformepossonoessereutilizzatipervarieoperazioni,
tracuiaggiungeredelleclassi.
Poichéquestoèsemplicementeunmodello,potremmoancheverificareseilformè
validodall’internodellafunzionesubmitinaddCtl:
$scope.submit=function(){
if(!$scope.addForm.$valid){
returnwindow.alert(‘Error!’);
}
$scope.contact.$save();
$scope.contact=contacts.create();
alert.show();
};
Secancelliamol’attributong-disabledefacciamoclicsulpulsante,vedremo
comparireunafinestradiavvisodelbrowser.Èunottimorisultato,mapotremmo
impostarloancoramegliofacendolosembrarepartedell’applicazione.Abbiamogià
definitounmessaggiodiavvisodiBootstrap;aggiungiamolorapidamentepericasi
incuisiverifichiunerroredivalidazione.
Sostituiamolavariabilealertall’iniziodelfileadd.jsconunnuovooggetto
contenentelefinestrediavvisoconmessaggidisuccessoedierrore:
varalerts={
success:$alert({
title:‘Success!’,
content:‘Thecontactwasaddedsuccessfully.’,
type:‘success’,
container:‘#alertContainer’,
show:false
}),
error:$alert({
title:‘Error!’,
content:‘Therearesomevalidationerrors.’,
type:‘danger’,
container:‘#alertContainer’,
show:false
})
}
Orapossiamosostituirelavecchiachiamataalertelafinestradiavvisodel
browsernelmetodosubmitdelcontroller:
$scope.submit=function(){
if(!$scope.addForm.$valid){
returnalerts.error.show();
}
$scope.contact.$save();
$scope.contact=contacts.create();
alerts.success.show();
};
Sericaricatel’applicazioneefateclicsulpulsantesubmit,vedretecomparireun
messaggiodiavvisodiBootstrapcheviinformadellapresenzadialcunierroridi
validazione.
Sarebbemoltoutilesaperediqualierrorisitratta.FortunatamenteAngularJS
consentediscoprirequalimodelligeneranoerroriedefinirediconseguenzaerrorio
stili.
Possiamoaccedereaogniinputdelform,ancheseènecessariospecificareun
nomeperciascunoalfinediriuscireavalidarlo.Peresempio,sepossiamo
aggiungereunnomeperiltelefononelcamporiservatoalnumerotelefonico,
possiamovalidareilcampoaccedendoaessotramiteilform:
addForm.phone.$valid
Èpossibileutilizzareladirettivang-classneigruppidiformperverificarela
validitàdell’inputeaggiungerelaclassehas-errornelcasoincuinonfossevalido:
<divclass=”form-group”ng-class=”{‘has-error’:
!addForm.phone.$valid}”>
NOTA
AngularJS aggiunge proprie classi agli elementi del form basate sulla validità, ma poiché
intendiamoutilizzarelaclassehas-errordiBootstrap,abbiamooptatoperng-class.
Purtroppoquestocodiceaggiungerà,perimpostazionepredefinita,laclasseerror,e
probabilmentenonèquestoilnostroobiettivo.
Lasoluzioneconsistenell’impostareunsecondomodellosutruenelcasoincuiil
formnonsiavalidoquandoloinviamo.
$scope.submit=function(){
$scope.formErrors=false;
if(!$scope.addForm.$valid){
$scope.formErrors=true;
returnalerts.error.show();
}
$scope.contact.$save();
$scope.contact=contacts.create();
alerts.success.show();
};
Abbiamoimpostato,all’inizio,formErrorssufalsepereliminareleclassierrornel
casoincuiilformsiavalidatocorrettamente.Possiamomodificareladirettivangpervederesialevalidazionisiailnuovomodello:
class
<divclass=”form-group”ng-class=”{‘has-error’:formErrors&&
!addForm.phone.$valid}”>
SeentrambigliinputnonsonovalidieilmodelloformErrorsèimpostatosutrue,
verràaggiuntalaclasse.Aggiungiamoladirettivang-classatuttiigruppidiformin
attesadivalidazione.Nondimenticatedicambiareilmodelloalqualefate
riferimentoinquellocheaveteinseritocomeattributoname.
Validazionedeipattern
Abbiamoimpostatounavalidazionedibasema,comesappiamo,Angularoffre
altredirettiveperrenderlapiùrigorosa.Peresempio,orapossiamodigitaretuttociò
chedesideriamonelcampo,esaràaggiuntotutto.Maquestanonèunasoluzione
idealepoichéilnostroscopoèottenereunnumeroditelefonoperognicontatto.
Ladirettivang-patternconsentedidefinireunpatternREGEX(espressione
regolare)concuiconfrontarel’input.Perilnumeroditelefonoaccetteremolecifre,
maancheilsegnopiùperinumeriinternazionalieleparentesiperinumeriopzionali
equellistatunitensi.Consentiremoancheglispazielelineettepersuddividerei
numeri.
Aggiungiamoladirettivang-patternall’inputriguardanteilnumerotelefonicoe
limitiamoloperoraainumeriinteri:
<inputtype=”tel”name=”phone”id=”phone”class=”form-control”ngmodel=”contact.phone”required=”true”ng-pattern=”/^[0-9]/”>
Quandodigiteremodeltestonelcampoeinvieremoilform,vedremoche
AngularJSgenereràunerroredivalidazione.
Soloquandodigiteremounnumero,ilformpotràessereinviato.Dobbiamoancora
consentireicaratteritalvoltapresentiinunnumerotelefonico.Sitrattadiaggiungere
icaratteriammessitraparentesiquadre.Eccol’espressioneregolarecompleta:
/^[0-9+()-]/
Potremmoancheestenderlaeforzareunadeterminatastrutturaolunghezza
massima,maperoraquestoèilrisultatochestavamocercando.
Utilizzareminlength,maxlength,minemax
LealtrequattrodirettiveoffertedaAngularJSperlavalidazionenonsonocosì
interessanti,masipossonorivelarepreziose.Peresempio,ladirettivaminlengthè
idealepergarantirelasicurezzadellapasswordemin/maxsidimostranoutilineicasiin
cuièpresenteuncarrello,consentendosoltantounminimodiunoeunmassimodi
unadeterminataquantitàdiarticoli.
Lequattrodirettivevengonoutilizzatenellostessomodoeaccettanounnumero
comevalore.Siang-minlengthsiang-maxlengthconsideranolalunghezzadell’input,
mentreng-mineng-maxconsideranol’effettivovalorenumerico.
Eccoleinazione.Comepertutteledirettive,èpossibileutilizzareuna
combinazioneperottenereleregoledesiderate.
<inputtype=”number”ng-min=”1”ng-max=”5”>
<inputtype=”password”ng-minlength=”8”ng-maxlength=”255”>
Creareunvalidatorpersonalizzato
AngularJSgestisceinmodoefficacelamaggioranzadeitipidiinputedeicasidi
utilizzo.Tuttavia,talvoltaavretenecessitàdiavereuncontrollomaggiore.
Fortunatamenteèpossibilecreareproprievalidazionipersonalizzate.Ivalidator
personalizzatisonodirettiveconunrequisitospeciale.Perrenderleoperativeè
necessarioimpostareng-modelsull’elemento.
Siccomelanostraapplicazionenonhauneffettivobisognodiunvalidator
personalizzato,vediamocomeèpossibilecrearneunoperverificarechel’inputnon
siapresenteinunalistapredefinita.Puòessereutileperfarsìcheunnomeutentesia
univoco.
ChiamiamoquestadirettivauniqueListeinseriamolanelfilecontactsMgr.directives.js.
Lalimiteremosoloadattributeeutilizzeremoilmetodolink.Perunriepilogoveloce,
rileggeteilCapitolo6incuiabbiamodescrittolacreazionedidirettive
personalizzate.
Possiamoinserireuncontrollercomeilquartoparametrodellafunzionelink.
Angularsaqualecontrollerintendiamoutilizzareosservandolaproprietàrequiredella
direttiva.L’abbiamoimpostatasungModel,peravereunaccessodirettoaun’APIperla
direttivang-model:
.directive(‘uniqueList’,function(){
return{
restrict:‘A’,
require:‘ngModel’,
link:function(scope,elem,attrs,ctrl){
}
};
});
LadocumentazionediAngularJSillustratuttiicontrollerdibase.Leinformazioni
sungModelControllersonoreperibiliall’indirizzo
.
https://docs.angularjs.org/api/ng/type/ngModel.NgModelController
All’internositrovaunmetodochiavechepossiamosfruttare:$setValidity.Ci
consentedidefinireseunmodelloèvalidoomeno.Loutilizzeremoinsiemecon
scope.$watchperverificarelavaliditàqualorailmodellocambi.
Impostiamoinnanzituttoscope.$watchnelmetodolinkdelladirettiva.Possiamo
recuperareilnomedelmodellodall’oggettoattrs:
.directive(‘uniqueList’,function(){
return{
restrict:‘A’,
require:‘ngModel’,
link:function(scope,elem,attrs,ctrl){
scope.$watch(attrs.ngModel,function(value){
});
}
};
});
Comesappiamo,èpossibileaccederealnuovoeunicovaloredaunwatcher.Per
questoesempiociservesoltantoilnuovovalore.Loconfronteremoconunalistadi
nomiutente.PotrebbeessereunarichiestaHTTPalserver,maperilmomentosaràdi
tipohard-codesuunarrayodeinomi:
.directive(‘uniqueList’,function(){
varusernames=[
‘bob’,
‘john’,
‘paul’
];
return{
restrict:‘A’,
require:‘ngModel’,
link:function(scope,elem,attrs,ctrl){
scope.$watch(atts.ngModel,function(value){
});
}
};
});
Lavaliditàdelmodelloconsisteinunasempliceverificapercontrollareseilvalore
sitrovanell’array:
varvalid=(usernames.indexOf(value)>-1)?false:true;
Ilmetodo$setValiditysulcontrollerngModelèmoltosempliceecontienesoltantodue
parametri.Ilprimoèlachiaveerrormentreilsecondoèunbooleanonelcasoincui
siavalidoomeno.Impostiamolonelmodoseguente:
varvalid=(usernames.indexOf(value)>-1)?false:true;
ctrl.$setValidity(‘uniqueList’,valid);
Orachetuttoèdefinito,esaminiamoladirettivacompletaeilsuoutilizzo:
.directive(‘uniqueList’,function(){
varusernames=[
‘bob’,
‘john’,
‘paul’
];
return{
restrict:‘A’,
require:‘ngModel’,
link:function(scope,elem,attrs,ctrl){
scope.$watch(atts.ngModel,function(value){
varvalid=(usernames.indexOf(value)>-1)?false:
true;
ctrl.$setValidity(‘uniqueList’,valid);
});
}
};
});
Perusarla,èsufficienteassociarlaall’inputtramiteunattributo:
<inputtype=”text”ng-model=”contact.name”unique-list>
Quiz
1. Indicatetredirettivecheèpossibileutilizzareperlavalidazione.
2. Inchemodoverifichiamolavaliditàdelform?
3. ComepossiamoaccederealcontrollerngModeldurantelacreazionediun
validatorpersonalizzato?
4. Inchemodoverifichiamol’inputsullabasediun’espressioneregolare?
Riepilogo
AbbiamovistocomefunzionalavalidazioneinAngularJSecomeèpossibile
combinarlaconglistilidiBootstrapperrenderel’apppiùuserfriendly.La
validazionedeiformèstatasemplificataconledirettiveintegrateinAngularJSela
validazioneautomaticadegliinputHTML5,comeemailetel.
Siamoriuscitiaverificarelavaliditàdelformnellafasediinvioeavisualizzare
messaggidierroreeavvertimentiappropriatinelcasoincuiquestaoperazionenon
abbiasuccesso.Ancheseivalidatorintegratisonoottimiperlamaggiorpartedei
casi,abbiamoprovatoasvilupparneunotramiteunadirettiva.
Orachel’applicazioneècompleta,nelprossimocapitolovedremoalcunistrumenti
sviluppatidallacommunitychecisemplificanolavitaquandooperiamocon
AngularJS.
Capitolo12
Strumentidellacommunity
Lanostraappdigestionedeicontattièterminata.Siamopassatidaunapagina
vuotaaun’applicazioneCRUDsingle-pagecompletachesiconnettealserveredè
interamentevalida.Questocapitoloillustreràduestrumentidellacommunitymolto
efficacieutilichevisemplificherannolavitaquandoutilizzereteAngularJS.
ImposteremoBatarangeng-annotateperilnostroprogetto.Ilprimomigliorerà,tra
l’altro,l’aspettodelloscope,mentreilsecondociconsentiràdiminificaremoltopiù
facilmenteifileJavaScript.Inquestocapitoloaffronteremoiseguentiargomenti.
Batarangeng-annotate.
InstallareBatarangeng-annotate.
Verificareloscope.
Monitorarelaperformancedell’app.
Utilizzareng-annotateconGruntegulp.
Batarang
Batarangèun’estensionediChrome(cispiacepergliutentidiFirefoxeSafari,ma
infindeicontiAngularJSèunprogettodiGoogle)cheoffreunaschedaaggiuntiva
neglistrumentideglisviluppatoriperconsentircidicreareunprofiloedeffettuareil
debuggingdelleappdiAngularJS.
InstallareBatarang
ÈfacileinstallareBatarangpoichéèun’estensionediChrome.Visitiamoilsito
https://chrome.google.com/webstore/ecerchiamoBatarangnellacasellainaltoasinistra
dellapagina.Dovrebbeesserel’unicorisultatonellasezionedelleestensioni,come
mostralaseguenteschermata.
Fateclicsulpulsanteperinstallarloevedretecomparireunanuovaschedanelweb
inspector,comemostralaseguenteschermata.
Verificareloscopeeleproprietà
ForselafunzionepiùutilediBatarangèlacapacitàdiverificareidiversiscope
presentinell’applicazione.L’estensioneaggiungeunanuovaschedaalwebinspector;
esaminiamola.
Batarangècostituitodacinqueschede:Models,Performance,Dependencies,
OptionseHelp.PerutilizzareBatarangnell’applicazione,ènecessarioselezionarela
casellaEnable.Inquestomodolapaginasiaggiorneràel’estensioneinizieràa
raccogliereleinformazioninecessarie.
EsistonotresistemiperverificareloscopeeleproprietàmedianteBatarang.
QuellopiùovvioèfareclicsullaschedaModels,dovevedreteunalistadituttigli
scopeannidati.
AllasinistradellaschedaModelsèvisibileunalistadituttigliscopedellapagina.
Selezionandoli,comparirannoadestraimodellicontenutinelloscope.Siaggiornano
automaticamentequandocambiaunvaloresullapagina,consentendodivederecon
estremafacilitàciòcheaccadeallorointerno.
Batarangaggiungeancheun’altraschedaquandoispezionaglielementi.Sichiama
AngularJSPropertiesemostratuttociòcheAngularassociaaunelemento:rilevase
èpari,l’indicedell’iteminunng-repeatoseunelementodelformèvalidoomeno,
comemostralaseguenteschermata.
L’ultimoeutilestrumentocheoffreBatarangperlaverificaèquellopresentenella
console.
DopoaverattivatoBatarang,potretedigitare$scopenellaconsolepervederelo
scopedell’ultimoelementoselezionato,comemostralafigura.
Monitorarelaperformance
LaschedaPerformanceriportaun’utilelistadituttociòchevieneverificatoalla
ricercadieventualicambiamentidapartediAngular,oltreaunelencodiespressioni
conladuratadiesecuzione.
Valelapenatenered’occhiolaperformancementrel’appsisviluppa.Forse
scopriretediaveresageratoconlefunzioni$scope.$watchochel’esecuzionediunfiltro
impiegamoltotempo.
QuellachesegueèlaschermatacheraffiguralaschedaPerformancedellapagina
AddContactdell’applicazione.AsinistracomparelasezioneWatchTree.Èunalista
dituttiiwatchersullapaginaemostrainqualescopesitrovano.Adestraèvisibile
un’analisidelleespressionicheimpieganopiùtempo.
Visualizzareledipendenze
LaschedapiùinteressantediBatarangèsicuramenteDependencies.Contieneun
graficointerattivochemostratuttociòsucuisibasanoiservizidell’applicazione.
Quandopassateconilmousesoprailnomediunservizio,inomideiservizichesi
basanosudiessovengonoevidenziatiinverde,mentreiservizichedipendonoda
essovengonoevidenziatiinrosso.
Nellaschermataseguente,siamopassaticonilmousesopra$resource,cheha
evidenziato$httpe$qinrossoecontactsinverde:
Probabilmenteavretenotatochenellaschermataprecedentecisonoalcuniservizi
chenondipendonodanullaedaessinondipendenulla.Accadeabbastanzaspesso,
mapuòanchesignificarechenonvengonoutilizzatidall’applicazioneequindiè
opportunotenerlid’occhio.
OpzionidiBatarang
NellaschedaOptionssonopresentitreutilicaselledicontrollo,cheevidenziano
conunbordocoloratoleapplicazioni,ibindingegliscopesullapagina.
Leapplicazionipresentanounbordoverde,ibindingunbordoblumentregli
scopeunorosso.Sitrattadiunaraffigurazioneefficacepercapireseunaparte
dell’appsitrovainundeterminatoscopeosesistaveramenteverificandoun
binding.
Eccocomeapparel’appdopoaverattivatotutto:
ng-annotate
ng-annotateèunprogettoopensourcediOlovLassustesoaeliminarele
conseguenzepiùfrustrantiderivantidall’utilizzodiAngularJS.Ricordereteche
quandoabbiamominificatoilcodice,abbiamoracchiusoledipendenzeinunarray
pertenereinordineinomi.Ilprogettorendesuperfluoquestorequisitoosservandoil
codiceeracchiudendoloalpostonostro.Insintesilostrumentoèunpre-minificatore
edovremmousarloperpreparareilcodiceperl’attivitàuglify.
InpassatoabbiamoutilizzatongMindiBrianFordperottenerelostessorisultato.
Questoprogettoèstatodeprecatoinfavoreding-annotate.
Installareng-annotate
Lostrumentong-annotateèunpacchettonpmepuòessereinstallatotramiterigadi
comando.EsistonoaltripacchettichefunzionanoconleinstallazionidiGruntegulp,
maperilmomentoesaminiamocomeèpossibileeseguiremanualmenteilprogetto.
1.Apriteilterminaleedeseguiteilprossimocomandoperinstallareglobalmenteil
pacchettosulcomputer:
npminstall–gng-annotate
NOTA
Ricordatecheserestituisceunerrorelegatoaipermessi,dovreteeseguirlocomeamministratore.
Potete farlo con il comando sudo in un sistema basato su *nix oppure eseguendo il prompt dei
comandicomeamministratoresuWindows.
Viforniràunnuovocomandodautilizzarenelterminale.Seeseguiteng-annotate,
dovrebbecomparireilfilehelpperlostrumento.Èfacileutilizzareng-annotateinun
file:èsufficienteavvalersidelcomandong-annotateseguitodalleopzioniedalnome
delfilecheintendiamoelaborare.
2.Nelladirectoryjsdelprogettoeseguite
ng-annotate-rcontrollers/app.js
Dovrebberestituireilcontenutodelcontrollerdell’appmaconunapiccola
variazione:vedretechetutteleannotazioniesistentiattornoalledipendenzesono
statecancellate.Infattiabbiamocomunicatoang-annotatedieliminarleconl’opzione
-r.
3.Possiamoanchefarsìcheng-annotateleaggiunganuovamentemediante-a.
Questedueopzionipossonoessereutilizzateinsiemeodasole:
ng-annotate-racontrollers/app.js
4.Questavoltavienerestituitoilcontrollerconleannotazioni.Possiamonotare
cheng-annotatehaoperatolasuamagiaperchélevirgolettesingolecheracchiudono
ledipendenzesonostatesostituitedavirgolettedoppie.
Orang-annotatefunziona,maèinutileseènecessarioeseguirlomanualmente.In
chemodoèpossibileinserirloneitaskrunner?Scopriamolo.
Utilizzareng-annotateconGrunt
Perutilizzareng-annotateconGrunt,dobbiamoinstallareunaltropacchettonpm.
Questavoltanonèglobale,maènecessarioinstallarlonelprogetto.Seguitequesti
passi.
1.Passateallacartelladelprogettonelterminale:
cd~/path/to/contacts-manager
2.OrainstallateilmoduloGruntsalvandolonelledipendenzedevdelprogettoin
mododapoterlosempreinstallarenuovamenteinseguito;inoltreunaltro
sviluppatorechelavoreràalprogettopotràinstallarecosìtuttoilnecessario:
npminstallgrunt-ng-annotate--save-dev
3.PrimadieffettuarequalsiasimodificanelGruntfile,nondimenticatedicaricare
leattivitàdalpacchettoappenainstallato:
grunt.loadNpmTasks(‘grunt-ng-annotate’);
Configurarel’attività
Noncirestacheconfigurarel’attivitàngAnnotatenelGruntfileedeffettuaredue
piccolemodifichenell’attivitàwatch.Eccocomeprocederemo.
1.Aggiungiamolanell’attivitàlessall’internodell’oggettogrunt.initConfig.
ngAnnotate:{
}
2.ComelamaggiorpartedelleattivitàdiGrunt,ngAnnotateaccettaunhashoptions
oltreapiùtarget.Impostiamoprimaoptions:
ngAnnotate:{
options:{
remove:true,
add:true,
singleQuotes:true
}
}
Abbiamodefinitotreopzioni.Abbiamosceltodieliminarequalsiasiannotazione
preesistenteperaggiungernedinuoveconng-annotateeutilizzarevirgolettesingole
enondoppie.Èanchepossibiledefinireun’espressioneregolareseintendiamo
operareinunasezionespecifica.
3.Iltargetguarderàtuttiifilenelladirectoryjselisalveràconl’estensione
.annotated.js.Inquestomodoèpossibileimpostarel’attivitàwatchpereseguirengannotateepoil’attivitàuglifyaffinchécerchiifilecheterminanocon.annotated.js.
4.Gliunicifileaiqualidobbiamoprestareattenzionesonoinostri.Tuttonella
directoryvendorsaràpreannotato,ononsaràlegatoadAngular.Possiamo
configurareGruntaffinchéconsiderituttotrannelecartellevendorebuild:
ngAnnotate:{
options:{
remove:true,
add:true,
singleQuotes:true
},
app:{
src:[
‘assets/js/**/*.js’,
‘!assets/js/vendor/*.js’,
‘!assets/js/build/*.js’
]
}
}
5.Ilprimopercorsonell’arraysrcincludetuttiifileJSnelladirectoryjsdel
progetto.Comeabbiamoprevisto,ènecessarioignorarelecartellevendorebuild.I
dueitemcheseguononell’arrayfannoproprioquesto.Ilpuntoesclamativoprimadel
percorsoindicaaGruntcheintendiamoescluderetuttiifileJSinquestecartelle.
6.Dopoaverspecificatoilsorgente,ènecessariocomunicareang-annotateciòche
vogliamofareconifiledopochesonostatielaborati.Intendiamorinominareilfile
perincluderel’estensione.annotate.jsesalvarlinellostessoposto:
ngAnnotate:{
options:{
remove:true,
add:true,
singleQuotes:true
},
app:{
src:[
‘assets/js/**/*.js’,
‘!assets/js/vendor/*.js’,
‘!assets/js/build/*.js’
],
expand:true,
ext:‘.annotated.js’,
extDot:‘last’
}
}
7.Nelcodiceprecedenteabbiamoaggiuntotrenuoveproprietàperiltarget:expand,
edextDot.Laproprietàexpanddivideilpercorsoeciconsentedicambiare
ext
l’estensione.Laproprietàextmodifical’estensionementrelaproprietàextDot
comunicaaGruntqualepuntonelnomefileguardare.Nelnostrocasoèl’ultimoe
questocitutelanelcasodovessimoutilizzarepiùpuntineinomifile.
8.Orasiamoprontiaeseguirel’attivitànelterminale.Eseguiamolaconilflag-evediamociòcheaccade:
verbose
gruntngAnnotate--verbose
9.Tuttosembrafunzionareallaperfezionemacreandoinuovifile.annotated.js,
avremodeiproblemiquandoeseguiremol’attivitàunasecondavolta.Otterremo
questooutputquandoeseguiremonuovamenteilcomandoprecedente:
Writingassets/js/modules/contactsMgr.services.annotated.
annotated.js
10.L’attivitàhapresoinconsiderazionenonsoloifile.jsnelledirectorymaanche
ifile.annotated.js.CiòaccadeperchéGruntnonconosceladifferenza.Siccome
prendel’ultimopuntoperdeterminarel’estensione,èsufficienteaggiungereun’altra
esclusioneall’arraysrcpercompletarel’attività:
ngAnnotate:{
options:{
remove:true,
add:true,
singleQuotes:true
},
app:{
src:[
‘assets/js/**/*.js’,
‘!assets/js/**/*.annotated.js’,
‘!assets/js/vendor/*.js’,
‘!assets/js/build/*.js’
],
expand:true,
ext:‘.annotated.js’,
extDot:‘last’
}
}
1. Cancellateglialtrifile.annotated.annotated.jsnelladirectoryjsedeseguite
l’attivitàun’ultimavolta.
Impostarel’attivitàwatch
Orachel’attivitàngAnnotatefunzionacomeprevisto,èpossibileeffettuarealcune
modifichenelleattivitàwatcheuglifypereseguirlaautomaticamenteeminificareifile
JavaScript,nelmodoseguente.
1.Innanzituttoènecessariomodificareiltargetjsnell’attivitàwatchpereseguire
invecediuglify:
ngAnnotate
js:{
files:[
‘assets/js/**/*.js’
],
tasks:[‘ngAnnotate’]
},
2.Aggiungiamoanchedueesclusioninell’arrayfiles.Nondobbiamoosservarela
directorybuild,edènecessariocomunicareaGruntdiignoraretuttiifileche
terminanocon.annotated.js:
js:{
files:[
‘assets/js/**/*.js’,
‘!assets/js/build/*.js’,
‘!assets/js/modules/**/*.annotated.js’,
‘!assets/js/controllers/**/*.annotated.js’
],
tasks:[‘ngAnnotate’]
},
3.Verifichiamorapidamentesefunzionatutto.Nelterminaleavviamol’attività
watch:
gruntwatch
4.OrasesalvateunfileJS,comeilcontrollerdell’app,Gruntdovrebberilevare
unamodificaeattivarengAnnotate:
Running“watch”task
Waiting…OK
>>File“assets/js/controllers/app.js”changed.
Running“ngAnnotate:app”(ngAnnotate)task
>>8filessuccessfullygenerated.
Done,withouterrors.
5.Sembrafunzionarebene.Possiamocreareunnuovotargetchecontrolliifile
annotatiallaricercadellemodificheedesegual’attivitàuglify:
annotated:{
files:[
‘assets/js/**/*.annotated.js’,
],
tasks:[‘uglify’]
},
6.Èfacile,ecercalemodificheneifileconestensione.annotated.js.Infine
dobbiamoeffettuareduemodifichenell’arraysrcall’internodeltargetdibuilddi
:
uglify
src:[
‘assets/js/vendor/jquery.js’,
‘assets/js/vendor/bootstrap.js’,
‘assets/js/vendor/angular.js’,
‘assets/js/vendor/angular-animate.js’,
‘assets/js/vendor/angular-resource.js’,
‘assets/js/vendor/angular-route.js’,
‘assets/js/vendor/angular-sanitize.js’,
‘assets/js/vendor/angular-strap.js’,
‘assets/js/vendor/angular-strap.tpl.js’,
‘assets/js/modules/*.annotated.js’,
‘assets/js/controllers/*.annotated.js’
],
7.Abbiamoeffettuatoduemodificheagliultimidueitemnell’arrayperguardarei
filegeneratiinvecedeiloroomologhinonannotati.Eseguiamonuovamentel’attività
watchdiGrunterisalviamounfileJSpervederecosasuccede:
Running“watch”task
Waiting…OK
>>File“assets/js/controllers/app.js”changed.
Running“ngAnnotate:app”(ngAnnotate)task
>>8filessuccessfullygenerated.
Done,withouterrors.
Running“uglify:build”(uglify)task
File“assets/js/build/ContactsMgr.js”created.
Done,withouterrors.
8.Quandoabbiamosalvatoilfileapp.js,Gruntharilevatounamodificaeha
eseguitol’attivitàngAnnotategenerandoottofile.L’attivitàwatchharilevatolapresenza
dimodificheneifileannotatiappenageneratiehacreatoilfileContactsMgr.js
eseguendol’attivitàuglify.Dopotuttiquesticambiamenti,eccocomeappare
:
Gruntfile.js
module.exports=function(grunt){
grunt.initConfig({
pkg:grunt.file.readJSON(‘package.json’),
watch:{
js:{
files:[
‘assets/js/**/*.js’,
‘!assets/js/build/*.js’,
‘!assets/js/**/*.annotated.js’
],
tasks:[‘ngAnnotate’]
},
annotated:{
files:[
‘assets/js/**/*.annotated.js’,
],
tasks:[‘uglify’]
},
less:{
files:[
‘assets/less/*.less’
],
tasks:[‘less:dev’]
},
css:{
files:[
‘assets/css/bootstrap.css’
],
options:{
livereload:true
}
}
},
uglify:{
options:{
banner:‘/*!<%=pkg.name%><%=
grunt.template.today(“yyyy-mm-dd”)%>*/\n’
},
build:{
src:[
‘assets/js/vendor/jquery.js’,
‘assets/js/vendor/bootstrap.js’,
‘assets/js/vendor/angular.js’,
‘assets/js/vendor/angular-animate.js’,
‘assets/js/vendor/angular-resource.js’,
‘assets/js/vendor/angular-route.js’,
‘assets/js/vendor/angular-sanitize.js’,
‘assets/js/vendor/angular-strap.js’,
‘assets/js/vendor/angular-strap.tpl.js’,
‘assets/js/modules/**/*.annotated.js’,
‘assets/js/controllers/**/*.annotated.js’
],
dest:‘assets/js/build/<%=pkg.name%>.js’
}
},
less:{
dev:{
files:{
‘assets/css/bootstrap.css’:
‘assets/less/bootstrap.less’
}
},
production:{
options:{
cleancss:true
},
files:{
‘assets/css/bootstrap.css’:
‘assets/less/bootstrap.less’
}
}
},
ngAnnotate:{
options:{
remove:true,
add:true,
singleQuotes:true
},
app:{
src:[
‘assets/js/**/*.js’,
‘!assets/js/**/*.annotated.js’,
‘!assets/js/vendor/*.js’,
‘!assets/js/build/*.js’
],
expand:true,
ext:‘.annotated.js’,
extDot:‘last’
}
}
});
grunt.loadNpmTasks(‘grunt-contrib-uglify’)
grunt.loadNpmTasks(‘grunt-contrib-watch’);
grunt.loadNpmTasks(‘grunt-contrib-less’);
grunt.loadNpmTasks(‘grunt-ng-annotate’);
grunt.registerTask(‘default’,[‘ngAnnotate’,‘uglify’]);
};
Utilizzareng-annotatecongulp
Propriocomeperqualsiasicosaabbiamovistofinora,esisteancheunaversione
gulpding-annotatecheciconsentiràdiutilizzarequestotaskrunneralternativo,seè
quellocheabbiamoscelto.Studiamocomeèpossibileimpostarloaffinchéopericon
gulpfilepreesistente.
1.Recuperiamoilpacchettodanpm:
npminstallgulp-ng-annotate--save-dev
2.Apriamogulpfile.jseinseriamoilpacchettoappenainstallato:
varngAnnotate=require(‘gulp-ng-annotate’);
3.Inquestomodoavremounanuovafunzionedautilizzareconglioperatoripipe
digulp.ÈincredibilelamaggiorerapiditàdiinstallazionedigulpperutilizzarengannotaterispettoaGrunt.Èsufficienteaggiungereunnuovopipeall’attivitàuglify:
gulp.task(‘uglify’,function(){
gulp.src(paths.js)
.pipe(concat(pkg.name+’.js’))
.pipe(ngAnnotate())
.pipe(uglify())
.pipe(gulp.dest(‘assets/js/build’));
});
4.IlpipeconlafunzionengAnnotatepuòessereinseritoprimadelpipeuglify.
L’attivitàwatchègiàimpostatapereseguirel’attivitàuglifyognivoltachecambiaun
fileJS;pertantoquestoètuttociòchedobbiamofareingulpfile.
5.Orapossiamoricorrereauglifydigulppereseguiremanualmenteng-annotatee
minificareilJavaScript,oppureutilizzarewatchdigulpperrilevareautomaticamentei
cambiamenti.Dopoquesteduepiccolemodifiche,eccocomeappareilfilegulpfile.js
:
vargulp=require(‘gulp’);
varuglify=require(‘gulp-uglify’);
varconcat=require(‘gulp-concat’);
varpkg=require(‘./package.json’);
varless=require(‘gulp-less’);
varlivereload=require(‘gulp-livereload’);
varngAnnotate=require(‘gulp-ng-annotate’);
varpaths={
js:[
‘assets/js/vendor/jquery.js’,
‘assets/js/vendor/bootstrap.js’,
‘assets/js/vendor/angular.js’,
‘assets/js/vendor/angular-animate.js’,
‘assets/js/vendor/angular-resource.js’,
‘assets/js/vendor/angular-route.js’,
‘assets/js/vendor/angular-sanitize.js’,
‘assets/js/vendor/angular-strap.js’,
‘assets/js/vendor/angular-strap.tpl.js’,
‘assets/js/modules/**/*.js’,
‘assets/js/controllers/**/*.js’
],
less:‘assets/less/**/*.less’
};
gulp.task(‘uglify’,function(){
gulp.src(paths.js)
.pipe(concat(pkg.name+’.js’))
.pipe(ngAnnotate())
.pipe(uglify())
.pipe(gulp.dest(‘assets/js/build’));
});
gulp.task(‘watch’,function(){
varserver=livereload();
gulp.watch(paths.js,[‘uglify’]);
gulp.watch(paths.less,[‘less’]);
gulp.watch(‘assets/css/bootstrap.css’).on(‘change’,
function(file){
server.changed(file.path);
});
});
gulp.task(‘less’,function(){
gulp.src(‘assets/less/bootstrap.less’)
.pipe(less({
filename:‘bootstrap.css’
}))
.pipe(gulp.dest(‘assets/css’));
});
gulp.task(‘default’,[‘uglify’]);
Quiz
1.
2.
3.
4.
5.
6.
Checos’èBatarang?
IndicatetrestrumenticheoffreBatarang.
Qualisonoiduesistemiperverificareloscope?
Checosaabbiamoutilizzatoprimading-annotate?
Qualiopzionioffreng-annotate?
Qualeproblemarisolveng-annotate?
Riepilogo
Inquestocapitoloabbiamotrattatoduestrumentidellacommunityefficaciemolto
affidabilieutilizzati.Batarangrappresentailcoltellinosvizzerodeglistrumentidi
debuggingcheaiutaasvilupparefantastichewebappconAngular;ng-annotateci
sollevadall’irritanteincombenzadelleannotazionidurantelaminificazionedeifile.
Questiduestrumentisonofacoltativienonsononecessariquandosiutilizza
Angular,maentrambiviaiuterannolungoilcamminoelisfruttereteregolarmente.
SonosoltantoduedeglistrumenticreatidallasorprendentecommunitydiAngularJS.
Esplorateescopritechecos’altrooffreperfacilitarvilavita.Esenonriusciteproprio
atrovareciòchestatecercando,sviluppatelovoieproponeteloallacommunity!
AppendiceA
Personeeprogetti
SiaAngularJSsiaBootstrapvantanounnutritoseguitoedueenormicommunity,a
dimostrazionecheiprogettibasatisuentrambiiframeworksononumerosi.Dietro
ogniprogettooperanosviluppatoriscrupolosi.Scopriamoalloraalcunepersonee
progettidegnidinota.
ProgettiepersonedietroBootstrap
AttualmenteBootstrapègiuntoallaterzaversione;ciòsignificacheesistono
numeroseestensioniestrumentiutilichevipossonoaiutaredurantelosviluppo.
Ilteamprincipale
Bootstrapènatoall’internodiTwitteredèlacreaturadidueingegneri
dell’azienda:MarkOtto(@mdo)eJacobThornton(@fat).Tuttieduehannoinseguito
lasciatoquestacompagnia,macontinuanoacontribuirealprogetto.
Attualmentesicontanoquasi600collaboratoriaBootstrap,ilchesottolineala
forzadiunsoftwareopensource.
EntrambiicreatorioriginarihannoabbandonatoTwitter,maMarkcontinuaa
essereilcollaboratoreegestorepiùattivodiBootstrap.
URL:http://www.getbootstrap.com
Twitter:@twbootstrap
Persone:MarkOtto(@mdo),JacobThornton(@fat)ealtri.
BootstrapExpo
Quandoparliamoconchiconosceunpo’Bootstrapmanonl’haancora
sperimentato,scopriamocheèconvintocheilframeworksiaunostilefisso.Buona
partedellasuacattivafamaderivadaiprimiprogettibasatisudiesso.Inpassatoogni
paginadeiplug-injQueryvenivasviluppataconBootstrapprivodistili.
Comesappiamo,Bootstrapèmoltopiùdiquesto;èunframeworkfront-endatutto
tondo.Persfatarequestaconvinzioneerrata,uncreatorediBootstraphaapertoil
blogBootstrapExpoincuiillustragliutilizzipiùstimolantidiBootstrapnelWeb.
URL:http://expo.getbootstrap.com
Persone:MarkOtto(@mdo)
BootSnipp
BootSnippèunarisorsaincredibileperchiunqueopericonBootstrap.Nellasua
versionepiùsemplice,èunaraccoltadicomponentiprecodificaticheèpossibile
copiareeincollarenelprogetto.Èpossibiletrovarestrumentiperlanavigazione,
immaginiscorrevoliefinestremodaliaccompagnateconunostile.
MaBootSnippnonèsoloquesto.Poteteeffettuareunaricercainbasealleversioni
diBootstrap,esaminarelealtreprezioserisorseeutilizzaregliutilicostruttoridi
formepulsantineiqualièsufficientetrascinareglielementidesideratiecopiare
l’HTML.
URL:http://www.bootsnipp.com
Twitter:@BootSnipp
Persone:MaksimSurguy(@msurguy)
Lineeguidasulcodicedi@mdo
Quandoilprogettosiamplierà,dovreteseguirealcunistandardperquantoriguarda
HTMLeCSS.Ovviamentequandolavorereteaunprogettoconaltrepersone,la
situazionesipuòcomplicareelanecessitàdiunaguidasuglistilirisulteràpiù
evidente.
MarkOtto,unodeico-creatoridiBootstrap,haideatounaguidaesaurientesugli
standardcheutilizzaneisuoiprogetti.Valelapenadareun’occhiata,ancheseuna
buonapartedipendedallepreferenzepersonaliedalleesigenzevostreedelvostro
team.Servitevenecomepuntodipartenzaperdefinireisetdistandardediregole.
URL:http://codeguide.co/
Persone:MarkOtto(@mdo)
Roots
RootsèunostarterthemeopensourcediWordPresscheaccompagnaBootstrap,
GrunteBoilerplateHTML5persvilupparefantasticitemiWordPress.Anchesenon
abbiamoaffrontatoquestoargomentonelcorsodellibro,vorremmosegnalarvila
possibilitàcheBootstrapoffreperrealizzarequalsiasicosa.Èunapiattaformamolto
versatileesolidasucuicreareiprogetti.
URL:http://roots.io/
Persone:BenWord(@retlehs)
Shoelace
SeavetedelledifficoltàasviluppareovisualizzarelegriglieconBootstrap,valela
penadareun’occhiataaShoelace.Èunpraticostrumentocheconsentedisviluppare
inmodointerattivolagrigliadelleapplicazioniegeneraretuttoilmarkupdicui
abbiamobisognoinHTML,JadeoEDN.
Èpossibilesalvareecondividerelegriglieeverificareilloroaspettosudispositivi
didiversedimensioni.Sipossonoaggiungerealtreclassiallerigheeallecolonne,
ancheseperimpostazionepredefinitaèpossibileutilizzaresoltantolecolonnenei
dispositividipiccoledimensioni.Ovviamentepotretesostituirleinseguito,sefosse
necessario.
URL:http://www.shoelace.io
Persone:ErikFlowers(@Erik_UX)eShaunGilchrist
SnippetdiBootstrap3perSublimeText
SublimeText2e3sonoeditorditestomoltopopolaritraglisviluppatorienon
sorprendecheesistaunplug-incheincludeinnumerevolisnippetperilvostro
frameworkfront-endpreferito.
Ilplug-inviconsentediinserirerapidamentequalsiasicomponentediBootstrap
semplificandonemoltol’utilizzo.PuòessereinstallatotramitePackageControl
cercandoBootstrap3Snippets,oppurepoteteprocurarvelodaGitHub.
URL:https://github.com/JasonMortonNZ/bs3-sublime-plugin
Persone:JasonMorton(@JasonMortonNZ)
FontAwesome
IlprogettohapresoilviapersostituireleiconediimmaginediBootstrap2con
alcuneequivalenti.Sièsviluppatofinoadiventareunadelleprincipaliraccoltedi
fontdiiconechepuòessereutilizzataconosenzaBootstrap.
SedesiderateestenderelagammadiiconegiàriccadiBootstrap,provateFont
Awesome.
URL:http://fortawesome.github.io/Font-Awesome/
Persone:DaveGandy(@davegandy)
BootstrapIcons
Bootstrapcomprendegiàunafantasticacollezionediiconeesipuòfacilmente
estendereconFontAwesome.BootstrapIconsèstatocreatopercercarerapidamente
traleiconeerecuperarelaclassenecessaria.
Èmoltopiùfaciletrovarequiciòcheviserverispettoalladocumentazione
ufficiale,perchéogniiconaèstatataggataconpiùparolechiave.Digitateciòche
cercatenellacaselladiricercaeilsitolotroverà.
URL:http://bootstrapicons.com/
Persone:BrentSwisher(@BrentSwisher)
ProgettiepersonedietroAngularJS
AlcuniprogettidellacommunitydiAngularJSpossonodiventarepartedelflusso
dilavoroquotidiano.Neabbiamogiàesaminatialcuninellibro,maorane
descriveremoaltridiunacertaimportanza.
Ilteamprincipale
Comesapete,AngularJSèunprogettoGooglee,cometale,ilsuoteamprincipale
ècostituitodadipendentidiquestaazienda.Forsenonsapeteperòcheilframework
fucreatoinBratTechLLCdaMiškoHeveryeAdamAbronspersupportareun
serviziodiarchiviazioneJSONchiamatohttp://getangular.com/.Inseguito
abbandonaronoilservizioereseroopensourceilframeworkAngularJscheabbiamo
imparatoaconoscereeapprezzare.
MiškocontinuaagestireilprogettocomedipendenteGooglecollaborandocon
diversialtriingegneri.Insiemel’hannoampliato,rilasciatoun’infinitàdimoduli
aggiuntiviecreatolapopolareestensioneperChromeBatarang.
URL:https://angularjs.org/
Persone:MiškoHevery(@mhevery),AdamAbrons(@abrons),BrianFord
(@briantford),BradGreen(@bradlygreen),IgorMinar(@IgorMinar),VotjaJína
(@vojtajina)ealtri.
RestAngular
AbbiamogiàillustratoiduesistemiconcuiAngularconsentediconnettersia
un’API:$httpengResource.Esisteancheunprogettodellacommunitymoltopopolare
chiamatoRestAngularchesegueunapprocciodiverso.
LadifferenzaprincipalerispettoangResourceconsistenelfattocheutilizzale
promise,mentre$resourceesegueautomaticamenteillorounwrapping.Asecondadi
comefunzionailvostroprogetto,puòessereunsistemaefficacepoichépotrete
risolvereidatidellaroutemediante$routeProvider.resolve.
SeoperateconunaRESTfulAPInelprogettoengResourcenonviconvincedeltutto,
dateun’occhiataaRestAngular.
URL:https://github.com/mgonto/restangular
Persone:MarinGonto(@mgonto)
AngularStrapeAngularMotion
AbbiamogiàstudiatoeutilizzatoAngularStrapnelnostroprogetto.Offreuna
raccoltafantasticadituttiiprincipaliplug-indiBootstrapperledirettivenativedi
AngularJS.SeutilizzateBootstrapinsiemeconAngularJS(comeabbiamofattonel
libro),èunmoduloimperdibile.
AngularMotionèprogettatopervenireutilizzatoconAngularStrap,anchesenonè
strettamentenecessario.Sitrattadianimazionigiàprontechefunzionano
nativamenteconngAnimateeaggiungonountoccoinpiùalprogetto.Sipossono
utilizzareconng-showeng-hideecondirettivecomeng-repeatperanimarel’aggiuntao
lacancellazionedegliitem.
URL:http://mgcrea.github.io/angular-strap/ehttp://mgcrea.github.io/angular-motion
Persone:OlivierLouvignes(@olouv)
AngularUI
IlprogettoAngularUIèprobabilmenteilpiùgrandecheèsortodallacommunity
diAngularJS.SidivideindiversimodulitracuiUI-Utils,UI-ModuleseUI-Router.
IlmoduloUI-Utilsvienedescrittocomeil“coltellinosvizzero”deglistrumenti,ed
effettivamenteloè.Consenteoperazionicomeevidenziaredeltesto,verificarele
pressioniditastiorendereunelementofissoalloscroll.
GliUI-ModulessonomoduliAngularJScondipendenzeesterneperGoogleMaps
oplug-injQuery.Quitroveretealcunistrumentiefficaci,epersonalmenteho
utilizzatoilmoduloSelect2indiverseoccasioni.
ForseilprogettopiùpopolareèilmoduloUI-Router.Offreunverorouting
annidatoperAngular.Consentedisuddividerelapaginainstati;peresempiopotreste
avereunostatoperlabarralateraleeunaltroperilcontenutoprincipale.Possono
avereentrambiunpartial;inoltrepermettedisviluppareinmodopiùsemplicegrandi
pagineowebapp.
EsisteperfinounmoduloperBootstrapsimileadAngularStrap,chevalelapena
considerare.
URL:http://angular-ui.github.io/
Twitter:@angularui
Persone:NateAbele(@nateabele),TasosBekos(@tbekos),AndrewJoslin
(@andrewtjoslin),PawelKozlowski(@pkozlowski_os),DeanSofer(@Unfolio),Douglas
Duteil(@douglasduteil)ealtri.
MobileAngularUI
AdifferenzadelprogettoAngularUI,questohaloscopodisviluppare
un’interfacciautente.Sitrattadiunsempliceframeworkmobilecheutilizzasia
AngularJSsiaBootstrap3.Glielementiappaionoabbastanzanativi,anchesealcune
particomelabarradinavigazionelateralepotrebberoesseremigliorate.Èancoraai
suoialbori,mahaunseriopotenzialeevalelapenaseguirelasuaevoluzione.
URL:http://mobileangularui.com/
Twitter:@mobileangularui
Persone:mcasimir
Ionic
Ionicèincredibile.Loèdavvero.RiuniscetuttociòcheèefficaceinAngularJSe
Cordova/Phonegapperconsentirvidisvilupparestraordinarieappibrideconi
linguaggiwebchegiàconoscete.
Tuttosembranativoedèfacilissimoiniziareanchesenonavetemaisviluppato
un’app.UtilizzaAngularJSel’estensioneUI-Routerinsiemeconillorocodice.
L’aspettomiglioreècheèinteramenteopensourceechiunquepuòcontribuirvisu
GitHub.
ÈstatoancherealizzatoilmodulongCordova,riccodidirettivecheèpossibile
sfruttareperinterfacciarsifacilmenteconnumerosiplug-inCordova.
URL:http://ionicframework.com/
Twitter:@ionicframework
Persone:TheDriftyTeam(http://drifty.com/):AndrewJoslin(@ajoslin)ealtri.
AngularGM
AncheseAngularUIincludeunadirettivaperutilizzareleGoogleMapsall’interno
diAngular,preferiamol’approcciopiùsemplicediAngularGM.Questomodulo
consentelacreazionesemplicedelleGoogleMapsnelprogetto,insiemeconmarker,
InfoWindowsepolyline.
Potetepersonalizzarequasituttociòchedesiderate,dallamodificadeicolorie
dalleimpostazionidellamappaall’utilizzodiunelementopersonalizzatoper
l’InfoWindowol’iconanonstandardperilmarker.
URL:https://github.com/dylanfprice/angular-gm
Persone:DylanPrice
Oratoccaavoi…
IlnumerodiprogettiopensourceriguardantiBootstrapeAngularJSèincredibile.
Tuttipossonocontribuirvi,perfinovoi!Setrovateunbug,segnalatelo,osesapete
comerisolverlo,inviateunapullrequestediventateuncontributor.
Naturalmentenontuttiiproblemisonostatirisolti.Orachesapetecomeutilizzare
entrambiiframework,toccaavoidivertirviasvilupparequalcosadistraordinario.
AppendiceB
Incasodidubbio
Ancheglisviluppatoripiùespertinonsannotalvoltachepescipigliareenonc’è
nulladimalenelchiedereunamano.
EsistonoalcunistrumentispecificiriguardantiBootstrapeAngularJSchepossono
aiutarvi,nelcasoviservisse.
Ladocumentazioneufficiale
Seviimbatteteinunproblemaoavetebisognodirinfrescarelamemoria
consultateinnanzituttoladocumentazioneufficiale.
SiaBootstrapsiaAngularJSvantanoun’ottimadocumentazione.Inpassatoerano
sortedellelamentelesuquelladiAngularJS,ritenutaconfusaeconpochiesempi,ma
negliultimiannièmiglioratanotevolmente.Perulterioridettagli,visitateisiti
http://www.angularjs.orgehttp://www.getbootstrap.com.
L’issuetrackerdiGitHub
SiaAngularsiaBootstrapsonoospitatisuGitHubedentrambisiavvalgono
dell’issuetrackerdelservizio.Sescopriteunbuginunodeidueframework,
segnalateloqui.
Ovviamenteseconoscetelanaturadelproblemaesapetecomerisolverlo,potete
inviareunapullrequestecontribuirealprogetto.Perulterioridettagli,visitate
http://www.github.com/angular/angular.js/issuesehttp://www.github.com/twbs/bootstrap/issues.
StackOverflow
Forsepensavatecheneavremmoparlato.StackOverflowèunarisorsaeccezionale
eunottimostrumentodautilizzareseaveteunadomandaspecifica.Ilpiùdellevolte
troveretequalcunaltrochehapostolostessoquesitoepotreteleggerelerisposte.
Altrimentiponeteunanuovadomandaetaggatelacome“AngularJS”o“Twitter
Bootstrap”.Perulterioridettaglivisitatehttp://www.stackoverflow.com.
IlgruppoGoogleAngularJS
ManmanocheutilizzateAngularJS,vipossiamogarantirechedopoalcune
ricerchesuGoogle,viritrovereteinquestogruppo.Sitrattadelgruppo/forum
ufficialediAngularJSedèmoltoattivo.
Contienepiùdi11.000argomentievalelapenaeffettuarequiunaricercaprimadi
porreunanuovadomanda.Perulterioridettaglivisitate
https://groups.google.com/forum/#!forum/angular.
Egghead.io
SecercatedeitutorialvideosuAngularJS,Egghead.ioèprobabilmentelarisorsa
migliore.Offreunserviziodiabbonamentoapagamento,masipuòconsultare
gratuitamentebuonapartedellasualibreria.Sevoletedisporredialtromateriale
videoinformativo,visitatehttps://egghead.io/tags/free.
Twitter
Puòsembrareunasegnalazionebizzarracomerisorsadisupporto,masuTwittersi
trovanopersonemoltoutili.Forsenonèilluogomiglioreperporredomande
complesse,maperpiccoledrittepuòessereprezioso.
Quisiincontranoappassionatidientrambiiframeworkedèpossibilepartecipare
allerispettivecommunity.IdueframeworkhannoaccountTwitterufficiali:@angularjs
e@twbootstrap.
Aproposito,sevoletemandarmiuntweet,iosono@steve228uk.
Sicuramente,conoscendopiùafondoAngularJSeBootstrap,fareteriferimento
alladocumentazioneechiederetesempremenoaiuto.Abbiamoimparatocheèmolto
importantetrasmettereciòchesiconosce.
Forsechiedereteunamanopersviluppareunadirettivamoltospecifica,maciònon
significachenonsapreteaiutareanchevoiglialtri.Quandoaveteunpo’ditempo,
accedeteaStackOverfloweprovatearispondereadalcunedomande;scommettoche
riusciretearispondereamoltepiùdomandediquellecheavrestemaiimmaginato!
AppendiceC
Risposteaiquiz
Capitolo1
1. Utilizzandol’attributong-app.
2. Lasintassichecomprendedoppieparentesigraffe:{{model}}.
3. ModelViewController.
4. CreiamouncontrollerutilizzandouncostruttoreJSstandardel’attributong.
controller
5. Jumbotron.
Capitolo2
1. Unabarradinavigazione(navbar)diBootstrap.
2. 12colonne.
3. Èunafunzionechiamatadaunattributoounelementopersonalizzato.
4. ng-repeat.
Capitolo3
1. Conilsimbolopipeinunmodello:{{modelName|filter}}.
2. Coniduepunti:{{modelName|filter:arg1:arg2}}.
3. Ilfiltrochiamatofilter.
4. Utilizziamoilservizio$filterinserendoilfiltrocomeservizioseguendoil
patternfilternameFilter.
5. UnmoduloAngularJS.
Capitolo4
1.
.
ngRoute
2. Ilmetodoconfignelmodulo.
3. Ilservizio$routeProvider.
4. Conilmetodo$routeProvider.when.
5. Ilmetodo$routeProvider.otherwise.
6. Utilizzandohtml5Mode.
Capitolo5
1. Perchéèinclusonellavistadellaradice.
2. Diverseclassi:table-bordered,table-striped,table-hoveretable-condensed.
3. Con<buttonclass=”btnbtn-primarybtn-lg”><button>.
4. Nellaclasseform-group.
5. Leetichettevengonoallineateallasinistradeglielementi.
6. Laclassehelp-block.
7.
percreareun’immaginedallaformacircolare,img-roundedpercrearne
img-circle
unadaibordiarrotondatieimg-thumbnailperaggiungereunbordodoppio.
Capitolo6
1. Serviziopersonalizzato,$rootScope,controlleralivellodell’applicazione.
2.
,
efactory.
value service
3. IlmodulongSanitize.
4. Ilmetodocontrollerconsentealladirettivadicomunicareconlealtredirettive,
mentreilmetodolinkno.
5. Ilsegno=significacheèpossibilelegaredirettamenteunmodello;@indicache
ladirettivautilizzeràilvaloreletteraledell’attributo.
6. ImpostandolaproprietarestrictsuEM.
7. Peraggiungerealcunefunzionihelperallabarradinavigazione.
8. Utilizzando$index.
Capitolo7
1. DangAnimate.
2. AngularMotion.
3. bs-.
4.
click hover focus
,
5.
show hide
,
,
emanualmente.
etoggle.
Capitolo8
1. Unapromise.
2. Con$http.get(‘http://localhost:8000’).success(function(data){$scope.contacts=data
.
});
3. Sicomportacomeunsegnaposto.
4. RESTAngularutilizzalepromiseenonènecessariotrascrivereisegnaposto
seguendoilpatternREST.
5. Intemporeale.
Capitolo9
1. SuNode.
2. PerindicareaNPMdiqualipacchettiabbiamobisogno.
3. Uglify.
4. Utilizzarelanotazionedegliarray.
Capitolo10
1. Variabili,mixineregoleannidate.
2. Utilizzandoilpattern&:beforeoppure&:after.
3. Modificandolavariabileinvariables.less.
4. UsaglistilidiBootstrapperrenderlosimileaBootstrap2.
Capitolo11
1. Unaqualsiasidelleseguenti:required,ng-required,ng-pattern,ng-minlength,ng,
eng-max.
maxlength ng-min
2. Verificandoleproprietà$valido$invalid.
3. Chiamandolonellaproprietàrequire.
4. Utilizzandong-pattern.
Capitolo12
1. Un’estensionediChromechecipermettediverificareleappdiAngularJS.
2. Unodeiseguenti:Models,Performance,Dependencies,Inspector,
evidenziazionedelleapplicazioni,bindingescope.
3. NellaschedaModelsdelwebinspectorselezionandoAngularJSProperties.
4. ngMin.
5. remove,addesingleQuotes.
6. Lanecessitàdiannotaremanualmenteledipendenze.
Indice
Introduzione
Gliargomentidellibro
Checosaoccorreperillibro
Achisirivolgeillibro
Convenzioni
Scaricaifiledegliesempi
L’autore
Irevisori
Capitolo1-Hello,{{name}}
Impostazione
InstallazionediAngularJSeBootstrap
Quiz
Riepilogo
Capitolo2-SviluppareconAngularJSeBootstrap
Impostazione
Scaffolding
Quiz
Riepilogo
Capitolo3-Ifiltri
Applicareunfiltrodallavista
ApplicareifiltridaJavaScript
Sviluppareunfiltro
Quiz
Riepilogo
Capitolo4-Routing
InstallarengRoute
Creareroutedibase
Routeconparametri
Routedifallback
RoutinginHTML5oeliminazionedelsimbolo#
Collegareleroute
Quiz
Riepilogo
Capitolo5-Leviste
PopolarelavistaIndex
PopolarelavistaAddContact
PopolarelavistaViewContact
Quiz
Riepilogo
Capitolo6-CRUD
Read
Create
Update
Delete
Quiz
Riepilogo
Capitolo7-AngularStrap
InstallareAngularStrap
UtilizzareAngularStrap
UtilizzareiservizidiAngularStrap
IntegrareAngularStrap
Quiz
Riepilogo
Capitolo8-Connessionealserver
Connettersicon$http
ConnettersiconngResource
Sistemialternatividiconnessione
Quiz
Riepilogo
Capitolo9-Itaskrunner
InstallareNodeeNPM
UtilizzareGrunt
Utilizzaregulp
Riorganizzareilprogetto
Quiz
Riepilogo
Capitolo10-PersonalizzareBootstrap
CompilareLessconGruntogulp
Less
PersonalizzareglistilidiBootstrap
ItemidiBootstrap
DovetrovarealtritemidiBootstrap
Quiz
Riepilogo
Capitolo11-Validazione
Validazionedeiform
Quiz
Riepilogo
Capitolo12-Strumentidellacommunity
Batarang
Verificareloscopeeleproprietà
ng-annotate
Quiz
Riepilogo
AppendiceA-Personeeprogetti
ProgettiepersonedietroBootstrap
ProgettiepersonedietroAngularJS
Oratoccaavoi…
AppendiceB-Incasodidubbio
Ladocumentazioneufficiale
L’issuetrackerdiGitHub
StackOverflow
IlgruppoGoogleAngularJS
Egghead.io
Twitter
AppendiceC-Risposteaiquiz
Capitolo1
Capitolo2
Capitolo3
Capitolo4
Capitolo5
Capitolo6
Capitolo7
Capitolo8
Capitolo9
Capitolo10
Capitolo11
Capitolo12