Backbone.js er et JavaScript-rammeverk for å bygge fleksible webapplikasjoner. Den leveres med modeller, samlinger, visninger, hendelser, rutere og noen andre flotte funksjoner. I denne artikkelen vil vi utvikle en enkel ToDo-applikasjon som støtter å legge til, redigere og fjerne oppgaver. Vi bør også kunne markere en oppgave som ferdig og arkiver den. For å holde denne postlengden rimelig, vil vi ikke inkludere noen kommunikasjon med en database. Alle dataene blir lagret på klientsiden.
Her er filstrukturen som vi skal bruke:
css └── styles.css js └─ - samlinger └── ToDos.js └─ - modeller └── ToDo.js └─ - leverandør └── backbone.js └── jquery-1.10.2.min.js └─ - underscore.js └─ - visninger └── App.js └─ - index.html
Det er få ting som er åpenbare, som /css/styles.css
og /index.html
. De inneholder CSS-stilene og HTML-oppslaget. I sammenheng med Backbone.js er modellen et sted hvor vi beholder våre data. Så, våre ToDos vil bare være modeller. Og fordi vi vil ha mer enn en oppgave, vil vi organisere dem inn i en samling. Forretningslogikken er fordelt mellom visningene og hovedprogrammets fil, App.js
. Backbone.js har bare en hard avhengighet - Underscore.js. Rammen spiller også veldig bra med jQuery, så de går begge til selger
katalogen. Alt vi trenger nå er bare et lite HTML-oppslag, og vi er klare til å gå.
Mine TODOer
Som du kan se, inkluderer vi alle de eksterne JavaScript-filene mot bunnen, da det er en god praksis å gjøre dette på slutten av kroppsmerket. Vi forbereder også oppstart av applikasjonen. Det er container for innholdet, en meny og en tittel. Hovednavigasjonen er et statisk element, og vi kommer ikke til å endre det. Vi erstatter innholdet i tittelen og div
under den.
Det er alltid godt å ha en plan før vi begynner å jobbe med noe. Backbone.js har ikke en super streng arkitektur, som vi må følge. Det er en av fordelene ved rammen. Så, før vi starter med implementeringen av forretningslogikken, la oss snakke om grunnlaget.
En god praksis er å sette koden i sitt eget omfang. Det er ikke en god ide å registrere globale variabler eller funksjoner. Det vi skal opprette, er en modell, en samling, en ruter og noen Backbone.js-visninger. Alle disse elementene skal leve i et privat rom. App.js
vil inneholde klassen som holder alt.
// App.js var app = (funksjon () var api = visninger: , modeller: , samlinger: , innhold: null, router: null, todos: null, init: this.content = $ ("# content");, changeContent: funksjon (el) this.content.empty (). append (el); return dette;, tittel: funksjon (str) $ ("h1 ") .text (str); returner dette;; Var ViewsFactory = ; var Router = Ryggrad.Router.extend (); api.router = ny ruter (); return api;)
Ovenfor er en typisk implementering av det avslørende modulmønsteret. De api
variabel er gjenstanden som returneres og representerer klassens offentlige metoder. De visninger
, modeller
og samlinger
Egenskaper vil fungere som innehavere for klassene returnert av Backbone.js. De innhold
er et jQuery-element som peker på hovedbrukerens grensesnittbeholder. Det er to hjelpemetoder her. Den første oppdaterer den beholderen. Den andre setter tittelen på siden. Da definerte vi en modul som ble kalt ViewsFactory
. Det vil levere våre synspunkter og på slutten opprettet vi ruteren.
Du kan spørre, hvorfor trenger vi en fabrikk for visningene? Vel, det er noen vanlige mønstre mens du jobber med Backbone.js. En av dem er relatert til etableringen og bruken av visningene.
var ViewClass = Backbone.View.extend (/ * logikk her * /); var view = new ViewClass ();
Det er godt å initialisere visningene bare én gang og la dem være i live. Når dataene er endret, kaller vi vanligvis metoder for visningen og oppdaterer innholdet av det el
gjenstand. Den andre svært populære tilnærmingen er å gjenskape hele visningen eller erstatte hele DOM-elementet. Det er imidlertid ikke veldig bra fra et ytelsesperspektiv. Så, vi ender opp med en verktøysklasse som skaper en forekomst av visningen og returnerer den når vi trenger den.
Vi har et navneområde, så nå kan vi begynne å lage komponenter. Slik ser hovedmenyen ut:
// views / menu.js app.views.menu = Ryggrad.View.extend (initialiser: funksjon () , gjengiv: funksjon () );
Vi opprettet en eiendom som heter Meny
som holder klassen av navigasjonen. Senere kan vi legge til en metode i fabrikkmodulen som skaper en forekomst av den.
var ViewsFactory = meny: funksjon () hvis (! this.menuView) this.menuView = new api.views.menu (el: $ ("# menu")); returner dette.menuView; ;
Over er hvordan vi skal håndtere alle visningene, og det vil sikre at vi bare får en og samme forekomst. Denne teknikken fungerer bra, i de fleste tilfeller.
Inngangspunktet for appen er App.js
og dets i det
metode. Dette er hva vi skal ringe i på Last
handler av vindu
gjenstand.
window.onload = function () app.init ();
Deretter tar den definerte ruteren kontroll. Basert på nettadressen, bestemmer den hvilken håndterer som skal utføres. I Backbone.js har vi ikke den vanlige modell-View-Controller-arkitekturen. Kontrolleren mangler, og det meste av logikken blir lagt inn i visningene. Så i stedet leder vi modellene direkte til metoder, inne i visningene og får en øyeblikkelig oppdatering av brukergrensesnittet, når dataene har endret seg.
Det viktigste i vårt lille prosjekt er dataene. Våre oppgaver er hva vi skal klare, så la oss starte derfra. Her er vår modelldefinisjon.
// modeller / ToDo.js app.models.ToDo = Backbone.Model.extend (standard: title: "ToDo", arkivert: false, ferdig: false);
Bare tre felt. Den første inneholder teksten til oppgaven, og de to andre er flagg som definerer statusen til posten.
Hver ting inne i rammen er faktisk en event dispatcher. Og fordi modellen er endret med settere, vet rammen når dataene er oppdatert og kan varsle resten av systemet for det. Når du har bundet noe til disse varslene, vil din søknad reagere på endringene i modellen. Dette er en veldig kraftig funksjon i Backbone.js.
Som jeg sa i begynnelsen, vil vi ha mange poster, og vi vil organisere dem inn i en samling som heter Todos
.
// samlinger / ToDos.js app.collections.ToDos = Backbone.Collection.extend (initialiser: funksjon () this.add (title: "Lær JavaScript grunnleggende"); this.add (title: "Go til backbonejs.org "); this.add (title:" Utvikle et ryggradsprogram ");, modell: app.models.ToDo up: funksjon (indeks) hvis (indeks> 0) var tmp = this.models [index-1]; this.models [index-1] = this.models [index]; this.models [index] = tmp; this.trigger ("change");, ned: funksjon indeks) hvis (indeks < this.models.length-1) var tmp = this.models[index+1]; this.models[index+1] = this.models[index]; this.models[index] = tmp; this.trigger("change"); , archive: function(archived, index) this.models[index].set("archived", archived); , changeStatus: function(done, index) this.models[index].set("done", done); );
De initial
Metoden er inngangspunktet for samlingen. I vårt tilfelle har vi lagt til noen få oppgaver som standard. Selvfølgelig i den virkelige verden, vil informasjonen komme fra en database eller et annet sted. Men for å holde deg fokusert, vil vi gjøre det manuelt. Den andre tingen som er typisk for samlinger, setter inn modell
eiendom. Det forteller klassen hvilken type data som lagres. Resten av metodene implementerer tilpasset logikk, relatert til funksjonene i vår søknad. opp
og ned
funksjoner endrer rekkefølgen til ToDos. For å forenkle ting identifiserer vi hver ToDo med bare en indeks i samlingens array. Dette betyr at hvis vi ønsker å hente en bestemt post, bør vi peke på indeksen. Så, bestillingen skifter bare elementene i en matrise. Som du kanskje gjetter fra koden ovenfor, this.models
er samlingen som vi snakker om. arkiv
og change
sett egenskapene til det gitte elementet. Vi legger disse metodene her, fordi visningene vil ha tilgang til Todos
samling og ikke direkte til oppgavene.
I tillegg trenger vi ikke å lage noen modeller fra app.models.ToDo
klasse, men vi trenger å lage en forekomst fra app.collections.ToDos
samling.
// App.js init: funksjon () this.content = $ ("# content"); this.todos = nye api.collections.ToDos (); returnere dette;
Det første vi må vise er hovedapplikasjonens navigasjon.
// view / menu.js app.views.menu = Backbone.View.extend (mal: _.template ($ ("# tpl-menu"). html ()), initialiser: funksjon () this.render ();, gjengi: funksjon () this. $ el.html (this.template ()););
Det er bare ni kodelinjer, men det er mange kule ting som skjer her. Den første er å sette inn en mal. Hvis du husker, la vi Underscore.js til vår app? Vi skal bruke sin templerende motor, fordi det fungerer bra og det er enkelt nok å bruke.
_.template (templateString, [data], [settings])
Hva du har på slutten, er en funksjon som aksepterer et objekt som holder informasjonen din i nøkkelverdierpar og templateString
er HTML markup. Ok, så det aksepterer en HTML-streng, men hva er det $ ( "# TPL-meny"). Html ()
gjør det? Når vi utvikler et lite enkelttsidsprogram, legger vi vanligvis maler direkte inn på siden slik:
// index.html
Og fordi det er et skript, er det ikke vist for brukeren. Fra et annet synspunkt er det en gyldig DOM-node, slik at vi kan få innholdet sitt med jQuery. Så, det korte kuttet over tar bare innholdet i skriptet.
De gjengi
Metoden er veldig viktig i Backbone.js. Det er funksjonen som viser dataene. Normalt binder du hendelsene som er trukket av modellene direkte til den metoden. Men for hovedmenyen trenger vi ikke slik oppførsel.
. Dette $ el.html (this.template ());
dette. $ el
er et objekt som er opprettet av rammen, og hver visning har det som standard (det er en $
foran el
fordi vi har med jQuery inkludert). Og som standard er det tomt . Selvfølgelig kan du endre det ved å bruke
TagNavn
eiendom. Men det som er viktigere her er at vi ikke tilordner en verdi direkte til det aktuelle objektet. Vi endrer ikke det, vi endrer bare innholdet sitt. Det er stor forskjell mellom linjen over og denne neste:
dette. $ el = $ (this.template ());
Poenget er at hvis du vil se endringene i nettleseren, bør du ringe gjengivelsesmetoden før, for å legge til visningen til DOM. Ellers vil bare den tomme div bli festet. Det er også et annet scenario hvor du har nestede visninger. Og fordi du endrer eiendommen direkte, blir ikke foreldrekomponenten oppdatert. De bundet hendelsene kan også bli ødelagte, og du må feste lytterne igjen. Så, du bør bare endre innholdet av dette. $ el
og ikke egenskapens verdi.
Utsikten er nå klar, og vi må initialisere den. La oss legge den til i vår fabrikkmodul:
// App.js var ViewsFactory = meny: funksjon () hvis (! This.menuView) this.menuView = nye api.views.menu (el: $ ("# menu")); returner dette.menuView; ;
På slutten kaller du bare Meny
metode i bootstrapping-området:
// App.js init: funksjon () this.content = $ ("# content"); this.todos = nye api.collections.ToDos (); ViewsFactory.menu (); returnere dette;
Legg merke til at mens vi lager en ny forekomst fra navigasjonsklassen, overfører vi et allerede eksisterende DOM-element $ ( "# Meny")
. Så dette. $ el
eiendom i utsikten er faktisk peker på $ ( "# Meny")
.
Backbone.js støtter push-tilstand operasjoner. Med andre ord kan du manipulere nettleserens nettadresse og reise mellom sider. Vi vil imidlertid holde fast med de gode URL-adressene for gamle hashtyper, for eksempel / # Redigere / 3
.
// App.js var Router = Ryggrad.Router.extend (ruter: "arkiv": "arkiv", "nytt": "newToDo", "edit /: index": "editToDo", "delete / ":" delteToDo "," ":" liste ", liste: funksjon (arkiv) , arkiv: funksjon () , newToDo: funksjon () , editToDo: funksjon (indeks) , delteToDo: funksjon (indeks) );
Over er ruteren vår. Det er fem ruter definert i et hashobjekt. Nøkkelen er hva du vil skrive inn i nettleserens adresselinje og verdien er funksjonen som vil bli kalt. Legg merke til at det er : indeks
på to av ruterne. Det er syntaksen du må bruke hvis du vil støtte dynamiske nettadresser. I vårt tilfelle, hvis du skriver # Redigere / 3
de editToDo
vil bli utført med parameter indeks = 3
. Den siste raden inneholder en tom streng som betyr at den håndterer hjemmesiden til søknaden vår.
Så langt hva vi har bygget er hovedvisningen for prosjektet vårt. Den henter dataene fra samlingen og skriver den ut på skjermen. Vi kunne bruke samme visning for to ting - viser alle aktive ToDos og viser de som er arkivert.
Før vi fortsetter med implementeringen av listevisningen, la oss se hvordan den faktisk er initialisert.
// i App.js visninger fabrikkliste: funksjon () if (! this.listView) this.listView = new api.views.list (model: api.todos); returner dette.listView;
Legg merke til at vi passerer inn i samlingen. Det er viktig fordi vi senere bruker this.model
for å få tilgang til lagrede data. Fabrikken returnerer listevisningen, men ruteren er fyren som må legge den til siden.
// i App.js ruter liste: funksjon (arkiv) var view = ViewsFactory.list (); api .title (arkiv? "Arkiv:": "Din ToDos:") .changeContent (se. $ el); view.setMode (arkiv? "arkiv": null) .render ();
For nå, metoden liste
i ruteren kalles uten noen parametere. Så visningen er ikke inne arkiv
modus, vil det bare vise de aktive ToDos.
// visninger / list.js app.views.list = Backbone.View.extend (modus: null, hendelser: , initialiser: funksjon () var handler = _.bind (this.render, this); dette .model.bind ('change', handler); this.model.bind ('add', handler); this.model.bind ('remove', handler);, gjengi: funksjon () , prioritet: funksjon (e) , prioritetDown: funksjon (e) , arkiv: funksjon (e) , changeStatus: funksjon (e) , setMode: funksjon (modus) this.mode = mode; );
De modus
Egenskapen vil bli brukt under gjengivelsen. Hvis verdien er mode = "arkiv"
da vil bare de arkiverte ToDos bli vist. De arrangementer
er et objekt som vi vil fylle med en gang. Det er stedet der vi plasserer DOM-hendelser kartlegging. Resten av metodene er svar på brukerinteraksjonen, og de er direkte knyttet til de nødvendige funksjonene. For eksempel, priorityUp
og priorityDown
endrer bestilling av ToDos. arkiv
flytter elementet til arkivområdet. change
markerer bare ToDo som gjort.
Det er interessant hva som skjer inni initial
metode. Tidligere sa vi at du normalt vil binde endringene i modellen (samlingen i vårt tilfelle) til gjengi
visningsmetode. Du kan skrive this.model.bind ('change', this.render)
. Men veldig snart vil du legge merke til at dette
søkeord, i gjengi
Metoden vil ikke peke på selve utsikten. Det er fordi omfanget er endret. Som en løsning oppretter vi en handler med et allerede definert omfang. Det er hva Underscore er binde
funksjonen brukes til.
Og her er implementeringen av gjengi
metode.
// visninger / list.js gjengir: funksjon () ) var html = '
Vi slår gjennom alle modellene i samlingen og genererer en HTML-streng, som senere settes inn i visningens DOM-element. Det er få sjekker som skiller ToDos fra arkivert til aktivt. Oppgaven er merket som ferdig ved hjelp av en avkrysningsboks. Så for å indikere dette må vi passere en sjekket == "merket"
attributt til det elementet. Du kan merke at vi bruker this.delegateEvents ()
. I vårt tilfelle er dette nødvendig, fordi vi fjerner og legger til visningen fra DOM. Ja, vi erstatter ikke hovedelementet, men hendelsene er fjernet. Det er derfor vi må fortelle Backbone.js å feste dem igjen. Malen som brukes i koden ovenfor er:
// index.html
Legg merke til at det er en CSS-klasse definert kalt gjort-ja
, som maler ToDo med en grønn bakgrunn. Dessuten er det en rekke lenker som vi skal bruke for å implementere den nødvendige funksjonaliteten. De har alle dataattributter. Hovednoden til elementet, li
, har data-indeks
. Verdien av dette attributtet viser indeksen for oppgaven i samlingen. Legg merke til at de spesielle uttrykkene er pakket inn <%=… %>
blir sendt til mal
funksjon. Det er dataene som injiseres i malen.
Det er på tide å legge til noen hendelser i visningen.
// / view.list.js events: 'klikk på [data-up]': 'priorityUp', 'klikk på [data ned]': 'priorityDown', 'klikk på et [dataarkiv]': 'arkiv ',' klikk inntasting [datastatus] ':' changeStatus '
I Backbone.js er arrangementets definisjon bare en hash. Du skriver først navnet på hendelsen og deretter en velger. Verdiene av egenskapene er faktisk visningsmetodene.
// views / list.js priorityUp: funksjon (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("data-index")); this.model.up (index); , prioritetDown: funksjon (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("data-index")); this.model.down (index); , arkiv: funksjon (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("data-index")); this.model.archive (this.mode! == "arkiv", indeks); , changeStatus: funksjon (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("data-index")); this.model.changeStatus (e.target.checked, indeks);
Her bruker vi e.target
kommer inn til handleren. Det peker på DOM-elementet som utløste hendelsen. Vi får indeksen til klikket ToDo og oppdaterer modellen i samlingen. Med disse fire funksjonene har vi avsluttet vår klasse, og nå er dataene vist på siden.
Som vi nevnte ovenfor, vil vi bruke samme visning for Arkiv
side.
liste: funksjon (arkiv) var view = ViewsFactory.list (); api .title (arkiv? "Arkiv:": "Din ToDos:") .changeContent (se. $ el); view.setMode (arkiv? "arkiv": null) .render (); , arkiv: funksjon () this.list (true);
Over er samme rutehåndterer som før, men denne gangen med ekte
som en parameter.
Etter primeren i listevisningen kan vi opprette en annen som viser et skjema for å legge til og redigere oppgaver. Slik er denne nye klassen opprettet:
// App.js / views fabrikkform: funksjon () if (! This.formView) this.formView = new api.views.form (model: api.todos). På ("lagret", funksjon ( ) api.router.navigate ("", trigger: true);) returner this.formView;
Stort sett det samme. Men denne gangen må vi gjøre noe når skjemaet er sendt inn. Og det er videre til brukeren på hjemmesiden. Som jeg sa, er hvert objekt som utvider Backbone.js-klasser, faktisk en hendelse-dispatcher. Det er metoder som på
og avtrekker
som du kan bruke.
Før vi fortsetter med visningskoden, la oss se på HTML-malen:
Vi har en textarea
og a knapp
. Malen forventer a tittel
parameter som skal være en tom streng, hvis vi legger til en ny oppgave.
// visninger / form.js app.views.form = Backbone.View.extend (indeks: falsk, hendelser: 'klikk-knapp': 'lagre', initialiser: funksjon () this.render (); , gjengivelse: funksjon (indeks) varmal, html = $ ("# tpl-form"). html (); hvis (typeof index == 'undefined') this.index = false; template = _.template html, title: ""); andre this.index = parseInt (indeks); this.todoForEditing = this.model.at (this.index); template = _.template ($ ("# tpl-form ") .html (), title: this.todoForEditing.get (" title ")); dette. $ el.html (mal); dette. $ el.find (" textarea ") .fokus (); this.delegateEvents (); return dette;, lagre: funksjon (e) e.preventDefault (); var title = dette. $ el.find ("textarea") .val (); if (title == "" ) alert ("Empty textarea!"; return; hvis (this.index! == false) this.todoForEditing.set ("title", tittel); else this.model.add (title: tittel); this.trigger ("saved"););
Utsikten er bare 40 linjer med kode, men det gjør jobben sin bra. Det er bare en hendelse knyttet og dette er å klikke på lagre-knappen. Gjenvinningsmetoden virker annerledes avhengig av det bestått index
parameter. For eksempel, hvis vi redigerer en ToDo, passerer vi indeksen og henter den eksakte modellen. Hvis ikke, er skjemaet tomt og en ny oppgave blir opprettet. Det er flere interessante punkter i koden ovenfor. Først, i gjengivelsen brukte vi .fokus()
Metode for å bringe fokuset til skjemaet når visningen gjengis. Igjen delegateEvents
funksjonen skal kalles, fordi skjemaet kunne løsnes og festes igjen. De lagre
Metoden starter med e.preventDefault ()
. Dette fjerner standardoppførelsen til knappen, som i noen tilfeller kan sende inn skjemaet. Og til slutt, når alt er gjort, utløste vi lagret
Hendelse som informerer omverdenen om at ToDo er lagret i samlingen.
Det er to metoder for ruteren som vi må fylle ut.
// App.js newToDo: funksjon () var view = ViewsFactory.form (); api.title ("Opprett ny ToDo:"). changeContent (se. $ el); view.render (), editToDo: funksjon (indeks) var view = ViewsFactory.form (); . Api.title ( "Edit:") changeContent (vis $ el.); view.render (index);
Forskjellen mellom dem er at vi passerer i en indeks, hvis redigere /: indeks
ruten er tilpasset. Og selvfølgelig er tittelen på siden endret tilsvarende.
For denne funksjonen trenger vi ikke en visning. Hele jobben kan gjøres direkte i ruterenes håndterer.
delteToDo: funksjon (indeks) api.todos.remove (api.todos.at (parseInt (indeks))); api.router.navigate ("", trigger: true);
Vi kjenner indeksen til ToDo som vi vil slette. Det er en fjerne
metode i samlingen klassen som aksepterer en modell objekt. På slutten, bare videresende brukeren til hjemmesiden, som viser den oppdaterte listen.
Backbone.js har alt du trenger for å bygge en fullt funksjonell, enkeltside applikasjon. Vi kan til og med binde den til en REST back-end-tjeneste, og rammen vil synkronisere dataene mellom appen din og databasen. Den hendelsesdrevne tilnærmingen oppfordrer modulær programmering, sammen med en god arkitektur. Jeg bruker personlig Backbone.js til flere prosjekter, og det fungerer veldig bra.