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