Aplicație One ToDo pe pagină cu Backbone.js

Backbone.js este un cadru JavaScript pentru construirea de aplicații web flexibile. Acesta vine cu modele, colecții, vizionări, evenimente, router și câteva alte caracteristici extraordinare. În acest articol vom dezvolta o aplicație simplă ToDo care acceptă adăugarea, editarea și îndepărtarea sarcinilor. De asemenea, ar trebui să putem marca o sarcină ca Terminat și arhivați-l. Pentru a păstra rezonabilitatea acestei postări, nu vom include nici o comunicare cu o bază de date. Toate datele vor fi păstrate pe partea clientului.

Înființat

Aici este structura de fișier pe care o vom folosi:

CSS └─────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────── └──────────────────────────────────────────────────────────────── 

Există câteva lucruri care sunt evidente, cum ar fi /css/styles.css și /index.html. Acestea conțin stilurile CSS și marcajul HTML. În contextul modelului Backbone.js, modelul este un loc în care păstrăm datele noastre. Deci, ToDos-ul nostru va fi pur și simplu modele. Și pentru că vom avea mai multe sarcini, le vom organiza într-o colecție. Logica de afaceri este distribuită între vizualizări și fișierul aplicației principale, App.js. Backbone.js are o singură dependență tare - Underscore.js. Cadrul joacă, de asemenea, foarte bine cu jQuery, așa că ambii merg la furnizor director. Tot ce avem nevoie acum este doar un mic marcaj HTML și suntem gata să mergem.

   TODO-urile mele    

După cum puteți vedea, includem toate fișierele JavaScript externe spre partea de jos, deoarece este o practică bună să faceți acest lucru la sfârșitul etichetei corporale. Pregătim, de asemenea, bootstraparea aplicației. Există un container pentru conținut, un meniu și un titlu. Navigația principală este un element static și nu o vom schimba. Vom înlocui conținutul titlului și div sub acesta.

Planificarea aplicației

Este întotdeauna bine să aveți un plan înainte de a începe să lucrăm la ceva. Backbone.js nu are o arhitectură super-strictă, pe care trebuie să o urmăm. Aceasta este una dintre beneficiile cadrului. Deci, înainte de a începe cu implementarea logicii de afaceri, să vorbim despre bază.

spațiu de nume

O bună practică este să vă puneți codul în domeniul său de aplicare. Înregistrarea variabilelor globale sau a funcțiilor nu este o idee bună. Ce vom crea este un model, o colecție, un router și câteva vederi Backbone.js. Toate aceste elemente ar trebui să trăiască într-un spațiu privat. App.js va conține clasa care deține totul.

// App.js var app = (functie () var api = vizualizari: , modele: , colectii: , content: null, router: null, () () () () () () () () () ");; ; retur;; var_Rester.extend (); 

Mai sus este o implementare tipică a modelului modulului revelator. api variabila este obiectul care este returnat și reprezintă metodele publice ale clasei. vizualizari, modele și colecții proprietățile vor acționa ca deținători pentru clasele returnate de Backbone.js. conţinut este un element jQuery care indică containerul de interfață principal al utilizatorului. Există două metode de ajutor aici. Primul actualizează acel container. Cel de-al doilea setează titlul paginii. Apoi am definit un modul numit ViewsFactory. Acesta va oferi punctele de vedere și, la final, am creat routerul.

Ați putea întreba: de ce avem nevoie de o fabrică pentru vizionări? Ei bine, există câteva modele comune în timp ce lucrați cu Backbone.js. Una dintre ele este legată de crearea și folosirea opiniilor.

Var ViewClass = Backbone.View.extend (/ * logic aici * /); Var vedere = nou ViewClass (); 

Este bine să inițializați vizualizările o singură dată și să le lăsați în viață. După schimbarea datelor, apelăm în mod normal metode ale vizualizării și actualizăm conținutul acesteia el obiect. Cealaltă abordare foarte populară este de a recrea întreaga viziune sau de a înlocui întregul element DOM. Cu toate acestea, acest lucru nu este foarte bun din punct de vedere al performanței. Deci, în mod normal, ajungem la o clasă de utilități care creează o instanță a vederii și o returnează atunci când avem nevoie de ea.

Definiția componentelor

Avem un spațiu de nume, deci acum putem începe să creăm componente. Iată cum arată meniul principal:

// views / menu.js app.views.menu = Backbone.View.extend (initialize: function () , render: functie () ); 

Am creat o proprietate numită meniul care deține clasa navigației. Mai târziu, este posibil să adăugăm o metodă în modulul fabricii care creează o instanță a acesteia.

Var: ViewFactory = meniu: function () if (! this.menuView) this.menuView = nou api.views.menu (el: $ ("# meniu"));  return this.menuView; ; 

Mai sus este modul în care ne vom ocupa de toate punctele de vedere și ne va asigura că vom obține doar unul și același exemplu. Această tehnică funcționează bine, în majoritatea cazurilor.

curgere

Punctul de intrare al aplicației este App.js si este init metodă. Aceasta este ceea ce vom numi în onload manipulator al fereastră obiect.

window.onload = funcție () app.init ();  

După aceasta, routerul definit preia controlul. Bazându-se pe adresa URL, ea decide care handler să execute. În Backbone.js, nu avem arhitectura obișnuită Model-View-Controller. Controlerul lipsește și cea mai mare parte a logicii este pusă în vizualizări. Deci, în loc de asta, conducem modelele direct la metode, în interiorul vizualizărilor și obținem o actualizare instantanee a interfeței utilizator, odată ce datele s-au schimbat.

Gestionarea datelor

Cel mai important lucru în proiectul nostru mic îl reprezintă datele. Sarcinile noastre sunt ceea ce ar trebui să gestionăm, așa că haideți să începem de acolo. Iată definiția noastră de model.

// modele / ToDo.js app.models.ToDo = Backbone.Model.extend (implicit: title: "ToDo", arhivat: false, done: false); 

Doar trei câmpuri. Primul conține textul sarcinii, iar celelalte două sunt steaguri care definesc starea înregistrării.

Fiecare lucru din interiorul cadrului este de fapt un dispecer de evenimente. Și deoarece modelul este schimbat cu setterii, cadrul știe când datele sunt actualizate și pot notifica restul sistemului pentru asta. Odată ce ați legat ceva la aceste notificări, aplicația dvs. va reacționa asupra modificărilor din model. Aceasta este o caracteristică foarte puternică în Backbone.js.

Așa cum am spus la început, vom avea multe înregistrări și le vom organiza într-o colecție numită Todos.

// colecții / ToDos.js app.collections.ToDos = Backbone.Collection.extend (initialize: function () this.add (title: "Aflați elementele de bază ale JavaScript"); this.add (title: la backbonejs.org "); this.add (title:" Dezvoltarea unei aplicații Backbone "); model: app.models.ToDo up: function (index) if (index> 0) var tmp = this.models [index-1]; this.models [index-1] = acest.models [index]; thismodels [index] = tmp; this.trigger ("change");, index) if (index < 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);  ); 

inițializa metoda este punctul de intrare al colecției. În cazul nostru, am adăugat câteva sarcini în mod implicit. Desigur, în lumea reală, informațiile vor proveni dintr-o bază de date sau în altă parte. Dar pentru a vă menține concentrați, vom face acest lucru manual. Celălalt lucru care este tipic pentru colecții este setarea model proprietate. Acesta spune clasei ce fel de date sunt stocate. Restul metodelor implementează logica personalizată, în funcție de caracteristicile din aplicația noastră. sus și jos funcțiile modifică ordinea ToDos. Pentru a simplifica lucrurile, vom identifica fiecare ToDo doar cu un indice din matricea colecției. Aceasta înseamnă că, dacă vrem să obținem o înregistrare specifică, ar trebui să indicăm indexul său. Deci, ordonarea este doar comutarea elementelor într-o matrice. Așa cum ați putea ghici din codul de mai sus, this.models este matricea despre care vorbim. Arhiva și schimba starea setați proprietățile elementului dat. Am pus aceste metode aici, pentru că punctele de vedere vor avea acces la Todos colectarea și nu la sarcinile în mod direct.

În plus, nu este necesar să creați niciun model din app.models.ToDo dar trebuie să creăm o instanță din app.collections.ToDos Colectie.

// App.js init: funcția () this.content = $ ("# content"); this.todos = noi api.collections.ToDos (); returnați acest lucru;  

Afișarea primei noastre vizionări (Navigația principală)

Primul lucru pe care trebuie să-l arătăm este navigarea principală a aplicației.

// views / menu.js app.views.menu = Backbone.View.extend (template: _.template ($ ("# tpl-menu"). ();, render: funcția () this. $ el.html (this.template ());); 

Sunt doar nouă linii de cod, dar aici se petrec multe lucruri reci. Prima este setarea unui șablon. Dacă vă aduceți aminte, am adăugat Underscore.js în aplicația noastră? Vom folosi motorul său templating, pentru că funcționează bine și este suficient de simplu să o utilizați.

_.template (șablonString, [date], [setări]) 

Ceea ce aveți la sfârșit, este o funcție care acceptă un obiect care ține informațiile dvs. în perechi cheie-valoare și templateString este marcajul HTML. Ok, deci acceptă un șir HTML, dar ce este $ ( "# TPL-meniu"). Html () a face acolo? Atunci când dezvoltăm o aplicație cu o singură pagină, în mod normal, știm șabloanele direct în pagină ca aceasta:

// index.html  

Și pentru că este o etichetă de script, nu este afișată utilizatorului. Din alt punct de vedere, este un nod DOM valid, astfel încât să putem obține conținutul său cu jQuery. Deci, fragmentul scurt de mai sus ia doar conținutul etichetei de script.

face metoda este foarte importantă în Backbone.js. Aceasta este funcția care afișează datele. În mod normal, legați evenimentele trase de modele direct de acea metodă. Cu toate acestea, pentru meniul principal, nu avem nevoie de un astfel de comportament.

. Acest $ el.html (this.template ()); 

acest lucru. $ el este un obiect creat de cadru și fiecare vizualizare o are în mod implicit (există $ în fața el pentru că am inclus jQuery). Și, în mod implicit, este gol

. Bineînțeles că puteți schimba acest lucru folosind nume eticheta proprietate. Dar ceea ce este mai important aici este că nu atribuim direct acest obiect. Nu o schimbăm, schimbăm doar conținutul său. Există o mare diferență între linia de mai sus și următoarea:

acest lucru: $ el = $ (this.template ()); 

Ideea este că, dacă doriți să vedeți modificările din browser, trebuie să apelați metoda de redare înainte, pentru a adăuga vizualizarea DOM. În caz contrar, numai div div va fi atașat. Există, de asemenea, un alt scenariu în care aveți viziuni imbricate. Și pentru că schimbați proprietatea direct, componenta părinte nu este actualizată. De asemenea, evenimentele legate pot fi rupte și trebuie să le atașați din nou pe ascultători. Deci, tu ar trebui să schimbi doar conținutul acest lucru. $ el și nu valoarea proprietății.

Vederea este acum pregătită și trebuie inițializată. Să o adăugăm la modulul fabricii noastre:

// App.js var ViewsFactory = meniu: function () if (! This.menuView) this.menuView = nou api.views.menu (el: $ ("# meniu"));  return this.menuView; ; 

La sfârșit, pur și simplu apelați meniul în zona de încărcare:

// App.js init: funcția () this.content = $ ("# content"); this.todos = noi api.collections.ToDos (); ViewsFactory.menu (); returnați acest lucru;  

Rețineți că în timp ce creăm o nouă instanță din clasa de navigație, trecem un element DOM deja existent $ ( "# Meniul"). Asa ca acest lucru. $ el proprietatea din interiorul punctului de vedere este de fapt indică $ ( "# Meniul").

Adăugarea de rute

Backbone.js acceptă împingeți starea operațiuni. Cu alte cuvinte, puteți manipula adresa URL a browserului curent și puteți călători între pagini. Cu toate acestea, vom păstra, de exemplu, URL-urile de tipul good hash / # Edita / 3.

// App.js var Router = Backbone.Router.extend (trase: "arhiva": "arhiva", "new": "newToDo", "editare /: index": "editToDo" ":" delteToDo ",": "lista", lista: functie (arhiva) , arhiva: function () , deltaToDo: funcție (index) ); 

Deasupra este ruterul nostru. Există cinci rute definite într-un obiect hash. Cheia este ceea ce veți scrie în bara de adrese a browserului, iar valoarea este funcția care va fi apelată. Observați că există :index pe două rute. Aceasta este sintaxa pe care trebuie să o utilizați dacă doriți să acceptați adrese URL dinamice. În cazul nostru, dacă tastați # Edita / 3 editToDo va fi executat cu parametru index = 3. Ultimul rând conține un șir gol, ceea ce înseamnă că se ocupă de pagina de pornire a aplicației noastre.

Se afișează o listă a tuturor sarcinilor

Până acum, ceea ce am construit este viziunea principală a proiectului nostru. Acesta va prelua datele din colecție și va imprima pe ecran. Am putea folosi aceeași vizualizare pentru două lucruri - afișarea tuturor documentelor ToDos active și afișarea celor care sunt arhivate.

Înainte de a continua implementarea vizualizării pe listă, să vedem cum este de fapt inițializată.

// în vizualizările App.js listă fabrică: function () if (! this.listView) this.listView = nou api.views.list (model: api.todos);  return this.listView;  

Observați că trecem în colecție. Este important pentru că vom folosi ulterior acest model pentru a accesa datele stocate. Fabrica returnează vizualizarea listei noastre, dar ruterul este tipul care trebuie să o adauge la pagină.

// în lista routerului App.js: funcția (arhivă) var view = ViewsFactory.list (); api .title (arhiva? "Arhiva:": "ToDos:") .changeContent (vezi. $ el); view.setMode (arhiva? "arhiva": null) .render ();  

Pentru moment, metoda listă în router se numește fără parametri. Deci nu este viziunea Arhiva mod, acesta va afișa numai ToDos activ.

// views / list.js app.views.list = Backbone.View.extend (mode: null, evenimente: , initialize: function () var handler = _.bind (this.render, this); .model.bind ('remove', handler);, render: function () , priorityUp: (e) , prioritateDescriere: functie (e) , arhiva: functie (e) , changeStatus: functie (e) , setMode: function (mode) this.mode = ); 

mod proprietatea va fi utilizată în timpul redării. Dacă valoarea lui este mode = "arhiva" atunci vor fi afișate doar fișierele arhivate ToDos. evenimente este un obiect pe care îl vom umple imediat. Acesta este locul unde plasăm maparea evenimentelor DOM. Restul metodelor sunt răspunsurile interacțiunii utilizatorilor și sunt direct legate de caracteristicile necesare. De exemplu, priorityUp și priorityDown modifică ordonarea pentru ToDos. Arhiva muta elementul în zona de arhivare. schimba starea marchează pur și simplu ToDo ca făcut.

Este interesant ce se întâmplă în interiorul inițializa metodă. Anterior am spus că în mod normal veți lega modificările din model (colecția în cazul nostru) la face metoda de vedere. Puteți scrie this.model.bind ("schimba", acest.render). Dar foarte curând veți observa că acest cuvânt cheie, în face metoda nu va indica punctul de vedere în sine. Asta pentru că domeniul de aplicare este schimbat. Ca soluție, suntem creați un handler cu un domeniu deja definit. Asta e ceea ce înseamnă Underscore lega funcția este utilizată pentru.

Și aici este punerea în aplicare a face metodă.

// vizualizări / list.js render: function () ) var html = '
    ', eu = aceasta; acest lucru este un mod de lucru (todo, index) if (self.mode === "arhiva"? todo.get ("arhivat") === true: todo.get ("arhivat") === false ) var template = _.template ($ ("# tpl-list-item"). .mode === "arhiva"? "arhiva": "arhiva", done: todo.get (" = "verificat" ': ");); html + = '
„; . Acest el.html $ (html); this.delegateEvents (); returnați acest lucru;

Ne confruntăm prin toate modelele din colecția și generând un șir HTML, care este introdus mai târziu în elementul de vizualizare DOM. Există puține verificări care distingază ToDosul de arhivate în active. Sarcina este marcată ca Terminat cu ajutorul unei casete de selectare. Deci, pentru a indica acest lucru trebuie să trecem a verificat == „verificat“ atribuiți acelui element. S-ar putea să observați că folosim this.delegateEvents (). În cazul nostru, acest lucru este necesar, deoarece detașăm și atașăm punctul de vedere din DOM. Da, nu înlocuim elementul principal, dar manipulatorii evenimentelor sunt eliminați. De aceea trebuie să le spunem Backbone.js să le atașăm din nou. Modelul utilizat în codul de mai sus este:

// index.html  

Observați că există o clasă CSS numită făcut-da, care picteaza ToDo cu un fundal verde. În plus, există o grămadă de legături pe care le vom folosi pentru a implementa funcționalitatea necesară. Toți au atribute de date. Nodul principal al elementului, Li, are date index. Valoarea acestui atribut este afișarea indexului sarcinii din colecție. Observați că expresiile speciale înfășurate în <%=… %> sunt trimise la șablon funcţie. Acestea sunt datele care sunt injectate în șablon.

Este timpul să adăugați câteva evenimente în vizualizare.

// views / list.js events: 'faceți clic pe [data-up]': 'priorityUp', 'faceți clic pe [data down-down]': 'priorityDown', ' ',' intrare pe clic [data-status] ':' changeStatus ' 

În Backbone.js definiția evenimentului este doar un hash. În primul rând, introduceți numele evenimentului și apoi un selector. Valorile proprietăților sunt de fapt metode ale vederii.

// view / list.js prioritateUp: funcția (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("date-index")); this.model.up (indice); , priorDown: funcție (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("index-date")); this.model.down (indice); , arhivă: funcția (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("index-date")); this.model.archive (this.mode! == "arhiva", index); , changeStatus: funcția (e) var index = parseInt (e.target.parentNode.parentNode.getAttribute ("index-date")); this.model.changeStatus (e.target.checked, index);  

Aici folosim e.target veniți la manipulator. Ea indică elementul DOM care a declanșat evenimentul. Obținem indicele pentru ToDo și actualizăm modelul în colecție. Cu aceste patru funcții am terminat clasa noastră și acum datele sunt afișate în pagină.

După cum am menționat mai sus, vom folosi aceeași viziune pentru Arhiva pagină.

listă: funcție (arhivă) var view = ViewsFactory.list (); api .title (arhiva? "Arhiva:": "ToDos:") .changeContent (vezi. $ el); view.setMode (arhiva? "arhiva": null) .render (); , arhiva: function () this.list (true);  

Mai sus este același manipulator de rută ca și înainte, dar de data aceasta cu Adevărat ca parametru.

Adăugarea și editarea dosarelor

În urma primului aspect al listei de listă, am putea crea un altul care arată un formular pentru adăugarea și editarea sarcinilor. Iată cum este creată această clasă nouă:

// app.js / vizualizări forma fabrică: function () if (! This.formView) this.formView = nou api.views.form (model: api.todos). ) api.router.navigate ("", trigger: true);) returnați această imagine;  

Destul de asemănător. Cu toate acestea, de data aceasta trebuie să facem ceva odată ce este trimis formularul. Și acest lucru este înainte de utilizator la pagina de start. După cum am spus, fiecare obiect care extinde clasele Backbone.js este de fapt un dispecer de evenimente. Există metode cum ar fi pe și trăgaci pe care le puteți utiliza.

Înainte de a continua cu codul de vizualizare, să aruncăm o privire la șablonul HTML:

 

Noi avem un textarea și a buton. Modelul așteaptă a titlu parametru care ar trebui să fie un șir gol, dacă adăugăm o nouă sarcină.

// views / form.js app.views.form = Backbone.View.extend (index: false, evenimente: 'click button': 'save', initialize: function () this.render (); , render: funcția (index) var template, html = $ ("# tpl-form"). html, title: ""); altceva this.index = parseInt (index); this.todoForEditing = this.model.at (this.index); template = _.template ($ ") .html (), title: this.todoForEditing.get (" title ")); $ el.html (template) ;. $ el.find (" textarea ") focus (); (e) (e) e.preventDefault (); var title = aceasta $ el.find ("textarea") val (); if (title == " ) if (this.index! == false) this.todoForEditing.set ("title", title); else this.model.add title: title); this.trigger ("salvat");); 

Vederea este de numai 40 de linii de cod, dar își face bine treaba. Există un singur eveniment atașat și acesta este clicul butonului de salvare. Metoda de randare acționează diferit, în funcție de cel trecut index parametru. De exemplu, dacă edităm un ToDo, trecem indexul și preluăm modelul exact. Dacă nu, formularul este gol și se va crea o nouă sarcină. Există mai multe puncte interesante în codul de mai sus. În primul rând, în redarea am folosit .se concentreze () pentru a aduce focalizarea pe formular odată ce imaginea este redată. Din nou delegateEvents funcția ar trebui să fie numit, deoarece forma ar putea fi detașată și atașat din nou. Salvați metoda începe cu e.preventDefault (). Acest lucru elimină comportamentul implicit al butonului, care, în unele cazuri, poate trimite formularul. Și, la sfârșit, odată ce totul se face, am declanșat salvate eveniment care anunță lumea exterioară că ToDo este salvată în colecție.

Există două metode pentru router pe care trebuie să le completăm.

// App.js newToDo: funcție () var view = ViewsFactory.form (); api.title ("Creați noul ToDo:") changeContent (vizualizare $ el); view.render (), editToDo: funcția (index) var view = ViewsFactory.form (); . Api.title ( "Edit") changeContent (vizualizare $ el.); view.render (indice);  

Diferența dintre ele este că vom trece într-un index, dacă edita /: indice ruta este potrivită. Și, bineînțeles, titlul paginii se modifică în consecință.

Ștergerea unei înregistrări din colecție

Pentru această caracteristică, nu avem nevoie de o vizualizare. Întreaga activitate poate fi efectuată direct în dispozitivul de rutare al routerului.

delteToDo: funcție (index) api.todos.remove (api.todos.at (parseInt (index))); api.router.navigate ("", trigger: true);  

Știm indicele de la ToDo pe care dorim să o ștergem. Este un elimina în clasa de colectare care acceptă un obiect model. La sfarsit, trimiteti-l direct pe pagina de start, care prezinta lista actualizata.

Concluzie

Backbone.js are tot ce aveți nevoie pentru a construi o aplicație complet funcțională, cu o singură pagină. Am putea chiar să o legăm la un serviciu de back-end REST și cadrul va sincroniza datele între aplicația dvs. și baza de date. Abordarea bazată pe evenimente încurajează programarea modulară, împreună cu o arhitectură bună. Personal folosesc Backbone.js pentru mai multe proiecte și funcționează foarte bine.

Cod