index-of.co.ukindex-of.co.uk/Programming/Stephen Radford - Sviluppare...Gli argomenti del libro Il...
Transcript of index-of.co.ukindex-of.co.uk/Programming/Stephen Radford - Sviluppare...Gli argomenti del libro Il...
SVILUPPAREAPPLICAZIONIWEBCONANGULARJSEBOOTSTRAP
StephenRadford
©Apogeo-IF-IdeeeditorialiFeltrinellis.r.l.SocioUnicoGiangiacomoFeltrinelliEditores.r.l.
ISBNedizionecartacea:9788850333424
Copyright©PacktPublishing2014.FirstpublishedintheEnglishlanguageunderthetitleLearningWebDevelopmentwithBootstrapandAngularJS(9781783287550).
Ilpresentefilepuòessereusatoesclusivamenteperfinalitàdicaratterepersonale.TuttiicontenutisonoprotettidallaLeggesuldirittod’autore.
Nomiemarchicitatineltestosonogeneralmentedepositatioregistratidallerispettivecaseproduttrici.
L’edizionecartaceaèinvenditanellemigliorilibrerie.
~
Sitoweb:www.apogeonline.com
ScoprilenovitàdiApogeosuFacebook
SeguicisuTwitter@apogeonline
Rimaniaggiornatoiscrivendotiallanostranewsletter
Introduzione
Durantelamiacarrierahosviluppatoprogettididiversedimensioni,dapiccolisiticommercialiainterisocialnetwork.EranotuttiaccomunatidallanecessitàdidisporrediJavaScripteCSSbenstrutturati.
Questolibroaffrontaduefantasticiprogettiopensourcechederivanodaquestaesigenza:BootstrapeAngularJS.
GliargomentidellibroIlCapitolo1,“Hello,{{name}}”,esaminaifondamentidiAngularJSeBootstrap
attraversolarealizzazionediunasempliceappHello,World.
IlCapitolo2,“SviluppareconAngularJSeBootstrap”,presental’appprincipalechesvilupperemonelcorsodellibro,ilsistemadellegrigliediBootstrapealcunicomponentidiAngularJS.
IlCapitolo3,“Ifiltri”,illustraalcunideifiltriintegratiinAngularJSelacreazionediunfiltro.
IlCapitolo4,“Routing”,sibasasulrouterintegratodiAngularJS,eillustral’utilizzodeipartialpercreareun’appmultiview.
IlCapitolo5,“Leviste”,esaminailsistemadellegrigliediBootstrapeapprofondisceipartial.
IlCapitolo6,“CRUD”,spiegacome,dopoaverimpostatoleviste,possiamoimplementare,creare,leggere,aggiornareecancellarelefunzioni.
IlCapitolo7,“AngularStrap”,descriveilmoduloditerzeparticheconsentediutilizzareiplug-indiBootstraptramiteAngularJS.
IlCapitolo8,“Connessionealserver”,esaminaduesistemiufficialipercollegarsiaunserver.
IlCapitolo9,“Itaskrunner”,spiegacomeminificaretuttiifileJSeLessutilizzandoGruntegulp.
IlCapitolo10,“PersonalizzareBootstrap”,mostracomepersonalizzarefacilmenteBootstrapdopoaverimpostatoGrunt.js.
IlCapitolo11,“Validazione”,affrontaglistrumentidivalidazionesubitoprontiall’uso;liimplementeremoegestiremoglierroridelserver.
IlCapitolo12,“Strumentidellacommunity”,presentaalcunistrumenticreatidallacommunitydiAngularJS.
L’AppendiceA,“Personeeprogetti”,presentaalcunepersoneimportantinell’ambientediAngularJSeBootstrap,oltreaprogettirilevanti.
L’AppendiceB,“Incasodidubbio”,offrerisposteaglieventualidubbideilettori.
L’AppendiceC,“Risposteaiquiz”,comprendetuttelerispostealledomandedeiquizpresentinellibro.
ChecosaoccorreperillibroAngularJSeBootstrapnonhannodipendenzeeperquestolibrodovretesolo
disporrediunbrowserediuneditorditesto.ViconsiglioChromeeAtom.
AchisirivolgeillibroSeviinteressalosviluppowebmoderno,sicuramenteconoscereteBootstrape
AngularJS.QuestolibrosirivolgealettoriconunminimodiesperienzainJavaScriptchedesideranoimpegnarsinellosviluppodiwebapp.
TuttaviaèassolutamentenecessarialaconoscenzadiJavaScript.Senonconosceteladifferenzatraunastringaeunoggetto,correteairipari.Ovviamente,seavetegiàutilizzatoAngularJSoBootstrapevoletesapernedipiù,visentireteavostroagio.
ConvenzioniInquestolibrotroveretealcunistilidicaratterechedifferenzianodiversitipidi
informazioni.Diseguitoalcuniesempieunaspiegazionedellorosignificato.
Ilcodicechetrovereteneltesto,inomidelletabelledeldatabase,inomieleestensionideifile,ipercorsi,gliURL,l’inputdegliutentieglihandlediTwittervengonopresentatiinmonospaziato.
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’interfacciasonoriportatiincorsivo.
NOTA
Isuggerimenti,gliavvertimentielenoteimportantiappaionoinquestomodo.
ScaricaifiledegliesempiSulsitodell’editoreoriginaleinglese,PacktPublishing,potetescaricareifiledegli
esempipresentatideltesto.Perfarloènecessarioregistrarsigratuitamenteall’indirizzohttps://www.packtpub.com/register.Quindiandatesullaschedadellibroall’indirizzohttps://www.packtpub.com/web-development/learning-web-development-bootstrap-and-angularjs(percomoditàinformaabbraviatahttp://bit.ly/packt-ab)efateclicsuCodeFiles.Perproblemididownloadpotetecontattarelanostraredazioneall’[email protected].
L’autore
StephenRadfordèunosviluppatorewebatuttotondonativodiBristol,chevivenelcentrodiLeicester,inGranBretagna.Dopoaverfrequentatoall’universitàilcorsodiGraficaeComunicazioneVisiva,siètrasferitoinquestacittà,doveèstatoassuntodaunadellepiùimportantisocietàdimarketingonlinedelpaese.
Mentrelavoravaperalcuneagenzie,Stephenhasviluppatodiversiprogetticollaterali,tracuiFTPloy,unSaaSconcepitoperrendereaccessibileatuttiildeploymentcontinuo.Questoprogettosièclassificatotraifinalistinellacategoria“SideProjectoftheYear”dei.NetAwards.
Attualmente,insiemeconilsuosocio,gestisceCocoon,unasocietàdisviluppowebcherealizzaegestisceappcomeFTPloyeFormer.Cocoonlavoraancheastrettocontattoconungruppodistartupeaziendechetrasformanoideeinsitieapp.
Vorrei ringraziare quanti mi hanno sostenuto durante la stesura di questo libro. Prima di tutto, il miopartner,Declan.Miè statodigrande supportoenonpotreidesideraredi avere accantonessunapersonamiglioredilui.PaulMckayèstatoilprimoalqualehomostratoillibroemihaancheaiutatoconilmioprofilo biografico perché, stranamente, ho incontrato grandi difficoltà a scrivere sulla mia vita.Ovviamente, voglio ringraziare imiei genitori.Mio padre ha atteso pazientemente la copia cartacea dellibro;sperocheorasiainbellamostrainsoggiorno.
Irevisori
TasosBekos,ingegnereinformatico,utilizzaletecnologiewebdapiùdidiecianni.Halavoratocomesviluppatorefreelanceeconsulenteperalcunedellepiùimportantisocietàinternazionalifinanziarieeditelecomunicazioni.AttualmentelavoracomesviluppatoreperZuluTrade,dovesiavvaledellepiùmodernetecnologiefront-end.ConosceafondoAngularJSedèunmembroattivodellacommunityopensource,nellaqualeoperacomeprincipalecollaboratorealprogettoAngularUIBootstrap.Quandononscrivecodice,trascorreiltempoagiocareconisuoiduefigli.
JackHsuèunosviluppatorewebspecializzatoneglistrumentienelletecnologiefront-end.Èilprincipalesviluppatorefront-endpressoNulogy,dovemettelasuaconoscenzadiJavaScripteAngularJSalserviziodeicolleghi.PrimadiNulogy,halavoratopernumeroseaziende,tracuiTheGlobe&Mail,OntarioInstituteofCancerResearcheWaveAccounting.Neltempoliberogiocaaivideogiochi,esploraidiversiquartieridiTorontooviaggiaperilmondo.Sulsuoblogpersonaleèpossibileleggerenumerosipostriguardantilaprogrammazione.
OleB.Michelsenlavoradapiùdi12anninellosviluppowebesièlaureatoininformaticapressolaDIKU,all’universitàdiCopenhagen.DirecentesièspecializzatonellosviluppoJavaScriptfront-end,concentrandosiinparticolaresuWebRTCesuframeworkperappsinglepage.
JurgenVandeMoereènatonel1978edècresciutoaEvergem,inBelgio,conigenitori,lasorellaeisuoianimalidomestici.A6annihainiziatoadaiutareilpadre,proprietariodiunnegoziodiinformatica,adassemblareicomputerpericlienti.Mentregliamicigiocavanoaivideogiochi,Jurgenpreferivascriverescripteprogrammiperrisolvereiproblemicheeranocostrettiadaffrontareiclientidelpadre.DopoessersilaureatoinlatinoematematicapressoilcollegeSint-LievensaGand,Jurgenhaproseguitolasuaformazionepressol’universitàdellastessacittà,dovehafrequentatoinformatica.All’universitàilsuonomeutenteUnixera“jvandemo,”ilnicknamecheusaancoraoggisuInternet.Nel1999hainiziatolacarrieraprofessionalepressoInfoworld.Dopoannididurolavorocomesviluppatoreenetworkengineer,nel2005enel2006haricopertoposizionimanageriali.Sentendosiunosviluppatore,eavvertendolamancanzadiscriverecodice,nel2007hadecisodiporrefineallacarrieradimanagerperdedicarsidinuovoallasuaverapassione:losviluppo.Daallorahastudiatoelavoratodacasa,inBelgio,dovevive
attualmenteconlasuafidanzata,suofiglioeisuoicani.Inunmondoinrapidaevoluzionediapplicazionidata-intensiveereal-time,siconcentrasulletecnologielegateaJavaScriptebasatesuAngularJSeNode.js.Isuoinumerosicontributipubblicieprivatihannopermessodiavviaremoltepliciprogettidisuccessointuttoilmondo.Seviserveunaconsulenzaperilvostroprogetto,potetescrivergliall’[email protected](@jvandemo)oleggereilsuoblog(http://www.jvandemo.com).
Capitolo1
Hello,{{name}}
Ilmodomiglioreperimparareaprogrammareèscriverecodice,edèproprioquestociòchefaremo.PercomprenderequantoèfacileesseresubitooperativiconBootstrapeAngularJS,realizzeremoun’applicazionemoltosemplicecheciconsentiràdidigitareunnomeevisualizzarlosullapaginaintemporeale.Scopriretecosìl’efficaciadelbindingdeidatibidirezionaleeillinguaggiopertemplateintegratodiAngular.UtilizzeremoBootstrapperattribuireunostileeunastrutturaall’app.
Primadiinstallareiframework,creeremolastrutturadellecartelleeilfileindex.htmlchesaràlabasedell’app.
ImpostazionePerrealizzarel’appconAngulareBootstrap,procediamoall’impostazione,che
consistenelcreareunapaginaHTMLeincluderealcunifile.Innanzituttocreateunanuovadirectory,chapter1,eapritelanelvostroeditor.Createalsuointernounnuovofile,index.html,eimmettetequestocodiceboilerplate:
<!DOCTYPEhtml>
<htmllang=”en”>
<head>
<metacharset=”utf-8”>
<title></title>
</head>
<body>
</body>
</html>
SitrattadiunapaginaHTMLstandardconlaqualeopereremodopoaverintegratoAngulareBootstrap.
Createduecartelleall’internodellacartellachapter1:cssejs.Lastrutturacompletadellecartelledovrebbeesseresimileallaseguente.
InstallazionediAngularJSeBootstrapInstallarequestiframeworkèsemplicecomeincluderefileCSSoJavaScriptnella
pagina.Lopotremmofareconuncontentdeliverynetwork(CDN)comeGoogleCodeoMaxCDN,maperorarecupereremoifilemanualmente.EsaminiamoipassichedovresteconoscerequandointegrereteAngularJSeBootstrapnelprogetto.
InstallazionediBootstrapAndateall’indirizzohttp://getbootstrap.comefateclicsulpulsanteDownload
Bootstrap.OtterreteunfileZIPconl’ultimaversionediBootstrapchecomprendeCSS,fontefileJavaScript.Leversioniprecedentiincludevanounadirectorydelleimmagini,chenellaVersione3diventaiconfonts.
Perquest’app,ciinteressaperorasoltantounfile:bootstrap.min.csspresentenelladirectorycss.Ilfogliodistileforniscetuttalastrutturaequeglielementigraziosi,tracuipulsantiemessaggidiavviso,percuiBootstrapèconosciuto.Copiatelonelladirectorycssdelprogettoeapriteilfileindex.htmlnell’editorditesto.
IntegrareBootstrapèfacilecomecollegareilfileCSScheabbiamoappenacopiato.Aggiungetequantosegueall’internodeltag<head>.Inseritequestotagnell’elemento<head>dellapagina:
<linkrel=”stylesheet”href=”css/bootstrap.min.css”>
InstallazionediAngularJSDopoaverintegratoBootstrapnellawebapp,procedeteainstallareAngular.
Visitateilsitohttps://angularjs.org/efateclicsulpulsanteDownload.Vedretealcuneopzioni;avoiservelaversionestabileminificata.
Copiateilfilecheavetescaricatonelladirectoryjsdelprogettoeapriteilfileindex.html.Angularpuòessereintegratonell’appcomequalsiasialtrofileJavaScript.
Èpreferibileincluderloneltag<head>dellapagina,altrimentialcunefunzioniacuiricorreretenellibrononsarannoattive.Anchesenonènecessario,dovretecompierealtripassiperintegrareancoradipiùAngularnelfileHTML.
Inseritequestotag<script>nell’<head>dellapagina.
<scriptsrc=”js/angular.min.js”></script>
Tuttofatto?Quasi.DobbiamodireadAngularcheintendiamoutilizzarlonell’app.Angularrichiedequestobootstrap,eilframeworksemplificamoltissimoquestaprocedura.Dovetesemplicementeincludereunaltroattributoneltag<html>diapertura:
<htmllang=”en”ng-app>
Eccofatto!OraAngularsachevogliamoutilizzarlo.
NOTA
Angularciconsenteanchedi farprecederequestiattributidadata- (peresempiodata-ng-app)nelcasovolessimoscrivereHTML5valido.
UtilizzareAngularJSAbbiamovistomoltateoriaallabasediAngular;ègiuntoilmomentodimetterla
inpratica.Dopoavercreatoun’appfunzionante,vedremocomeabbellirlaconBootstrap.
Riapriteilfileindex.html,maquestavoltaapriteloanchenelbrowsercosìdavedereciò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’appconl’attributong-appneltagdiapertura<html>;orapassiamorapidamenteall’azione.
Realizzeremoun’appHello,Worldunpo’diversa.Invecedifarcomparirequestascritta,avremouncampoditestocheeffettueràilbindingdeidatieliripeteràautomaticamentenellavista;tuttoquestosenzascrivereunasolarigadiJavaScript.
Iniziamoadaggiungereiltag<h1>neltag<body>:
<h1>Hello,World</h1>
NelbrowservedretecheBootstraphaaggiornatolavisualizzazionepredefinita.AlpostodelfontTimesNewRomancomparel’Helveticaeimarginiditroppoattornoalbordosonostatieliminati.
Oradobbiamoincluderel’inputditestoespecificareancheilmodellocheintendiamoutilizzare.Ilmodellopuòesserediqualsiasitipo,mainquestocasosaràunastringachel’inputrestituirà:
<inputtype=”text”ng-model=”name”>
L’attributong-modeldichiarailbindingdelmodellosuquestoelemento,etuttociòchedigiteremonelcampoditestosaràassociatoaessodaAngular.Ovviamentenonverràvisualizzatocomepermagiasullapagina;dovremodirealframeworkdoveripeterlo.Pervisualizzareilmodellosullapagina,èsufficienteracchiudereilnometradoppieparentesigraffe:
{{name}}
InseriteloalpostodiWorldneltag<h1>eaggiornatelapaginanelbrowser.Sedigitateilvostronomenelcampoditesto,vedretechevienevisualizzatoautomaticamentenell’headerintemporeale.AngularlofaalpostovostrosenzadoverscrivereunasolarigadiJavaScript.
Unrisultatoottimo,masarebbeopportunoavereun’impostazionepredefinitacheevitidifarsembrarechenonfunzionaancoraprimacheunutentedigitiilsuonome.Fortunatamentetuttociòcheècompresotraleparentesigraffevieneanalizzatocomeun’espressioneAngularJS,ecosìèpossibileverificareseilmodellohaunvalore;incasocontrario,puòripetereWorld.InAngularquestaèun’espressione,edèsufficienteaggiungereildoppiopipecomeinJS:
{{name||‘World’}}
NOTA
Angulardescriveinquestomodoun’espressione:“FrammentidicodicesimiliaJavaScriptchedisolitosonopostiinbindingcome{{espressione}}.”
ÈopportunoricordarechesitrattadiJavaScript,edeccoperchédobbiamoinserirelevirgolettepersegnalarechesitrattadiunastringaenondelnomediunmodello.Seprovasteacancellarle,vedrestecheAngularnonvisualizzadinuovonulla.CiòaccadeperchéimodellinameeWorldnonsonodefiniti.
Questimodellipossonoesseredefinitidirettamenteall’internodell’HTMLutilizzando,comeabbiamovisto,unattributo,maèanchepossibileassegnareaessiunvaloredauncontroller.AquestoscopocreateunnuovofileJS,controller.js,eincludetelonell’app:
<scripttype=”text/javascript”src=”js/controller.js”></script>
InseritelodopoaverinclusoAngularnellapaginaperevitareerrori.
IcontrollersonosemplicementefunzionicheAngularpuòutilizzare;esaminiamoneuno:
functionAppCtrl($scope){
}
Quiabbiamodichiaratoilcontroller(insostanzaunasemplicefunzionedelcostruttoreJavaScript)einessoabbiamoinseritoloscope.Loscopeèciòacuipossiamoaccedereall’internodellavista.Suun’unicapaginapossonoesisteremolteplicicontrolleremoltepliciscope.SitrattainsostanzadiunoggettoJavaScriptdeinostrimodelliedellenostrefunzioni,suiqualiAngularoperalasuamagia;peresempio,loscopedellanostraapplicazioneperilmomentoapparecosì:
{
name:“Stephen”
}
Loscopecambiaasecondadiciòchesidigitanelcampoditesto.Aessosipuòaccederesiadallavistasiadalcontroller.
Dopoavercreatoilcontroller,dobbiamodireadAngularcheintendiamoutilizzarlo.Perlanostraappciserveunsolocontroller;aggiungiamounsecondoattributoaltag<html>:
ng-controller=”AppCtrl”
QuestoattributodiceadAngularchevogliamoutilizzarelafunzioneAppCtrlcheabbiamoappenacreatocomecontrollerperlapagina.Potremmoovviamenteaggiungerloaqualsiasielementosullapaginacompreso,sevolessimo,ilbody.
Perverificarechetuttofunzioni,specificheremounvaloreinizialeperilmodello.Èfacilecomeimpostareunaproprietàsuqualsiasioggetto:
functionAppCtrl($scope){
$scope.name=“World”;
}
Seaggiornatel’appnelbrowser,vedretecheWorldèprecompilatocomevaloredelmodello.Questoèunottimoesempiodell’efficacebindingdeidatibidirezionalediAngular.CiconsentediutilizzaredatipredefinitidiunaAPIodiundatabaseepoimodificarlidirettamentenellavistaprimadiriottenerlinelcontroller.
NOTA
Angular descrive il binding dei dati come “la sincronizzazione dei dati tra i componenti delmodelloedellavista”.Così,semodifichiamo ilvalorediunmodellonellavistaonelcontrollerJavaScript,tuttosiaggiornadiconseguenza.
BootstrapDopoavercreatol’applicazioneHelloWorldeaververificatochetuttofunzioni
comeprevisto,èilmomentodiutilizzareBootstrapperaggiungerestileestruttura
allanostraapp.Peroral’appèmalallineatasullasinistraetuttosembratroppofitto;rimediamoconunpo’discaffolding.Bootstrapoffreunottimosistemadigriglieresponsivemobilefirstchepossiamoutilizzareaggiungendoalcunidivealcuneclassi.
Prima,però,racchiudiamoilcontenutoinuncontenitoreperfarsubitounpo’diordine.
NOTA
Ilconcettodimobilefirstconsistenelprogettare/sviluppareinnanzituttoperglischermipiùpiccolieaggiungereelementialdesigninvecedieliminarli.
<divclass=”container”>
<h1>Hello,{{name||‘World’}}</h1>
<inputtype=”text”ng-model=”name”>
</div>
Seridimensionatelafinestradelbrowser,dovresteiniziareaosservarelacapacitàdiadattamentodelframeworkevederelafinestracomprimersi.
PuòessereunabuonaidearacchiuderloinquellochenellaterminologiadiBootstrapè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èancoratroppovicinoallapartesuperioredellafinestradelbrowser.Possiamointrodurreunulterioremiglioramentoconunheaderdipagina,ancheseilcampoditestomisembraancorafuoriposto.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,oltreaunsottilebordoinferiore.L’ultimoelementochepotremmomigliorareèlacaselladitesto.Bootstrapoffrealcuniottimistiliditestoevalelapenautilizzarli.Perprimacosadobbiamoaggiungerelaclasseform-controlall’inputditesto.
Inquestomodolalarghezzaverràimpostataal100%ealcuniaspettistilisticimiglioreranno,comeibordiarrotondatieunbagliorenelmomentoincuil’elementoottieneilfocus:
<inputtype=”text”ng-model=”name”class=”form-control”>
Vamoltomeglio,masembraancoraunpo’piccolorispettoall’header.Bootstrapoffrealtredueclassicherimpicciolisconooingrandisconol’elemento,rispettivamenteinput-lgeinput-sm.Inquestocaso,scegliamolaclasseinput-lgelaaggiungiamoall’input:
<inputtype=”text”ng-model=”name”class=”form-controlinput-lg”>
Dobbiamoancorarisolverelaquestionedellaspaziaturaperchéètroppoaridossodeltag<h1>.Forseèunabuonaideaaggiungereun’etichetta,cosìl’utentecapiràchedevedigitarenellacasella.Bootstrapciconsentediprendereduepiccioniconunafavaperchéincludeunmarginenell’etichetta:
<labelfor=”name”>EnterYourName</label>
<inputtype=”text”ng-model=”name”class=”form-controlinput-lg”id=”name”>
Quiz1. ComevieneinizializzatoAngularsullapagina?2. Checosasiusapervisualizzaresullapaginaunvaloredelmodello?3. Achecosacorrispondel’acronimoMVC?4. ComecreiamouncontrollerecomediciamoadAngularcheintendiamo
utilizzarlo?5. InBootstrap3qualèilnuovonomediHeroUnit?
RiepilogoLanostraappèbellaefunzionapropriocomedovrebbe;riepiloghiamociòche
abbiamoimparatoinquestoprimocapitolo.
InnanzituttoabbiamovistocomeèfacileinstallareAngularJSeBootstrapincludendounsolofileJavaScripteununicofogliodistile.Abbiamoancheosservatocomevieneinizializzataun’applicazioneAngulareabbiamoiniziatoarealizzarelanostraprimaapp.
L’appHello,Worldcheabbiamocreato,seppurmoltosemplice,illustraalcunecaratteristichefondamentalidiAngular:
espressioni;scope;modelli;bindingdeidatibidirezionale.
TuttoquestoèstatopossibilesenzascrivereunasolarigadiJavaScript;infattiilcontrollercheabbiamocreatoservivasoloaillustrareilbindingbidirezionaleenonerauncomponenteobbligatoriodell’app.
ConBootstrap,abbiamoutilizzatoalcunideinumerosicomponentidisponibili,tracuileclassijumbotronepage-headerperattribuireall’appunpo’distileesostanza.Inoltreabbiamovistoinazioneilnuovodesignresponsivemobilefirstsenzadoveraffollareilmarkupconclassioelementinonnecessari.
NelCapitolo2esamineremopiùneldettaglioalcunecaratteristichefondamentalidiAngularJSeBootstrapepresenteremoilprogettocheimplementeremonelcorsodellibro.
Capitolo2
SviluppareconAngularJSeBootstrap
DopoavercreatoufficialmentelaprimaapputilizzandoAngularJSeBootstrap,ègiuntoilmomentodifareunsaltodiqualità.
Inquestolibroviserviretedientrambiiframeworkpersviluppareun’appdigestionedeicontatticontantodiricercatestuale,creazione,modificaecancellazione.
Esamineremounabasedicodicechepuòesseremantenutaesfrutteremotuttoilpotenzialedientrambiiframework.Iniziamoasviluppare!
ImpostazioneCreiamorapidamenteunanuovadirectoryperl’appeimpostiamounastruttura
simileall’appHello,WorldcheabbiamosviluppatonelCapitolo1.
Peresempio,nellastrutturadicartellemostratanellaprossimafigura,abbiamoinseritoledirectorynelladirectoryassetsperteneretuttoinordine.CopiateAngulareBootstrap,comedescrittonelCapitolo1all’internodelledirectorypertinentiecreateilfileindex.htmlnellaradice,chediventeràlabasedell’appdigestionedeicontatti.IlseguentesnippetèunasemplicepaginaHTMLconintegratiBootstrapeAngular.AbbiamoancheinizializzatoAngularsullapaginaconl’attributong-appneltag<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>
ScaffoldingDopoaverimpostatoilfileelastrutturadellecartelledibase,possiamoiniziarea
effettuareloscaffoldingdell’appgrazieaBootstrap.Oltreaoffrireunaseriedicomponenti,comelanavigazioneeipulsanti,chepossiamoutilizzarenell’appdigestionedeicontatti,Bootstrapproponeancheunsistemadigriglieresponsivemoltopotentedicuisfrutteremoognipotenzialità.
BarradinavigazioneAvremobisognodiunabarradinavigazione(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èquellodiuncomponentedellapaginamoltosemplice,masel’analizzateneldettaglio,noteretecheognielementoènecessario.
Iltag<nav>contienetuttociòcheèpresentenellabarradinavigazione.Alsuointernositrovanoduesezioni:navbar-headerenavbar-collapse.Questielementisonoriservatiallanavigazionemobileecontrollanociòchevienemostratoonascostodalpulsanteinterruttore.
L’attributodata-targetperilpulsantecorrispondedirettamenteall’attributoiddell’elementonavbar-collapse;inquestomodoBootstrapsachecosadeveattivare.Laprossimaschermatariproducelabarradinavigazionesudispositivipiùgrandidiuntablet.
Includeremolabarradinavigazionedirettamenteall’internodeltag<body>.Inquestomodooccuperemotuttalalarghezzadellafinestradelbrowser.
Selaridimensionate,vedretecheBootstrapvisualizzal’headermobileconilpulsanteinterruttoreperschermididimensioniinferioriai768px,ossialedimensionidelloschermodiuniPadconorientamentoverticale.Tuttaviasefateclicsulpulsantepermuovervinellanavigazione,vedretechenonsuccedenulla.CiòaccadeperchénonabbiamoinclusoinBootstrapilfileJavaScript,presentenelfileZIPcheabbiamoscaricatoinprecedenza.
Copiatelonelladirectoryjsdell’appefateviriferimentonelfileindex.html.DovreteincludereanchejQuerynell’applicazione,poichéilJSdiBootstrapdipendedaquesto.
Potetetrovarel’ultimaversioneall’indirizzohttp://jquery.com/;ancoraunavoltaaggiungetelaalladirectoryeincludetelanellapaginaprimadibootstrap.js.VerificatecheifileJavaScriptcompaianonelseguenteordine:
<scriptsrc=”assets/js/jquery.min.js”></script>
<scriptsrc=”assets/js/bootstrap.min.js”></script>
<scriptsrc=”assets/js/angular.min.js”></script>
Seriaggiornatelapaginadovresteriuscireafarclicsulpulsanteinterruttorepervisualizzarelanavigazioneperdispositivimobili.
LegrigliediBootstrap
Ilsistemaagrigliacon12colonnediBootstrapèmoltopotenteecipermettedieffettuareloscaffoldingdellanostraappresponsiveconpochissimielementi,approfittandodelCSSmodulare.Lagrigliaècompostadarigheecolonnecheèpossibileadattareutilizzandounaseriediclassi.Primadiiniziare,dobbiamoinserireuncontenitoreperlerighe,altrimentiilframeworknonsicomporteràcomeprevisto.Èsufficienteunsemplicetag<div>dacollocaresottolabarradinavigazione:
<divclass=”container”></div>
Inquestomodolagrigliasaràcentrataeaggiungeremolaproprietàmax-widthperdisporretuttoperbene.
Esistonoquattroprefissiperleclassi,chedefinisconoilcomportamentodellecolonne.Utilizzeremoperlopiùilprefissocol-sm-,checomprimelecolonneinmodocheappaianounasopral’altraquandoilcontenitorehaunalarghezzainferiorea750px.
Lealtreclassisiriferisconotutteadiversedimensionidelloschermodeidispositiviesicomportanoinmodosimile.Laseguentetabella,trattadahttp://getbootstrap.com/,mostraledifferenzetralequattroclassi.
Cellulari(<768px)
Tablet(≥768px)
Desktop(≥992px)
Desktop(≥1200px)
Comportamentodellagriglia
Sempreorizzontale
All’iniziocompressa,orizzontalesopraibreakpoint
Larghezzamassima Nessuna
delcontenitore (automatico) 750px 970px 1170px
Prefissodelleclassi .col-xs- .col-sm- .col-md- .col-lg-
Larghezzamassimadellacolonna Automatica 60px 78px 95px
Offset N/D Sì
Ordinamentodellecolonne N/D Sì
Creiamorapidamenteunlayoutaduecolonneconun’areadelcontenutoprincipaleeunabarralaterale.Siccomelagrigliaècostituitada12colonne,dovremofarsìchel’areadelcontenutorientriinesse,altrimentiavremodellospaziovuoto.
Ottocolonneperl’areadelcontenutoequattroperlabarralateraledovrebberoessereunasoluzioneottimale,macomeimplementarle?
All’internodelcontenitorecreiamounnuovotag<div>conlaclasserow.Possiamoimpostarequanterighedesideriamo;ciascunapuòcontenerealmassimododicicolonne:
<divclass=”container”>
<divclass=”row”>
</div>
</div>
Siccomevogliamochelecolonnevenganovisualizzatesudispositivimobili,utilizzeremoilprefissocol-sm-.Lacreazionediunacolonnaèsemplice;èsufficientescegliereilprefissoopportunoeaggiungereilnumerodellecolonnechesidesideraimpostare.Osservateillayoutdibaseaduecolonne:
<divclass=”container”>
<divclass=”row”>
<divclass=”col-sm-8”>
Thisisourcontentarea
</div>
<divclass=”col-sm-4”>
Hereisoursidebar
</div>
</div>
</div>
Selovisualizzatesuunoschermodidimensionisuperioriaquellodiundispositivomobile,Bootstrapaggiungeràautomaticamente30pxdispaziotralecolonne(15pxsuciascunlato).Tuttavia,talvoltavorreteinserirealtrospaziotralecolonneedistanziarleunpo’.Bootstrappermettediottenerequestorisultatoaggiungendoun’altraclasseallacolonna.
Sceglietedinuovoilprefissoopportuno,maquestavoltaaggiungetelaparolachiaveoffset:
<divclass=”col-sm-4col-sm-offset-1”></div>
Inquestocasoilnumeropresenteallafinecontrollailnumerodellecolonnesucuisiimpostal’offset.Lanuovaclasseaggiungeunulterioremargineasinistra.
NOTA
Ricordate: lasommadellecolonne inunariga,compreso l’offset,nondeveesseresuperiorea12.
All’internodellecolonneèpossibilenidificarealtrerigheecolonnepercreareunlayoutpiù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’internodelcontenitoreprincipalecheabbiamoimpostatoprima.Atitolodiesempio,abbiamoinseritoall’internodeltestofittizio.
Nelbrowservedretecheorasonopresentitrecolonne.Tuttavia,siccomelagrigliaènidificata,possiamocreareunanuovarigaeunasolacolonna,trecolonneoquellocherichiedeillayout.
Classihelper
Bootstrapincludealcuneclassihelperchepossiamoutilizzareperadattareillayout.Ingeneresonofunzionalierispondonoaununicoscopo.Osserviamoalcuniesempi.
Elementiflottanti
GlielementiflottantisonospessoessenzialipercreareunlayoutefficacesulWeb,eBootstrapoffredueclassiperrenderemobiliglielementiasinistraoadestra:
<divclass=”pull-left”>...</div>
<divclass=”pull-right”>...</div>
Perutilizzareefficacementeglielementiflottanti,dobbiamoracchiuderliinunaclasseclearfix.Inquestomodoliisoleremo,eilflussodeldocumentoverràvisualizzatocomeprevisto:
<divclass=”clearfix”>
<divclass=”pull-left”>...</div>
<divclass=”pull-right”>...</div>
</div>
Seleclassifloatsitrovanodirettamenteall’internodiunelementoconlaclasserow,glielementi“mobili”vengonoisolatiautomaticamentedaBootstrapenonènecessarioapplicaremanualmentelaclasseclearfix.
Centrareglielementi
Insiemeconglielementiflottanti,spessoènecessariocentrareglielementialivellodiblocco.Bootstrappermettedifarloconlaclassecenter-block:
<divclass=”center-block”>...</div>
Inquestomodosiimpostanoleproprietàdelmarginesinistroedestrosuautomatico,el’elementosaràcentrato.
Mostrareonascondere
PotrestevolermostrareonascondereglielementiconilCSS;Bootstrapoffredueclassiperottenerequestorisultato:
<divclass=”show”>...</div>
<divclass=”hidden”>...</div>
Èimportanteosservarechelaclasseimpostalaproprietàdisplaysublock;quindiapplicatelasoltantoaglielementialivellodibloccoenonaquelliinlineoinline-block.
Bootstrapincludeinoltrenumeroseclassiperpermettereaglielementidiveniremostrationascostisuschermididimensionispecifiche.LeclassiutilizzanolestessedimensionipredefinitedellagrigliadiBootstrap.
Peresempio,ilcodiceseguentenasconderàunelementosuunoschermocondimensionispecifiche:
<divclass=”hidden-md”></div>
Nasconderàl’elementosudispositiviconschermididimensionimedie,malolasceràancoravisibilesudispositivimobili,tabletegrandidesktop.Pernascondereunelementosupiùdispositivi,doveteutilizzarepiùclassi:
<divclass=”hidden-mdhidden-lg”></div>
Leclassivisiblehannouncomportamentooppostoemostranoglielementisuschermididimensionispecifiche.Tuttavia,adifferenzadelleclassihidden,bisognaimpostareilvaloredisplay,chepuòessereblock,inlineoinline-block:
<divclass=”visible-md-block”></div>
<divclass=”visible-md-inline”></div>
<divclass=”visible-md-inline-block”></div>
Ovviamenteèpossibileutilizzarelediverseclassiinassociazione.Seperesempiovoletechecompaiaunelementoalivellodibloccosuunoschermopiccolo,cheinseguitodevediventareinline-block,dovreteutilizzareilcodiceseguente:
<divclass=”visible-sm-blockvisible-md-inline-block”></div>
Senonricordatelediversedimensionidelleclassi,consultateilparagrafo“LegrigliediBootstrap”.
UtilizzareledirettiveSenzasaperloabbiamogiàutilizzatociòcheAngulardefiniscedirettive.Sitratta
insostanzadipotentifunzionichepossonoesserechiamatedaunattributoodalsuostessoelemento.Angularneincludemolte.Siacheintendiamoeseguireilciclodeidati,gestireiclicoinviareiform,Angularvelocizzeràtuttequesteoperazioni.
AbbiamoutilizzatounadirettivaperinizializzareAngularsullapaginatramiteng-app,etutteledirettivecheesamineremoinquestocapitolovengonousateallostessomodo:aggiungendounattributoaunelemento.
Primadiosservarelealtredirettiveintegrate,ènecessariocrearerapidamenteuncontroller.Createunnuovofileechiamatelocontroller.js.Salvatelonelladirectoryjsall’internodelprogettoeapritelonell’editor.
ComeabbiamovistonelCapitolo1,icontrollersonosemplicifunzionistandarddelcostruttoreJSnellequalièpossibileinserireiservizidiAngularcome$scope.Diquestefunzionivienecreataun’istanzaquandoAngularrileval’attributong-controller.Inquestomodopossiamoaveremoltepliciistanzedellostessocontrollerall’internodell’applicazioneeriutilizzarebuonapartedelcodice.Questadichiarazionedifunzioneètuttociòdicuiabbiamobisognoperilcontroller:
functionAppCtrl(){
}
Percomunicarealframeworkchequestoèilcontrollercheintendiamoutilizzare,dobbiamoincluderlonellapaginadopocheAngularvienecaricato,eaggiungereladirettivang-controlleraltagdiapertura<html>:
<htmlng-controller=”AppCtl”>
…
<scripttype=”text/javascript”
src=”assets/js/controller.js”></script>
ng-clickeng-mouseover
UnadelleoperazionifondamentalicheèpossibileeseguireconJavaScriptconsistenelgestireuneventoclic.Aquestoscoposiutilizzal’attributoonclicksuunelementoricorrendoajQueryoaunlistenerdieventi.InAngularricorreremoaunadirettiva.
Comeesempio,creeremounpulsantecheapriràunafinestradiavviso:un’operazionesemplice.Iniziamoadaggiungereilpulsanteall’areadelcontenutocreatainprecedenza:
<divclass=”col-sm-8”>
<button>ClickMe</button>
</div>
Selovisualizzatenelbrowser,vedreteunpulsanteHTMLstandard:finquinessunasorpresa.Primadiassociareladirettivaaquestoelemento,dobbiamocreareunhandlernelcontroller.Sitrattadiunasemplicefunzioneall’internodelcontrollerassociatoalloscope.Èmoltoimportanteassociarelafunzionealloscope,altrimentisaràimpossibileaccedervidallavista:
functionAppCtl($scope){
$scope.clickHandler=function(){
window.alert(‘Clicked!’);
};
}
Comesappiamo,possiamoaveremoltepliciscopesuunapagina;questisonooggettiaiqualiinAngularpossonoaccederelavistaeilcontroller.Perfareinmodocheilcontrollerviabbiaaccesso,abbiamoinseritoilservizio$scopenelcontroller.QuestoserviziometteadisposizioneloscopecheAngularcreasull’elementoalqualeabbiamoaggiuntol’attributong-controller.
Angularsibasamoltosull’inserimentodelledipendenze,cheforsepotrestenonconoscere.Comeabbiamovisto,Angularèsuddivisoinmodulieservizi.Ciascunodiquestimodulieservizidipendel’unodall’altroel’inserimentodelledipendenzeconsentelatrasparenzareferenziale.Duranteiltestdell’unità,possiamoanchesimularedeglioggetticheverrannoinseritiperconfermareirisultatideitest.L’inserimentodelledipendenzecipermettedidireadAngulardaqualiservizidipendeilcontroller,eilframeworkliconvertiràpernoi.
Potetetrovareunaspiegazionedettagliatasull’inserimentodelledipendenzediAngularJSnelladocumentazioneufficialeall’indirizzohttps://docs.angularjs.org/guide/di.
Oral’handlerèimpostato;comeabbiamofattoinprecedenza,dobbiamoaggiungereladirettivaalpulsante,comeattributoaggiuntivo.Questavoltapasseremoilnomedellafunzionecheintendiamoeseguire,cheinquestocasoèclickHandler.Angularvaluteràtuttociòcheinseriremonelladirettivacomeespressione
diAngularJS;pertantodobbiamofareattenzioneadaggiungeredueparentesicheindicanochestiamochiamandounafunzione:
<buttonng-click=”clickHandler()”>ClickMe</button>
Selovisualizzatenelbrowser,vedreteunafinestradiavvisoquandofareteclicsulpulsante.Nonènecessarioincluderelavariabile$scopequandosichiamalafunzionenellavista.Lefunzionielevariabiliallequalièpossibileaccederedallavistarimangonoall’internodelloscopecorrenteodiqualsiasiscopeprecedente.
Sedesideratevisualizzarelafinestradiavvisoall’hover(cioèalpassaggio)invecechealclic,èsufficientemodificareilnomedelladirettivainng-mouseover,poichéentrambefunzionanonellostessomodo.
ng-init
Ladirettivang-initvalutaun’espressionesulloscopecorrenteepuòessereutilizzatadasolaoinassociazioneconaltredirettive.Laprioritàdiesecuzioneèmassimarispettoadaltredirettive,perfareinmodochel’espressionevengavalutataintempo.
Eccounsempliceesempiodelladirettivang-initinazione:
<divng-init=”test=‘Hello,World’”></div>
{{test}}
InquestomodocompariràsulloschermoHello,Worldquandol’applicazionesaràcaricatanelbrowser.Sopraabbiamoimpostatoilvaloredelmodelloditesteutilizzatolasintassiconledoppieparentesigraffepervisualizzarlo.
ng-showeng-hide
Talvoltadovremocontrollarelavisualizzazioneprogrammatadiunelemento.Siang-showsiang-hidepossonoesserecontrollatedalvalorerestituitodaunafunzioneodaunmodello.
PossiamoavvalercidellafunzioneclickHandlercheabbiamocreatopermostrareinazioneladirettivang-clickalfinedirenderevisibileonasconderel’elemento.Aquestoscopocreeremounnuovomodelloealterneremoilvaloretraveroefalso.
Creiamol’elementochemostreremoenasconderemo.Immetteteilseguentecodicesottoilpulsante:
<divng-hide=”isHidden”>
Clickthebuttonabovetotoggle.
</div>
Ilvalorenell’attributong-hideèilnostromodello.Siccomeècompresonelloscope,possiamofacilmentemodificarlonelcontroller:
$scope.clickHandler=function(){
$scope.isHidden=!$scope.isHidden;
};
Quistiamoinvertendoilvaloredelmodello,cheasuavoltarendevisibileomenoil<div>.
Seloapritenelbrowser,vedretechel’elementoènascostoperimpostazionepredefinita.Esistonoalcunisistemipergestirequestoaspetto.Potremmoimpostareilvaloredi$scope.hiddensutruenelcontroller,oppureimpostareilvaloredihiddensutrueutilizzandoladirettivang-init.Altrimentièpossibilericorrerealladirettivang-showchehauncomportamentocontrariorispettoang-hideerenderàvisibileunelementoseilvalorediunmodelloèimpostatosutrue.
NOTA
Verificate cheAngular sia caricato nell’header, altrimenti ng-hide e ng-show non funzionerannocorrettamente.QuestoperchéAngularutilizzalesueclassipernascondereglielementiequestedevonoesserecaricatealrenderingdellapagina.
ng-ifAngularincludeancheunadirettivang-ifchesicomportainmodosimileang-showe
ng-hide.Tuttaviang-ifeliminal’elementodalDOM,mentreng-showeng-hiderendonovisibiliomenoglielementi.
Osserviamorapidamentecomeèpossibileutilizzareng-ifnelcodiceprecedente:
<divng-if=”isHidden”>
Clickthebuttonabovetotoggle.
</div>
Sevolessimoinvertireilsignificatodell’istruzione,sarebbesufficienteaggiungereunpuntoesclamativoprimadell’espressione:
<divng-if=”!isHidden”>
Clickthebuttonabovetotoggle.
</div>
ng-repeat
Benpresto,durantelosviluppodiunawebapp,avretebisognodirappresentareunarraydiitem.Peresempio,nell’appdigestionedeicontatti,potrebbeessereunelencodicontatti,oqualsiasialtracosa.Angularconsentediraggiungerequestoscopoconladirettivang-repeat.
Vediamounesempiodialcunidatichepotresteincontrare.Sitrattadiunarraydioggetticonmoltepliciproprietàalsuointerno.Pervisualizzareidati,dovremoriuscireadaccedereaogniproprietà.ng-repeatserveaquestoscopo.
Eccoilcontrollerconunarraydioggettidelcontattoassegnatialmodellodeicontatti:
functionAppCtrl($scope){
$scope.contacts=[
{
name:‘JohnDoe’,
phone:‘01234567890’,
email:‘[email protected]’
},
{
name:‘KaranBromwich’,
phone:‘09876543210’,
email:‘[email protected]’
}
];
}
Inquestocasoabbiamosoloduecontatti,macomepoteteimmaginare,un’APIpotrebbeservirnecentinaiachenonsarebbepossibilegestiresenzang-repeat.
Aggiungeteunarraydicontattialcontrollereassegnateloa$scope.contacts.Apriteilfileindex.htmlpercreareuntag<ul>.Ripeteremounavocepresenteinquestoelencononordinato,quindiquestoèl’elementoalqualedobbiamoaggiungereladirettiva:
<ul>
<ling-repeat=”contactincontacts”></li>
</ul>
SesapetecomefunzionanoicicliinPHPoRuby,visentireteavostroagio.Createunavariabileallaqualepoteraccedereall’internodell’elementocorrentecheentranelciclo.Lavariabiledopolaparolachiaveinfariferimentoalmodellocheabbiamocreatosu$scopenelcontroller.Cosìèpossibileaccedereaqualsiasiproprietàimpostatasull’oggetto,eogniiterazioneoitemripetutiacquisisconounnuovoscope.PossiamovisualizzarlisullapaginautilizzandolasintassidelledoppieparentesigraffediAngular,comeabbiamovistonelCapitolo1:
<ul>
<ling-repeat=”contactincontacts”>
{{contact.name}}
</li>
</ul>
Cosìverràvisualizzato,comeprevisto,ilnomeall’internodellavocedell’elencoesaràpossibileaccederefacilmenteaqualsiasiproprietàdell’oggettodelcontattofacendoriferimentoaessotramitelasintassiapuntostandard.
ng-class
Spessocapitadivolermodificareoaggiungereunaclasseaunelemento.Aquestoscopopossiamoricorrerealladirettivang-class,perdefinireunaclassedaaggiungereodaeliminareinbasealvalorediunmodello.
Esistonoduesistemiperutilizzareng-class.Nellasuaformapiùsemplice,AngularapplicheràilvaloredelmodellocomeclasseCSSall’elemento:
<divng-class=”exampleClass”></div>
Seilmodelloalqualefacciamoriferimentoèindefinitoofalso,Angularnonapplicheràlaclasse.Questosistemaèottimoperlesingoleclassi,masevolessimoaveremaggiorecontrollooapplicarepiùclassiaunsoloelemento?Provatequestocodice:
<divng-class=”{className:model,class2:model2}”></div>
Inquestocasol’espressioneèunpo’diversa.Disponiamodiuninsiemedinomidiclassieilmodelloconilqualeconfrontarlo.Seilmodellorestituiscetrue,laclasseverràaggiuntaall’elemento.
Osserviamoloinazione.Utilizzeremolecaselledicontrolloconl’attributong-modelcheabbiamogiàesaminatonelCapitolo1perapplicarealcuneclassiaunparagrafo:
<png-class=”{‘text-center’:center,‘text-danger’:error}”>
Loremipsumdolorsitamet
</p>
HoaggiuntodueclassiBootstraptext-centeretext-danger.Questeesaminanounpaiodimodelli,chepossiamomodificarerapidamenteconalcunecaselledicontrollo:
<label><inputtype=”checkbox”ng-model=”center”>text-
center</label>
<label><inputtype=”checkbox”ng-model=”error”>text-
danger</label>
NOTA
Levirgolettesemplicicheracchiudonoinomidelleclassinell’espressionesononecessariesoloquandosiutilizzanoitrattini(-),altrimentiAngulargeneraunerrore.
Quandoquestecaselledicontrollosonospuntate,leclassiopportunevengonoapplicateall’elemento.
ng-style
Inmodosimileang-class,questadirettivaciconsentediassegnaredinamicamenteunostileaunelementoconAngular.Atitolodiesempiocreeremounaterzacaselladicontrollocheapplicheràaltristiliall’elementoparagrafo.
Ladirettivang-styleutilizzaunoggettoJavaScriptstandard,eleparolechiavesarannolaproprietàcheintendiamomodificare(peresempiocoloreesfondo).Questosipuòapplicaredaunmodelloounvalorerestituitodaunafunzione.
Esaminiamocomeassociarloaunafunzionecheverificheràunmodello.Poipossiamoaggiungerloallacaselladicontrollopercambiareglistili.
Apriteilfilecontroller.jsecreateunanuovafunzioneassociataalloscope.QuilachiameremostyleDemo:
$scope.styleDemo=function(){
if(!$scope.styler){
return;
}
return{
background:‘red’,
fontWeight:‘bold’
};
};
All’internodellafunzionedobbiamoverificareilvalorediunmodello;inquestoesempiosichiamastyler.Seèfalso,nonrestituirànulla,altrimentirestituiràunoggettoconleproprietàCSS.AbbiamoutilizzatofontWeightinvecedifont-weightnell’oggettorestituito.EntrambivannobeneeAngularapplicheràautomaticamentelasintassicamelcasenellacorrettaproprietàCSS.Ricordatechequandosiutilizzano
itrattininelleparolechiavedeglioggettiJavaScript,questevannoracchiusetravirgolette.
Questomodellosaràassociatoaunacaselladicontrollo,comeabbiamofattoconng-class:
<label><inputtype=”checkbox”ng-model=”styler”>ng-style</label>
L’ultimacosachedobbiamofareèaggiungereladirettivang-styleall’elementodelparagrafo:
<p..ng-style=”styleDemo()”>
Loremipsumdolorsitamet
</p>
Angularèingradodirichiamarequestafunzioneognivoltachecambialoscope.Ciòsignificachenonappenailvaloredelmodellocambiadafalseatrue,verrannoapplicatiglistilieviceversa.
ng-cloak
L’ultimadirettivacheesamineremoèng-cloak.QuandoutilizzateitemplatediAngularinunapaginaHTML,vengonovisualizzatetemporaneamenteledoppieparentesigraffeprimacheAngularJSabbiafinitodicaricareecompilaretuttosullapagina.Perovviareaquestocomportamento,dobbiamonasconderetemporaneamenteiltemplateprimacheterminiilrendering.
Angularconsentedifarloconladirettivang-cloak,cheimpostaunulteriorestilesull’elementomentrevienecaricato,ovverodisplay:none!important;.
NOTA
Per evitare il flashing durante il caricamento del contenuto, è importante che Angular vengacaricatonellasezioneheaddellapaginaHTML.
Quiz1. Checosaaggiungiamoincimaallapaginaperpoterpassaredaunavista
all’altra?2. QuantecolonnecomprendeilsistemaagrigliadiBootstrap?3. Checos’èunadirettivaecomevieneutilizzatalamaggiorpartediesse?4. Qualedirettivadobbiamoutilizzarepereseguireilciclodeidati?
RiepilogoInquestocapitoloabbiamoaffrontatomoltiargomenti;primadiproseguireconil
prossimo,riepiloghiamoli.
Bootstrapciconsentedicrearerapidamenteunanavigazioneresponsive.DobbiamoincludereilfileJavaScriptpresentenellaversionediBootstrapcheabbiamoscaricatoperrenderedisponibileilpulsanteinterruttoreperlanavigazionemobile.
AbbiamoancheesaminatoilpotentesistemaagrigliaresponsiveinclusoinBootstrapecreatounsemplicelayoutaduecolonne.Durantequestaoperazioneabbiamoanalizzatoiquattrodiversiprefissidelleclassiperlecolonneenidificatolagriglia.Peradattareillayoutabbiamoscopertoalcuneclassihelperinclusenelframeworkcheciconsentonodirenderemobili,centrareenascondereglielementi.
AbbiamoesaminatoindettaglioledirettiveintegrateinAngular,lefunzionicheèpossibileutilizzaredallavista.Primadiosservarleinazione,abbiamocreatouncontroller,ossiaunafunzioneincuipossiamopassareiservizidiAngularsfruttandol’inserimentodelledipendenze.
Ledirettivecheabbiamodescrittosarannofondamentalimanmanochesvilupperemol’appdigestionedeicontattinelcorsodellibro.Direttivecomeng-clickeng-mouseoversonoinsostanzanuovisistemipergestireglieventi,operazionecheavetesicuramentesvoltoconjQueryovanillaJavaScript,madirettivecomeng-repeatrappresenterannoforseunmodotuttonuovodilavorare,cheintrodurràunalogicanellavistapereseguireilciclodeidatievisualizzarlisullapagina.
Abbiamoancheanalizzatoledirettivechevalutanoimodellinelloscopeecompionodiverseazionisullabasedeilorovalori.ng-showeng-hidemostranoonascondonounelementosullabasedelvalorediunmodello.Loabbiamovistoancheconng-class,checihapermessodiaggiungeredelleclassiaglielementisullabasedeivalorideimodelli.
Capitolo3
Ifiltri
NelcapitoloprecedenteabbiamoesaminatounodeicomponentifondamentalidiAngularJS:ledirettive.Comemoltiframework,Angularoffrealtriparadigmicheciaiutanoasviluppareleapp.Ifiltripermettonodimanipolareeordinarefacilmenteidatidallavistaodalcontroller,ecomeperledirettive,neesistonoalcuniefficacigiàintegrati.
Sipossonoapplicareinmolticasieinquestocapitoloneesamineremoalcuni.Peresempio,èpossibilemanipolareunastringa,convertendola,localizzandolaotroncandola.IfiltriconsentonoanchedioperareconaltritipiJavaScript,comearrayeoggetti.Forsevicapiteràdiimpostareunaricercaperfiltrareunsetdidatidicuiaveteeseguitoilciclousandong-repeat.Tuttoquestoèpossibileconifiltri.
Primadiosservarealcunifiltriinclusi,esamineremocomeèpossibileapplicareunfiltrodallavista.
ApplicareunfiltrodallavistaÈpossibileapplicareifiltridirettamentealleespressioniall’internodeitemplate.
Ricordatecheun’espressioneètuttociòcheècompresoall’internodellasintassicondoppieparentesigraffeodiunadirettiva:
{{expression|filter}}
Èfacileservirsidiunfiltro;èsufficienteaggiungereilsimbolopipe(|)seguitodalnomedelfiltrocheintendiamoapplicareall’espressione.Possiamoprocedereallostessomodoperapplicarepiùfiltriaunasolaespressione.Èpossibileconcatenarnepiùdiunoeapplicarliinsequenza.Nell’esempioseguente,filter2verràapplicatoall’outputdelfilter1ecosìvia:
{{expression|filter1|filter2|filter3}}
Alcunifiltripossonoaveredegliargomenti,chesipossonoapplicarericorrendoaunasintassisimile:
{{expression|filter:argument1:argument2}}
InquestocapitoloillustreremoalcunifiltriinclusiinAngulardirettamentedallavistautilizzandolasintassicheabbiamoesaminato.Vedremocomeapplicareglistessifiltridalcontrollerecomecrearneuno.
CurrencyenumberIlprimofiltrocheanalizzeremoformattainumeriinvaluta.Nellaversione
localizzatabritannica-americana,aggiungealpostogiustounavirgolapersepararelemigliaiadaidecimali.Lifaancheprecederedalsimboloopportuno:
{{12345|currency}}
Ilsimbolodellavalutadipenderàdallaversionelocalizzata.Seutilizziamoquellabritannica-americana,perimpostazionepredefinita,Angularanteponeilsimbolodeldollaro($),mapossiamopassareilsimbolodanoisceltocomeargomento:
{{12345|currency:’£’}}
Èimportantericordarsidiracchiuderlotravirgolette,comesefosseunastringa.
Angularincludeancheunsecondofiltroperformattareinumeri,checioffremaggiorecontrollo;ciconsentedispecificareilnumerodicifredecimaliacuivogliamoarrotondareilnumero:
{{12345.225|number:2}}
L’outputdiquestofiltrosarà12,345.23.Comepoteteosservare,ilnumeroèstatoarrotondatoaduedecimaliedèstataaggiuntaunavirgolapersepararelemigliaia.
LowercaseeuppercaseQuestiduefiltrisonoforseipiùsempliciinclusiinAngular.Convertonolastringa
inminuscoloomaiuscolo:
{{‘Stephen’|lowercase}}
{{‘Declan’|uppercase}}
L’outputdiquestifiltrièilseguente:
stephen
DECLAN
limitToInalcunicasipotrestevolerlimitareilnumerodicaratteridiunastringaodiun
array;questorisultatosipuòottenerefacilmenteinAngularJSutilizzandoilfiltrolimitTo:
{{‘Loremipsumdolorsitamet’|limitTo:15}}
Questofiltroaccettaunsoloargomento,cheèilnumerodicaratterialqualedovrebbelimitarsil’input.Inquestocasoabbiamolimitatoicaratteridiunastringa,mapotrebbetrattarsidiunarrayinunadirettivang-repeat,peresempio:
<divng-repeat=”array|limitTo:2”></div>
DateQuandosioperaconidatidaunaAPI,spessoladatavienefornitacomeoraUNIX
ocometimestampcompleto.Nonèmoltocomodo,mafortunatamenteAngularincludeunsistemasempliceperformattareledateconunfiltro:
{{expression|date:format}}
Questofiltroaccettaunargomento:format.Peresempio,seabbiamountimestampeintendiamoottenerel’anno,potremmoraggiungerefacilmentequestorisultatoconl’espressioneseguente:
{{725508723000|date:’yyyy’}}
Possiamocombinarloconl’inputgiornomeseeavremofacilmenteunastringadidatastandard:
{{725508723000|date:’dd/MM/yyyy’}}
Eccounelencodialcunideglielementipiùutilidacuipuòesserecostituitalastringaformat.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;esaminiamoneuno:
{{725508723000|date:’medium’}}
Laparolachiavemediumèsolounodeiformatididefaultchequestofiltroriconosce,egeneraDec28,19922:12:03AM.
Eccounelencocompletodeiformatipredefinitiaccettatidalfiltrodate.
Parolachiave Equivalente Esempiomedium MMMd,yh:mm:ssa 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.Dobbiamoquindicambiarelevirgolettesemplicicheracchiudonol’argomentoconlevirgolettedoppie.Sevolesteincluderenellastringaunasolavirgoletta,dovresteutilizzareduevirgolettesemplici:
{{725508723000|date:”h‘o’’clock’”}}
FilterQuestofiltro,conunnomechepuògenerareconfusione,consentediselezionare
facilmenteunsetsecondariodiiteminunarray.All’internodellavistaèpossibileutilizzarlocombinatoconladirettivang-repeatcheabbiamoillustratonelcapitoloprecedente.
Conessopossiamosviluppareunostrumentodiricercapotentechefiltreràl’array.Esaminiamol’esempiong-repeatdelCapitolo2:
<ul>
<ling-repeat=”contactincontacts”>
{{contact.name}}-{{contact.phone}}
</li>
</ul>
Primadiaggiungereilfiltro,dobbiamoaggiungerel’oggettopatternchesaràutilizzatoperlaselezionedall’array.Puòessereunmodello,unastringa,unoggettopatternounafunzione.Mentrecreiamounaricerca,aggiungiamounmodelloauninputditesto:
<inputtype=”text”ng-model=”search”>
Nonrestacheassociareilmodelloalladirettivang-repeat.Lopotetefarecomeconqualsiasialtrofiltro:inseriteunpipeseguitodalnomedelfiltro.Inquestocasodobbiamoancheaggiungereunargomentocheindicaalfiltroqualemodello,stringa,oggettoofunzioneintendiamoutilizzare:
<ling-repeat=”contactincontacts|filter:search”>
Cosìpossiamoservircidelcampoditestochecreiamopereffettuarequalsiasiricercanell’array,checomprendenomi,numeriditelefonoeindirizzie-mail.Tuttavia,checosasuccedeseintendiamorestringerelaricercasoltantoallaproprietànamedeinostrioggetti?Èsufficientecambiareilmodello:
<inputtype=”text”ng-model=”search.name”>
Èimportantechelasciamoilnomedelmodelloding-repeatsusearch,altrimentiilfiltrononsilimiteràallaproprietàdesiderata.
Inalternativapotremmoutilizzarelasintassiseguenteperladirettivang-repeatperlimitareilfiltroaproprietàspecifiche.Inquestocasopossiamolasciareilnomedelmodellosusearch:
<ling-repeat=”contactincontacts|filter:{‘name’:search}”>
orderByOltreafiltrarel’oggettoall’internodelladirettivang-repeat,possiamoanche
ordinarlo.Èun’ottimasoluzioneseidatifornitidaunarraynonsonogiàordinatiosenonesisteun’opzioneperfarlo.
Attualmentel’oggettoètuttoconfusoenonpresentaalcunordineapparente.Osserviamocomepossiamoordinarlopernome:
<ling-repeat=”contactincontacts|filter:search|
orderBy:’name’”>
Ilprimoargomentochepossiamopassareèunastringaconilnomedellaproprietàinbaseallaqualeintendiamoordinarel’array.Sevolessimofiltraresecondoilnumeroditelefonool’indirizzoe-mail,potremmopassarequestivalori.
Possiamoanchepassareunsecondoargomentoattraversounbooleanochecontrollaseilfiltrodeveinvertirel’ordineomeno:
<ling-repeat=”..|orderBy:’name’:true”>
JSONL’ultimofiltroinclusoservesoprattuttoperildebugging.Trasformeràqualsiasi
oggettoJavaScriptinunastringaJSONperlavisualizzazionesullapagina.
Prendiamol’arraydicontatticheabbiamoutilizzatonelcapitoloprecedentepermostrareinazioneng-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’arraydioggetticreatoinprecedenza.
ApplicareifiltridaJavaScriptTalvoltavorreteapplicareunfiltroutilizzandoJavaScript,disolitodalcontroller;è
importantecapirecomepossiamoottenerequestorisultato,tramitedueopzioni.
Possiamoinserireilservizio$filternelcontrollereutilizzarequalsiasifiltroinclusonell’applicazione.Inalternativapossiamoinserireilfiltrocomesuoserviziodedicatoeutilizzarlodasolo.Entrambiimetodisonoperfettamentevalidiespettaavoisceglierequellochepreferite.
Esaminiamoliricorrendoinnanzituttoalservizio$filter.Consideriamoilfiltrojsoncheabbiamoappenavistoeutilizziamoconsole.logsullostessoarray.Inseriamoilservizionelcontroller:
functionAppCtl($scope,$filter){
…
}
Ottimo!Orapossiamoutilizzarlocosìcomefacciamocon$scope.Aquestofineèsufficientechiamarlocomeunafunzioneepassareilnomedelfiltrochedesideriamousare,chenelnostrocasoèjson:
$filter(‘json’);
Lastringarestituisceilfiltrostesso;possiamovederlonell’outputseapplichiamodirettamentelasintassiconsole.log.Ciòsignificachepossiamochiamareimmediatamentelafunzioneaggiungendounasecondaseriediparentesisubitodopo:
$filter(‘json’)($scope.contacts);
Comesappiamo,ilfiltrojsonnonaccettaargomenti.Tuttaviailprimoargomentodituttelefunzionideifiltrièinrealtàl’input.NonlovediamoquandolechiamiamodallavistapoichéAngularoperalasuamagiadietrolequintepersemplificarelecose.
Seracchiudetel’espressioneprecedenteinunaconsole.log,vedretechel’outputèidenticoaquellonellavistautilizzandolostessofiltro:
Inalternativa,senonvoletericorrerealservizio$filter,poteteinserireognifiltroseparatamentecomeunservizio.NelpatternvengonochiamatifilternameFilter.Perilnostroesempio,dobbiamoinserirejsonFilter:
functionAppCtl($scope,jsonFilter){
…
}
Puòessereutilizzatoallostessomododellafunzionerestituitadalservizio$filter,cosìdapermettercidipassarel’oggettodafiltrare:
jsonFilter($scope.contacts);
Orachesappiamocomeutilizzareifiltridalcontroller,vediamocomecrearneuno.
SviluppareunfiltroComeabbiamovisto,ilfiltrolimitToèutilissimopertroncarestringheditesto.Ho
sempresentitolanecessitàdiunfiltrocheaggiungessedeipuntinidisospensionenelcasoincuiunastringasuperasseillimite.
FortunatamenteAngularciconsentediestendereifiltriinclusiesvilupparneunonuovo.
ModuliAquestoscopodobbiamocreareciòchevienechiamatomodulo.Essociconsente
diassociareunfiltroeutilizzarlonellevisteoneicontroller.LadocumentazionediAngularJSspiegaperfettamentecos’èunmodulo:
Sipuòpensareaunmodulocomeauncontenitoreperlediversepartidiun’app:controller,servizi,filtri,direttiveecosìvia.
Maperchédovremmoutilizzarneuno?Cisonounpaiodimotivi.Laragionepiùimportantechegiustificalaloroesistenzaèlafacilitàconcuièpossibilecrearecodiceriutilizzabile.
Immaginatedistarlavorandosuunapiattaformadiblogging.Potrestesviluppareunmodulodestinatoaunmediabrowseroaunmediauploader.Conterrebbeunaseriedicontroller,serviziefiltri,tuttiinsieme.Sevolesteutilizzarequestomediabrowserinunaltroprogetto,potrestesemplicementecopiareilmodulo.
Esistonoaltrimotiviperutilizzareimoduli.Sesicollaudal’unità,èsufficientecheitestcarichinosoloimodulipertinentiperassicurarelarapiditàdell’operazione,eilcodicediventapiùsemplicedacomprendereeseguireperchéognicomponenteèbeninseritotraglialtrielementi.
Creareunmodulo
Èmoltofacilecreareunmodulo;ciconsentediampliareilcoreperchéAngularnonammetteifiltripersonalizzatisenzalacreazionediunmodulo.InquestocasorealizziamounnuovomodulochiamatocontactsMgr.Ilsecondoargomentoèunsemplicearrayvuoto.Possonoesserciquantimodulidesideriamoelipossiamoincluderecomedipendenze,maperilmomentoquestololasceremovuoto:
angular.module(‘contactsMgr’,[]);
Tuttaviaènecessarioinserireunapiccolamodificanelmodoincuivieneaggiuntoilcontroller.Attualmenteèsolounafunzione,maènecessarioaggiungerloalmoduloperchéAngularlopossarecuperare:
angular.module(‘contactsMgr’,[])
.controller(‘AppCtl’,function($scope,jsonFilter){
…
});
Possiamoconcatenareicontrollernelmodulo.Avretenotatocheènecessarioutilizzareilmetodocontroller.Ilprimoargomentoèilnomedelcontroller,mentreilsecondoèlafunzionedicallbackconiserviziinseriti.
Seoracaricatel’app,vedretechenullafunzionacomeprevistoenonvienetrovatalafunzionedelcontroller.QuestoperchénonabbiamoindicatoadAngularqualemodulodesideriamoutilizzare.Ènecessarioaggiungereilnomedelmoduloalladirettivang-app:
<htmllang=”en”ng-app=”contactsMgr”ng-controller=”AppCtl”>
Unavoltainserito,tuttodovrebbeiniziareafunzionareperilversogiusto.Orastiamoutilizzandoilmoduloappenacreato.
CreareunfiltroDopoavercreatoilmoduloeaverlovistoinazione,possiamolavoraresulfiltro
limitTomigliorandolo.Convienepensareinnanzituttoalleoperazionichevorremosvolgesse.Possiamosuddividerelefunzioniinpochibrevipassi.
Consideral’inputconunsoloargomentoperillimite.Verificalalunghezzadell’inputrispettoallimite.Sel’inputèmaggioredellimite,troncalastringaeaggiungedeipuntinidisospensione.Altrimentirestituiscel’input.
Operandoconimodelli,lacreazionediunfiltrorisultamoltosimileallacreazionediuncontroller:
.filter(‘truncate’,function(){
});
Propriocomeabbiamofattoquandoabbiamospostatoilcontrollersuunnuovomodulo,utilizziamounnuovometodocheaccettadueargomenti:ilnomedelfiltroe
unafunzionedicallback.Comevistoquandoabbiamoapplicatoifiltridalcontroller,quandosichiamaunfiltro,essorestituisceunasecondafunzione,chedobbiamoaggiungerequi:
.filter(‘truncate’,function(){
returnfunction(){
};
})
Abbiamoanchescopertocheilprimoargomentodiunfiltroèsemprel’inputoidatichefiltreremo.All’internodiquestafunzionepossiamoancheincludereargomentiaggiuntivi.Nelcasoditruncate,occorreunargomentoperindicarealfiltroaquanticaratteridovrebbelimitarelastringa:
.filter(‘truncate’,function(){
returnfunction(input,limit){
};
})
Lacostruzionedelfiltroècompletaeorapossiamoutilizzarlonellostessomododeglialtrifiltriesaminatiinprecedenza.Ovviamentenonabbiamoimpostatoalcunalogicaenonvienerestituitonulladallafunzionefiltro;pertantononverràvisualizzatonullasullapagina.
Oradobbiamoverificarelalunghezza,troncarelastringaeaggiungeredeipuntinidisospensione.Sipossonoottenerequestirisultatiinunastringagrazieaun’istruzioneternaria:
return(input.length>limit)?input.substr(0,limit)+’…’:
input;
Verifichiamolalunghezzadellastringa,eseèsuperioreallimite,latagliamoeaggiungiamodeipuntinidisospensione.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,mentreinprecedenzalastringaeratroncatadopoillimite.
Quiz1. Comesipuòapplicareunfiltrodallavista?2. Inchemodopassiamogliargomentialfiltrodallavista?3. Qualefiltrodovremmoutilizzarepercreareunalivesearch?4. Comepossiamoutilizzareunfiltrodalcontroller?5. Checosadobbiamocreareprimadisviluppareunfiltro?
RiepilogoOradovresteconoscerelafunzioneelagrandeutilitàdeifiltri,mariepiloghiamo
tuttociòcheabbiamoaffrontatoinquestocapitolo.
Abbiamoiniziatoaosservarecomevieneapplicatounfiltrodirettamentedallavistautilizzandolasintassicheprevedel’usodelpipeelaseparazionedegliargomenticonduepunti.Dopoaveranalizzatolebasi,abbiamoesaminatoinumerosifiltriinclusi.
Alcunisonofondamentalienonrichiedonoalcunargomento,macenesonoanchedipiùavanzaticheconsentonodiordinareofiltrareunarraydioggetti.
Oltreadapplicareifiltridallavista,abbiamoancheconsideratoiduemetodiperfiltraredalcontroller.Possiamoutilizzareilservizioincluso$filterodeciderediinserireifiltriseparatamente.
InfineabbiamovistocomeestendereAngularpercreareunfiltroalfineditagliareunastringaditesto.Primaperòabbiamovistocomecreareunmodulochecontenesseifiltrieicontroller.Dopoaverlorealizzato,siamoriuscitiacreareunfiltroeautilizzarlonellostessomododiquelliintegrati.
AbbiamoaffrontatomoltiparadigmieconcettifondamentalidiAngular.Nelprossimocapitolovedremocomeimpostareilsistemadiroutingpergestiremolteplicivisteecontrollerperl’appdigestionedeicontatti.
Capitolo4
Routing
Tuttelewebappsonocostituitedapiùpagineoviste,eAngularhatuttelecarteinregolapergestirequestoaspettoconilrouter(intesocomesistemadirouting,enoncomedispositivofisico).Forseconosceteilsistemadiroutingneiframeworklatoserver,comeRubyonRailsoLaravel.Angularèinteramentelatoclient,etuttalamagiaaccadeall’internodelfileHTMLalqualepuntailbrowser.Inquestocapitoloesamineremocomecreareroutestatichecontenentideiparametri.Individueremoinoltrealcuneinsidienellequalipotresteimbattervi.
Primadiiniziareelenchiamoleroutecheoccorrerannoperl’appdigestionedeicontatti.
Index:saràlapaginaprincipale,cheelencheràtuttiicontattiinunatabella.ViewContact:quipotremovisualizzareilcontattoneldettaglioemodificarequalunqueinformazionecorrelata.AddContact:includeràunformcheciconsentiràdiaggiungereuncontattoalgestore.
Questesonoleroutefondamentali;vediamocomecrearle.
InstallarengRouteFindaAngularJS1.2,ilroutersièpresentatocomeunmoduloseparatoedesterno
rispettoaicomponentifondamentalidiAngular.Ilfilechecioccorre,angular-route.min.js,puòesserescaricatodalsitowebdiAngularnellasezioneExtrasall’internodellafinestradidownload.
Dopoaverloscaricato,trascinatelonelladirectoryjsdelprogettoeincludetelonellapaginadopoAngularJS:
<scriptsrc=”assets/js/angular-route.min.js”></script>
Inoltreènecessariocomunicarealmodulocheintendiamoutilizzareilrouter;dobbiamoquindiaggiungerloallalistadelledipendenzedelmodulo.Possonoesserepresentiquantedipendenzedesideriamo;peroraèsufficienteincluderengRoute:
angular.module(‘contactsMgr’,[‘ngRoute’])
CreareroutedibaseComesappiamo,perconfigurareilrouterall’internodiAngularJS,ènecessarioun
modulo.NelCapitolo3neabbiamocreatounoalfinedisviluppareunfiltropersonalizzato.Possiamoutilizzarequestostessomoduloperleroute.
Leroutevengonocreatenelmetodoconfigdelmodulodell’applicazione:
angular.module(‘contactsMgr’,[‘ngRoute’])
.config(function($routeProvider){
})
Ilmetodoaccettaunafunzioneanonimaincuiinserireilservizio$routeProvidernecessario,chepresentasoltantoduemetodi:wheneotherwise.Peraggiungereunaroute,utilizziamoilmetodowhen,cheaccettadueparametri:ilpercorsodellastringaeleopzionidellarouteinquantooggetto:
angular.module(‘contactsMgr’,[‘ngRoute’])
.config(function($routeProvider){
$routeProvider.when(‘/’,{});
})
Nell’oggettodelleopzionidellaroutesonopresentidueproprietàcheciinteressano:controlleretemplateUrl.Laproprietàcontrollerchiamauncostruttorecontrolleresistenteonedefinisceunonuovotramiteunafunzioneanonima.LaproprietàtemplateUrlconsentedidefinireilpercorsoaunfileHTMLcheospiteràl’interomarkupdellavista.Inalternativapotremmodefinireiltemplatedirettamenteall’internodell’oggettoroute.Tuttavia,cosìfacendo,lasituazionesipotrebbebenprestocomplicare;questasoluzioneèconsigliabilesolopertemplatecontenentiunaoduerighe.
Esaminiamolaroutechedefiniremoperlapaginaindex:
$routeProvider.when(‘/’,{
controller:‘indexCtl’,
templateUrl:‘assets/partials/index.html’
});
IlpercorsodeltemplateèrelativoalfileHTMLdibase,quindiincludeladirectoryassets.PossiamoprocedereecreareiltemplateHTML.InAngularquestielementivengonochiamatipartialeliutilizzeremopertutteleviste.
L’argomentocontrollerall’internodellarouteèfacoltativo,mal’abbiamoinclusoperchéciserviràperl’applicazione.Creiamoilcontrollercheciconsentedisvilupparemodelliefunzionisoltantoperlavistaindex.
Nelfilecontroller.jspossiamoconcatenarloallafine:
.controller(‘indexCtrl’,function($scope){
});
Aggiungiamolasecondarouteconilmetodoconfig.Ospiteràilformperaggiungereicontatti:
$routeProvider.when(‘/’,{
controller:‘indexCtl’,
templateUrl:‘assets/partials/index.html’
})
.when(‘/add-contact’,{
controller:‘addCtl’,
templateUrl:‘assets/partials/add.html’
});
Comenelcasodeicontroller,possiamoconcatenareleroute.Noncirestachecreareicontrollereipartialopportuni:
.controller(‘addCtl’,function($scope){
});
L’ultimaoperazionecheènecessariosvolgereprimacheAngularrendaoperativoilrouterconsistenell’includerenellapaginaladirettivang-view,cheinserisceilpartialdefinitonellaroute:
<divclass=”container”>
<ng-view></ng-view>
</div>
NOTA
Èpossibileincludereng-viewsoltantounavoltaperpagina.
Sipuòincluderequestadirettivacomesuoelemento.Quiabbiamopreferitoincluderlacomeunelementonelfileindex.htmlall’internodellaradice.Seèpresentequalcosanelcontenitore,cancellateloesostituiteloconng-view.
Seapriteilprogettonelbrowser,vedretechelarouteèstataaggiuntaall’URLedèprecedutadalsimbolo#.PurtropposeusereteChrome,probabilmenteipartialnonriuscirannoacaricarsi.Seapritelaconsole,vedreteunerroresimilealseguente:
CrossoriginrequestsareonlysupportedforHTTP.
Esistonoduesoluzioni.Possiamocaricareilcodicesuunserverweboppure,conChrome,possiamoavviareilbrowserutilizzandounflagperconsentirerichiestecross-originperilprotocollofile://inambienteOSXoperc:/inambienteWindows.
InOSX,eseguitequestocomandonelterminale:
open-a‘GoogleChrome’--args-allow-file-access-from-files
InaltrisistemibasatisuUnixeseguiteilseguente:
google-chrome--allow-file-access-from-files
InWindows,ènecessariomodificareilcollegamentosuldesktopperaggiungereunflagallafinedelladestinazione:
C:\...\Application\chrome.exe--allow-file-access-from-files
SenonvoleteeseguireChromeconunflag,potetefargirarel’appdigestionedeicontattisuunserverweb.PotresteservirvidiquellointegratoinPythonoPHP,odiun’appveraepropriacomeMAMPoWAMP.
Modificateladirectorydelprogettoedeseguiteilseguentecomandopertestarel’applicazionesuunserverwebPython:
python-mSimpleHTTPServer8000
Poteteaccederealocalhost:8000nelbrowserpervisualizzarel’app.Inalternativa,sepreferiteeseguireunserverwebintegratoinPHP,potetefarlonelseguentemodo:
php-Slocalhost:8000
RouteconparametriDopoaverimpostatopiùroute,dobbiamocapirecomeincludereiparametrialloro
interno.Questoèunaspettoimportanteperconsentireuncertodinamismoall’internodell’appdigestionedeicontatti;peresempio,liutilizzeremopervisualizzareuncontattospecificofacendoriferimentoaunIDnumericooaunindice.
Èfacileinserirli;èsufficienteaggiungereunsegnapostoconduepuntiseguitidalnomedelparametrocheintendiamocreare.Osserviamolaroutecherealizzeremopervisualizzareilcontatto.Possiamodinuovoconcatenarlaalleroutegiàesistenti:
.when(‘contact/:id’,{
controller:‘contactCtl’,
templateUrl:‘assets/partials/contact.html’
});
Possiamoaggiungerefacilmentenelcontrollerquantiparametridesideriamo.Saràsufficienteinserireunservizioepotremoaccedereatuttiiparametridellaroutecomeoggetti:
.controller(‘contactCtl’,function($scope,$routeParams){
console.log($routeParams);
});
Seaccedetealocalhost:8000/#/contact/1eapritelaconsole,vedreteiparametridellarouteregistraticomeoggettoJS.
Ciòsignificachepossiamoaccedereaqualsiasiproprietàdell’oggettotramitelasintassistandard:
$routeParams.id;
RoutedifallbackL’ultimaroutedaconfigurareèquellacheverràvisualizzataquandononsitrova
nessunaroutecorrispondente.Potrestecreareunapagina404,mavediamocomeèpossibilereindirizzareunarouteinvecedivisualizzareuntemplate.
Percreareunaroutedifallback,utilizziamoilsecondometodochecioffreilservizio$routeProvider:otherwise:
.otherwise({
redirectTo:‘/’
});
Inquestomodo,selarouterichiestanoncorrispondeanessunadiquelledefinitenelrouter,Angularciriportaallapaginadell’index.
RoutinginHTML5oeliminazionedelsimbolo#
Oraabbiamoconfiguratotutteleroutefondamentaliepossiamoaccedereapartialdistintiperognuna.Èunottimorisultato,ancheseleroutecheseguonoilsimbolo#nell’URLnoncipiacciono.Fortunatamenteesisteunsistemafacileperrimediare,attivandociòcheAngulardefiniscehtml5Mode.
QuestamodalitàconsenteadAngulardisfruttareilpushStateneibrowsermodernifornendoalcontenutounfallbackperibrowserlegacy,comeIE8.
AbilitareHTML5ModePerconsentirequestanuovamodalità,riesaminiamoilmetodoconfig.Come
abbiamofattoinprecedenza,dobbiamoinserirviunservizio:
.config(function($routeProvider,$locationProvider){
...
$locationProvider.html5Mode(true);
})
Oraabbiamoinseritounsecondoservizio:$locationProvider,checiconsentedisfruttareilmetodohtml5Mode,cheaccettaunvalorebooleanoperattivarloodisattivarlo.
Ilserviziooffreunsecondometodo,eanchesenonneapprofitteremonelcorsodellosviluppodell’app,èopportunoconoscerlo.IlmetodohashPrefixpermettediaggiungerenell’URLunprefissodopoilsimbolo#.Peresempio,potremmoaggiungereunpuntoesclamativoetrasformareilprefissoinunhashbang(#!):
$locationProvider.hashPrefix(‘!’);
Laprossimafiguramostral’URLdell’applicazioneesuddividel’indirizzonellediversesezionidellaroute.
CollegarelerouteCollegareleroutenonèdiversodacollegarelepaginesuunsitoweb.Utilizziamo
untagàncorae,invecedicollegarelapagina,colleghiamolaroute.
Peresempio,sevolessimocollegareilpulsanteAddContactnellabarradinavigazione,dovremmoimmetterequestocodice:
<ahref=”/add-contact”>AddContact</a>
Alclicsullink,Angularvisualizzerebbeautomaticamenteilpartialcorrettoecambierebbeanchel’URL.Seavetedecisodinonutilizzarehtml5Mode,potetecomunqueeffettuareilcollegamentomedianteun’àncora,mal’attributohrefsaràleggermentediverso,perchéènecessarioaggiungereilsimbolodelcancelletto:
<ahref=”#/add-contact”>AddContact</a>
Quiz1. Qualefileomodulodobbiamoincludereperconsentireilrouting?2. Qualemetodoutilizziamopercreareleroute?3. Checosadobbiamoinserirenelmetodoperriuscireacreareunaroute?4. Inchemodocreiamounaroute?5. Checosapossiamoutilizzarequandonessunaroutecorrispondealpercorso
corrente?6. Inchemodopossiamoeliminareilsimbolo#nell’URL?
RiepilogoInquestocapitoloabbiamotrasformatol’applicazione,edaun’appconun’unica
paginasiamopassatiaunamultipaginaemultiroutenellaqualesvilupperemoilnostrogestoredicontatti.Abbiamoiniziatoaprogettareleroutefondamentalidell’appprimadiinstallareilmodulonecessario.
Abbiamovistocomeèpossibileutilizzarenelmoduloilmetodoconfigperimpostareleroute,eperfarloabbiamoinseritoilservizio$routeProvidereutilizzatoimetodiwheneother.Cosìabbiamoimpostatoroutestaticheedinamichecontenentiparametri.
Infineabbiamoanalizzatocomeeliminareilsimbolo#dall’URLmedianteilpushStatediHTML5ecollegareentrambiitipidiroute.NelprossimocapitolopopoleremoipartialconilayoutchesvilupperemograzieaBootstrap.
Capitolo5
Leviste
NelCapitolo4abbiamovistocomeèpossibiletrasformarel’applicazioneinunawebappconpiùrouteepiùviste.AbbiamosfruttatoilrouterdiAngulareimpostatoipartialpertuttelevistefondamentali.OraèilmomentodicrearelevistemedianteBootstrapcosìdapoterpopolarel’appconidati.Analizziamounoperunoipartial.
PopolarelavistaIndexLavistaIndexèciòchevienevisualizzatoquandoapriamol’app.Forseconviene
elencarequituttiicontatti;infattiavremobisognodiunrapidoaccessoalleinformazionimemorizzate.
Unatabellapuòessereun’opzioneefficace,maènecessariorifletteresuciòcheverràmemorizzatonelgestoredeicontatti.Eccounalistadeipossibiliitem:
NomeIndirizzoe-mailNumeroditelefonoIndirizzoSitowebNoteGravatar(unserviziodiavatarriconosciutoalivellomondialesviluppatodaicreatoridiWordPress)
NonoccorrevisualizzaretuttequesteinformazioninellavistaIndex.Nondimenticatechesaràanchepresentel’opzioneperscorrereicontattiequindiavremolapossibilitàdivisualizzarepiùinformazioni.
Unasceltaragionevolesembraquelladivisualizzarenome,indirizzoe-mailenumeroditelefonoinunatabellaconunlinksucuifareclic.
ApriteilpartialdellavistaIndex;sitrovainassets/partials/index.html.Peroraquestofileèvuoto;aggiungeteall’iniziol’headerdellapagina:
<divclass=”page-header”>
<h1>AllContacts</h1>
</div>
Nondobbiamoracchiuderloinuncontenitorepoichéilpartialènidificatonelfileprincipaledell’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.Comelamaggiorpartedeicomponenti,Bootstrappermettediapplicareglistilialletabelle,ancheseènecessarioincludereunaclasseaggiuntivaperrenderlidisponibili.AggiungetelaclassetablealtagdiaperturadellatabellaeBootstrapmetteràsubitounpo’d’ordineaggiungendoibordinecessariefacendoinmodocheoccupil’interalarghezza.
Sonopresentiinoltrealcuneclassisecondariecheèpossibileincluderepervivacizzareunpo’latabella.
table-bordered:aggiungeunbordoattornoatuttiilatidellatabellaedellecelle.table-striped:aggiungeunosfondogrigioarighealternateperfacilitarelalettura.table-hover:modificalosfondodellarigaall’hover.table-condensed:eliminaunapartedelpaddingsuperioreeinferioreinmododaridurrel’altezzadellatabella.
Oltreaquesteclassi,neesistonoaltrecheèpossibileapplicareallerigheoallecelleechecoloranolosfondodellerighepercontestualizzarle.
active:aggiungeallarigalostatohover.success:coloralosfondodiverde,perindicarecheun’azionehaavutosuccesso.info:attiral’attenzionesuunarigaosuunacellasegnalandoun’informazione.warning:indicachepuòesserenecessariaun’azioneecoloralacelladigiallo.danger:richiamal’attenzionesuunerroreosuunproblema.
Perilmomentoaggiungeremolaclassetable-striped,mastaavoisperimentarelealtreclassiintegrate.
Latabellainiziaadavereunbell’aspetto.Vedreteperòchesuunoschermodidimensionipiùpiccolerisultatagliataorizzontalmente.Perrimediareènecessarioracchiuderlainunaltroelementochecipermettadieffettuareloscorrimentosuschermipiùpiccoli:
<divclass=”table-responsive”>
…
</div>
Orahaunaspettonettamentemigliorepoichéilcontenutononvienepiùtagliatoeillayoutresponsivenonsiinterrompe.L’ultimaoperazionechefaremosullatabellaètrasformareillinkdellavistainunpulsante.Bootstrapoffrenumerosistiliperipulsantidicuipossiamoavvalerci.
Tuttiipulsantisonocombinazionidelleclassirelative:
alpulsantepredefinito;alcontesto;alledimensioni.
Tutteinsiemeoffronoilcontrollochecioccorrealfinediscegliereilpulsantegiustoperl’occasionegiusta.Combiniamolepercreareunpulsantechebensiintegrinellatabella:
<ahref=”#”class=”btnbtn-defaultbtn-xs”>View</a>
Laprimaclasseapplicaalcunistilipredefinitiaipulsanti;lasecondaassegnailcolore(inquestocaso,quellopredefinitoèilbianco)el’ultimadefinisceledimensioni.Inalternativapotremmoutilizzareunatraleclassidescrittenellaprossimatabellapermodificareilcoloredelpulsante.
Nomedellaclasse Descrizionebtn-default Pulsantebiancoconunbordogrigio
btn-primary Pulsanteblu
btn-success Pulsanteverde
btn-info Pulsanteazzurro
btn-warning Pulsantearancione
btn-danger Pulsanterosso
btn-link Pulsanteconlostilediunlink
Oltreaimpostareledimensionipredefinite,altretreclassilemodificano.Nelcodiceprecedenteabbiamogiàutilizzatobtn-xsperrimpiccioliremoltoilpulsante,mapotremmoutilizzareanchebtn-smperfareinmodocheappaiaunpo’piùpiccolodiquellopredefinitoobtn-lgperingrandirlo.
LavistaIndexècompletaedèprontaperesserepopolata.Nell’immagineseguenteosserviamoilrisultato.
PopolarelavistaAddContactÈchiarociòdicuiavremobisognonellavistaAddContact:unformcheci
consentadidigitareleinformazionirichieste.FortunatamenteBootstrapcioffremoltocontrollonell’organizzazionedeicampi.Abbiamogiàelaboratoidatichememorizzeremo;orasitrattasolodiindividuarequalèiltipodicampomigliore:
Name:campoditestoEmailaddress:campoe-mailPhonenumber:campoperilnumeroditelefonoAddress:areaditestoWebsite:campoditestoNotes:areaditestoGravatar:N/D
SiccomeGravatarusal’indirizzoe-mailperfornirel’immagine,quinondobbiamorichiederealcunainformazioneaggiuntiva.Abbiamointuttoseicampi;quindiduecolonnesonosufficienti.
Innanzituttoapriamoilformeaggiungiamoalsuointernolecolonne.Abbiamogiàlaclassedelcontenitore;cioccorronounanuovarigaeleduecolonne.ComeabbiamovistonelCapitolo2,ilsistemaagrigliadiBootstrapcontiene12colonne;ricordiamoceloquandocreeremoillayout:
<form>
<divclass=”row”>
<divclass=”col-sm-6”>
</div>
<divclass=”col-sm-6”>
</div>
</div>
</form>
Comeabbiamofattoinprecedenza,utilizziamoilprefissocol-smperfareinmodochelecolonnesicomprimanosuitabletosuidispositivimobilipiùpiccoli.
Potremmoinserireleetichetteegliinputdirettamentenellecolonne,maperunaspaziaturaottimale,ènecessarioracchiudereglielementiinuntagdivconlaclasseform-group:
<divclass=”form-group”>
<labelfor=”name”>Name</label>
<inputtype=”text”id=”name”>
</div>
PersfruttareglistilidiBootstrapnegliinput,aggiungiamolaclasseform-control.Seaggiungessimolaclassecontrol-labelall’etichetta,applicherebbeunpo’dipaddingaggiuntivo:
<divclass=”form-group”>
<labelfor=”name”class=”control-label”>Name</label>
<inputtype=”text”id=”name”class=”form-control”>
</div>
Aggiungiamoall’internoglielementirestanti:ilnome,ilnumeroditelefonoel’indirizzonellacolonnadisinistrael’indirizzoe-mail,ilsitowebelenoteinquelladidestra.
FormorizzontaliIlformpresentamoltobene.Senonvipiaccionoleetichetteinalto,potete
collocarleasinistraconunaleggeramodifica.Aggiungendolaclasseform-horizontalaltagdiaperturadelform,leclassiform-groupsicomportanocomelerighediunagriglia;pertantopossiamoapplicareleclassidellecolonneaglielementiallorointerno.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èpossibileaggiungereall’etichettaleclassiapplicabiliallecolonnediBootstrap.Siccomeform-controlimpostalalarghezzasu100%,siadattaall’elementogenitorechedobbiamoracchiudereinunelementoaggiuntivo.Abbiamoinclusoanchelaclassecontrol-labelequindil’etichettaècentrataverticalmente.
Ilformapparemoltomenodisordinatograzieallaclasseform-horizontal;proseguiamoeracchiudiamotuttigliinputnell’elementoform-control.
Talvoltavorretefornireall’utentemaggioriinformazionidiquellestrettamentenecessarie.Lepoteteincluderesottol’inputcorrispondenteutilizzandountagspanassociatoallaclassehelp-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.NelleversioniprecedentidiBootstrap,eraracchiusoinunelementoconlaclasseform-actions.InBootstrap3,invece,èsufficienteutilizzarelostessoform-groupcheabbiamousatofinora.Seapplicheretelostileform-horizontal,dovretedistanziarelecolonne:
<divclass=”form-group”>
<divclass=”col-sm-offset-4col-sm-8”>
<buttonclass=”btnbtn-primary”>AddContact</button>
</div>
</div>
Poichél’etichettaoccupaquattrocolonne,ènecessariostaccareilpulsantedellostessospazioinmodochesiabenallineato.
Amepiacevailcontrastocheoffrivalavecchiaclasseform-actions.FortunatamentepossiamoottenereunrisultatosimilericorrendoalcomponentewelldiBootstrap.Inquestocasoabbiamospostatolaclasseform-groupchecontieneilpulsantesubmitsubitosottolarigagiàesistente(ricordatevicheform-horizontalfasìchetuttiigruppisicomportinocomerighe)eabbiamoaggiuntolaclassewell:
<divclass=”form-groupwell”>
<divclass=”col-sm-offset-2col-sm-10”>
<inputtype=”submit”class=”btnbtn-primary”value=”Add
Contact”>
</div>
</div>
Infine,percompletarelapagina,assegneremolostessoelementopage-headercheabbiamoinclusonellavistaIndexelocollocheremosubitoall’iniziodelmarkup:
<divclass=”page-header”>
<h1>AddContact</h1>
</div>
Ilrisultatofinalesaràilseguente.
PopolarelavistaViewContactL’ultimopartialchedobbiamopopolareèlaschermataincuiverràvisualizzatoil
contatto.Siamostatitentatidiinserirlocomeunform,macipiacel’ideadiaveredeltestostatico,chepossiamodecideredimodificareinalcuneparti.
DovremovisualizzarelestesseinformazionicheabbiamoimmessonellavistaAddContact,oltrealgravatar.
TitoloegravatarInnanzituttoincluderemounaclassepage-header.Ospiteràuntagh1conall’internoil
nomedelcontatto:
<divclass=”page-header”>
<h1>DeclanProud</h1>
</div>
Quiincluderemoancheilgravatar:vediamocome.Perorautilizzeremoalcuneimmaginisegnapostoprovenientidahttp://placehold.it.Forsenonconoscetequestosito:quitrovatesegnapostoditutteledimensioni.Abbiamobisognodiun’immagine50px×50px,cheinseriremomedianteilcodiceseguente:
<imgsrc=”http://placehold.it/50x50”>
Modificatenepureledimensioniavostropiacimento.Poteteinserirlasubitoprimadelnomedelcontattoneltagh1.Vogliamoancheaggiungerelaclasseimg-circle:
<divclass=”page-headerrow”>
<h1><imgsrc=”http://placehold.it/50x50”class=”img-circle”>
DeclanProud</h1>
</div>
Questaclasseèunadelletredisponibiliperapplicareunpo’distilealleimmagini;aggiungeunarrotondamentodegliangoliparial50%percreareuncerchio.Èanchedisponibileimg-rounded,chearrotondagliangoli,oltreaimg-thumbnail,cheaggiungeunbelbordodoppio.
Laclasseform-horizontalSiamofortunatiperchépossiamoriutilizzarebuonapartediquellocheabbiamo
fattonellavistaAddContact.Laclasseform-horizontalsicomporteràaltrettantobeneseèpresentedelcontenutostaticoalpostodeicampi.Questapaginadiventeràin
seguitolaschermatadiediting,oltreaesserelaschedadelcontatto;pertantoèutilepoterutilizzarelaclasseperentrambeleviste.
Questavoltaperòricorreremoauntagdivenonaunelementodelformperracchiudereillayoutconduecolonne:
<divclass=”form-horizontal”>
<divclass=”row”>
<divclass=”col-sm-6”>
...
</div>
<divclass=”col-sm-6”>
...
</div>
</div>
</div>
Apartequestapiccolamodifica,illayoutèidentico.Abbiamolastessarigaeleseicolonnedellavistacreateinprecedenza.
Possiamoancheutilizzareleclassiform-groupoltrealleclassicontrol-label,checiconsentonodistrutturareefficacementel’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’etichettaeilcontattononsonobenallineati.Perrimediarepossiamoincludereun’altraclasseneltagparagraph:
<pclass=”form-control-static”>DeclanProud</p>
Aggiungetelaintuttiicampi.Abbiamoripresolostessolayoutprecedenteconnome,numeroditelefonoeindirizzonellacolonnadisinistraeindirizzoe-mailesitowebenoteinquelladidestra.
Siamosoddisfattidiquestolayout.Tuttavia,quandoloridimensioniamo,sembracherimangamoltospaziobiancoadestra.Sarebbepreferibilemodificarelecolonneprincipaliinmodochesianovisibiliinquestolayoutanchesudispositivimobili.
Nellarigasostituiamolaclassecol-sm-6conlaseguente:
<divclass=”col-xs-6”>
…
</div>
Inquestomodootterremoduecolonnesudispositivipiùpiccoliedeviteremoilproblemadellospaziobiancoineccesso.
Diseguitopoteteosservareilrisultato:
Quiz1. Perchénonènecessarioincludereuncontenitoreneipartial?2. Oltreatable,qualiclassièpossibileaggiungereaunatabellaperattribuireun
po’distile?3. Comepossiamocreareungrandepulsanteazzurro?4. Inchecosadevonoessereracchiusileetichetteegliinput?5. Inchemodolaclasseform-horizontalmodificailform?6. Checosautilizziamopervisualizzareunmessaggiodiaiutoaggiuntivoperun
campodelform?7. Qualitreclassipossiamoapplicarealleimmaginiequalirisultatiproducono?
RiepilogoOraabbiamotrelayoutprincipalicompleti.Abbiamoottenutoquestorisultato
senzascrivereunasolarigadiCSSsfruttandoglistilieicomponentifondamentalidiBootstrap.
NellavistaIndexabbiamoesaminatocomeBootstrapincludestilipredefinitieapplicaleclassisecondarieperaggiungereunpo’distile.ÈunpatterncheBootstraputilizzasempre,el’abbiamovistoinazioneconipulsantiegliinput.
ConlavistaAddContactabbiamoimparatoqualisonoisistemimiglioriperorganizzareunformeabbiamostabilitodiapplicareform-horizontal.Lapotremoriutilizzarequandocreeremolaschedadelcontatto.
Nelprossimocapitoloimpareremoacollegarelevisteadatidinamici.UtilizzeremoAngularJSpercrearecontatti,passaredaunoall’altroevisualizzarlinellatabella,modificarliocancellarli.
Capitolo6
CRUD
FinoraabbiamoesploratoiparadigmidiAngularesviluppatolastrutturadell’appgrazieaBootstrap.Inquestocapitolocibaseremosulleideeeiconcetticheabbiamoesaminatonelcorsodellibroperrendereoperatival’app.
CRUDèl’acronimodiCreate,Read,UpdateeDelete(creare,leggere,aggiornareecancellare),tuttociòcheservepersviluppareun’appdigestionedeicontattiefficiente.
AnalizzeremoogniletteradiquestoacronimoevedremocomeèpossibilesfruttarepienamenteAngular.
ReadPerilmomentoignoreremolapartecreativaepasseremosubitoallaletturadei
dati.Neutilizzeremodifittizisottoformadiunarraydioggettinelcontroller.
Nelprossimolistatovediamocomevengonoformattatieciòchedobbiamoincludere.
.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,possiamoaccedervidirettamenteall’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-defaultbtn-
xs”>View</a></td>
</tr>
Questocodicehaunaspettomoltofamiliare.Abbiamoassociatoladirettivaall’elementocheintendiamoripetere(inquestocasolarigadellatabella)eutilizzatoledoppieparentesigraffepervisualizzareidatideicontatti.
Nellinkosserveretequalcosadileggermentediverso.Ladirettivang-repeatciconsentediottenerel’indicedell’oggettocorrenteutilizzando$index.RicorderetechelaroutedelsingolocontattoaccettaunnumeroID.Utilizzeremol’indicedell’arraycomeIDinmododapoteraccederefacilmentealcontattoquandoneavremobisogno.
Condividereidatitraleviste
Ciòcheabbiamorealizzatofinorapresentaunproblema.AnchesefunzionaperfettamenteperlavistaIndex,èdeltuttoinutilequandointendiamovisualizzareununicocontatto.Infattiilnostroarraydicontattiècontenutonelcontrollerdell’indiceepertantononpuòesserecondiviso.
Condividereidatimediante$rootScope
Sonodisponibiliduesistemipercondividereidatitraleviste:ilprimoè$rootScope.Cosìcomeesisteunoscopeperognivista,l’applicazionestessanehauno,loscoperadice(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.Inquestocasol’abbiamoaggiuntoanchealloscopedellavista,maavremmopotutomodificarefacilmentelavistaperaccederedirettamentealloscoperadice:
<trng-repeat=”contactin$root.contacts”>
Peraccederedallavistaaqualsiasielementonelloscoperadice,èsufficienteanteporrealnomedelmodelloilprefisso$rootseguitodaunpunto.
QuestometododicondivisionedeidatinonsfruttatuttiglistrumentichecioffreAngularegeneramoltaconfusionenell’applicazione.
NOTA
Ilricorsoa$rootScope,esoprattutto ilsuoaccessodallavista,èunapraticasconsigliataepuòostacolare lagestionedelprogetto;percondividere idatièpreferibileutilizzareuncontrolleralivellodell’applicazioneounserviziopersonalizzato.
Creareunserviziopersonalizzato
Unasoluzionemigliorepercondividereidatitralevisteèsviluppareunsistemapersonalizzato.Unservizioèinsostanzaunaclasseallaqualeèpossibileaccederedopoaverlainseritaneicontroller,cosìcomeabbiamovistocon$scope.
InAngularJSneesistonotretipi:.service(),.factory()e.value().Tuttisonosingleton,deidesignpatternchefannoinmodochediessiesistaunasolaistanzasuunoggetto.Liesamineremotuttiprimadisvilupparneuno.
Value
Ilpiùessenzialedeitreèilmetodovalue.Aggiungiamoloalmodulo:
.value(‘demoService’,‘abc123’);
Comepossiamovedere,sitrattadiunserviziosemplicissimocheaccettadueparametri:ilnomedelserviziocheintendiamocreareeilvalorechedovrebbeavere.Questidatipossonoesserecondivisinell’applicazioneinserendolineicontroller:
.controller(‘indexCtl’,function($scope,demoService){
$scope.demo=demoService;
});
Èmoltosemplice,maoffreunmodorapidoefacilepercondividereidati.Potremmovolercondividere,peresempio,unachiaveAPItramolteplicicontroller.Ilmetodovaluesarebbelasoluzioneideale.
Factory
Valueèsempliceedefficace,manonoffremoltefunzioni.InveceilserviziofactorydiAngularcipermettedichiamarealtriservizitramitelaDI(dependencyinjection),econsenteanchel’inizializzazionedelservizioel’inizializzazionelazy.Riscriviamol’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,mavieneutilizzato,piùfrequentemente,perconnettersiaun’API.Comenelcasodivalue,factoryrestituiscetipiJavaScript.Eccocomerestituisceunoggetto:
.factory(‘anotherService’,function[‘demoService’,
anotherServiceFactory(demoService){
return{
connect:function(){
}
};
});
Ilmetodoconnectdefinitonell’oggettorestituitosaràdirettamenteaccessibilequandoinseriamoilservizionelcontroller:
.controller(‘indexCtl’,function($scope,anotherService){
anotherService.connect();
});
Service
L’ultimotipodiserviziopresenteinAngularJSvienechiamatoservice(unnomechegeneraconfusione,comeilfiltroFilter).Produceunsingletonalparidivalueefactory,invocandoperòuncostruttoremediantel’operatorenew.Anchequestopuògenerareconfusione;cerchiamodifarechiarezza.
NelseguentecasoèpresentelafunzionedelcostruttoreNotifiercheèunsemplicealiasperilmetodowindow.alertdelbrowser:
functionNotifier(){
this.send=function(msg){
window.alert(msg);
}
}
Potremmoaggiungerladirettamentenelcontrollerechiamarlainquestomodo:
varnotifier=newNotifier();
notifier.send(‘Hello,World’);
Anchesefunzionerebbe,nonèlasoluzioneottimale.EsevolessimoutilizzarenuovamentelafunzioneNotifierinunaltrocontroller?Comesappiamo,possiamocondividerequestotipodielementiricorrendoaunserviziodiAngularJS.Confactory,otterremmoqualcosadelgenere:
.factory(‘notifierService’,functionnotifierFactoryService(){
returnnewNotifier();
});
Nientemale,maquestoèproprioiltipodirisultatoperilqualeèstatoconcepitoservice.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,masiccomenoncreiamoalcunaistanza,serviamocidifactory:
.factory(‘contacts’,functioncontactsFactory(){
})
Comeabbiamovisto,èsolol’oggettorestituitoacontenereimetodieleproprietàpubbliche.Èpossibilesfruttarequestoaspettoincludendol’arraydicontattiprivatamente,consentendochesiarestituitoeaccessibilesoltantodametodipubblici:
varcontacts=[
…
];
Ilservizioincluderàduemetodiperleggereicontatti.Ilprimorestituiràl’interoarray,mentreilsecondounsolooggettocontattochedipendedall’indiceassegnatoaesso.Restituiamol’oggettocontenentequestiduemetodievediamoinchecosaconsistono:
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,equestohamessoordinenelcontroller.Seaggiungiamoconsole.logalserviziodicontatti,vedremochenonèpossibilevedereidatigrezzi,masoloiduemetodiaiqualiabbiamoavutoaccesso.
Utilizzareiparametridellaroute
Oracheilserviziofunzionaadovere,possiamoiniziareapopolarelavistacontenenteununicocontatto.Aquestoscopodovremoestrarrel’IDdallaroute.
NelCapitolo4abbiamoesaminatorapidamentecomeèpossibileaccedereaiparametridellaroute,maricapitoliamol’argomento.Innanzituttoènecessarioinserireunaltroservizionelcontrollerdell’unicocontatto:$routeParams.Questoserviziorestituisceunoggettocontuttiiparametrinellaroutecomeproprietàseparate:
.controller(‘contactCtl’,function($scope,$routeParams,
contacts){
$scope.contact=contacts.find($routeParams.id);
});
Inquestocasoabbiamoaccessoalparametroidcheutilizziamopertrovareilcontattocorrettomedianteilserviziocheabbiamocreatoinprecedenza.Lopassiamoallavistacreandounnuovomodellosulloscopechiamatocontact.
Estraiamotutteleinformazionipertinentinelpartialcontact.html.Ricordatechetuttiidatisonoproprietàdelcontattodelmodelloepossiamoaccederviinquestomodo:
{{contact.name}}
Funzionatuttotranneperalcunidettagli.Idatiriguardantil’indirizzodovrebberorispettareifineriga,ecipiacerebbericavaredinamicamentel’immaginedelgravatar.Perottenerequestirisultatiènecessariocreareunfiltroeunadirettiva.
CreareunadirettivapersonalizzataAbbiamosperimentatol’efficaciadelledirettiveefinoranonavevamoalcun
motivopersvilupparneunanuova.Maperincludereilgravatar,ènecessariocrearneunapersonalizzata.
Cosìcomepericontroller,ifiltrieiservizi,lanuovadirettivadeveessereassociataalmoduloutilizzandoilmetodopertinente:
.directive(‘gravatar’,function(){
})
Comepericontroller,ifiltrieiservizi,ilmetododirectiverichiededueparametri.Ilprimoèilnomedelladirettiva,ilsecondoèunafunzione.Unadirettivadeverestituireunoggetto,eleproprietàdell’oggettorestituitodefinisconoilcomportamentodelladirettiva.
Laprimaproprietàcheimposteremoèrestrict;definisceilmodoincuièpossibileutilizzareladirettiva.Abbiamogiàvistocomeèpossibileavvalersidellamaggiorpartedelledirettivecomeattributioelementipersonalizzati,maAngularciconsenteanchediusarleinaltriduemodi.Èpossibileimpostarequestivaloriperlaproprietàrestrict.
A:ladirettivapuòessereassociatasoloutilizzandounattributo,<divgravatar></div>.E:ladirettivapuòessereutilizzatacomeelementopersonalizzato,<gravatar></gravatar>.
C:ladirettivapuòessereutilizzataaggiungendolacomeclasseall’elemento,<divclass=”gravatar”></div>.M:consentel’esecuzionedelladirettivaattraversouncommentoHTML,<!--directive:gravatar-->.
NOTA
Perledirettiveèpreferibileutilizzareattributiedelementirispettoaclassiecommenti.
L’impostazionepredefinitadiAngularèlaprima,comeattributo.Possiamoutilizzareperòlacombinazionedeivalorimenzionatipermettereapuntoilmodoincuiintendiamoutilizzareladirettiva.SelaimpostiamosuAEpotremochiamarlatramiteunattributoounelementopersonalizzato:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’
}
})
Seènecessario,potremmoanchecreareuntemplateperladirettiva.Puòessereinclusodirettamentenell’oggettoutilizzandolaproprietàtemplate,oppure,ricorrendoallaproprietàtemplateUrl,possiamocaricaredall’URIspecificatounfileditemplateesterno.Siccomequicreiamosoltantountagimg,possiamoaggiungerlodirettamentenell’oggetto:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’,
template:‘<imgng-src=”{{img}}”class=”{{class}}”>’
}
})
Questotemplatesicomportacomeleviste.Abbiamoaggiuntoduesegnapostoperl’URIdell’immagineoltrealleclassicheintendiamoincludere.
Perfarfunzionareiltutto,occorreassociareunafunzioneallaproprietàlinknell’oggetto.Daquièpossibileaccederealloscope,all’elementoalqualeèassociataladirettivaeaqualsiasiattributodiquell’elemento:
.directive(‘gravatar’,function(){
return{
restrict:‘AE’,
template:‘<imgsrc=”{{img}}”class=”{{class}}”>’,
link:function(scope,elem,attrs){
}
}
})
Perrecuperarel’immaginedelgravatar,sideveapplicarelafunzionehashall’indirizzoe-maildelcontattotramitemd5.Purtroppononèunmetodonativodi
JavaScriptedènecessarioincludereunalibreriaseparata.Neabbiamoinclusaunanegliassetriguardantiquestocapitolo,chepotetescaricare;puòessereinclusacomevariabilesingle-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[I-
1]=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’immaginedelcontattodalgravatar.Passeremodueelementiallafunzionedellink.Ilprimosaràl’indirizzoe-mail,mentreilsecondoparametrofacoltativosaràcostituitodalledimensionidell’immaginecheintendiamorecuperare.
Gliattributipassatiallafunzionesonosemplicioggettiaiqualipossiamoaccedere.Peresempio,seintendiamorecuperareilvaloredell’attributodell’indirizzoe-mail,possiamoaccedervimedianteattrs.email.
Abbiamoanchedefinitolaproprietàreplace,chesostituiràl’elementoalqualeabbiamoassociatoladirettivaconiltemplatespecificato.Perimpostazionepredefinita,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àdiimpostareledimensionidell’immagine.Abbiamoancheassociatoleclassiassegnateall’elemento,oltreaunirel’URLdelgravataralloscope.
Ladirettivaèpronta.Proviamoautilizzarlaconunattributo:
<divgravataremail=”{{contact.email}}”size=”50”class=”img-
circle”></div>
Ottimo,sembrafunzionaretutto.Vienevisualizzatoilgravatarevieneinclusalaclassecheproduceunabellaimmaginecircolare.Purtroppoessaèracchiusaneltagdivalqualeabbiamoassociatoladirettivaequindisispostasuunanuovariga.CiòaccadeperchénonabbiamocomunicatoadAngularcheintendiamosostituirel’elementoesistenteconiltemplatecompilato.Aquestoscopoimpostiamosutruelaproprietà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’immagineracchiusanell’elementooriginarioèpresentelanostraimmagine.Inalternativaavremmopotutochiamareladirettivatramiteunelementopersonalizzato:
<gravataremail=”{{contact.email}}”size=”50”class=”img-
circle”></gravatar>
SenonservespecificatamenteilsupportoaIE8(nonpiùsupportatoinAngularJS1.3+),ledirettivecheinseriscononuovielementitramitel’utilizzodiuntemplatedovrebberovenirechiamatemedianteunelementopersonalizzato,comenell’esempioprecedente.
Ledirettivechemanipolanoelementiesistenti,peresempiochiamandounplug-injQuery,dovrebberovenirechiamatesoltantotramiteunattributo.
Laprossimafiguramostralanuovadirettivaquandovienevisualizzatouncontatto.
RispettareifinerigaAttualmenteicampiaddressenotesnonrispettanoifinerigaperchélenuoverighe
devonoessereconvertiteininterruzionidirigaHTML.FortunatamenteAngularsemplificaalmassimoquestaoperazionemedianteunfiltropersonalizzato.
ComeabbiamovistorealizzandounfiltronelCapitolo3,descriveremorapidamenteilfiltroparagraphcheènecessariocreareperconvertireilfineriga\nin<br/>:
.filter(‘paragraph’,function(){
returnfunction(input){
return(input)?input.replace(/\n/g,‘<br/>’):input;
};
})
Seaggiungiamoquestofiltroall’indirizzousandolasintassicheprevedel’operatorepipe(|),noteremoqualcosadistrano.LeinterruzionidipaginavengonoconvertiteinentitàHTMLevisualizzatesullapagina.Permotividisicurezza,Angularesegueautomaticamentel’escapedelleentitàHTMLperimpedireloscriptingcross-site.
Siccomenonintendiamoovviamentevisualizzarle,dobbiamoutilizzareunadirettivainclusaperlegareilmodelloallapagina.Ladirettivang-bind-htmlprodurràesattamentequestorisultato.Eccolainazioneneltagparagraph:
<pclass=”form-control-static”ng-bind-html=”contact.address|
paragraph”></p>
Manonfunzionaancora.Secontrolliamolaconsole,Angulargenerailseguenteerrore:
Error:[$sce:unsafe]Attemptingtouseanunsafevalueinasafe
context.
InfattiAngularrichiedeilmodulongSanitizecheèpossibilescaricaredallasezioneExtrasall’indirizzohttps://angularjs.org/.Ilmodulofiltraiframmentidicodicepericolosi,comegliscript,producendounoutputripulitoesicuro.
ProcuratevelodalsitodiAngular,aggiungeteloalladirectoryjseincludetelonelfileprincipaleindex.html:
<scripttype=”text/javascript”src=”/assets/js/angular-
sanitize.min.js”></script>
Dopoaverinclusoilmodulonellapagina,ènecessarioinserirlocomedipendenzadelnostromodulo,cosìcomeabbiamofattoconngRoute:
angular.module(‘contactsMgr’,[‘ngRoute’,‘ngSanitize’])
Seaggiornatelapagina,vedretecheoral’indirizzooccupapiùrighe,comeprevisto.
ImpostarelaricercaeaggiungerelapageClassactive
L’ultimoargomentodaaffrontarenell’ambitodelReadèlaricerca.PoichélaricercafiltreràlatabellanellavistaIndex,dobbiamoreindirizzarlaquandoiniziamoadigitare.Inoltreilmodellocheutilizzeremodovràessereaccessibileovunque.
DobbiamoanchecapirecomeimpostarelapageClassactive.AlmomentoèfissasuBrowsemasarebbebellorenderladinamica.
Entrambiquestiobiettivinonsipossonoraggiungereconng-view;ènecessariocreareuncontrollerperl’interaapplicazione.Questosignificachesaremoingradodiaccedereovunquealmodelloperlaricerca:
.controller(‘appCtl’,function($scope,$location){
});
Perreindirizzarelapaginaabbiamoinseritoilservizio$location.Essooffrel’accessoalmetodopath,chepossiamosfruttareperrealizzareilnostrointento.Adifferenzadeicontrollerdellaroute,ènecessariochiamarlosullapaginaaggiungendoloaltagdiaperturaHTML:
<htmllang=”en”ng-app=”contactsMgr”ng-controller=”appCtl”>
Laricerca
Oracheilcontrollerèinizializzato,possiamoprocedere.Ladirettivang-keyupsiattiverànonappenainizieremoadigitarenellacaselladiricerca,reindirizzandociallavistaIndex.
Aggiungiamol’handleralcontroller:
.controller(‘appCtl’,function($scope,$location){
$scope.startSearch=function(){
$location.path(‘/’);
};
});
Sitrattadiunafunzioneabbastanzasempliceedifacilecomprensione.Quandoaggiungeremoladirettivaallacaselladiricerca,essamodificheràilpercorsocorrentenellavistaIndex.Procediamoeimpostiamola.Senonl’avetegiàfatto,èilmomentodiassegnareancheunmodelloallacaselladiricerca:
<formclass=”navbar-formnavbar-right”role=”search”>
<inputtype=”text”class=”form-control”placeholder=”Search”ng-
model=”search”ng-keyup=”startSearch()”>
</form>
Oracheabbiamovistocomereindirizzare,èsufficientefiltrareng-repeatcomeabbiamofattoinprecedenza:
<trng-repeat=”contactincontacts|filter:search”>
Ricordatechesevoletelimitarloalnome,ènecessariomodificareilmodelnellacaselladiricercanelseguentemodo:
<inputtype=”text”class=”form-control”placeholder=”Search”ng-
model=”search.name”ng-keyup=”startSearch()”>
LapageClassactive
InfineènecessarioimpostarelapageClassactive.Dobbiamoverificareilpercorsocorrenteeaggiungere,senecessario,unaclasse.Aquestoscopoutilizziamong-classe
unafunzionenelcontrollerdell’app.
Lafunzioneverificheràseilpercorsocorrenteèlostessodiquellocheèstatopassato:
$scope.pageClass=function(path){
return(path==$location.path())?‘active’:‘’;
};
Sec’ècorrispondenza,restituiràlaclasseactive,altrimentinulla.Aggiungiamolaaentrambiglielementidellanavigazione:
<ling-class=”pageClass(‘/’)”><ahref=”/”>Browse</a></li>
<ling-class=”pageClass(‘/add-contact’)”><a
href=”/add-contact”>AddContact</a></li>
Dopoaveraggiuntoladirettivang-classall’elementodellalista,lapaginacorrentemostratadallaclasseactivediventainteramentedinamicaecorretta.
CreateFinoraabbiamotrascuratolaprimaletteradell’acronimoCRUD,maoraèvenuto
ilmomentodifareunpassoindietroeimpostareilformdiaggiuntadeicontatti.Dobbiamoinnanzituttoverificarechetuttigliinputdelformabbianoassociatoilmodelloopportuno:
<inputtype=”text”id=”name”class=”form-control”ng-
model=”contact.name”>
Poichéabbiamorealizzatounserviziopergestireicontatti,èragionevoleestenderloperriuscireacreareconessoicontatti.Definiamounmetodocreatecheportiilcontattonell’arrayeloaggiungaall’oggettodelservizio:
create:function(contact){
contacts.push(contact);
}
Orariflettiamosuciòchevogliamoaccadadopol’inviodelform.Intendiamoinserireuncontattonell’arrayutilizzandoilmetodoappenacreatonelservizio,fornireunfeedbackpositivosuquestaoperazioneeinfinecancellaretuttodalform:
$scope.submit=function(){
contacts.create($scope.contact);
$scope.contact=null;
$scope.added=true;
};
Conilprecedentecodicesiamoriuscitinelnostrointento:abbiamopassatoilcontattoalservizio,reimpostatoilmodellodelcontattoedefinitounmodellochepossiamoverificareperfornireilfeedback.
Esistonoduesistemiperchiamarelafunzioneappenacreata.Potremmoaggiungerlacomedirettivang-clickalpulsantesubmit,maforseèpiùsaggioeaccessibileaggiungerlaaunadirettivang-submitnelform:
<formclass=”form-horizontal”ng-submit=”submit()”>
Sitrattadiincluderelafinestradiavvisopercomunicareall’utentecheilcontattoèstatoaggiuntoconsuccesso.Vogliamocheperimpostazionepredefinitasianascosta,cosìutilizzandong-showeosservandoilmodelaggiunto,possiamodeciderequandovisualizzarla:
<divclass=”alertalert-success”ng-show=”added”>
Thecontactwasaddedsuccessfully.
</div>
UpdateComericorderete,nonabbiamocreatounavistadestinataunicamenteallamodifica
deicontatti.Possiamosfruttarelostessopartialcheabbiamoutilizzatoperaggiungereicontattioppurefarequalcosadipiùstimolanteconlavistacontenenteunsolocontattoecreareunadirettivapermodificareidati.
Èimprobabilechesianecessariomodificareinunavoltasolatuttiidatidiuncontatto;spessositratteràdicambiaresoltantoilnumeroditelefonool’indirizzoe-mail.LanostraideaèquelladivisualizzareiltestoafiancodiunpulsanteEdit.Alclicsudiesso,potremomodificarequeldatodelcontatto.
Chiamiamoladirettivaeditableedeseguiamolacomeabbiamofattoinprecedenza:
.directive(‘editable’,function(){
return{
};
})
Èopportunolasciarelasceltadiincluderlacomeelementopersonalizzatooppureutilizzarlamedianteunattributo.Perilmomentolautilizzeremosoltantocomeattributo,maforseinfuturopotrebberorichiederlaaltriprogetti:
.directive(‘editable’,function(){
return{
restrict:‘AE’,
templateUrl:‘/assets/partials/editable.html’
};
})
Questavoltanonsaràsufficienteunarigadimarkup,cosìabbiamodecisodiutilizzarelaproprietàtemplateUrl.Sitrattasoltantodicreareilreferencepartial,comeabbiamofattoconleroute.
ScopeOltreadeciderediutilizzareunURLperiltemplate,studieremounanuova
proprietà:scope.Essacioffremaggiorecontrollosulloscopecheintendiamoutilizzareconladirettiva.
Inquestocasoselaimpostiamocomehashsicreeràunnuovoscopeisolato.Nonereditadalgenitore,equindinondovremopreoccuparcidellaletturaodellamodificaaccidentaledeidatinelloscopedellavista.Inquestocasoabbiamoaggiuntoduevaloriallafunzionehashdelloscope:
.directive(‘editable’,function(){
return{
restrict:‘AE’,
templateUrl:‘/assets/partials/editable.html’,
scope:{
value:‘=editable’,
field:‘@fieldType’
}
};
})
Lachiaveèilnomecheassegniamoalnuovoscope,mentreilvaloreèunattributodell’elemento.Notatecheabbiamoantepostoduediversiprefissiaivalori,chepertantosicomporterannoinduemodimoltodiversi.
NOTA
RicordatechegliattributiseparatidauntrattinosonoconvertitiinCamelCasedaAngular.
Quandoilprefissocorrispondealsegno=,possiamolegareunmodellodelloscopegenitorealloscopedelladirettiva.Nondobbiamoquindiutilizzarelasintassi{{}}epossiamoapprofittaredelbindingdeidatibidirezionale.
Neavremobisognoperchémodificheremoilvaloredelmodellocollegatoall’internodelladirettiva.
Seanteponiamoilsimbolo@,ladirettivautilizzeràilvaloreletteraledell’attributo.Possiamoutilizzarelasintassi{{}}perpassareilvalorediunmodellooimmettereunastringa.Quandoutilizziamoilsimbolo@nessunmodelloècollegato.
ControllerAbbiamovistoinprecedenzacomepossiamoutilizzareilmetodolinknella
direttiva,maneabbiamoancheunaltroadisposizione.Ilmetodocontrollerfunzionaesicomportapropriocomeuncontrollerdirettamenteassociatoalmodulo.Possiamoinserirequalsiasiservizionecessarioetuttocisembreràmoltofamiliare.
Ladifferenzarispettoalinkconsistenell’ordinenelqualevieneelaborato.Ilmetodocontrollervieneeseguitoprimachel’applicazioneabbiafinitodiesserecompilata,mentreilmetodolinkdopo.Dovremmoutilizzaresemprecontroller,amenochenoncreiamounadirettivawrapperperunplug-injQueryoqualcosachedeveessereeseguitodopochetuttohafinitodicaricarsi.
Inprecedenzaabbiamoutilizzatolinkperladirettivadelgravatarpoichévolevamodescrivereledifferenzetraidueapprocciall’internodelladirettiva.Aggiungiamoilmetodocontrolleralladirettiva.Inquestafasedovrebbepresentarsicosì:
.directive(‘editable’,function(){
return{
restrict:‘AE’,
templateUrl:‘/assets/partials/editable.html’,
scope:{
value:‘=editable’,
field:‘@fieldType’
},
controller:function($scope){
}
};
})
NOTA
Laproprietàcontrollernelladirettivasi comportacomeunasortadiAPIeconsenteallealtredirettivedicomunicareleuneconlealtre,adifferenzadilink.
AssemblareiltuttoAbbiamoimpostatoloscopeeilcontroller.Oraèilmomentodipopolareilpartial
edefinirelafunzionalità.VisualizziamoilvaloredelmodelloeincludiamoilpulsanteEditcheattiveràl’editor:
<spanng-bind-html=”value|paragraph”></span><buttonclass=”btn
btn-defaultbtn-xs”>Edit</button>
Comericorderete,abbiamoimpostatol’aliasdelnomedell’attributomodificabilesuvalue,edèproprioquestocheutilizzeremo.Dobbiamoinoltreprevederetipidicampidiunarigaodipiùrighe;pertantoricorreremoang-bind-htmlealfiltroparagraph.
Sfrutteremong-showeng-hideperanalizzareunmodellonelloscopedelladirettiva.Èopportunoconsentireall’utentedicancellarelemodificheequindinonmodificheremoilvalorecheabbiamopassatodirettamente.Aggiungereilseguentecodicealcontrollercreaunnuovomodellochepossiamomodificare,oltreagenerarequalcosacheng-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,chechiameremodang-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,areaditestoecosìvia)anchesepreferiamol’impostazionepredefinita,unacaselladitestodiunariga,perchéèciòcheutilizzeremoconmaggiorefrequenza.Inquestocasoabbiamooptatoperunoperatoreternarioperverificareseèstatoimpostatounvaloreosesideveutilizzarel’impostazionepredefinita:
$scope.field=($scope.field)?$scope.field:‘text’;
Ladirettivang-ifèstataaggiuntadirecenteinAngularJS1.2.Adifferenzading-showeng-hide,associaodissociaelementidalDocumentObjectModel(DOM)senonsoddisfanolacondizione;inquestocasoèperfettaperverificareiltipodicampo:
<divng-show=”editor.showing”>
<divng-if=”field==‘textarea’”>
<textareang-model=”editor.value”class=”form-
control”></textarea>
</div>
<divng-if=”field!=‘textarea’”>
<inputtype=”{{field}}”ng-model=”editor.value”class=”form-
control”>
</div>
</div>
Comepotetevedere,èsemplicedautilizzareeoffreuncontrollocompletosiacheintendiamoutilizzareun’areaditestoounelementodiinput.Abbiamoanchepopolatol’attributotypedeltaginputconilvalorecheèstatopassatoeinclusoinentrambiglielementinelnuovomodello.
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=”form-
control”></textarea>
</div>
<divng-if=”field!=‘textarea’”>
<inputtype=”{{field}}”ng-model=”editor.value”class=”form-
control”>
</div>
<hr>
<buttonclass=”btnbtn-successbtn-xs”ng-
click=”save()”>Save</button>
<buttonclass=”btnbtn-defaultbtn-xs”ng-
click=”toggleEditor()”>Cancel</button>
</div>
AbbiamoassociatoilpulsanteSaveaunanuovafunzionesavechedobbiamoancoracreare;ilpulsanteCancelutilizzalastessafunzionetoggleEditorprecedente.
Lafunzionesaveèsemplice;assegnailnuovomodellocheabbiamocreatoaquellocheabbiamolegatoinprecedenzaalladirettivaechiamalafunzionetoggleEditorpernasconderetutto:
$scope.save=function(){
$scope.value=$scope.editor.value;
$scope.toggleEditor();
};
Abbiamoterminatoladirettivamodificabile,macomepossiamoutilizzarla?Nelpartialcontact.htmlabbiamovisualizzatoinprecedenzatuttiimodelliall’internodeitagparagraphricorrendoallasintassiconledoppieparentesigraffe.Oracheladirettivafatuttoquestoalpostonostro,possiamosostituireilcontenutodeltag<p>eaggiungeredueattributi:
<pclass=”form-control-static”editable=”contact.email”field-
type=”email”></p>
Dopoaversostituitotuttiimodelli,dovresteottenereuneditorvisivodifacileutilizzoperognisezionedelcontatto.
DeleteÈpossibilecancellareicontattigrazie,nuovamente,alservizio.Creeremoun
metodofinalecheaccetteràunindicedell’arrayelocancellerà.SiccomedeleteèunaparolachiavediJavaScript,utilizzeremodestroycomenomedelmetodo:
destroy:function(index){
contacts.splice(index,1);
}
Èunmetodomoltosemplice.Prendiamol’indiceeutilizziamoilmetodonativosplicepercancellarlodall’array.Oradobbiamocreareunafunzionesulloscopedellavistaindexingradodichiamarequestometododalservizio:
$scope.delete=function(index){
contacts.destroy(index);
};
Infineaggiungiamounpulsanteallacolonnadelleazionidellatabellapercancellarealclicilcontattovoluto:
<buttonclass=”btnbtn-dangerbtn-xs”ng-
click=”delete($index)”>Delete</button>
Quiz1. 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?
RiepilogoInquestocapitoloabbiamoaffrontatomoltiargomentiedèunabuonaidea
riepilogarli.Agrandilineeabbiamotrasformatociòcheeranotemplatestaticiinun’apppienamentefunzionantechepermetteilCRUD(Create,Read,Update,Delete).
Nelfrattempoabbiamoscopertomoltoaltro.Abbiamoesaminatoilsistemamigliorepercondividereidatitralevistecreandounserviziopersonalizzatopergestireicontatti.Ilserviziocompletociconsente,ovunquenell’applicazione,direcuperaretuttiicontatti,trovarneunosolo,aggiungerneunonuovoocancellarlo.
Oltreadefinireunserviziopersonalizzato,abbiamoanchecreatonuovedirettive.Laprimacihapermessodivisualizzarel’immaginediungravatarbasatasuunindirizzoe-mail.Abbiamoscopertoimoltepliciediversimodidiutilizzodiunadirettiva,tramiteunattributooperfinouncommentoHTML.
Lasecondadirettivacheabbiamocreatoèunpo’piùcomplessa.Abbiamoconsideratoscopeisolatieladifferenzatraimetodilinkecontrollerdiunadirettiva;inoltreabbiamosviluppatounottimosistemapermodificareidatidelcontatto.
NelcapitoloseguentevedremoinazionelalibreriaditerzepartiAngularStrapcheciconsentediavvalercideiplug-indiBootstrapall’internodiAngular.
Capitolo7
AngularStrap
Abbiamodescrittol’elevatonumerodicomponentipresentiinBootstrap;oraesamineremol’utilizzodeiplug-inJavaScriptdisponibili.Èpossibilecrearedelledirettiveperciascunodiessi,malacommunitydiAngularforniscegiàunriccomodulochiamatoAngularStrap.
InquestocapitoloconsidereremoAngularStrap,iplug-incheBootstrapcipermettediutilizzareeilmodoincuièpossibileinserirlinell’applicazione.
InstallareAngularStrapInnanzituttodobbiamoscaricareAngularStrap.Poteteprocurarvelodalsito
http://mgcrea.github.io/angular-strap/.Fateclicsulpulsantedidownloadinaltoadestraescaricatel’ultimaversionecomefileZIP.
Quest’ultimocontiene,tral’altro,tuttiimodulisingolisottoformadiuncomodofileminificato.Trovereteiduefilecheciservononelladirectorydist.Copiateangular-strap.min.jseangular-strap.tpl.min.jsnelladirectoryjsdelprogetto.Includetelinelfileindex.htmldellaradicedopoAngulareprimadelmodulodelprogetto:
<scripttype=”text/javascript”src=”/assets/js/angular-
strap.min.js”></script>
<scripttype=”text/javascript”src=”/assets/js/angular-
strap.tpl.min.js”></script>
Comeperognialtromodulo,ènecessarioinserirlonell’applicazione.Ladichiarazioneèsituatanellaprimarigadelcontroller.js;ilnomedelmoduloAngularStrapèmgcrea.ngStrap.DiseguitoabbiamoaggiuntoAngularStrapcomeunadipendenzadelmodulocontactsMgr:
angular.module(‘contactsMgr’,[‘ngRoute’,‘ngSanitize’,
‘mgcrea.ngStrap’])
Manonèfinitaqui.AngularStrapdipendedalmodulongAnimatechedobbiamoancoraincluderenelprogetto.LopossiamoscaricarefacendoclicsullinkExtrasnellafinestramodaledidownloadall’indirizzohttps://angularjs.org/.
AggiungetelaversioneminificataalladirectoryjsdelprogettoeincludetelaprimadiAngularStrap:
<scripttype=”text/javascript”src=”/assets/js/angular-
animate.min.js”></script>
IlmodulongAnimatenondeveessereinseritonelprogettoamenochenonvogliateusarloaldifuoridelledirettivediBootstrap.ConsentedisfruttareleanimazioniCSSinmodulicomengShowengHideperottenereuneffettoditransizioneinvecedivisualizzaresemplicementequalcosa.
PotremmoanchesvilupparedelleanimazionidautilizzareinsiemeconAngularStrap,ancheseAngularMotionèilsuocompagnoperfetto.SitrattadiunsemplicefogliodistilechecontieneanimazionipredefiniteprontedautilizzareconilmodulongAnimate.
Possiamoscaricarel’ultimaversionedalsitohttp://mgcrea.github.io/angular-motion/facendoclicsulpulsantedidownloadinaltoadestra.AnchequestofileZIPcontieneifilesorgenteoltreallaversioneminificataprontaperlaproduzione,presentenelladirectorydist.Copiatelanelprogettoeincludetelanellapaginacomefogliodistilesecondario:
<linkrel=”stylesheet”href=”/assets/css/angular-motion.min.css”>
Orapotremocreareeffettiditransizione,farscorrere,ridimensionareeruotareleanimazionifornitedaAngularMotioninsiemeconledirettiveAngularStrap.
Forsevisembreràstranochenonsianecessarioincludereloscriptdeiplug-indiBootstrap.C’èunmotivo:ledirettivecheabbiamoinclusoconAngularStrapnonsonosemplicifunzioniwrappercheeseguonojQuery,mariscritturecompletechesfruttanoappienoAngular.
UtilizzareAngularStrapDopoaverinstallatoAngularStrap,esaminiamoalcunisuoiplug-inecome
utilizzarli.
Primadiiniziare,impostiamorapidamenteunambientedemo.Duplichiamoilfileindex.htmlerinominiamolodemo.html.ModificheremoancheilcontrollerindemoCtrleloaggiungeremoalfilecontroller.js.Inquestomodoavremounospazioincuioperare.
LafinestramodaleUnafinestramodaleèunparadigmaUImoltocomunenelleapp.Èunottimo
sistemapervisualizzarepocheinformazionisenzaindirizzarel’utenteaunanuovapagina.
Puòesserechiamataalclicsuunpulsanteacuièapplicataladirettivabs-modal:
<buttonclass=”btnbtn-primary”bs-modal=”modal”>Show
Modal</button>
Ilvalorechevienepassatoèunmodellodelloscope;èunhashcontenenteduevalori:titleecontent.Eccoloall’internodelcontroller:
$scope.modal={
title:‘ModalTitle’,
content:‘Modalcontent’
};
Esistonoalcuneopzionidasfruttareconladirettivamodal;sonoapplicateall’elementocomeattributiefatteprecederedadata-.Peresempio,seintendiamomodificarel’animazione,possiamoscriverequestocodice:
<buttonclass=”btnbtn-primary”bs-modal=”modal”data-
animation=”am-fade-and-scale”>ShowModal</button>
cheutilizzeràl’animazionefade-and-scalediAngularMotion.RicordatevidivisitareilsitodiAngularMotionperunalistacompletadelleanimazionidisponibili.
Latabellaseguente,trattadalsitodiAngularStrap,illustralalistaditutteleopzionidisponibiliperladirettivamodal.
Nome Tipo Impostazionepredefinita 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.
backdropbooleanoo'static'
true
Includeunelementomodaledellosfondo.Utilizzastaticperlosfondo,chenonchiudelafinestramodalealclic.
keyboard booleano trueChiudelafinestramodalequandosipremeiltastoEsc.
container stringa/false falseAggiungelafinestramodaleaunelementospecifico.Esempio:container:'body'.
template percorso false Sefornito,scavalcailtemplatepredefinito.
contentTemplate percorso falseSefornito,recuperailpartialeloincludecomecontenutointerno.
TooltipItooltipsonounottimosistemaperoffriresuggerimentieconsiglisenzaessere
invadenti.AngularStrapnesemplifical’inclusione;liattiviamoconilclic,l’hoveroilfocus:
<buttonclass=”btnbtn-link”bs-tooltip=”tooltip”>what’s
this?</button>
Inquestocasoabbiamounpulsante(alqualeèstatoapplicatounostileinmododafarlosembrareunlinkgraziealleclassidiBootstrap)eabbiamoinclusoladirettivabsTooltip.
Cosìcomeabbiamofattoconladirettivamodal,possiamopassareunmodelloalladirettiva.Questavoltadobbiamoincluderesoltantolaproprietàtitlenell’oggetto:
$scope.tooltip={
title:‘TooltipTitle’
};
Perimpostazionepredefinitailtooltipcompariràall’hoversulpulsante,maèpossibilemodificarlafacilmenteutilizzandogliattributidatacheabbiamovistoinprecedenza:
<buttonclass=”btnbtn-link”bs-tooltip=”tooltip”data-
trigger=”click”>what’sthis?</button>
Ladirettivaciconsenteanchedilegarlaauninputemostrareiltooltipalfocus.Ancheilposizionamentopuòesseredefinitodall’attributodata:
<inputtype=”text”bs-tooltip=”tooltip”data-trigger=”focus”data-
placement=”right”>
Questocodicemostreràiltooltipadestraquandol’inputottieneilfocus.DiseguitopotetevederelalistaditutteleopzionipresentenelladocumentazionediAngularStrap.
Nome Tipo Impostazionepredefinita Descrizione
animation stringa am-fade Applicaun’animazioneCSS.
placement stringa 'top'Posizionailtooltip:top/bottom/left/rightoqualsiasialtracombinazionecomebottom-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.Sevieneindicatounnumero,ilritardosiapplicasiaahidesiaashow.Lastrutturadell’oggettoèlaseguente:delay:{show:500,hide:100}.
container stringa/false falseAggiungelafinestramodaleaunelementospecifico.Esempio:container:'body'
template percorso false Sefornito,scavalcailtemplatepredefinito.
contentTemplate percorso falseSefornito,recuperailpartialeloincludecomecontenutoall’interno.
PopoverIpopoversonounasortaditooltipestesoefornisconoun’areatitleecontent.
Similiaitooltip,sipossonoattivarealclic,all’hoveroalfocus:
<buttonclass=”btnbtn-primary”bs-popover=”popover”>Show
Popover</button>
Ilmodelloassociatoèidenticoperformatoaquelloutilizzatoperlafinestramodalepoichécontieneleproprietàtitleecontent:
$scope.popover={
title:‘Title’,
content:‘Popovercontent’
};
Ovviamentetuttoèmodificabilemediantegliattributidata.Laprossimatabellariportaunalistacompletadelleopzioni.
Nome Tipo Impostazionepredefinita Descrizione
animation stringa am-fade Applicaun’animazioneCSS.
placement stringa 'top'Posizionailtooltip:top/bottom/left/right,oqualsiasialtracombinazionecomebottom-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.Sevieneindicatounnumero,ilritardosiapplicasiaahidesiaashow.Lastrutturadell’oggettoèlaseguente:delay:{show:500,hide:100}
container stringa/false falseAggiungelafinestramodaleaunelementospecifico.Esempio:container:'body'.
template percorso false Sefornito,scavalcailtemplatepredefinito.
contentTemplate percorso falseSefornito,recuperailpartialeloincludecomecontenutoall’interno.
AlertAbbiamogiàvistocomeèpossibileutilizzarelefinestrediBootstrapperoffrireun
feedbackagliutenti.AngularStrapciconsentedievidenziarle,applicareuneffettoditransizioneopermettereagliutentidirimuoverle.Utilizziamoladirettivaalertaggiungendol’attributobs-alertaunelemento:
<buttonclass=”btnbtn-primary”bs-alert=”alert”>Show
Alert</button>
L’oggettodelmodellodefiniscenonsoloiltitoloeilcontenutomaanchelaclassecontextcheutilizzeremo.Puòesseresuccess,info,warningodangeremodificheràopportunamenteilcoloredellosfondoedeltesto:
$scope.alert={
title:‘Title’,
content:‘Alertcontent’,
type:‘success’
};
Definiremoconprecisionedoveverràaggiuntalafinestradiavviso;possiamoutilizzarel’attributodata-containerperdefinireunelementospecificoincuidesideriamovisualizzarla.Creiamounnuovoelementoincimaallapaginaperilcontenitore:
<divid=”alertContainer”></div>
Aggiungiamoloalpulsantemediantel’attributodata-container:
<buttonclass=”btnbtn-primary”bs-alert=”alert”
data-container=”#alertContainer”>ShowAlert</button>
Oraquandofaremoclicsulpulsante,incimaalloschermocompariràlafinestradiavviso.SulsitodiAngularStrapèdisponibilelalistaseguenteditutteleopzionidisponibili.
Nome Tipo Impostazionepredefinita Descrizione
animation stringa am-fade Applicaun’animazioneCSS.
placement stringa 'top'Posizionailtooltip:top/bottom/left/rightoqualsiasialtracombinazionecomebottom-left.
title stringa '' Valorepredefinitodeltitolo.
content stringa '' Valorepredefinitodelcontenuto.
type stringa 'info' Valorepredefinitodeltipo.
keyboard booleano trueChiudelafinestradiavvisoquandosipremeiltastoEsc.
container stringa/false falseAggiungelafinestramodaleaunelementospecifico.Esempio:container:'body'.
template percorso false Sefornito,scavalcailtemplatepredefinito.
UtilizzareiservizidiAngularStrapLamaggioranzadeimoduliinclusiinAngularStrapesponeancheiserviziper
l’applicazione.Possiamoutilizzarlipermostrareelementi,comefinestremodali,finestrediavvisoepopoversenzaesserecostrettiaricorrerealledirettive.
Vediamocomeutilizzareilservizio$alertpermostrareunafinestradiavvisodalcontroller.Ciserviremodelladirettivang-clicksuunpulsanteperavviarla.Creiamoinnanzituttounpulsanteeassociamoladirettivang-click:
<buttonclass=”btnbtn-success”ng-click=”showAlert()”>Alertvia
Service</button>
ImposteremorapidamentelafunzioneshowAlert()nelcontroller.Comeprimacosaoccorrecreareunafinestradiavvisoutilizzandoquestoservizio.Inseriamo$alertnelcontrollerecreiamounanuovaistanzadiunafinestradiavvisoconilcodiceseguente:
controller(‘demoCtl’,function($scope,$alert){
varalert=$alert({
title:‘AlertTitle!’,
content:‘Here\’ssomecontent.’,
type:‘danger’,
container:‘#alertContainer’,
show:false
});
});
Ilcostruttoredelservizioaccettaunafunzionehashseguendolostessopatternaccettatodalladirettiva.Quipossiamoincluderequalsiasiopzione,comeilcontenitorealqualedesideriamoaggiungerelafinestradiavviso.Perimpostazionepredefinitalafinestradiavvisochevienecreatacompariràautomaticamente.Pernasconderlaènecessarioincluderelaproprietàshoweimpostarlasufalse.
Infinenonrimanechedefinirel’handlershowAlert().L’istanzadellafinestradiavvisocreatadalserviziocioffretremetodidicuipossiamoservirci:show(),hide()etoggle().Utilizziamoshow():
$scope.showAlert=alert.show;
Sefacciamoclicsulnuovopulsante,lafinestradiavvisocompariràincimaallapagina(oovunqueabbiamoposizionatoilcontenitore)esicomporteràcomeprevisto.
IntegrareAngularStrapDopoavervistocomeèpossibileutilizzaremoltiplug-in,ènecessariorenderli
operativievivacizzarel’appdigestionedeicontatti.Utilizzeremoiplug-intooltipealertperfornireagliutentisuggerimentiefeedback.
SostituiamoinnanzituttoiltestodelsuggerimentosottolacasellaNotesnellavistaAddContactconuntooltip:
<textareaid=”notes”class=”form-control”ng-model=”contact.notes”
bs-tooltipdata-title=”Anyadditionalinformationaboutthe
contact.”data-trigger=”focus”
data-placement=”bottom”></textarea>
Invecedicreareunmodelloelegarloalladirettiva,hapiùsensosfruttarel’attributodata-titledisponibile.Inquestocasoabbiamopreferitocollocarlosottoeavviarloalfocus.
Potrebberoessereopportuneduefinestrediavviso.Unaèquellapreesistentedopol’aggiuntadiunnuovocontatto;l’altracomparedopoavercancellatouncontattonellavistaIndex.
Consideriamoprimalafinestradiavvisopreesistente.Sidevesostituirel’elementoalertconilcontenitorecreatoinprecedenza:
<divid=”alertContainer”></div>
Possiamoinserireilservizio$alerteprepararel’istanzadellafinestradiavvisoprimadivisualizzarlaall’internodellafunzionesubmit.Essaavràlaseguenteconfigurazione:
varalert=$alert({
title:‘Success!’,
content:‘Thecontactwasaddedsuccessfully.’,
type:‘success’,
container:‘#alertContainer’,
show:false
});
L’aggiungeremoall’alertContainercreatoinprecedenza.Inquestocasoilcontextrichiedeunmessaggiodisuccesso;pertantoabbiamoimpostatotypesusuccess.
Noncirestachemostrarelafinestradiavvisodopocheèstatocreatoconsuccessouncontatto:
$scope.submit=function(){
contacts.add($scope.contact);
$scope.contact=null;
alert.show();
};
Possiamoprocedereallostessomodoquandocancelliamouncontattoperoffriremaggiorefeedbackall’utente.Comeprima,collochiamoilcontenitorenellaposizioneincuivorremmochecomparisserolefinestrediavvisonellavistaIndex:
<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,noncirestachefarcomparirelafinestradiavvisoquandofacciamoclicsulpulsantedelete:
$scope.delete=function(index){
contacts.destroy(index);
deletionAlert.show();
};
Eccocomedovrebbeesserel’output:
Quiz1. DaqualemodulodipendeAngularStrap?2. QualèilnomedelprogettochepossiamoutilizzareperleanimazioniCSS
predefinite?3. Checosaènecessarioanteporreagliattributiperutilizzarlicomeopzioni
all’internodelledirettivediAngularStrap?4. Qualisonoiquattrosistemiconiqualièpossibileattivareunpopoveroun
tooltip?5. Qualisonoitremetodidisponibilipercreareun’istanzamedianteilserviziodi
alert?
RiepilogoInquestocapitoloabbiamovistocomeèfacileavvalersideinumerosimoduli
integratiinAngularStrap.AnchesenonpossonoutilizzaredirettamenteJavaScriptdiBootstrap,sonotutticomponentilegatiaquestoframeworkeoperanosenzasoluzionedicontinuitàall’internodell’applicazione.Abbiamoesaminatosoloalcunideiplug-indisponibilieilmodoincuièpossibileutilizzarlimedianteledirettive.Talvoltaunadirettivanonèlasoluzionemigliore;inalternativaèpossibilericorrereaiservizicompresiinAngularStrap.Nelcapitoloseguentevedremocomeèpossibileconnetterel’applicazionealserverperrecuperareememorizzareicontatti.
Capitolo8
Connessionealserver
Finoral’applicazioneèancorainteramentefront-endedè,pertanto,piuttostoinutile.Ènecessariomemorizzareicontattiperpoterlirecuperareinseguito.Aquestoscopociconnetteremoaunservercheospiteràun’APIRESTfulchegeneraJSON.
Angularoffrediversepossibilitàperconnettersialserver.Inquestocapitoloneesamineremoalcune,oltreaintrodurredellealternativechepotreteapprofondireinseguito.
Nonvedremocomesvilupparel’aspettorelativoallatoserver,chenonrientranegliambitidellanostratrattazione.Tuttaviaèpresentenellerisorsescaricabilidellibro.
Eccogliargomenticheaffronteremo:
comeestrarreidatidalservermediante$http;comeutilizzareedovetrovarengResource;lealternativedellacommunitytracuiRestAngular;integrazionenell’applicazionedellanuovaconnessioneconilserver.
Mettiamociall’opera.
Connettersicon$httpAngularincludegiàalcunimetodidibassolivelloperrecuperareeinviareidati.
Seaveteutilizzato$.ajax,$.posto$.getinjQuery,sareteavostroagio.
Comesapete,questimetodisonodisponibilisottoformadiunserviziocheèpossibileinserireneicontrolleroneiservizi.Diseguitopoteteosservareilservizio$httpinseritonelcontroller:
.controller(‘indexCtrl’,function($scope,contacts,$alert,
$http){
})
IlservizioincludealcunimetodichefunzionanocontuttiiverbidelprotocolloREST.Imetodiseguentisonodisponibiliall’internodi$http.
$http.get():accettaunURLeunoggettoconfigfacoltativo.EsegueunarichiestaHTTPGET.$http.head():accettaunURLeunoggettoconfigfacoltativo.EsegueunarichiestaHTTPHEAD.$http.post():accettaunURL,unoggettodataeunoggettoconfigfacoltativo.EsegueunarichiestaHTTPPOST.$http.put():accettaunURL,unoggettodataeunoggettoconfigfacoltativo.EsegueunarichiestaHTTPPUT.$http.delete():accettaunURLeunoggettoconfigfacoltativo.EsegueunarichiestaHTTPDELETE.$http.jsonp():accettaunURLeunoggettoconfigfacoltativo.IlnomedellafunzionedicallbackdovrebbeesserelastringaJSON_CALLBACK.$http.patch():accettaunURL,unoggettodataeunoggettoconfigfacoltativo.EsegueunarichiestaHTTPPUT.
Tuttiquestimetodisonoscorciatoieperlafunzioneprincipale$http(),cheaccettaunargomento:unoggetto.Lefunzioniprimamenzionateimpostanoilverboe/oiltipodicontenutocheintendiamorecuperare.
Peresempio,idueframmentidicodiceseguentisonoidentici,mailsecondoèmoltopiùleggibile:
$http({
method:‘GET’,
url:‘http://localhost:8000’
});
$http.get(‘http://localhost:8000’);
RecuperareidatièfacileeAngularbeneficiadeipatternPromisessviluppatidaPromises/A+eresipopolaridajQuery.Ilpatternciconsentedideterminarefacilmentesel’URLalqualeabbiamoavutoaccessoharestituitounarispostapositivaohageneratounerrore.
Puòsembrarecomplesso,mainsostanzasitrattadiunaseriedimetodichepossiamoconcatenarepercrearefacilmenteunapprocciotry/catchperlechiamateasincrone.Seracchiudiamoconsole.login$http.get(),vedremotuttiimetodidisponibilivisualizzatinellaconsole.
Tuttiimetodiaccettanoun’unicafunzionedicallback,conl’eccezionedithen,cheaccettaduemetodi,unoperilsuccessodell’operazioneeunaltroperl’errore.Vediamocomepossiamoutilizzarli.Nelcontrollerdell’index,sostituiamoallarigacontacts.get()ilseguentecodice:
$http.get(‘http://localhost:8000’)
.success(function(data){
$scope.contacts=data;
})
.error(function(){
window.alert(‘Therewasanerror!’);
});
Angularsioccuperàdelresto.Lafunzionedicallbackperilmetodosuccessvieneeseguitaquandovienerestituitouncodicedistatus2xx;altrimentivieneeseguitounerrore.
Avremmopotutoabbreviareilcodiceprecedenteutilizzandoilmetodotheneduefunzionidicallback,nelseguentemodo:
$http.get(‘http://localhost:8000’)
.then(function(result){
$scope.contacts=result.data;
},function(){
window.alert(‘Therewasanerror!’);
});
QuestofaperòrisparmiarepococodiceedèmenoleggibileperaltrisviluppatorichepotrebberononconoscereAngularJS.Notatechedatanonèl’argomentopassatoallefunzionidicallbackall’internodelmetodothen;otteniamoinveceunoggettocontenentedati,statoeheader.
InviareidatiComeilrecuperodeidati,illoroinviomediante$httpèmoltofacileesimile
all’implementazionedijQuery.Lafunzione$http.post()sicomportanellostessomododi$http.get()maaccettaunsecondoparametro:unhashcontenentetuttiidaticheintendiamoinviarealserver:
$http.post(‘http://localhost:8000’,{
name:‘DeclanProud’,
email:‘[email protected]’,
...
});
Analogamente,ilmetodopostrestituisceancheunapromiseconglistessimetodiesaminatiprima:
$http.post(‘http://localhost:8000’,{
name:‘DeclanProud’,
email:‘[email protected]’,
...
})
.success(function(){
...
})
.error(function(){
...
});
ConnettersiconngResourceGlihelperdellaconnessionedibassolivellocome$httpsonoottimiper
connessionisingole,madiventanobenprestoscomodiperlagestionediuninteroprogetto.FortunatamenteAngularoffreunaltrosistemaconilqualeaccedereaidatilatoservergrazieaunmodulofacoltativochiamatongResource.
IncluderengResourceComengRoute,ilmodulongResourcesitrovaallinkExtrasnellafinestramodaledi
downloadall’indirizzohttps://angularjs.org/.Scaricateloetrascinatelonelladirectoryjsdelprogetto.IncludetelodopoAngularnelfileradiceHTML:
<scripttype=””text/javascript””src=”/assets/js/angular-
resource.min.js”></script>
VerificatecheilmodulocontactsMgrsappiachengResourceèunadipendenza:
angular.module(‘contactsMgr’,[‘ngRoute’,‘ngSanitize’,
‘mgcrea.ngStrap’,‘ngResource’])
ConfigurarengResourceIlmoduloesponeilservizio$resourcecheèpossibileinserireneicontrolleronei
servizi.Imetodiinclusisonodiunlivellopiùalto,einfattiutilizzano$httpperinteragireconilserver.Inseriamoilservizio$resourcenelserviziodigestionedeicontatticreatonelCapitolo7evediamocomeconnettersialserver:
.factory(‘Contact’,functionContactFactory($resource){
...
})
Ilservizioincludeunmetodocheutilizzeremoperimpostarelaconnessione.Restituiscealcunefunzionisottoformadiunoggettoresource.Sarannoquestefunzioniarecuperareeainviareidatialedalserver.
Vediamocomepossiamoservircidiquestometodosingoloeciòcherestituisce:
varResource=$resource(‘http://localhost:8000/contacts/:id’,
{id:‘@id’});
Ilcodicerestituiràl’oggettoseguente,consistenteinazionicheèpossibileutilizzareperrecuperare,salvareocancellareidati:
{
‘get’:{method:’GET’},
‘save’:{method:’POST’},
‘query’:{method:’GET’,isArray:true},
‘remove’:{method:’DELETE’},
‘delete’:{method:’DELETE’}
};
Ilprimoparametroèlaradicedellarisorsasulserver.Peresempio,sestessimosviluppandounsistemadiblogging,avremmoalcunerisorsecomepost,tageautori.Èanchepossibileaggiungeredeisegnaposto,comepotremmofaredurantelacreazionediunaroute.
Ilsecondoparametroèunhashcheincludeivaloripredefinitideisegnaposto.Seilvalorepredefinitodiunsegnapostopresentasseilprefissocontenenteilsimbolo@,quelvaloresarebberecuperatodall’oggettodatachevienepassatoquandoaccediamoalserver.
Possiamoanchepassareunterzoparametroperestendereleazionipredefinitechevengonorestituite.AggiungiamounmetodoupdatecheutilizzeràilverboPUTperaggiornareuncontattoesistentesulserver:
varResource=$resource(‘http://localhost:8000/contacts/:id’,
{id:‘@id’},{
update:{method:‘PUT’}
});
Comepotetevedere,sitrattadiunoggettoJSstandardincuièpossibiledefiniremoltepliciazionipersonalizzate.Esistonoalcunielementicheèpossibileincludereall’internodell’oggettoconfigurationassociatoall’azione,maprobabilmentesarànecessarioimpostaresoltantomethodeisArray.LaproprietàmethodselezionaqualeverboHTTPènecessarioutilizzare(inquestocasoPUT),eisArrayèunbooleanocheserveaindicareangResourceseilserverrestituiràununicoitemounarraydiitem.
RichiederealserverSiamoriuscitiaconfigurarengResource;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.Noncirestacherieseguirelachiamata$httpinindexCtlalmetodoContact.get():
$scope.contacts=Contact.get();
Siccomenonutilizzeremopiùunarrayditipohard-code,nonpossiamoaccedereasingolicontattiservendocidiunindice.LamaggiorpartedelleAPIrestituisceunIDdegliitemequestanonfaeccezione.Modifichiamo{{$index}}dang-repeatutilizzatonellink,peravvalercidell’IDinvecechedeisingolicontatti;dovrebbetrovarsiall’incircaariga23nelfilepartials/index.html:
<ahref=”/contact/{{contact.id}}”class=”btnbtn-defaultbtn-
xs”>View</a>
Ènecessariomodificareilmetodofindnelserviziodeicontattiperrecuperareununicocontattoinbaseall’IDaessoassegnato.Abbiamogiàimpostatolarisorsaperammettereunparametroid;noncirestachepopolarloquandoutilizzeremoilmetodogetdellarisorsa:
find:function(id){
returnResource.get({id:id});
},
Abbiamoanchemodificatoilnomedelparametrodaindexaidperrenderlopiùleggibilesequalcunaltrodovesselavorareinseguitoalprogetto.
InviarealserverQuestoèciòchevienerecuperatodalserver,mainchemodopossiamocreareun
nuovocontattooaggiornarneunoesistenteconngResource?EsaminiamoprimacomecrearneunoeosserviamocomengResourcegestiscequestoaspetto.Modificheremoilmetodocreatenelserviziodigestionedeicontattiperrestituireunanuovaistanzadellarisorsa:
create:function(){
returnnewResource();
},
QuestodiventeràilmodellonellavistaAddContact.Sicomportacomeprevisto,madàaccessoaunmetodo$saveperpassarloalserver.Chiamiamoilnuovometodocreateeassegniamoloalmodellocontactall’internodiaddCtl:
$scope.contact=Contact.create();
Tuttodovrebbecomportarsicomeprevistoquandocarichiamolavista;oraènecessariosostituirealmetodocontacts.set,ormainonpiùattivo,l’handlersubmitperlanuovafunzione$saveoffertadallarisorsa:
$scope.submit=function(){
$scope.contact.$save();
$scope.contact=Contact.create();
alert.show();
};
Abbiamoanchemodificato$scope.contact,chenonvienepiùeliminatomarecuperaunanuovaistanzadellarisorsadalservizio.
Analogamentepossiamoutilizzarelastessaazioneupdatecreatainprecedenzapersalvarelemodifichediuncontattoesistente.Aquestoscopoènecessarioservirsidiuneventopersonalizzatoinmodocheilcontrollersappiaquandosalviamolemodificheall’internodelladirettivamodificabile.GlieventipersonalizzatisicomportanopropriocomegliomologhinativiJavaScript,alparidiclickemouseover.Èpossibilemettersiinascoltoedeseguiredelleazioniquandovengonoattivati.
Percreareuneventopersonalizzato,utilizziamoilmetodo$scope.$emit.Accettadueparametri:ilnomedell’eventoel’arraydiparametricheintendiamopassareallistener.Inquestocasononènecessariopassarealcunparametro;chiamiamosemplicementel’eventosalvatoeinseriamolonellafunzione$scope.saveall’internodelladirettivamodificabile:
$scope.$emit(‘saved’);
Ascoltareuneventoèaltrettantofacile:èsufficienteutilizzareilmetodo$scope.$on.Essoaccettadueparametri:ilprimoèilnomedell’eventodaascoltare,ilsecondoèlafunzionedell’handler.AggiungiamoilseguentecodicealcontrollercontactCtl:
$scope.$on(‘saved’,function(){
...
});
Seavessimopassatodeiparametriall’evento,sarebberostatiaccessibilicomeparametrinellistenerdeglieventieilprimosarebbesemprestatol’eventoJS.
Illistenereseguiràilmetodo$updatesulmodellocontact.Tuttavia,siccomel’eventovieneemessoprimacheilmodelloabbiaconclusol’aggiornamento,dobbiamospingerloallafinedellostackodellacodacorrente.SeconosceteJavaScript,sapretecheèpossibileutilizzaresetTimeout.Èpropriociòchefaremo,mainvecediservircidisetTimeout,opteremoperilserviziowrapperdiAngular:$timeout.
Dobbiamoinserirlonelcontroller:
.controller(‘contactCtrl’,function($scope,$routeParams,Contact,
$timeout){
...
})
PoisitrattasemplicementediutilizzarlocomesetTimeout:
$scope.$on(‘saved’,function(){
$timeout(function(){
$scope.contact.$update();
},0);
});
Orasemodifichereteuncontattoefareteclicsulpulsantesave,idativerrannosalvatisulserver.
CancellareicontattiInfine,noncirestacheimpostareilpulsantedelete.Modifichiamoilmetodo
all’internodelserviziodeicontatti:
destroy:function(id){
resource.delete({id:id});
}
Inquestocasochiamiamoilmetododeletesullarisorsa;volendoavremmopotutoutilizzareremovechesvolgelastessaoperazione.Abbiamonuovamentemodificatoilnomedelparametroindexperrenderlopiùleggibile.
Dobbiamoancoracompiereun’operazioneinquestaistanza:aggiornarelafunzionedeletenelcontrollerindexCtl.Analizziamoallorailmetodofinito:
$scope.delete=function(index){
Contact.destroy($scope.contacts[index].id);
$scope.contacts.splice(index,1);
alert.show();
};
Siccomedobbiamosiaeseguireilpingdelserversiarimuoverlodall’arraylocale,ènecessariocontinuareautilizzareindex.NellachiamataContact.destroy()accediamoalcontattoopportunoerecuperiamoilsuoID.Locancelliamodirettamentedall’arraylocaleutilizzandoilmetodoJSnativospliceperfareinmodochetuttosiasincronizzato.
GestionedeglierroriÈpossibilegestireglierroricomefaremmocon$http.Tutteleazionicheabbiamo
esaminatoaccettanoduefunzionidicallback:unaperilsuccessoel’altraperl’errore.Eccoilmetodogetconincluseentrambelefunzionidicallback:
returnResource.get({id:id},function(){
window.alert(‘Success!’);
},function(){
window.alert(‘Error!’);
});
Cosìpossiamoinformarel’utentecheesisteunproblemaoeseguirealtreazionisenecessario.Siccomelofacciamodaunservizio,èopportunoincluderedueparametriperconsentirechequestefunzionidicallbackvenganoimpostatedalcontrollerquandovienechiamatoilmetodoinquestione:
find:function(id,success,error){
returnRgesource.get({id:id},success,error);
},
SistemialternatividiconnessioneAbbiamogiàesaminatoalcunisistemiconiqualièpossibileconnettersialservere
configurarel’apppersfruttarengResource.Esistonoaltrimodulicheèpossibileutilizzareperconnettersiaunserver;neconsidereremorapidamentedue.
RestAngularRestAngularèunprogettodellacommunitycheoffreunservizioperconnettersi
adAPIRESTful,comengResource.Presentaalcunedifferenzesignificativecheèopportunoconoscere.
L’aspettopiùimportantedatenerepresenteècheRestAngularutilizzapromisepropriocome$http.Pertantosiaccedeaunpatternperdeterminareseunachiamatahaavutosuccessoomeno,maquestosignificacheesistonopassiaggiuntividacompiereechenonèpossibileassegnarlosemplicementeaunmodello.
Ancheseènecessarioscrivereunpo’piùdicodiceacausadell’utilizzodellepromise,nondovretetrascrivereisegnapostoseguendoilpatternREST;RestAngularlofaràalpostovostro.
PreferiamongResource.RestAngularrichiedealcunipassaggiinpiù,mentrengResourcefunzionaperfettamenteconinostriservizi.TuttaviavalesemprelapenasperimentareciòchepotrebberivelarsiefficaceperleproprieesigenzeequindiconsigliamodiprovareancheRestAngular.
UtilizzareRestAngular
ÈpossibilescaricareRestAngulardalsitohttps://github.com/mgonto/restangulareincluderlocomequalunquealtromodulo.Vedremorapidamentecomeimpostarloeinchemodoèpossibileottenereunalistadeicontatti.
ApprezziamolacapacitàdiRestAngulardiimpostareunURLdibase.ÈpossibiledefinirlonelmetodoglobaleconfigdelmodulotramiteilservizioRestangularProvider:
.config(function($routeProvider,$locationProvider,
RestangularProvider){
RestangularProvider.setBaseUrl(‘http://localhost:8000/’);
})
Dopoaverimpostatol’URLdibase,possiamoutilizzareRestAngularnominandolarisorsaallaqualeintendiamoaccedereeunmetododiRestAngular:
Restangular.all(‘contacts’).then(function(contacts){
$scope.contacts=contacts;
});
Comepotetenotare,RestAngularsibasasulpatterndellepromiseutilizzatoda$httpedènecessarioeseguirel’unwrappingperassegnarealmodelloidatirestituiti.
LeggeteladocumentazionediRestAngularsuGitHubperunalistacompletadeimetodicheèpossibileutilizzare.
FirebaseFirebaseèunserviziorelativamentenuovocheconsentedicrearefacilmente
un’applicazioneintemporealesenzascrivereunasolarigadicodicediback-end.QuandosioperaconAngular,l’aziendaoffreun’utilelibreriadihelperpersincronizzarefacilmenteidaticonilsuoservizio.
LadashboarddiFirebaseconsentedivisualizzareidatiinunastrutturaadalberocomprimibilesimileaquellamostratasuccessivamente.
Dopoavercreatounaccountall’indirizzohttp://firebase.comeconfiguratol’app,ègiuntoilmomentodiimpostareAngularFire.DobbiamoincludereilclientFirebaseeAngularFire,chesipossonotrovareall’indirizzohttp://angularfire.com.
ÈmoltofacilerecuperareidatidaFirebase,considerandochetuttoavvieneintemporealeequalsiasimodificacompiutaaltrovesirifletteautomaticamentenell’applicazione.Comelamaggiorpartedeimoduli,AngularFireesponeunservizio,inquestocaso$firebase.Possiamoinserirloneicontroller,nelledirettiveoneiservizi,nelseguentemodo:
.factory(‘Contact’,functionContactFactory($resource,$firebase){
...
})
ÈpossibileavviarelaconnessioneconFirebaseutilizzandoilsuoclientJS:
varcontacts=new
Firebase(“https://<yourbase>.firebaseio.com/contacts”);
IlservizioAngularFirefacilitailrecuperodeidatidaFirebase.Questoèciòchepotremmofareconilmetodogetdelnostroservizio:
get:function(){
return$firebase(contacts);
},
Ancheaggiungereicontattièmoltofacile.Dopoaverrecuperatoidati,otteniamol’accessoaimetodi$add,$removee$update:
$firebase(contacts).$add({
name:‘DeclanProud’,
...
});
SeapriteilpannellodicontrollodiFirebaseeaggiungetemanualmenteuncontatto,vedretechequestocompariràautomaticamentenellalistadeicontatti.Ovviamenteèesageratoperun’appcomequestachegestisceicontatti,maapreinfinitepossibilitàperclientdichat,notificheealtroancorasenzaesserecostrettiascrivereunasolarigadicodicediback-end.
Quiz1. Chetipodioggettorestituisceilmetodo$http?2. Inchemodoèpossibileottenereunarraydicontattieassegnarliaunmodello
con$http?3. Checosasignificailsimbolo@inunaconfigurazionediparametripredefinita?4. IndicateleduedifferenzeprincipalitrangResourceeRestAngular.5. InchemodoFirebasecreal’applicazione?
RiepilogoInquestocapitoloabbiamotrasformatol’applicazionedaun’appfront-endche
utilizzavadatiditipohard-codeinunachesiinterfacciaconun’APIpermemorizzareerecuperareleinformazioni.AbbiamoscopertocomeèflessibileAngularesaminandoquattrodiversimetodiperconnettersiaunserver.
Iservizidibassolivellocome$httpsonoottimiinalcunicasi,maperlosviluppodiun’applicazionecompleta,abbiamovistocheèopportunoutilizzarequalcosadipiùraffinato.ModulicomengResourcemantengonolabasedicodicegestibileerispondentealprincipioDRY(Don’tRepeatYourself).
Nelprossimocapitoloapprofondiremoquestoconcettoanalizzandoduerunnerdicodice:Gruntegulp.
Capitolo9
Itaskrunner
Ilprogettohaunbell’aspetto,manonèefficiente.AbbiamoinclusodiecifileJavaScript,checomportanodiecirichiestedirete,senzaconsiderareilfogliodistile.Ciòsignificachelapaginaimpiegheràpiùtempoacaricarsi.Alterminedelcaricamento,ilbrowserdovràrecuperareognifileJavaScriptecompilarlo.
Potremmoprenderemanualmentequestifileeconcatenarliinunosolo.Tuttavialavorandoalprogettoèprobabilechecontinueremoaeffettuarealtremodifiche,esarebbeirritanteripeterecontinuamentequestoprocesso.
Itaskrunnersonounottimosistemaperautomatizzareattivitànoiose.Nondovremopiùconcatenareeminificaremanualmente,maavremoun’installazionechecontrolleràlemodificheneifileelicreeràautomaticamente.
Anchesenonliavetemaiutilizzati,probabilmenteavretesentitoparlareditaskrunnercomeGruntegulp.InquestocapitololimetteremoentrambiallaprovaperconcatenareeminificareifileJavaScriptinununicofile.
InstallareNodeeNPMSiaGruntsiagulpsibasanosuNodeeilsuoNodePackageManager(NPM).Se
avetegiàinstallatoeconfiguratoNode,tralasciatequestoparagrafo.Esamineremol’installazionesuMac,mailprocessoèsimileperWindows.GliutentiLinuxdovrannocompilaredalfilesorgenteovisitarehttps://github.com/joyent/node/wiki/Installing-Node.js-via-package-managerperinstallarlotramiteungestoredipacchetti.
Andateall’indirizzohttp://nodejs.org/download/escaricatel’installerappositoperlapiattaforma.Apritelo,accettateilcontrattodilicenzaecompletatelaprocedura.
Setuttosisvolgecomeprevisto,dovrestevedercomparireunmessaggiosimile:
Nodewasinstalledat
/usr/local/bin/node
npmwasinstalledat
/usr/local/bin/npm
Verificateche/usr/local/binsitroviin$PATH.Senonsietesicurichesitrovinelvostropercorso,eseguitequestocomandodaterminale:
echo$PATH
Cercate/usr/local/bin.Senonlotrovate,aggiungetequantoseguea~/.bash_profileoppure~/.zshrc:
exportPATH=/usr/local/bin:$PATH
AllafinedellaproceduraNodeeNPMsarannoinstallati.Potreteaccedereaicomandinodeenpm,evisaràpossibileinstallareGruntogulpnelprogetto.
NOTA
Potrebbe essere necessario riavviare la sessione del terminale per far funzionare tutto comeprevisto.
UtilizzareGruntDopoaverinstallatoNode,sfruttereteGrunt.Laconfigurazioneavvieneintrefasi:
lostrumentodarigadicomando,l’installazionelocalediGruntnelprogettoelaconfigurazionedelGruntfile.
Installarel’interfacciaarigadicomandoInstallarelacommand-lineinterface(CLI)èmoltofacilegrazieaNPM.È
sufficienteeseguirequantoseguenelterminale.Ilflag–gverificheràl’installazioneglobalediGrunt:
npminstall-ggrunt-cli
Asecondadeipermessi,potresteeseguirlocomeroot.SusistemiOSXobasatisu*nix,sitrattadieseguirloconilprefissosudo.InWindows,ènecessarioaprirelashelldicomandocomeamministratori.
Alterminedell’installazione,ilcomandogruntsaràdisponibileeverràaggiuntoalpercorsodelsistema,epotràvenireeseguitodaqualsiasidirectory.
InstallareGruntPerutilizzareGruntnelprogettoènecessarioaggiungereduefile:package.jsone
Gruntfile.js.
Ilfilepackage.jsonnonvieneutilizzatodaGrunt,madaNPM.Indicaalgestorequalipacchettiservonoalprogettoquandoeseguiamol’installer.Gruntfile.jsèciòcheconfiguraGrunt.Forniscediverseindicazionialtaskrunner,daqualifileconsiderareaqualiattivitàeseguire.
Creareunfilepackage.json
Creiamoilfilepackage.jsonaffinchéNPMsappiaqualifilerecuperare.DiseguitoilfileJSONcompletato.IlNodePackageManagerciconsentiràdicrearlofacilmenteeseguendoilcomandonpminit:
{
“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,mautilizzatisoltantoquandodistribuiamoilprogettosottoformadiunpacchettosuNPM.Laproprietàdependenciesèilpuntoincuiavvengonoprocessiinteressanti.
Gruntsitrovaincimaallalistadelledipendenze.Glialtripacchetticheabbiamoinclusosvolgerannolapartepiùrilevantedellavoro.Ilpacchettogrunt-contrib-uglifyconcateneràeminificheràifileJSel’ultimopacchettonellalistacontrolleràifileallaricercadimodificheedeseguiràattivitàspecifiche.
NOTA
Èimportantericordarecheilnomedelpacchettonondevecontenerespaziocaratterispeciali.
CreareilfileGruntfile.js
Gruntfileèmoltoimportante.ConsideratelocomeilmanualediistruzionidiGrunt,chesenzadiessononsaprebbechecosafare.Tuttalaconfigurazioneavvieneall’internodellaseguentefunzionewrapperdiGrunt:
module.exports=function(grunt){
};
NOTA
ÈimportantecheGruntfile.jsvengasalvatonellaradicedovesivuoleeseguireGruntecheilnomedelfileiniziconlaGmaiuscola.
Lamaggiorpartedeiplug-inrichiederàl’utilizzodelmetodoinitConfigdiGrunt,edèciòcheutilizzeremoconilplug-inuglify:
module.exports=function(grunt){
grunt.initConfig({
...
});
};
All’internopossiamoconfigurareiplug-inutilizzandoilnomecomechiave.Possiamoancheottenereleinformazionidirettamentedalfilepackage.json,comeilnomedautilizzarenelleattività:
grunt.initConfig({
pkg:grunt.file.readJSON(‘package.json’)
});
QuestocodicecaricheràilfileJSONeloassegneràallachiavepkg,consentendocidiaccedereaqualsiasiinformazioneimpostatainprecedenzaavvalendocidellasintassistandardperitemplate(<%=%>).
Quandoconfigureremol’attivitàuglify,imposteremodueproprietà:optionsebuild.Laproprietàoptionsconsentedidefinireelementicomeibannercheintendiamoincluderenelfilecompilato,creareunamappadeifilesorgenteoseintendiamoconcatenareperildebugging.
Laproprietàbuildèiltargetepossiamoattribuirlequalsiasinomedesideriamo.Peresempio,unapotrebbechiamarsideveun’altraproductioncondiverseopzioni.Puòaccettareleproprietàsrcedest,checiconsentonodiimpostarequalifilevengonoinclusiequaligenerati.Èanchepossibiledefinireunaltrooggettooptions,utilequandointendiamoutilizzaremolteplicitarget.Eccol’attivitàuglifyconl’hashoptions:
grunt.initConfig({
pkg:grunt.file.readJSON(‘package.json’),
uglify:{
options:{
banner:‘/*!<%=pkg.name%><%=
grunt.template.today(“yyyy-mm-dd”)%>*/\n’
}
}
});
Inquestocasoabbiamoinclusoilbannernell’oggettooptions.Siccomeilfilepackage.jsonèstatoconvertitoinunoggettoJS,èsufficienteutilizzarelasintassistandardperaccederealnome.Gruntcomprendeancheduehelperdicuipossiamoavvalerci.Vedretecheinquestocasoricaviamoladataodierna,maèanchepossibileutilizzareilmetodogrunt.template.dateperformattareuntimestampJS.Puòessereutilequandointendiamoincludereladatainunbanneroinunnomefile.
Oraimpostiamoiltarget.Laproprietàsrcpuòessereunastringaounarray.Inquestocaso,siccomeutilizziamoalcunifileJS,dovremoservircidiunarray.LaproprietàdestèilpercorsorelativoalfilechevogliamocheGruntcreiperimpostazionepredefinita,mapuòancheesseremodificatomedianteilmetodogrunt.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’
}
}
});
Abbiamoutilizzatoilnomedelpacchettocomenomefileetuttiifileminificatisonostatisostituiticonleversioninonminificate.Poichéminificheremotutto,ènecessarioutilizzareleversionidisviluppodeifileperevitareproblemidurantelacompilazione.
L’ordinedeifilenell’arraysrcèlostessoconcuisarannoinclusinelfiledidestinazione.SiccomeabbiamobisognochejQuerysiainclusoprimadiAngulareAngularprimadeimoduli,èimportanteimpostarlibene.
Ilplug-inècompilato,maGruntnonsacheintendiamoutilizzarel’attivitàuglifycheabbiamoscaricatoinprecedenzadaNPM.AquestoscopoènecessarioutilizzareilmetodoloadNpmTasks:
grunt.loadNpmTasks(‘grunt-contrib-uglify’);
Siinseriscenellafunzionemodule.exportsefainmodocheilGruntfilecompletosiacomeilseguente:
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.jscompleto.
Sesostituiteaidieciscriptilnuovofilenellaradiceindex.htmlecaricatel’applicazione,noteretechenonfunzionanullaevedreteilseguenteerroredellaconsole:
Error:[$injector:unpr]Unknownprovider:a
Comepartedelprocessodiminificazione,inomidellevariabilivengonosostituiticonleloroversioniabbreviate.SappiamocheAngularsibasamoltosulladependencyinjection,cheosservailnomedellavariabileperinserireilserviziocorrettoneicontrollerenelledirettive.
FortunatamenteAngularoffreunasoluzionerapidaefacile.Sitrattadicambiarelefunzioninellequaliinseriamoiservizi,congliarraycontenentiinomideiservizicheintendiamoinserireelafunzionecomesuoivalori.Eccocomeappareconfigdelmodulo:
.config([‘$routeProvider’,‘$locationProvider’,
function($routeProvider,$locationProvider){
...
}])
Finchélafunzioneèl’ultimanell’array,Angularesamineràlevariabilieutilizzeràilserviziocorrispondentedell’array.Gruntnonmodificheràilvaloredegliitem
nell’arraypoichésitrattadistringheenondinomidivariabili;èquindiimportantechel’ordinenell’arraycorrispondaaciòchevieneinseritonellafunzione.
Èsufficienteaggiungerequestiwrapperperl’arraynelfilecontroller.jspoichéatuttelelibrerieeaimodulicheabbiamoutilizzatoègiàstatoapplicatoquestoprocesso.
Ricordatecheancheicontrollernelledirettivedevonoutilizzarelanotazionedegliarray.
Impostarewatch
SiamoriuscitiaconfigurareGruntpercompilareifileefunzionabenissimo.TuttavianonsarebbepiùunprocessoautomaticosedovessimoeseguiregruntuglifyognivoltacheintroduciamounamodificaneifileJavaScript.Gruntpuòtenered’occhiopernoiquestiaspettiedeseguireautomaticamentealcuneattivitàquandoapportiamodellemodificheaifile.
Aquestoscopoutilizziamoilpacchettogrunt-contrib-watchcheabbiamorecuperatoprimadaNPM.Laconfigurazioneèmoltosempliceerichiedesolodueproprietà:filesetasks:
watch:{
files:[
‘assets/js/*.js’
],
tasks:[‘uglify’]
},
Utilizziamol’asteriscocomecaratterejollyaffinchéGruntindividuiifile.jsnelladirectoryassets/js.Possiamocollocarequanteattivitàdesideriamonell’arraytask,everrannoeseguiteinordine.
L’esecuzionedigruntwatchnelTerminalefaràinmodocheGruntcontinuiavenireeseguitoinbackground.Nonappenaunfilevienemodificato,entrainazioneedeseguel’attivitàuglify,concatenandoeminificandoifileJavaScript.
Crearel’attivitàpredefinita
Spessoavretebisognodieseguiremoltepliciattivitàinunavoltasola.Gruntviconsentedifarloregistrandol’attività:
grunt.registerTask(‘default’,[‘uglify’]);
Ilprimoparametroèilnomedell’attività,mentreilsecondoèl’arraydelleattivitàchedesideriamoeseguire.L’utilizzodellaparolachiavedefaultcomunicaaGruntche
questaèl’attivitàdaeseguirequandononnespecifichiamouna.Peresempio,potremmoeseguirel’attivitàdefaultinunodeiduemodiseguenti:
gruntdefault
grunt
UtilizzaregulpGulpèabbastanzanuovoeprendelemossedaGrunt.Acausadellasuavita
relativamentebreve,nonesistonomoltiplug-indisponibili.Tuttaviac’èuglifyeilloronumeroèincontinuacrescita.IlvantaggioèchegulpmiraasemplificarelaconfigurazioneeaeseguireleattivitàpiùvelocementediGrunt:spettaavoideciderequaledeidueèpiùadattoalvostroprogetto.
ComeGrunt,gulpècostituitodadueparti:lostrumentodarigadicomandoglobaleel’installazionelocalecheincluderemonelprogetto.
InstallaregulpglobalmenteÈmoltofacileinstallaregulpglobalmentemedianteununicocomandoNPM:
npminstall-ggulp
PotrestedoverloeseguirecomeradiceutilizzandosudooattraversoilpromptdeicomandidiWindowscomeamministratori.
Alterminedell’installazione,ilcomandogulpsaràdisponibiledalterminale.
InstallareledipendenzedigulpPropriocomeconGrunt,ènecessariocreareunfilepackage.json,checonterràtutte
ledipendenzedelprogetto.Iniziamoainstallaregulp:
{
“name”:“ContactsMgr”,
“version”:“1.0.0”,
“description”:“AsimplecontactsmanagerinAngularJS+
Bootstrap”,
“dependencies”:{
“gulp”:“~3.6.0”
}
}
Possiamoaggiungeremanualmenteuglifyalfilepackage.json,oppurericorrereaNPMperquestaoperazione:
npminstall--save-devgulp-uglify
Ilflagsave-devindicaaNPMchevogliamocheloaggiungaalfilepackage.json.Inalternativapotremmoutilizzareilflag--save,masiccomegulpvieneusatosoltantoinfasedisviluppo,nonciservedurantelaproduzione.
Adifferenzadelplug-inuglifydiGrunt,nonconcateneràifileedovremoricorrereaunaltroplug-inperquestoscopo:
npminstall--save-devgulp-concat
ImpostaregulpfileAdifferenzadiGrunt,ilfilegulpfilenondeveiniziareconlaletteraGmaiuscola,
ancheseospitalaconfigurazioneesicollocanellaradicedelprogetto.Seilconcettoallabasedelfileèsimile,laconfigurazioneèmoltodiversa.
RicorderetecheGruntfilerichiedevaunafunzionewrapperperpoteraccedereatuttiimetodiGrunt.Congulpèunpo’differenteeognipacchettocheotterremograzieaNPMpotrebbeesserenecessarionelfile.Includetequantosegueall’iniziodelgulpfile.
vargulp=require(‘gulp’);
varuglify=require(‘gulp-uglify’);
varconcat=require(‘gulp-concat’);
varpkg=require(‘./package.json’);
Possiamoancheincludereleinformazionidelfilepackage.jsonrichiedendolonellostessomododiunpacchettoNPM.Icaratteri./all’inizio,indicanoaNodediguardarenellastessadirectorydelgulpfilequandoeseguiamogulp.
Inquestocasononesistealcunoggettodiconfigurazione,equestosemplificalecose.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àeunafunzioneanonimachecontienetuttociòchefaràl’attività.
Abbiamoinclusounavariabileingulp.src.Cosìlapotremoutilizzareinseguitoeoffrirà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.SitrattadellostessoarraydifilecheabbiamoinclusoprimainGruntfile.
Gulputilizzaglioperatoripipeperelaborareidati.Tuttiipacchettiaiqualiabbiamofattoriferimentoall’iniziodelfilegulpfilesonofunzioni.Ilplug-inconcataccettailnomedelfilecheintendiamogenerarecomeoutput.Abbiamorecuperatoilnomedalfilepackage.jsoneaggiuntol’estensione.js.Ilplug-inuglifyoffreunnumerodiopzionichepossiamopassarecomeunhashJSperfacilitareildebugging,eilmetodogulp.destcheutilizziamociconsentediimmettereilnomedelladirectorydell’output.
Dopoaverimpostatol’attività,potremmoeseguiregulpuglify,maprimaènecessarioimpostareanchewatch.AdifferenzadiGrunt,nonènecessarioincludereunulterioreplug-inperquestoscopo,poichésipresentacomeunaltrometododigulp:
gulp.task(‘watch’,function(){
gulp.watch(paths.js,[‘uglify’]);
});
L’impostazioneèmoltosemplice.Creiamounanuovaattivitàeutilizziamoilmetodogulp.watch,attraversandol’arraydifilecheintendiamoosservareepoil’arraydiattività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’]);
OrapossiamoeseguiregulpuglifyogulpnelterminaleperconcatenareeminificareifileJavaScript.Aparteunsingoloutilizzo,possiamoancheeseguiregulpwatchperverificarel’esistenzadieventualimodificheedeseguireautomaticamentel’attivitàuglify.
RiorganizzareilprogettoDopoaverimpostatoiltaskrunner,èilmomentodiriorganizzareilprogettoper
ottenereunabasedicodicepiùgestibile.Separeremoicontroller,ledirettive,ifiltrieiserviziinfiledistintiperteneretuttoinordine.
Iniziamoaelaborareunanuovastrutturadidirectorycomeillustralaseguentefigura.
Abbiamospostatotuttofuoridelladirectoryassets/jsnelladirectoryappdellaradice.Quandopasseremoallaproduzione,nonintendiamodistribuireifilesorgente,quindièunabuonaideatoglierlidalladirectoryassetsdoverimarrannoifilecompressi.
Lanuovadirectoryappèstatastrutturatainmodoleggermentediverso.Cisonotrecartellecomponents,vendoreviewseunfilemodule.js.Icomponentisonoitemcondivisi,quindinelcasodell’app,collochiamoquiledirettiveeiservizi.LacartellavendorcontienetuttiifileJSditerzepartimentrelacartellaviewstuttiiprincipalicontrollerperleviste.
Dopoavercreatolenuovedirectory,possiamoiniziareaseparareicontrollerindiversifileperincluderlinelladirectoryviews.Devonoancoraessereassociatialmoduloeaquestoscopopossiamoutilizzareladichiarazioneangular.module(‘contactsMgr’)all’iniziodelfile.
Ecco,peresempio,ilcontrollercontactCtl;nominatelocontact.jseinseritelonellacartellaviews:
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.Lesposteremonellacartellacomponentsconlanuovadirectoryappdellaradice.Cosìcomeconicontroller,ènecessarioverificarechesianoancoraassociatealmodulo.
Copiateognidirettivainfileseparatieassegnateaessel’estensione.directive.js.Peresempio,ilgravatarsitroverànellacartellacomponentsesaràgravatar.directive.js,mentreladirettivamodificabilesaràeditable.directive.js.
Siccomeifiltrisonoanch’essicomponenticondivisi,possiamocollocarliinsiemeconledirettiveenominarliallostessomodo.Posizionateiduefiltriinfileseparati:truncate.filter.jsenewLine.filter.js.L’ultimocomponenteèilserviziocontactscheutilizziamoperconnettercialserver.Createunnuovofilecontacts.service.jsecopiatelo.
Dopoaversistematovisteecomponenti,dobbiamoverificarecheilnuovofilemodule.jscontengaciòchedovrebbe,ossiailmodulo.CopiateilcontenutodelfilecontactsMgr.jsdalladirectoryassets/jsnelfilemodule.js.Dopoaverspostatotuttiifile,cancellateilcontenutodelladirectoryjs.Quiaggiungeremoinseguitounfileinteramenteminificato.
DobbiamoconfigurareGrunt/gulpaffinchéosservilenuovedirectoryegeneril’ouputnelladirectoryjsall’internodellacartellaassets.IpercorsiaggiornatinelGruntfileoifilegulpfiledovrebberoessereiseguenti:
‘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éAngularsappiachevogliamoutilizzarequestoenoniljqLiteincluso.
TuttiimodulivendornecessitanodiAngularchedeveessereinclusoprimadiessi.Inoltreicomponentielevisterichiedonoilcaricamentodelmodulo,altrimentinon
sapràachecosaassociarsi.
Primadieseguireiltaskrunnerdesiderato,modificateladestinazionedaassets/js/buildadassets/js.
Eseguiteiltaskrunnerpercompilarel’applicazionecheaveteriorganizzato.Infinemodificateilfilealqualefateriferimentoinindex.htmloracheladestinazioneècambiata:
<scripttype=”text/javascript”
src=”assets/js/ContactsMgr.js”></script>
Apriteilbrowserpercontrollarechetuttoappaiacomeprevisto.Setuttosicomportasecondoipiani,ilgestoredeicontattidovrebbefunzionareallaperfezione.
NOTA
Senonfunzionacomedovrebbe,controllatelaconsole.Probabilmenteavetetralasciatoqualcosaol’aveteinclusonell’ordinesbagliato.
Quiz1. SuqualeambientesibasanosiaGruntsiagulp?2. Perchéserveunfilepackage.json?3. Qualeplug-invieneutilizzatoperminificareifile?4. ChecosaènecessariofareaifileAngularprimadiminificarli?
RiepilogoInquestocapitoloabbiamoesaminatoduestrumentimoltoefficaciesimili.Ci
hannoconsentitononsolodiridurrenotevolmenteilnumerodellerichiesteHTTP,maanchediriorganizzarecompletamentel’app.
SiaGruntsiagulpottengonolostessorisultato,edèunaquestionestrettamentepersonalescegliereiltaskrunnerdautilizzare.Riteniamogulppiùrapidoefaciledaconfigurare,maindubbiamenteGruntvantaunnumeromaggiorediplug-inedèunostrumentodisponibiledapiùtempoepiùcollaudato.
NelcapitoloseguentevedremocomeutilizzarequestiduetaskrunnerperprendereifileLesscheutilizzaBootstrapecompilarliinunaversionepersonalizzata.
Capitolo10
PersonalizzareBootstrap
Finoral’applicazioneèpiuttostoconvenzionale.SiamoriuscitiasfruttareappienoBootstrap,mal’aspettopredefinitononhanulladinuovo.BootstrapèprogettatoperesserepersonalizzatoeutilizzaLessoilpreprocessoreCSSSASSpervelocizzareesemplificarequestaoperazione.
NelcorsodiquestocapitolostudieremocomeèpossibilecompilareilsorgenteLessdiBootstrapprimadipersonalizzarel’aspettoaffinchél’applicazionesiaveramenteunanostracreazione.Affronteremoiseguentiargomenti.
IfondamentidiLess.PersonalizzareBootstrap.CompilareLessconGruntogulp.ImpostareLiveReload.ItemidiBootstrap.
CompilareLessconGruntogulpPrimadiiniziareapersonalizzarel’app,èunabuonaideascoprirecome
trasformarenumerosifileLessinununicofogliodistile.AbbiamogiàvistocomeimpostareGruntegulpperconcatenareeminificareifileJavaScript;orautilizzeremoquestistessitaskrunnerpercompilareLess.
ScaricareilsorgenteInnanzituttoprocuriamocil’ultimaversionediBootstrapeinseriamoifileLessnel
progetto.Visitiamohttp://getbootstrap.com/efacciamoclicsuDownloadBootstrap.Comparirannotreopzioni:Bootstrap,SourcecodeeSass.Scegliamol’opzioneSourcecodeperchéincludeifileLesscheèpossibilepersonalizzareecompilare.
Comepotetenotare,ilsorgentediBootstrapèdiecivoltepiùgrandedellaversioneminificata.Pertenereinordineiltutto,copiateladirectorylessdalmaterialecheavetescaricatonellacartellaassetsall’internodelprogetto.Ladirectorycontienei40filelesschecostituisconoglistilidiBootstrap.
CompilareconGruntComeabbiamovisto,Gruntèunefficientetaskrunner.Possiamoavvalercidella
minificazionedeiJavaScriptperautomatizzarelacompilazionediLessconCSS.AquestoscopoGruntricorreaunplug-incheèpossibilerecuperaredaNPM.Includiamolonelpackage.jsondelprogettoedeseguiamonpminstalldalterminale.TuttaviaèmoltopiùsempliceeseguireununicocomandoelasciarecheNPMaggiungaladipendenzanelfilepackage.jsondelprogetto,nelmodoseguente:
npminstallgrunt-contrib-less--save-dev
Dopoaverinstallatoilplug-in,possiamoconfigurarlonelfileGrunt.Tuttoavvienenuovamentenell’oggettoconfig.Imposteremoduetarget:unoperlosviluppoeunaltroperlaproduzione.Cosìpotremodefinireleopzioniperogniscenario.Peresempio,perlaproduzionepotremmovolerminificareilCSS,maquestaoperazionenonè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,maproductionpresentailflagcleancssimpostatosutrueperridurreledimensionideifileminificandol’output.L’oggettofilesusalasintassiabbreviataperleproprietàsrcedestcheabbiamoesaminatoconfigurandoGrunteutilizzandouglifyperifileJavaScript.
NondimenticatedicaricareanchequestomodulodaNPM,altrimentiGruntnonriusciràadaccedereall’attivitàless.InseritelonelwrapperdiGrunt:
grunt.loadNpmTasks(‘grunt-contrib-less’);
Orasaràpossibilecompilareglistili.Possiamoeseguirequantoseguenelterminalepersvolgerel’attività:
gruntless
Questoperòproduceuneffettononvoluto,poichél’attivitàvieneeseguitasuentrambiitargetunodopol’altro.
Possiamolimitarlaaunsolotargetspecificandoilnomedeltargetdopoiduepunti:
gruntless:dev
ImpostareWatcheLiveReload
Ovviamenteilprincipioallabasedell’utilizzodiuntaskrunnercomeGruntogulpèautomatizzaretuttiquestiprocessi.Èpossibileutilizzareilplug-inwatchcomeabbiamofattonelCapitolo9pereseguireun’attivitàquandounfilecambia.Questocipermetteanchediutilizzarelivereloadsullapaginaconl’ausiliodiunplug-indelbrowser.
Laconfigurazioneèfacilepoichéabbiamogiàinstallatoilplug-in.Eccol’impostazionecorrenteperl’attivitàwatch:
watch:{
files:[
‘assets/js/*.js’
],
tasks:[‘uglify’]
}
Potremmoaggiungereladirectorylessall’arrayfilesel’attivitàlessall’arraytasks.Inquestomodoentrambeleattivitàverrannoeseguitequandocambieràunfile.jso.less;tuttavianonèquestoilnostroobiettivo.Separandoleinduetarget,avremomaggiorecontrollosulleattivitàchesarannoeseguite:
watch:{
js:{
files:[
‘assets/js/*.js’
],
tasks:[‘uglify’]
},
less:{
files:[
‘assets/less/*.less’
],
tasks:[‘less:dev’]
}
}
Perquantoriguardalaconfigurazione,noncambianulla.Abbiamosoloseparatoiduetipidifilepereseguirerispettivamenteuglifyeleattivitàless:dev.
Ilplug-inhaancheunaltroassonellamanica.Fungedaserverperilplug-inLiveReloadpermolteplicibrowser.Perutilizzarloconl’app,ènecessarioincludereunulterioretagscriptnellapagina:
<scriptsrc=”http://localhost:35729/livereload.js”></script>
Inalternativaesisteun’estensioneperChrome,FirefoxeSafari,chepuòesserescaricatadahttp://livereload.com/.Dopoaverlainstallata,nelbrowsersaràpossibileeffettuareilpingdelserverLiveReloadognivoltachesiverificanodellemodifiche.
ImpostareGruntpereseguireilpingdelnuovoplug-indelbrowserèmoltofacile;èsufficienteimpostarelaproprietàlivereloadsutrueinunoggettooptions.Aggiungiamolarapidamentealtargetless:
less:{
files:[
‘assets/less/*.less’
],
tasks:[‘less:dev’],
options:{
livereload:true
}
}
SeapriteilbrowsereattivateLiveReload,vedretechelapaginasiricaricaogniqualvoltasieffettuanodellemodificheaifileless.Ottimo,manonsarebbepreferibilesesiaggiornassesoloilCSSenonl’interapagina?Secambiaunfile,Gruntricaricheràl’interapagina.Perottenerequestorisultato,èpossibileaggiungereunsecondotargetallaconfigurazionecheverificalapresenzadimodifichenelfilebootstrap.css:
css:{
files:[
‘assets/css/bootstrap.css’
],
options:{
livereload:true
}
}
SedisattiviamoLiveReloadneltargetless,ilbrowserriceveràunnuovofileCSSenonricaricheràpiùlapagina.
CompilarecongulpOraesaminiamol’altrotaskrunner:gulp.PropriocomeGrunt,gulputilizzaun
altroplug-inperconsentirelacompilazionediLess.Sarànecessarioincludereunsecondoplug-inperLiveReload,poichénonègiàinclusoinGrunt.
Installiamoeconfiguriamoilplug-inless.Possiamofarlodarigadicomandoeseguendo:
npminstallgulp-less--save-dev
Cosìloinstalleremonelprogettoeloincluderemoanchenelfilepackage.jsonperunusofuturo.Perutilizzarlonelgulpfile,ricorreremoalmetodorequirediNodeperincludereilpacchetto.Inseriteloall’iniziodelgulpfile:
varless=require(‘gulp-less’);
Creiamounanuovaattivitàchiamatalesspergestiretuttalacompilazione,servendocidelplug-incheabbiamoappenaincluso:
gulp.task(‘less’,function(){
});
ComeabbiamofattoconJavaScript,utilizzeremoilmetodosrcdigulpcondueoperatoripipeperottenereilrisultatodesiderato.Consideriamol’attivitàcompletaedesaminiamolaneldettaglio:
gulp.task(‘less’,function(){
gulp.src(‘assets/less/bootstrap.less’)
.pipe(less({
filename:‘bootstrap.css’
}))
.pipe(gulp.dest(‘assets/css’));
});
Inquestocasoabbiamoinclusounsolofile;glialtrisonoinclusitramite@import.Ilplug-inLessaccettaqualsiasiparametroaccettatodalcompilatoreLessufficiale.Inquestaconfigurazioneimpostiamounnomefile,maperimpostazionepredefinitautilizzeràilnomefiledelsorgente.
Infineciserviremodelmetodogulp.destperesportareilfilecompilatonelladirectoryCSS.Questipiperappresentanoipassichel’attivitàesegueinordineedèpossibileaggiungernefacilmentealtrioriordinarliinfuturosefossenecessario.
OraGulpèconfiguratoperutilizzareilplug-inLessedèprontopercompilareglistilimedianteilcomandolessdigulp.
ImpostareWatcheLiveReload
Ovviamentemancheremmol’obiettivodell’automazionesedovessimoeseguirequestocomandomanualmente.Abbiamogiàscopertochewatchèintegratoingulp;includerelessnell’attualeconfigurazioneconsistenell’aggiungeresoloun’altrarigadicodice:
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’installazionediunaltroplug-in.RecuperiamolodaNPMconilseguentecomando:
npminstall--save-devgulp-livereload
Dopoaverloinstallato,fateriferimentoalplug-inall’iniziodelgulpfile:
varlivereload=require(‘gulp-livereload’);
LafunzionerestituitaèilservizioLiveReloadelopossiamoutilizzareperindicareall’estensionedelbrowserqualifilesonocambiati.Perconfigurarlocorrettamenteènecessariosvolgeredueoperazioninell’attivitàwatch.InnanzituttofacciamoriferimentoalserverLiveReloadacuipossiamopassareifilechangedauneventoattivatodalmetodogulp.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()allavariabileservereaggiuntounlistenerperl’eventochangealwatcher.Dopochel’eventoverificaunoggettofile,èpossibilepassareilpercorsodelfilealserver.
ComeconGrunt,ènecessarioaffrontarelaquestionedelricaricamentodelbrowserquandocambiaunfilenonCSS.Possiamorisolverlaaggiungendounterzowatcher.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 appenacompilato.
LessPercomprenderemegliociòcheoffreLess,introduciamoneifondamentiper
capirnelanaturaelasintassiallabasedelpreprocessore.
Osserveremoquattrotralesueprincipalicaratteristiche:importazione,mixin,regoleannidateevariabili.Unalistacompletaèdisponibilesulsitohttp://lesscss.org/features/.
IlvantaggiodiLessèchesenonvoleteutilizzarenessunasintassiofunzionenuova,nonsaretecostrettiafarlo.QualsiasiCSSvalidoèancheLessvalido.
ImportazionedifileComeperCSS,inLesspossiamoincludereunfileall’internodiunaltro.Inoltre
seguelastessasintassidiCSS:
@import“file.less”
Tuttavia,adifferenzadiCSS,cheeffettuaun’ulteriorerichiestaHTTPperilfilealqualesifariferimento,Lessuniràilfilequandoècompilato.Seapritebootstrap.less,vedretetuttiifileLessnecessariaiqualivienefattoriferimento.
NOTA
ConleversionipiùrecentidiLesspotetetralasciare.lessquandoincludeteifileecompilate.
VariabiliBootstraputilizzalevariabiliLessperconsentircidimodificareicolorieifont
deglielementivariable.Possiamocambiarlirapidamenteaprendoilfilevariables.less.Unavariabilevienedefinitadalsimbolo@seguitodalsuonome,peresempio:
@brand-primary:#428bca;
Sitrattadisempliciriferimentidautilizzareneglistili.Possiamochiamarelavariabilefacendoviriferimentoall’internodelleproprietà:
color:@brand-primary;
Durantelacompilazione,Lesssostituiràaquestiriferimentiilcoloredefinitoinprecedenza.Bootstraputilizzaquestevariabiliintuttiisuoielementi;pertantoèpossibilemodificarerapidamentecoloriefontinquestofile.
RegoleannidateForseunodeipatternpiù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,èpossibileannidarepseudoclassiutilizzandolasintassi&.Sitrattadiunriferimentoallaregolariguardanteilgenitore.Nellostessomodoèpossibileanchedefinireunaclassesecondaria.Peresempio,eccounpulsanteconduestilipericoloriarancioneeblu:
button{
color:#fff;
&.orange{
background:orange;
}
&.blue{
background:blue;
}
}
MixinUnmixinconsentediincludereglistilidaun’altraregola.Accettaancheargomenti
cheèpossibilepassareperotteneremaggioreflessibilità.Vediamounesempio:
.border-radius(@radius:5px){
border-radius:@radius;
}
Possiamoutilizzarequestomixinneglistili:
button{
.border-radius;
}
Ilvalorepredefinitocheabbiamoimpostatoverràutilizzatoautomaticamente.Tuttaviapossiamoscavalcarlofacilmenteracchiudendolotraparentesi:
button{
.border-radius(15px);
}
PersonalizzareglistilidiBootstrapPoichéutilizziamoilsorgentediBootstrap,possiamoanalizzareepersonalizzare
qualsiasifile.LessestendeCSSecioffrenuovefunzionisfruttatedaBootstrap.
CaratteritipograficiBootstraputilizzaHelvetica,forseilfontpiùpopolarealmondo.Perdare
all’applicazioneunpo’dicarattere,vediamocomeèpossibilesostituireaquestounaltrofontprovenientedallalibreriaGoogleFonts,visitabileall’indirizzohttps://www.google.com/fonts.Dateun’occhiataetrovatequellochepiùvipiace.PerorautilizzeremoRoboto,uncaratterebastone.
AggiungeteilfontallaraccoltaeselezionateglistiliLight,NormaleBold,comenellafigurariportataallapaginasuccessiva.
Copiatelariga@importeincludetelaall’iniziodelfilebootstrap.less,cosìavreteaccessoallafamigliadifontRobotoneivostristili.CercateTypographynelfilevariables.less.Lasezioneiniziaall’incircaariga38.Modificheremolavariabile@font-family-sans-serifcheBootstraputilizzaperimpostazionepredefinitacomebase:
@font-family-sans-serif:Roboto,“HelveticaNeue”,Helvetica,
Arial,sans-serif;
Potremmoanchecambiareladimensionedelcarattere,maperoranoneffettuiamoalcunamodifica;cisembratuttograziosoedequilibrato.
navbarCercatenavbarinvariables.less;lasezionedovrebbeiniziareall’incircaariga324.
Effettueremoalcunemodificheperrenderlomenoanonimo.Iniziamoacambiareilgrigioscialboinuncolorepiùstimolante,comeunbelblumetallizzato:
@navbar-default-bg:#667591;
Cambieremoancheilcoloredeltestoedeilinkaffinchésiintoninoconilcolorepiùscurodellosfondo:
@navbar-default-color:#fff;
@navbar-default-link-color:#fff;
@navbar-default-link-hover-color:#ccc;
@navbar-default-link-active-color:#fff;
Perquantoriguardalabarradinavigazionemobile,ènecessariomodificareancheilcoloredelpulsanteinterruttore:
@navbar-default-toggle-hover-bg:darken(@navbar-default-
bg,15%);
@navbar-default-toggle-icon-bar-bg:#fff;
@navbar-default-toggle-border-color:#fff;
Lessincludediversefunzionihelpercheèpossibileutilizzarepermodificareicolori,tracuisaturate,desaturateefade.LeduefunzionidiBootstrappiùutilizzatesonodarkenelighten.Schiarisconooscurisconopercentualmenteuncolore;sono
idealiperglistatihover.Inquestocasoabbiamooptatoperlavariabiledarkenel’abbiamopassataperlosfondodellabarradinavigazione.
Infinemodifichiamol’altezzaedeliminiamol’arrotondamentodegliangoliperottenereunaspettopiùordinato:
@navbar-height:60px;
@navbar-border-radius:0;
FormIformdiBootstrapsonoforseglielementipiùriconoscibili.Effettuiamoalcune
modificheperrenderlileggermentediversi.Modificheremovariables.lesseunpaiodialtrifile.Innanzituttoinvariables.lesseliminiamol’arrotondamentodegliangolidagliinput:
@input-border-radius:0;
Possiamoanchemodificareilcoloredelbordoel’ombradelfocus:
@input-border-focus:#667591;
Forsel’ombraèunpo’eccessiva,mapossiamoeliminarlafacilmentemodificandounmixindiBootstrap.Inpassatotuttiimixindelframeworkeranocontenutinell’unicofile:mixins.less,maquestaimpostazioneèrecentementecambiataeimixinsonostatisuddivisiinfileseparati.
Nelladirectorymixins,apriteforms.lessecercate.form-control-focus.Èilmixincheassegnaglistilialfocusperglielementidelform;poteteosservarlonellerighedicodiceseguenti:
.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,eliminailcontornopredefinitodelbrowsereaggiungeilboxconombra.Perilmomentotrasformiamoquest’ultimoinuncommento:
//.box-shadow(~”inset01px1pxrgba(0,0,0,.075),008px@{color-
rgba}”);
Lessciconsentediutilizzareicommenti//neifoglidistile,chenonsarannoriprodottinell’output.Seinvecericorriamoaicommenti(/**/)CSSstandard,sirifletterannonell’output.
NellavistaAddContactutilizzeremoancheunwell,cheavràunaspettostranovistocheicampidiinputdell’utenteorasonoprividiangoliarrotondati.Potremmorimuoverel’arrotondamentodatuttiglielementiimpostandosu0lavariabile@border-radius-base,maperoraapriamoilfilewells.less.Nellaclassewellbase,impostiamosu0border-radiuspereliminaregliangoliarrotondati.
PulsantiBootstrapincludemolticoloriedimensioniperipulsanti.Possiamoeffettuarele
modifichenelfilevariables.TroveretelasezionecercandoButtons;dovrebbeessereall’incircaallariga140.Bootstraputilizzaglistessicoloripermoltisuoicomponenti;lasezioneriguardanteicoloriiniziaintornoallariga6.
@brand-primary:#428bca;
@brand-success:#5cb85c;
@brand-info:#5bc0de;
@brand-warning:#f0ad4e;
@brand-danger:#d9534f;
Modifichiamobrand-primaryaffinchésiintoniallosfondodellabarradinavigazione:
@brand-primary:#667591;
Bootstraputilizzalafunzionedarkenperapplicareilcoloredelbordodelpulsanteeicoloridellostatohover.
Èpossibilepersonalizzareogniaspettodeglistili,maperilmomentononcambieremonulla.IlContactsManagerapparemenoanonimoesciattograzieainuovicoloriecaratteripiùvivaci.Alterminedellapersonalizzazione,l’applicazionedovrebbeavereilseguenteaspetto.
ItemidiBootstrapUncambiamentorilevantediBootstrap3èstatal’eliminazionedituttiglistili
visivi.InfattiBootstrapintendeessereunframeworksucuisviluppareenonsemplicementedautilizzareconl’aspettopredefinito.
Tuttaviaglistiliesistonoancora:sonostatispostatiinunfileseparato,inclusonelsorgente.Ilfiletheme.lessreintroducelesfumaturepresentiinBootstrap2;èsufficienteimportarlonelfileLessprincipale.
Apritebootstrap.lesseaggiungetequantosegueallafinedelfileperincludereiltema:
@import“theme.less”;
Pervedereunesempiorelativoall’utilizzodeltemadiBootstrap,dateun’occhiataagetbootstrap.com/examples/theme.
DovetrovarealtritemidiBootstrapEsistonoalcunisitiwebchepropongonotemidiBootstrap.Offronounsistema
rapidoeveloceperaggiungereunpo’dicarattereall’aspettostandarddelframework.Consultateisitiseguentisedesideratesperimentareunodiquestitemi:
http://www.blacktie.co/
https://wrapbootstrap.com/
http://startbootstrap.com/
http://bootswatch.com/
Quiz1. QualisonoalcunedellecaratteristicheprincipalicheaggiungeLess?2. Inchemodoèpossibilefareriferimentoaunapseudoclasseinunaregola
annidata?3. Comeèpossibilemodificareilfont?4. Acosaserveilfiletheme.less?
RiepilogoNelCapitolo9abbiamoesaminatoitaskrunnerperconcatenareeminificareifile
JavaScriptinununicofile.InquestocapitoloabbiamoosservatocomeèpossibileestenderlialsorgenteLessdiBootstrapecompilarlonelCSS.
Ciòconsentedipersonalizzarel’aspettodiBootstraputilizzandol’estensionediLessnelCSS:regoleannidate,variabiliemixin.L’applicazionehaacquisitouncertocarattereeabbiamoanchevistocomeèpossibilereintrodurreunpo’dellostilevisivoprovenientedaBootstrap2.
NelprossimocapitolodescriveremolavalidazioneinAngularJSelasuaintegrazionenell’app.
Capitolo11
Validazione
Tuttofunzionabeneehaunbell’aspetto,maalmomentononesistealcuntipodivalidazioneperiformoglierrorichepotrebberoessererinviatidalserver.InquestocapitoloesamineremocomeoperalavalidazioneinAngularJSecomeèpossibilecombinarlaconglistilidiBootstrapperfornireunfeedbackall’utente.
Osserveremoinoltrecomeèpossibileespandereleregoleintegratecreandounvalidatorpersonalizzatosullabasedeiconcetticheabbiamoappresoneicapitoliprecedenti.
ValidazionedeiformUnadellecaratteristichenascostediAngularJSèlavalidazionenativa.Sono
disponibiliunavalidazionedibasedeitipidiinputHTML5piùcomuni,oltreadirettivepersonalizzatecomerequired,patterneminlength,solopercitarnealcune.Vedremocomeaggiungerleall’applicazioneedestenderelavalidazioneintegrataconunvalidatorpersonalizzato.
PerutilizzarelavalidazionediAngularJS,ènecessarioaggiungerequalcosaaltagdiaperturaform:
<formname=”addForm”novalidateclass=”form-horizontal”ng-
submit=”submit()”>
Larigadicodiceprecedentecontieneiltagformpresentenelpartialadd.html.Abbiamoaggiuntonameoltreaunattributonovalidate.L’attributonameassegnaunoggettoalloscopecorrente;pertantopossiamoaccedervidallavistaedalcontroller.L’attributonovalidatedisattivalavalidazionenativadelbrowser.L’abbiamofattoperchélagestiremonoieintendiamoevitarechel’impostazionepredefinitarappresentiunostacoloogeneririsultatinonvoluti.
Angularvalideràautomaticamentel’inputdell’indirizzoe-mail,efaràlostessosecambiamoilcampodelsitowebdatestoaURL.Possiamoaggiungererapidamentel’attributonecessarioaqualsiasiinputcheconsideriamoobbligatorio:
<inputtype=”text”id=”name”class=”form-control”ng-
model=”contact.name”required>
Inalternativapossiamoutilizzareng-required.Imposteràsutruel’attributorichiestodelbrowserseanchel’espressionediAngularJSètrue.Peresempio,almomentodelpagamento,potrestevoleraggiungereunacaselladicontrolloperpermettereall’utentedidigitareunindirizzodiversoperlaspedizioneelafatturazione.Neicasiincuilacasellaèselezionata,èpossibileimpostareicampisurequirednelmodoseguente:
<label><inputtype=”checkbox”ng-model=”shippingAddress”>Sendthisto
anotheraddress</label>
<divng-show=”shippingAddress”>
<inputtype=”text”ng-required=”shippingAddress”>
</div>
Siccomenonabbiamobisognodinessunacondizionenell’applicazione,manteniamol’attributorequiredpredefinito.Aggiungiamolorapidamentealnome,alnumeroditelefonoeall’indirizzoe-mailpoichésonoquestiicampidelcontattorichiestipiùfrequentemente.
Oraènecessarioimpedirealformdiinviareidatiquandononècompletamentevalidato.Possiamofarloall’internodelcontrolleroppuredisattivandoilpulsantesubmit.
Aggiungendounnomealform,AngularJShacreatounnuovomodello,checioffrel’accessodirettoaessoall’internodellavista.Èleggermentediversodaquellochecreeremmodisolitoperchéracchiudemolteproprietàperverificarelavaliditànonsolodelform,maanchedieventualielementispecificidanoiindicati.Vediamorapidamentecomedisattivareilpulsantesubmit:
<inputtype=”submit”class=”btnbtn-primary”value=”AddContact”
ng-disabled=”addForm.$invalid”>
Possiamoutilizzareilnuovomodelloinunadirettiva.Inquestocasositrattading-disabledecomunichiamoadAngularJSdidisattivareilpulsanteseilformnonèvalido.Inalternativaavremmopotutoverificarelapresenzadierrori:
<inputtype=”submit”class=”btnbtn-primary”value=”AddContact”
ng-disabled=”addForm.$error.required||addForm.$error.email”>
Laproprietà$errordelmodelloèunhashconidiversitipidierrorigeneratidalform.Possonoessereerroridivalidazionedegliindirizzie-mail,dicorrispondenzadeipatternodicampimancanti.Ovviamentelaverificadiognitipocomportamaggioripossibilitàdisbagliareerichiedemoltopiùcodice.Tuttaviaèpiùprolissoequestotalvoltafavoriscelachiarezza.
Inoltreabbiamoaccessoa$dirtyea$pristine.Questidueflagriconosconosel’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,vedremocomparireunafinestradiavvisodelbrowser.Èunottimorisultato,mapotremmoimpostarloancoramegliofacendolosembrarepartedell’applicazione.AbbiamogiàdefinitounmessaggiodiavvisodiBootstrap;aggiungiamolorapidamentepericasiincuisiverifichiunerroredivalidazione.
Sostituiamolavariabilealertall’iniziodelfileadd.jsconunnuovooggettocontenentelefinestrediavvisoconmessaggidisuccessoedierrore:
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
})
}
Orapossiamosostituirelavecchiachiamataalertelafinestradiavvisodelbrowsernelmetodosubmitdelcontroller:
$scope.submit=function(){
if(!$scope.addForm.$valid){
returnalerts.error.show();
}
$scope.contact.$save();
$scope.contact=contacts.create();
alerts.success.show();
};
Sericaricatel’applicazioneefateclicsulpulsantesubmit,vedretecomparireunmessaggiodiavvisodiBootstrapcheviinformadellapresenzadialcunierroridivalidazione.
Sarebbemoltoutilesaperediqualierrorisitratta.FortunatamenteAngularJSconsentediscoprirequalimodelligeneranoerroriedefinirediconseguenzaerroriostili.
Possiamoaccedereaogniinputdelform,ancheseènecessariospecificareunnomeperciascunoalfinediriuscireavalidarlo.Peresempio,sepossiamoaggiungereunnomeperiltelefononelcamporiservatoalnumerotelefonico,possiamovalidareilcampoaccedendoaessotramiteilform:
addForm.phone.$valid
Èpossibileutilizzareladirettivang-classneigruppidiformperverificarelavalidità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,eprobabilmentenonèquestoilnostroobiettivo.
Lasoluzioneconsistenell’impostareunsecondomodellosutruenelcasoincuiilformnonsiavalidoquandoloinviamo.
$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,formErrorssufalsepereliminareleclassierrornelcasoincuiilformsiavalidatocorrettamente.Possiamomodificareladirettivang-classpervederesialevalidazionisiailnuovomodello:
<divclass=”form-group”ng-class=”{‘has-error’:formErrors&&
!addForm.phone.$valid}”>
SeentrambigliinputnonsonovalidieilmodelloformErrorsèimpostatosutrue,verràaggiuntalaclasse.Aggiungiamoladirettivang-classatuttiigruppidiforminattesadivalidazione.Nondimenticatedicambiareilmodelloalqualefateriferimentoinquellocheaveteinseritocomeattributoname.
ValidazionedeipatternAbbiamoimpostatounavalidazionedibasema,comesappiamo,Angularoffre
altredirettiveperrenderlapiùrigorosa.Peresempio,orapossiamodigitaretuttociò
chedesideriamonelcampo,esaràaggiuntotutto.Maquestanonèunasoluzioneidealepoichéilnostroscopoèottenereunnumeroditelefonoperognicontatto.
Ladirettivang-patternconsentedidefinireunpatternREGEX(espressioneregolare)concuiconfrontarel’input.Perilnumeroditelefonoaccetteremolecifre,maancheilsegnopiùperinumeriinternazionalieleparentesiperinumeriopzionaliequellistatunitensi.Consentiremoancheglispazielelineettepersuddividereinumeri.
Aggiungiamoladirettivang-patternall’inputriguardanteilnumerotelefonicoelimitiamoloperoraainumeriinteri:
<inputtype=”tel”name=”phone”id=”phone”class=”form-control”ng-
model=”contact.phone”required=”true”ng-pattern=”/^[0-9]/”>
Quandodigiteremodeltestonelcampoeinvieremoilform,vedremocheAngularJSgenereràunerroredivalidazione.
Soloquandodigiteremounnumero,ilformpotràessereinviato.Dobbiamoancoraconsentireicaratteritalvoltapresentiinunnumerotelefonico.Sitrattadiaggiungereicaratteriammessitraparentesiquadre.Eccol’espressioneregolarecompleta:
/^[0-9+()-]/
Potremmoancheestenderlaeforzareunadeterminatastrutturaolunghezzamassima,maperoraquestoèilrisultatochestavamocercando.
Utilizzareminlength,maxlength,minemaxLealtrequattrodirettiveoffertedaAngularJSperlavalidazionenonsonocosì
interessanti,masipossonorivelarepreziose.Peresempio,ladirettivaminlengthèidealepergarantirelasicurezzadellapasswordemin/maxsidimostranoutilineicasiincuièpresenteuncarrello,consentendosoltantounminimodiunoeunmassimodiunadeterminataquantitàdiarticoli.
Lequattrodirettivevengonoutilizzatenellostessomodoeaccettanounnumerocomevalore.Siang-minlengthsiang-maxlengthconsideranolalunghezzadell’input,mentreng-mineng-maxconsideranol’effettivovalorenumerico.
Eccoleinazione.Comepertutteledirettive,èpossibileutilizzareunacombinazioneperottenereleregoledesiderate.
<inputtype=”number”ng-min=”1”ng-max=”5”>
<inputtype=”password”ng-minlength=”8”ng-maxlength=”255”>
CreareunvalidatorpersonalizzatoAngularJSgestisceinmodoefficacelamaggioranzadeitipidiinputedeicasidi
utilizzo.Tuttavia,talvoltaavretenecessitàdiavereuncontrollomaggiore.Fortunatamenteèpossibilecreareproprievalidazionipersonalizzate.Ivalidatorpersonalizzatisonodirettiveconunrequisitospeciale.Perrenderleoperativeènecessarioimpostareng-modelsull’elemento.
Siccomelanostraapplicazionenonhauneffettivobisognodiunvalidatorpersonalizzato,vediamocomeèpossibilecrearneunoperverificarechel’inputnonsiapresenteinunalistapredefinita.Puòessereutileperfarsìcheunnomeutentesiaunivoco.
ChiamiamoquestadirettivauniqueListeinseriamolanelfilecontactsMgr.directives.js.Lalimiteremosoloadattributeeutilizzeremoilmetodolink.Perunriepilogoveloce,rileggeteilCapitolo6incuiabbiamodescrittolacreazionedidirettivepersonalizzate.
Possiamoinserireuncontrollercomeilquartoparametrodellafunzionelink.Angularsaqualecontrollerintendiamoutilizzareosservandolaproprietàrequiredella
direttiva.L’abbiamoimpostatasungModel,peravereunaccessodirettoaun’APIperladirettivang-model:
.directive(‘uniqueList’,function(){
return{
restrict:‘A’,
require:‘ngModel’,
link:function(scope,elem,attrs,ctrl){
}
};
});
LadocumentazionediAngularJSillustratuttiicontrollerdibase.LeinformazionisungModelControllersonoreperibiliall’indirizzohttps://docs.angularjs.org/api/ng/type/ngModel.NgModelController.
All’internositrovaunmetodochiavechepossiamosfruttare:$setValidity.Ciconsentedidefinireseunmodelloèvalidoomeno.Loutilizzeremoinsiemeconscope.$watchperverificarelavaliditàqualorailmodellocambi.
Impostiamoinnanzituttoscope.$watchnelmetodolinkdelladirettiva.Possiamorecuperareilnomedelmodellodall’oggettoattrs:
.directive(‘uniqueList’,function(){
return{
restrict:‘A’,
require:‘ngModel’,
link:function(scope,elem,attrs,ctrl){
scope.$watch(attrs.ngModel,function(value){
});
}
};
});
Comesappiamo,èpossibileaccederealnuovoeunicovaloredaunwatcher.Perquestoesempiociservesoltantoilnuovovalore.Loconfronteremoconunalistadinomiutente.PotrebbeessereunarichiestaHTTPalserver,maperilmomentosaràditipohard-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àdelmodelloconsisteinunasempliceverificapercontrollareseilvaloresitrovanell’array:
varvalid=(usernames.indexOf(value)>-1)?false:true;
Ilmetodo$setValiditysulcontrollerngModelèmoltosempliceecontienesoltantodueparametri.Ilprimoèlachiaveerrormentreilsecondoèunbooleanonelcasoincuisiavalidoomeno.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>
Quiz1. Indicatetredirettivecheèpossibileutilizzareperlavalidazione.2. Inchemodoverifichiamolavaliditàdelform?3. ComepossiamoaccederealcontrollerngModeldurantelacreazionediun
validatorpersonalizzato?4. Inchemodoverifichiamol’inputsullabasediun’espressioneregolare?
RiepilogoAbbiamovistocomefunzionalavalidazioneinAngularJSecomeèpossibile
combinarlaconglistilidiBootstrapperrenderel’apppiùuserfriendly.LavalidazionedeiformèstatasemplificataconledirettiveintegrateinAngularJSelavalidazioneautomaticadegliinputHTML5,comeemailetel.
Siamoriuscitiaverificarelavaliditàdelformnellafasediinvioeavisualizzaremessaggidierroreeavvertimentiappropriatinelcasoincuiquestaoperazionenonabbiasuccesso.Ancheseivalidatorintegratisonoottimiperlamaggiorpartedeicasi,abbiamoprovatoasvilupparneunotramiteunadirettiva.
Orachel’applicazioneècompleta,nelprossimocapitolovedremoalcunistrumentisviluppatidallacommunitychecisemplificanolavitaquandooperiamoconAngularJS.
Capitolo12
Strumentidellacommunity
Lanostraappdigestionedeicontattièterminata.Siamopassatidaunapaginavuotaaun’applicazioneCRUDsingle-pagecompletachesiconnettealserveredèinteramentevalida.QuestocapitoloillustreràduestrumentidellacommunitymoltoefficacieutilichevisemplificherannolavitaquandoutilizzereteAngularJS.
ImposteremoBatarangeng-annotateperilnostroprogetto.Ilprimomigliorerà,tral’altro,l’aspettodelloscope,mentreilsecondociconsentiràdiminificaremoltopiùfacilmenteifileJavaScript.Inquestocapitoloaffronteremoiseguentiargomenti.
Batarangeng-annotate.InstallareBatarangeng-annotate.Verificareloscope.Monitorarelaperformancedell’app.Utilizzareng-annotateconGruntegulp.
BatarangBatarangèun’estensionediChrome(cispiacepergliutentidiFirefoxeSafari,ma
infindeicontiAngularJSèunprogettodiGoogle)cheoffreunaschedaaggiuntivaneglistrumentideglisviluppatoriperconsentircidicreareunprofiloedeffettuareildebuggingdelleappdiAngularJS.
InstallareBatarangÈfacileinstallareBatarangpoichéèun’estensionediChrome.Visitiamoilsito
https://chrome.google.com/webstore/ecerchiamoBatarangnellacasellainaltoasinistradellapagina.Dovrebbeesserel’unicorisultatonellasezionedelleestensioni,comemostralaseguenteschermata.
Fateclicsulpulsanteperinstallarloevedretecomparireunanuovaschedanelwebinspector,comemostralaseguenteschermata.
VerificareloscopeeleproprietàForselafunzionepiùutilediBatarangèlacapacitàdiverificareidiversiscope
presentinell’applicazione.L’estensioneaggiungeunanuovaschedaalwebinspector;esaminiamola.
Batarangècostituitodacinqueschede:Models,Performance,Dependencies,OptionseHelp.PerutilizzareBatarangnell’applicazione,ènecessarioselezionarelacasellaEnable.Inquestomodolapaginasiaggiorneràel’estensioneinizieràaraccogliereleinformazioninecessarie.
EsistonotresistemiperverificareloscopeeleproprietàmedianteBatarang.QuellopiùovvioèfareclicsullaschedaModels,dovevedreteunalistadituttigliscopeannidati.
AllasinistradellaschedaModelsèvisibileunalistadituttigliscopedellapagina.Selezionandoli,comparirannoadestraimodellicontenutinelloscope.Siaggiornanoautomaticamentequandocambiaunvaloresullapagina,consentendodivedereconestremafacilitàciòcheaccadeallorointerno.
Batarangaggiungeancheun’altraschedaquandoispezionaglielementi.SichiamaAngularJSPropertiesemostratuttociòcheAngularassociaaunelemento:rilevaseèpari,l’indicedell’iteminunng-repeatoseunelementodelformèvalidoomeno,comemostralaseguenteschermata.
L’ultimoeutilestrumentocheoffreBatarangperlaverificaèquellopresentenellaconsole.
DopoaverattivatoBatarang,potretedigitare$scopenellaconsolepervedereloscopedell’ultimoelementoselezionato,comemostralafigura.
MonitorarelaperformanceLaschedaPerformanceriportaun’utilelistadituttociòchevieneverificatoalla
ricercadieventualicambiamentidapartediAngular,oltreaunelencodiespressioniconladuratadiesecuzione.
Valelapenatenered’occhiolaperformancementrel’appsisviluppa.Forsescopriretediaveresageratoconlefunzioni$scope.$watchochel’esecuzionediunfiltroimpiegamoltotempo.
QuellachesegueèlaschermatacheraffiguralaschedaPerformancedellapaginaAddContactdell’applicazione.AsinistracomparelasezioneWatchTree.Èunalistadituttiiwatchersullapaginaemostrainqualescopesitrovano.Adestraèvisibileun’analisidelleespressionicheimpieganopiùtempo.
VisualizzareledipendenzeLaschedapiùinteressantediBatarangèsicuramenteDependencies.Contieneun
graficointerattivochemostratuttociòsucuisibasanoiservizidell’applicazione.
Quandopassateconilmousesoprailnomediunservizio,inomideiservizichesibasanosudiessovengonoevidenziatiinverde,mentreiservizichedipendonoda
essovengonoevidenziatiinrosso.
Nellaschermataseguente,siamopassaticonilmousesopra$resource,chehaevidenziato$httpe$qinrossoecontactsinverde:
Probabilmenteavretenotatochenellaschermataprecedentecisonoalcuniservizichenondipendonodanullaedaessinondipendenulla.Accadeabbastanzaspesso,mapuòanchesignificarechenonvengonoutilizzatidall’applicazioneequindièopportunotenerlid’occhio.
OpzionidiBatarangNellaschedaOptionssonopresentitreutilicaselledicontrollo,cheevidenziano
conunbordocoloratoleapplicazioni,ibindingegliscopesullapagina.
Leapplicazionipresentanounbordoverde,ibindingunbordoblumentregliscopeunorosso.Sitrattadiunaraffigurazioneefficacepercapireseunaparte
dell’appsitrovainundeterminatoscopeosesistaveramenteverificandounbinding.
Eccocomeapparel’appdopoaverattivatotutto:
ng-annotateng-annotateèunprogettoopensourcediOlovLassustesoaeliminarele
conseguenzepiùfrustrantiderivantidall’utilizzodiAngularJS.Ricorderetechequandoabbiamominificatoilcodice,abbiamoracchiusoledipendenzeinunarraypertenereinordineinomi.Ilprogettorendesuperfluoquestorequisitoosservandoilcodiceeracchiudendoloalpostonostro.Insintesilostrumentoèunpre-minificatoreedovremmousarloperpreparareilcodiceperl’attivitàuglify.
InpassatoabbiamoutilizzatongMindiBrianFordperottenerelostessorisultato.Questoprogettoèstatodeprecatoinfavoreding-annotate.
Installareng-annotateLostrumentong-annotateèunpacchettonpmepuòessereinstallatotramiterigadi
comando.EsistonoaltripacchettichefunzionanoconleinstallazionidiGruntegulp,maperilmomentoesaminiamocomeèpossibileeseguiremanualmenteilprogetto.
1.Apriteilterminaleedeseguiteilprossimocomandoperinstallareglobalmenteilpacchettosulcomputer:
npminstall–gng-annotate
NOTA
Ricordatecheserestituisceunerrorelegatoaipermessi,dovreteeseguirlocomeamministratore.Potete farlocon ilcomandosudo inunsistemabasatosu*nixoppureeseguendo ilpromptdeicomandicomeamministratoresuWindows.
Viforniràunnuovocomandodautilizzarenelterminale.Seeseguiteng-annotate,dovrebbecomparireilfilehelpperlostrumento.Èfacileutilizzareng-annotateinunfile:èsufficienteavvalersidelcomandong-annotateseguitodalleopzioniedalnomedelfilecheintendiamoelaborare.
2.Nelladirectoryjsdelprogettoeseguite
ng-annotate-rcontrollers/app.js
Dovrebberestituireilcontenutodelcontrollerdell’appmaconunapiccolavariazione:vedretechetutteleannotazioniesistentiattornoalledipendenzesonostatecancellate.Infattiabbiamocomunicatoang-annotatedieliminarleconl’opzione-r.
3.Possiamoanchefarsìcheng-annotateleaggiunganuovamentemediante-a.Questedueopzionipossonoessereutilizzateinsiemeodasole:
ng-annotate-racontrollers/app.js
4.Questavoltavienerestituitoilcontrollerconleannotazioni.Possiamonotarecheng-annotatehaoperatolasuamagiaperchélevirgolettesingolecheracchiudonoledipendenzesonostatesostituitedavirgolettedoppie.
Orang-annotatefunziona,maèinutileseènecessarioeseguirlomanualmente.Inchemodoèpossibileinserirloneitaskrunner?Scopriamolo.
Utilizzareng-annotateconGruntPerutilizzareng-annotateconGrunt,dobbiamoinstallareunaltropacchettonpm.
Questavoltanonèglobale,maènecessarioinstallarlonelprogetto.Seguitequestipassi.
1.Passateallacartelladelprogettonelterminale:
cd~/path/to/contacts-manager
2.OrainstallateilmoduloGruntsalvandolonelledipendenzedevdelprogettoinmododapoterlosempreinstallarenuovamenteinseguito;inoltreunaltrosviluppatorechelavoreràalprogettopotràinstallarecosìtuttoilnecessario:
npminstallgrunt-ng-annotate--save-dev
3.PrimadieffettuarequalsiasimodificanelGruntfile,nondimenticatedicaricareleattivitàdalpacchettoappenainstallato:
grunt.loadNpmTasks(‘grunt-ng-annotate’);
Configurarel’attività
Noncirestacheconfigurarel’attivitàngAnnotatenelGruntfileedeffettuareduepiccolemodifichenell’attivitàwatch.Eccocomeprocederemo.
1.Aggiungiamolanell’attivitàlessall’internodell’oggettogrunt.initConfig.
ngAnnotate:{
}
2.ComelamaggiorpartedelleattivitàdiGrunt,ngAnnotateaccettaunhashoptionsoltreapiùtarget.Impostiamoprimaoptions:
ngAnnotate:{
options:{
remove:true,
add:true,
singleQuotes:true
}
}
Abbiamodefinitotreopzioni.Abbiamosceltodieliminarequalsiasiannotazionepreesistenteperaggiungernedinuoveconng-annotateeutilizzarevirgolettesingoleenondoppie.Èanchepossibiledefinireun’espressioneregolareseintendiamooperareinunasezionespecifica.
3.Iltargetguarderàtuttiifilenelladirectoryjselisalveràconl’estensione.annotated.js.Inquestomodoèpossibileimpostarel’attivitàwatchpereseguireng-annotateepoil’attivitàuglifyaffinchécerchiifilecheterminanocon.annotated.js.
4.Gliunicifileaiqualidobbiamoprestareattenzionesonoinostri.Tuttonelladirectoryvendorsaràpreannotato,ononsaràlegatoadAngular.PossiamoconfigurareGruntaffinchéconsiderituttotrannelecartellevendorebuild:
ngAnnotate:{
options:{
remove:true,
add:true,
singleQuotes:true
},
app:{
src:[
‘assets/js/**/*.js’,
‘!assets/js/vendor/*.js’,
‘!assets/js/build/*.js’
]
}
}
5.Ilprimopercorsonell’arraysrcincludetuttiifileJSnelladirectoryjsdelprogetto.Comeabbiamoprevisto,ènecessarioignorarelecartellevendorebuild.Idueitemcheseguononell’arrayfannoproprioquesto.IlpuntoesclamativoprimadelpercorsoindicaaGruntcheintendiamoescluderetuttiifileJSinquestecartelle.
6.Dopoaverspecificatoilsorgente,ènecessariocomunicareang-annotateciòchevogliamofareconifiledopochesonostatielaborati.Intendiamorinominareilfileperincluderel’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,extedextDot.Laproprietàexpanddivideilpercorsoeciconsentedicambiarel’estensione.Laproprietàextmodifical’estensionementrelaproprietàextDotcomunicaaGruntqualepuntonelnomefileguardare.Nelnostrocasoèl’ultimoequestocitutelanelcasodovessimoutilizzarepiùpuntineinomifile.
8.Orasiamoprontiaeseguirel’attivitànelterminale.Eseguiamolaconilflag--verboseevediamociòcheaccade:
gruntngAnnotate--verbose
9.Tuttosembrafunzionareallaperfezionemacreandoinuovifile.annotated.js,avremodeiproblemiquandoeseguiremol’attivitàunasecondavolta.Otterremoquestooutputquandoeseguiremonuovamenteilcomandoprecedente:
Writingassets/js/modules/contactsMgr.services.annotated.
annotated.js
10.L’attivitàhapresoinconsiderazionenonsoloifile.jsnelledirectorymaancheifile.annotated.js.CiòaccadeperchéGruntnonconosceladifferenza.Siccomeprendel’ultimopuntoperdeterminarel’estensione,èsufficienteaggiungereun’altraesclusioneall’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.jsnelladirectoryjsedeseguitel’attivitàun’ultimavolta.
Impostarel’attivitàwatch
Orachel’attivitàngAnnotatefunzionacomeprevisto,èpossibileeffettuarealcunemodifichenelleattivitàwatcheuglifypereseguirlaautomaticamenteeminificareifileJavaScript,nelmodoseguente.
1.Innanzituttoènecessariomodificareiltargetjsnell’attivitàwatchpereseguirengAnnotateinvecediuglify:
js:{
files:[
‘assets/js/**/*.js’
],
tasks:[‘ngAnnotate’]
},
2.Aggiungiamoanchedueesclusioninell’arrayfiles.Nondobbiamoosservareladirectorybuild,edènecessariocomunicareaGruntdiignoraretuttiifilecheterminanocon.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,GruntdovrebberilevareunamodificaeattivarengAnnotate:
Running“watch”task
Waiting…OK
>>File“assets/js/controllers/app.js”changed.
Running“ngAnnotate:app”(ngAnnotate)task
>>8filessuccessfullygenerated.
Done,withouterrors.
5.Sembrafunzionarebene.Possiamocreareunnuovotargetchecontrolliifileannotatiallaricercadellemodificheedesegual’attivitàuglify:
annotated:{
files:[
‘assets/js/**/*.annotated.js’,
],
tasks:[‘uglify’]
},
6.Èfacile,ecercalemodificheneifileconestensione.annotated.js.Infinedobbiamoeffettuareduemodifichenell’arraysrcall’internodeltargetdibuilddiuglify:
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’arrayperguardareifilegeneratiinvecedeiloroomologhinonannotati.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,Gruntharilevatounamodificaehaeseguitol’attivitàngAnnotategenerandoottofile.L’attivitàwatchharilevatolapresenzadimodificheneifileannotatiappenageneratiehacreatoilfileContactsMgr.jseseguendol’attivitàuglify.Dopotuttiquesticambiamenti,eccocomeappareGruntfile.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,esisteancheunaversionegulpding-annotatecheciconsentiràdiutilizzarequestotaskrunneralternativo,seèquellocheabbiamoscelto.Studiamocomeèpossibileimpostarloaffinchéopericongulpfilepreesistente.
1.Recuperiamoilpacchettodanpm:
npminstallgulp-ng-annotate--save-dev
2.Apriamogulpfile.jseinseriamoilpacchettoappenainstallato:
varngAnnotate=require(‘gulp-ng-annotate’);
3.Inquestomodoavremounanuovafunzionedautilizzareconglioperatoripipedigulp.Èincredibilelamaggiorerapiditàdiinstallazionedigulpperutilizzareng-annotaterispettoaGrunt.È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àuglifyognivoltachecambiaunfileJS;pertantoquestoètuttociòchedobbiamofareingulpfile.
5.Orapossiamoricorrereauglifydigulppereseguiremanualmenteng-annotateeminificareilJavaScript,oppureutilizzarewatchdigulpperrilevareautomaticamenteicambiamenti.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’]);
Quiz1. Checos’èBatarang?2. IndicatetrestrumenticheoffreBatarang.3. Qualisonoiduesistemiperverificareloscope?4. Checosaabbiamoutilizzatoprimading-annotate?5. Qualiopzionioffreng-annotate?6. Qualeproblemarisolveng-annotate?
RiepilogoInquestocapitoloabbiamotrattatoduestrumentidellacommunityefficaciemolto
affidabilieutilizzati.BatarangrappresentailcoltellinosvizzerodeglistrumentididebuggingcheaiutaasvilupparefantastichewebappconAngular;ng-annotatecisollevadall’irritanteincombenzadelleannotazionidurantelaminificazionedeifile.
QuestiduestrumentisonofacoltativienonsononecessariquandosiutilizzaAngular,maentrambiviaiuterannolungoilcamminoelisfruttereteregolarmente.SonosoltantoduedeglistrumenticreatidallasorprendentecommunitydiAngularJS.Esplorateescopritechecos’altrooffreperfacilitarvilavita.Esenonriusciteproprioatrovareciòchestatecercando,sviluppatelovoieproponeteloallacommunity!
AppendiceA
Personeeprogetti
SiaAngularJSsiaBootstrapvantanounnutritoseguitoedueenormicommunity,adimostrazionecheiprogettibasatisuentrambiiframeworksononumerosi.Dietroogniprogettooperanosviluppatoriscrupolosi.Scopriamoalloraalcunepersoneeprogettidegnidinota.
ProgettiepersonedietroBootstrapAttualmenteBootstrapègiuntoallaterzaversione;ciòsignificacheesistono
numeroseestensioniestrumentiutilichevipossonoaiutaredurantelosviluppo.
IlteamprincipaleBootstrapènatoall’internodiTwitteredèlacreaturadidueingegneri
dell’azienda:MarkOtto(@mdo)eJacobThornton(@fat).Tuttieduehannoinseguitolasciatoquestacompagnia,macontinuanoacontribuirealprogetto.
Attualmentesicontanoquasi600collaboratoriaBootstrap,ilchesottolinealaforzadiunsoftwareopensource.
EntrambiicreatorioriginarihannoabbandonatoTwitter,maMarkcontinuaaessereilcollaboratoreegestorepiùattivodiBootstrap.
URL:http://www.getbootstrap.comTwitter:@twbootstrapPersone:MarkOtto(@mdo),JacobThornton(@fat)ealtri.
BootstrapExpoQuandoparliamoconchiconosceunpo’Bootstrapmanonl’haancora
sperimentato,scopriamocheèconvintocheilframeworksiaunostilefisso.Buonapartedellasuacattivafamaderivadaiprimiprogettibasatisudiesso.Inpassatoognipaginadeiplug-injQueryvenivasviluppataconBootstrapprivodistili.
Comesappiamo,Bootstrapèmoltopiùdiquesto;èunframeworkfront-endatuttotondo.Persfatarequestaconvinzioneerrata,uncreatorediBootstraphaapertoilblogBootstrapExpoincuiillustragliutilizzipiùstimolantidiBootstrapnelWeb.
URL:http://expo.getbootstrap.comPersone:MarkOtto(@mdo)
BootSnippBootSnippèunarisorsaincredibileperchiunqueopericonBootstrap.Nellasua
versionepiùsemplice,èunaraccoltadicomponentiprecodificaticheèpossibile
copiareeincollarenelprogetto.Èpossibiletrovarestrumentiperlanavigazione,immaginiscorrevoliefinestremodaliaccompagnateconunostile.
MaBootSnippnonèsoloquesto.PoteteeffettuareunaricercainbasealleversionidiBootstrap,esaminarelealtreprezioserisorseeutilizzaregliutilicostruttoridiformepulsantineiqualièsufficientetrascinareglielementidesideratiecopiarel’HTML.
URL:http://www.bootsnipp.comTwitter:@BootSnippPersone:MaksimSurguy(@msurguy)
Lineeguidasulcodicedi@mdoQuandoilprogettosiamplierà,dovreteseguirealcunistandardperquantoriguarda
HTMLeCSS.Ovviamentequandolavorereteaunprogettoconaltrepersone,lasituazionesipuòcomplicareelanecessitàdiunaguidasuglistilirisulteràpiùevidente.
MarkOtto,unodeico-creatoridiBootstrap,haideatounaguidaesaurientesuglistandardcheutilizzaneisuoiprogetti.Valelapenadareun’occhiata,ancheseunabuonapartedipendedallepreferenzepersonaliedalleesigenzevostreedelvostroteam.Servitevenecomepuntodipartenzaperdefinireisetdistandardediregole.
URL:http://codeguide.co/Persone:MarkOtto(@mdo)
RootsRootsèunostarterthemeopensourcediWordPresscheaccompagnaBootstrap,
GrunteBoilerplateHTML5persvilupparefantasticitemiWordPress.Anchesenonabbiamoaffrontatoquestoargomentonelcorsodellibro,vorremmosegnalarvilapossibilitàcheBootstrapoffreperrealizzarequalsiasicosa.Èunapiattaformamoltoversatileesolidasucuicreareiprogetti.
URL:http://roots.io/Persone:BenWord(@retlehs)
ShoelaceSeavetedelledifficoltàasviluppareovisualizzarelegriglieconBootstrap,valela
penadareun’occhiataaShoelace.ÈunpraticostrumentocheconsentedisviluppareinmodointerattivolagrigliadelleapplicazioniegeneraretuttoilmarkupdicuiabbiamobisognoinHTML,JadeoEDN.
Èpossibilesalvareecondividerelegriglieeverificareilloroaspettosudispositivididiversedimensioni.Sipossonoaggiungerealtreclassiallerigheeallecolonne,ancheseperimpostazionepredefinitaèpossibileutilizzaresoltantolecolonneneidispositividipiccoledimensioni.Ovviamentepotretesostituirleinseguito,sefossenecessario.
URL:http://www.shoelace.ioPersone:ErikFlowers(@Erik_UX)eShaunGilchrist
SnippetdiBootstrap3perSublimeTextSublimeText2e3sonoeditorditestomoltopopolaritraglisviluppatorienon
sorprendecheesistaunplug-incheincludeinnumerevolisnippetperilvostroframeworkfront-endpreferito.
Ilplug-inviconsentediinserirerapidamentequalsiasicomponentediBootstrapsemplificandonemoltol’utilizzo.PuòessereinstallatotramitePackageControlcercandoBootstrap3Snippets,oppurepoteteprocurarvelodaGitHub.
URL:https://github.com/JasonMortonNZ/bs3-sublime-pluginPersone:JasonMorton(@JasonMortonNZ)
FontAwesomeIlprogettohapresoilviapersostituireleiconediimmaginediBootstrap2con
alcuneequivalenti.SièsviluppatofinoadiventareunadelleprincipaliraccoltedifontdiiconechepuòessereutilizzataconosenzaBootstrap.
SedesiderateestenderelagammadiiconegiàriccadiBootstrap,provateFontAwesome.
URL:http://fortawesome.github.io/Font-Awesome/
Persone:DaveGandy(@davegandy)
BootstrapIconsBootstrapcomprendegiàunafantasticacollezionediiconeesipuòfacilmente
estendereconFontAwesome.BootstrapIconsèstatocreatopercercarerapidamentetraleiconeerecuperarelaclassenecessaria.
Èmoltopiùfaciletrovarequiciòcheviserverispettoalladocumentazioneufficiale,perchéogniiconaèstatataggataconpiùparolechiave.Digitateciòchecercatenellacaselladiricercaeilsitolotroverà.
URL:http://bootstrapicons.com/Persone:BrentSwisher(@BrentSwisher)
ProgettiepersonedietroAngularJSAlcuniprogettidellacommunitydiAngularJSpossonodiventarepartedelflusso
dilavoroquotidiano.Neabbiamogiàesaminatialcuninellibro,maoranedescriveremoaltridiunacertaimportanza.
IlteamprincipaleComesapete,AngularJSèunprogettoGooglee,cometale,ilsuoteamprincipale
ècostituitodadipendentidiquestaazienda.ForsenonsapeteperòcheilframeworkfucreatoinBratTechLLCdaMiškoHeveryeAdamAbronspersupportareunserviziodiarchiviazioneJSONchiamatohttp://getangular.com/.InseguitoabbandonaronoilservizioereseroopensourceilframeworkAngularJscheabbiamoimparatoaconoscereeapprezzare.
MiškocontinuaagestireilprogettocomedipendenteGooglecollaborandocondiversialtriingegneri.Insiemel’hannoampliato,rilasciatoun’infinitàdimoduliaggiuntiviecreatolapopolareestensioneperChromeBatarang.
URL:https://angularjs.org/Persone:MiškoHevery(@mhevery),AdamAbrons(@abrons),BrianFord(@briantford),BradGreen(@bradlygreen),IgorMinar(@IgorMinar),VotjaJína(@vojtajina)ealtri.
RestAngularAbbiamogiàillustratoiduesistemiconcuiAngularconsentediconnettersia
un’API:$httpengResource.EsisteancheunprogettodellacommunitymoltopopolarechiamatoRestAngularchesegueunapprocciodiverso.
LadifferenzaprincipalerispettoangResourceconsistenelfattocheutilizzalepromise,mentre$resourceesegueautomaticamenteillorounwrapping.Asecondadicomefunzionailvostroprogetto,puòessereunsistemaefficacepoichépotreterisolvereidatidellaroutemediante$routeProvider.resolve.
SeoperateconunaRESTfulAPInelprogettoengResourcenonviconvincedeltutto,dateun’occhiataaRestAngular.
URL:https://github.com/mgonto/restangular
Persone:MarinGonto(@mgonto)
AngularStrapeAngularMotionAbbiamogiàstudiatoeutilizzatoAngularStrapnelnostroprogetto.Offreuna
raccoltafantasticadituttiiprincipaliplug-indiBootstrapperledirettivenativediAngularJS.SeutilizzateBootstrapinsiemeconAngularJS(comeabbiamofattonellibro),èunmoduloimperdibile.
AngularMotionèprogettatopervenireutilizzatoconAngularStrap,anchesenonèstrettamentenecessario.SitrattadianimazionigiàprontechefunzionanonativamenteconngAnimateeaggiungonountoccoinpiùalprogetto.Sipossonoutilizzareconng-showeng-hideecondirettivecomeng-repeatperanimarel’aggiuntaolacancellazionedegliitem.
URL:http://mgcrea.github.io/angular-strap/ehttp://mgcrea.github.io/angular-motionPersone:OlivierLouvignes(@olouv)
AngularUIIlprogettoAngularUIèprobabilmenteilpiùgrandecheèsortodallacommunity
diAngularJS.SidivideindiversimodulitracuiUI-Utils,UI-ModuleseUI-Router.
IlmoduloUI-Utilsvienedescrittocomeil“coltellinosvizzero”deglistrumenti,edeffettivamenteloè.Consenteoperazionicomeevidenziaredeltesto,verificarelepressioniditastiorendereunelementofissoalloscroll.
GliUI-ModulessonomoduliAngularJScondipendenzeesterneperGoogleMapsoplug-injQuery.Quitroveretealcunistrumentiefficaci,epersonalmentehoutilizzatoilmoduloSelect2indiverseoccasioni.
ForseilprogettopiùpopolareèilmoduloUI-Router.OffreunveroroutingannidatoperAngular.Consentedisuddividerelapaginainstati;peresempiopotresteavereunostatoperlabarralateraleeunaltroperilcontenutoprincipale.Possonoavereentrambiunpartial;inoltrepermettedisviluppareinmodopiùsemplicegrandipagineowebapp.
EsisteperfinounmoduloperBootstrapsimileadAngularStrap,chevalelapenaconsiderare.
URL:http://angular-ui.github.io/Twitter:@angularuiPersone:NateAbele(@nateabele),TasosBekos(@tbekos),AndrewJoslin(@andrewtjoslin),PawelKozlowski(@pkozlowski_os),DeanSofer(@Unfolio),DouglasDuteil(@douglasduteil)ealtri.
MobileAngularUIAdifferenzadelprogettoAngularUI,questohaloscopodisviluppare
un’interfacciautente.SitrattadiunsempliceframeworkmobilecheutilizzasiaAngularJSsiaBootstrap3.Glielementiappaionoabbastanzanativi,anchesealcuneparticomelabarradinavigazionelateralepotrebberoesseremigliorate.Èancoraaisuoialbori,mahaunseriopotenzialeevalelapenaseguirelasuaevoluzione.
URL:http://mobileangularui.com/Twitter:@mobileangularuiPersone:mcasimir
IonicIonicèincredibile.Loèdavvero.RiuniscetuttociòcheèefficaceinAngularJSe
Cordova/Phonegapperconsentirvidisvilupparestraordinarieappibrideconilinguaggiwebchegiàconoscete.
Tuttosembranativoedèfacilissimoiniziareanchesenonavetemaisviluppatoun’app.UtilizzaAngularJSel’estensioneUI-Routerinsiemeconillorocodice.L’aspettomiglioreècheèinteramenteopensourceechiunquepuòcontribuirvisuGitHub.
ÈstatoancherealizzatoilmodulongCordova,riccodidirettivecheèpossibilesfruttareperinterfacciarsifacilmenteconnumerosiplug-inCordova.
URL:http://ionicframework.com/Twitter:@ionicframeworkPersone:TheDriftyTeam(http://drifty.com/):AndrewJoslin(@ajoslin)ealtri.
AngularGM
AncheseAngularUIincludeunadirettivaperutilizzareleGoogleMapsall’internodiAngular,preferiamol’approcciopiùsemplicediAngularGM.QuestomoduloconsentelacreazionesemplicedelleGoogleMapsnelprogetto,insiemeconmarker,InfoWindowsepolyline.
Potetepersonalizzarequasituttociòchedesiderate,dallamodificadeicoloriedalleimpostazionidellamappaall’utilizzodiunelementopersonalizzatoperl’InfoWindowol’iconanonstandardperilmarker.
URL:https://github.com/dylanfprice/angular-gmPersone:DylanPrice
Oratoccaavoi…IlnumerodiprogettiopensourceriguardantiBootstrapeAngularJSèincredibile.
Tuttipossonocontribuirvi,perfinovoi!Setrovateunbug,segnalatelo,osesapetecomerisolverlo,inviateunapullrequestediventateuncontributor.
Naturalmentenontuttiiproblemisonostatirisolti.Orachesapetecomeutilizzareentrambiiframework,toccaavoidivertirviasvilupparequalcosadistraordinario.
AppendiceB
Incasodidubbio
Ancheglisviluppatoripiùespertinonsannotalvoltachepescipigliareenonc’ènulladimalenelchiedereunamano.
EsistonoalcunistrumentispecificiriguardantiBootstrapeAngularJSchepossonoaiutarvi,nelcasoviservisse.
LadocumentazioneufficialeSeviimbatteteinunproblemaoavetebisognodirinfrescarelamemoria
consultateinnanzituttoladocumentazioneufficiale.
SiaBootstrapsiaAngularJSvantanoun’ottimadocumentazione.InpassatoeranosortedellelamentelesuquelladiAngularJS,ritenutaconfusaeconpochiesempi,manegliultimiannièmiglioratanotevolmente.Perulterioridettagli,visitateisitihttp://www.angularjs.orgehttp://www.getbootstrap.com.
L’issuetrackerdiGitHubSiaAngularsiaBootstrapsonoospitatisuGitHubedentrambisiavvalgono
dell’issuetrackerdelservizio.Sescopriteunbuginunodeidueframework,segnalateloqui.
Ovviamenteseconoscetelanaturadelproblemaesapetecomerisolverlo,poteteinviareunapullrequestecontribuirealprogetto.Perulterioridettagli,visitatehttp://www.github.com/angular/angular.js/issuesehttp://www.github.com/twbs/bootstrap/issues.
StackOverflowForsepensavatecheneavremmoparlato.StackOverflowèunarisorsaeccezionale
eunottimostrumentodautilizzareseaveteunadomandaspecifica.Ilpiùdellevoltetroveretequalcunaltrochehapostolostessoquesitoepotreteleggerelerisposte.Altrimentiponeteunanuovadomandaetaggatelacome“AngularJS”o“TwitterBootstrap”.Perulterioridettaglivisitatehttp://www.stackoverflow.com.
IlgruppoGoogleAngularJSManmanocheutilizzateAngularJS,vipossiamogarantirechedopoalcune
ricerchesuGoogle,viritrovereteinquestogruppo.Sitrattadelgruppo/forumufficialediAngularJSedèmoltoattivo.
Contienepiùdi11.000argomentievalelapenaeffettuarequiunaricercaprimadiporreunanuovadomanda.Perulterioridettaglivisitatehttps://groups.google.com/forum/#!forum/angular.
Egghead.ioSecercatedeitutorialvideosuAngularJS,Egghead.ioèprobabilmentelarisorsa
migliore.Offreunserviziodiabbonamentoapagamento,masipuòconsultaregratuitamentebuonapartedellasualibreria.Sevoletedisporredialtromaterialevideoinformativo,visitatehttps://egghead.io/tags/free.
TwitterPuòsembrareunasegnalazionebizzarracomerisorsadisupporto,masuTwittersi
trovanopersonemoltoutili.Forsenonèilluogomiglioreperporredomandecomplesse,maperpiccoledrittepuòessereprezioso.
Quisiincontranoappassionatidientrambiiframeworkedèpossibilepartecipareallerispettivecommunity.IdueframeworkhannoaccountTwitterufficiali:@angularjse@twbootstrap.
Aproposito,sevoletemandarmiuntweet,iosono@steve228uk.
Sicuramente,conoscendopiùafondoAngularJSeBootstrap,fareteriferimentoalladocumentazioneechiederetesempremenoaiuto.Abbiamoimparatocheèmoltoimportantetrasmettereciòchesiconosce.
Forsechiedereteunamanopersviluppareunadirettivamoltospecifica,maciònonsignificachenonsapreteaiutareanchevoiglialtri.Quandoaveteunpo’ditempo,accedeteaStackOverfloweprovatearispondereadalcunedomande;scommettocheriusciretearispondereamoltepiùdomandediquellecheavrestemaiimmaginato!
AppendiceC
Risposteaiquiz
Capitolo11. Utilizzandol’attributong-app.2. Lasintassichecomprendedoppieparentesigraffe:{{model}}.3. ModelViewController.4. CreiamouncontrollerutilizzandouncostruttoreJSstandardel’attributong-
controller.5. Jumbotron.
Capitolo21. Unabarradinavigazione(navbar)diBootstrap.2. 12colonne.3. Èunafunzionechiamatadaunattributoounelementopersonalizzato.4. ng-repeat.
Capitolo31. Conilsimbolopipeinunmodello:{{modelName|filter}}.2. Coniduepunti:{{modelName|filter:arg1:arg2}}.3. Ilfiltrochiamatofilter.4. Utilizziamoilservizio$filterinserendoilfiltrocomeservizioseguendoil
patternfilternameFilter.5. UnmoduloAngularJS.
Capitolo41. ngRoute.2. Ilmetodoconfignelmodulo.3. Ilservizio$routeProvider.4. Conilmetodo$routeProvider.when.5. Ilmetodo$routeProvider.otherwise.6. Utilizzandohtml5Mode.
Capitolo51. 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. img-circlepercreareun’immaginedallaformacircolare,img-roundedpercrearne
unadaibordiarrotondatieimg-thumbnailperaggiungereunbordodoppio.
Capitolo61. Serviziopersonalizzato,$rootScope,controlleralivellodell’applicazione.2. value,serviceefactory.3. IlmodulongSanitize.4. Ilmetodocontrollerconsentealladirettivadicomunicareconlealtredirettive,
mentreilmetodolinkno.5. Ilsegno=significacheèpossibilelegaredirettamenteunmodello;@indicache
ladirettivautilizzeràilvaloreletteraledell’attributo.6. ImpostandolaproprietarestrictsuEM.7. Peraggiungerealcunefunzionihelperallabarradinavigazione.8. Utilizzando$index.
Capitolo71. DangAnimate.2. AngularMotion.3. bs-.4. click,hover,focusemanualmente.5. show,hideetoggle.
Capitolo81. Unapromise.2. Con$http.get(‘http://localhost:8000’).success(function(data){$scope.contacts=data
});.3. Sicomportacomeunsegnaposto.4. RESTAngularutilizzalepromiseenonènecessariotrascrivereisegnaposto
seguendoilpatternREST.5. Intemporeale.
Capitolo91. SuNode.2. PerindicareaNPMdiqualipacchettiabbiamobisogno.3. Uglify.4. Utilizzarelanotazionedegliarray.
Capitolo101. Variabili,mixineregoleannidate.2. Utilizzandoilpattern&:beforeoppure&:after.3. Modificandolavariabileinvariables.less.4. UsaglistilidiBootstrapperrenderlosimileaBootstrap2.
Capitolo111. Unaqualsiasidelleseguenti:required,ng-required,ng-pattern,ng-minlength,ng-
maxlength,ng-mineng-max.2. Verificandoleproprietà$valido$invalid.3. Chiamandolonellaproprietàrequire.4. Utilizzandong-pattern.
Capitolo121. 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
AppendiceC-Risposteaiquiz
Capitolo1
Capitolo2
Capitolo3
Capitolo4
Capitolo5
Capitolo6
Capitolo7
Capitolo8
Capitolo9
Capitolo10
Capitolo11
Capitolo12