Тъй като съвременните уеб приложения правят все повече и повече от страна на клиента (самият факт, че сега ги наричаме „уеб приложения“, за разлика от „уеб сайтове“, е доста показателен), нараства интересът към рамки от страна на клиента . В тази област има много играчи, но за приложения с много функционалност и много движещи се части, два от тях се открояват по-специално: Angular.js и Ember.js .
Вече публикувахме изчерпателен [урок за Angular.js] [https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app], така че „ ще се съсредоточим върху Ember.js в тази публикация, в която ще изградим просто приложение Ember, за да каталогизираме вашата музикална колекция. Ще се запознаете с основните градивни елементи на рамката и ще надникнете в нейните принципи на проектиране. Ако искате да видите изходния код, докато четете, той е достъпен като рокендрол на Github .
Ето как ще изглежда нашето приложение Rock & Roll в окончателната си версия:
Вляво ще видите, че имаме списък с изпълнители и вдясно списък с песни от избрания изпълнител (можете също да видите, че имам добър вкус към музиката, но отстъпвам). Нови изпълнители и песни могат да се добавят просто чрез въвеждане в текстовото поле и натискане на съседния бутон. Звездите до всяка песен служат за оценка, а ла iTunes.
Можем да разделим елементарната функционалност на приложението на следните стъпки:
Трябва да извървим дълъг път, за да работим, така че нека започнем.
Една от отличителните черти на Ember е големият акцент върху URL адресите. В много други рамки липсата на отделни URL адреси за отделни екрани или липсва, или се добавя като допълнителна мисъл. В Ember рутерът - компонентът, който управлява URL адреси и преходи между тях - е централната част, която координира работата между строителните блокове. Следователно, това е и ключът към разбирането на вътрешната работа на приложенията на Ember.
Ето маршрутите за нашето приложение:
App.Router.map(function() { this.resource('artists', function() { this.route('songs', { path: ':slug' }); }); });
Определяме маршрут на ресурса, artists
и songs
маршрут, вложен вътре в него. Това определение ще ни даде следните маршрути:
Използвах страхотния плъгин Ember Inspector (той съществува и за Chrome и Firefox ), за да ви покаже генерираните маршрути по лесно четим начин. Ето основните правила за маршрутите на Ember, които можете да проверите за нашия конкретен случай с помощта на горната таблица:
Има имплицитен application
маршрут.
llc тип s или c
Това се активира за всичко заявки (преходи).
Има имплицитен index
маршрут.
Това се въвежда, когато потребителят навигира до корена на приложението.
принципи за определяне на разнообразието на дизайна
Всеки ресурсен маршрут създава маршрут със същото име и имплицитно създава индексен маршрут под него.
Този индекс маршрут се активира, когато потребителят навигира до маршрута. В нашия случай, artists.index
се задейства, когато потребителят навигира до /artists
.
Един прост (без ресурс), вложен маршрут ще има името на своя родителски префикс.
Маршрутът, който определихме като this.route('songs', ...)
ще има artists.songs
като името му. Той се задейства, когато потребителят навигира до /artists/pearl-jam
или /artists/radiohead
.
Ако пътят не е даден, се приема, че е равен на името на маршрута.
Ако пътеката съдържа :
, тя се счита за динамичен сегмент .
Присвоеното му име (в нашия случай slug
) ще съответства на стойността в съответния сегмент на URL адреса. slug
горният сегмент ще има стойността pearl-jam
, radiohead
или друга стойност, която е извлечена от URL адреса.
Като първа стъпка ще изградим екрана, който показва списъка с изпълнители вляво. Този екран трябва да се показва на потребителите, когато преминат към /artists/
:
За да разберете как се изобразява този екран, е време да въведете друг всеобхватен принцип на дизайн на Ember: конвенция за конфигурация . В горния раздел видяхме, че /artists
активира artists
маршрут. По споразумение името на този маршрут обект е ArtistsRoute
. Отговорността на този обект на маршрута е да извлича данни, които приложението да изобрази. Това се случва в куката на модела на маршрута:
App.ArtistsRoute = Ember.Route.extend({ model: function() { var artistObjects = []; Ember.$.getJSON('http://localhost:9393/artists', function(artists) { artists.forEach(function(data) { artistObjects.pushObject(App.Artist.createRecord(data)); }); }); return artistObjects; } });
В този фрагмент данните се извличат чрез XHR повикване от задния край и - след преобразуване в обект на модел - се преместват в масив, който впоследствие можем да покажем. Отговорностите на маршрута обаче не се простират до осигуряване на логика на дисплея, която се обработва от контролера. Нека да разгледаме.
Хммм - всъщност на този етап не е нужно да дефинираме контролера! Ember е достатъчно умен, за да генерира контролера когато е необходимо и задайте контролера M.odel
приписва на връщаната стойност на самата кука на модела, а именно списъка на изпълнителите. (Отново това е резултат от парадигмата „конвенция за конфигурация“.) Можем да отстъпим един слой надолу и да създадем шаблон за показване на списъка:
{{#each model}} {{#link-to 'artists.songs' this class='list-group-item artist-link'}} {{name}} {{/link-to}} {{/each}} {{outlet}}
Ако това изглежда познато, това е така, защото Ember.js използва Дръжки шаблони, които имат много прост синтаксис и помощници, но не позволяват нетривиална логика (напр. ORing или ANDing термини в условие).
В горния шаблон ние итерираме през модела (настроен по-рано от маршрута до масив, който съдържа всички изпълнители) и за всеки елемент в него изобразяваме връзка, която ни отвежда до artists.songs
маршрут за този художник. Връзката съдържа името на изпълнителя. #each
helper в Handlebars променя обхвата в него на текущия елемент, така че {{name}}
винаги ще се позовава на името на изпълнителя, което в момента е в итерация.
Друга интересна точка в горния фрагмент: {{outlet}}
, който определя слотове в шаблона, където може да се изобрази съдържание. При влагане на маршрути, шаблонът за външния, ресурсен маршрут се изобразява първо, последван от вътрешния маршрут, който прави съдържанието на неговия шаблон в {{outlet}}
дефиниран от външния маршрут. Тук се случва точно това.
По споразумение всички маршрути изобразяват съдържанието си в едноименния шаблон. По-горе, data-template-name
атрибутът на горния шаблон е artists
което означава, че ще се визуализира за външния маршрут, artists
. Той посочва изход за съдържанието на десния панел, в който вътрешният маршрут, artists.index
прави съдържанието му:
Select an artist.
В обобщение, един маршрут (artists
) показва съдържанието му в лявата странична лента, като неговият модел е списъкът на изпълнителите. Друг маршрут, artists.index
прави собственото си съдържание в слота, предоставен от artists
шаблон. Той може да извлече някои данни, които да служат като негов модел, но в този случай всичко, което искаме да покажем, е статичен текст, така че няма нужда.
След това искаме да можем да създаваме художници, а не просто да разглеждаме скучен списък.
колко фирми за рисков капитал има
Когато показах, че artists
шаблон, който изобразява списъка с художници, измамих малко. Изрязах горната част, за да се съсредоточа върху важното. Сега ще добавя това обратно:
{{input type='text' class='new-artist' placeholder='New Artist' value=newName}} Add ...
Използваме помощник на Ember, input
, с текстов тип, за да направим просто въвеждане на текст. В него ние обвързвам стойността на въведения текст в newName
свойство на контролера, който архивира този шаблон, ArtistsController
. В резултат на това, когато свойството стойност на входа се промени (с други думи, когато потребителят въведе текст в него), newName
свойството на контролера ще бъде синхронизирано.
Също така съобщаваме, че createArtist
действието трябва да се задейства при щракване върху бутона. Накрая свързваме деактивираното свойство на бутона с disabled
собственост на контролера. И така, как изглежда контролерът?
App.ArtistsController = Ember.ArrayController.extend({ newName: '', disabled: function() { return Ember.isEmpty(this.get('newName')); }.property('newName') });
newName
е настроено на празно в началото, което означава, че въвеждането на текст ще бъде празно. (Спомняте ли си какво казах за обвързването? Опитайте се да промените newName
и вижте как ще се отрази като текст в полето за въвеждане.)
disabled
е реализиран така, че когато няма текст в полето за въвеждане, той ще се върне true
и по този начин бутонът ще бъде деактивиран. .property
call в края прави това „изчислено свойство“, поредното изискано парче от тортата Ember.
Изчислени свойства са свойства, които зависят от други свойства, които сами по себе си могат да бъдат „нормални“ или изчислени. Ember кешира стойността на тези, докато едно от зависимите свойства не се промени. След това преизчислява стойността на изчисленото свойство и го кешира отново.
Ето визуално представяне на горния процес. За да обобщим: когато потребителят въведе име на изпълнител, newName
актуализации на свойства, последвани от disabled
и накрая името на художника се добавя към списъка.
изграждане на мобилни приложения с html css и javascript
Помислете за това за момент. С помощта на обвързвания и изчислени свойства можем да установим (моделираме) данни като единствен източник на истина . По-горе промяната в името на новия изпълнител задейства промяна в свойството на контролера, което от своя страна задейства промяна в деактивираното свойство. Когато потребителят започне да въвежда името на новия изпълнител, бутонът се активира, сякаш с магия.
Колкото по-голяма е системата, толкова повече лост получаваме от принципа „един източник на истината“. Той поддържа нашия код чист и стабилен, а дефинициите на свойствата ни - по-декларативни.
Някои други рамки също акцентират върху това моделните данни да бъдат единственият източник на истината, но или не стигат чак до Ембер, или не успяват да свършат толкова задълбочена работа. Например Angular има двупосочни обвързвания, но няма изчислени свойства. Той може да “емулира” изчислени свойства чрез прости функции; проблемът тук е, че той няма начин да знае кога да опресни „изчислено свойство“ и по този начин прибягва до мръсна проверка и от своя страна води до загуба на производителност, особено забележима при по-големи приложения.
Ако искате да научите повече по темата, препоръчвам ви да прочетете публикация в блога на eviltrout за по-кратка версия или този въпрос на Quora за по-продължителна дискусия, в която теглет основните разработчици от двете страни.
Да се върнем, за да видим как createArtist
действие се създава след задействане (след натискане на бутона):
App.ArtistsRoute = Ember.Route.extend({ ... actions: { createArtist: function() { var name = this.get('controller').get('newName'); Ember.$.ajax('http://localhost:9393/artists', { type: 'POST', dataType: 'json', data: { name: name }, context: this, success: function(data) { var artist = App.Artist.createRecord(data); this.modelFor('artists').pushObject(artist); this.get('controller').set('newName', ''); this.transitionTo('artists.songs', artist); }, error: function() { alert('Failed to save artist'); } }); } } });
Обработващите действия трябва да бъдат обвити в actions
обект и може да бъде дефиниран по маршрута, контролера или изгледа. Избрах да го дефинирам по маршрута тук, защото резултатът от действието не се ограничава до контролера, а по-скоро „глобален“.
Тук няма нищо изискано. След като бек-ендът ни информира, че операцията по записване е приключила успешно, правим три неща, за да:
newName
обвързване, спестявайки ни от необходимостта да манипулираме DOM директно.artists.songs
), като се въведе прясно създаденият изпълнител като модел за този маршрут. transitionTo
е начинът за вътрешно придвижване между маршрути. (Помощникът link-to
служи за това чрез действие на потребителя.)Можем да покажем песните за изпълнител, като кликнете върху името на изпълнителя. Предаваме и художника, който ще се превърне в модел на новия маршрут. Ако обектът на модела е предаден по този начин, model
куката на маршрута няма да бъде извикана, тъй като няма нужда да се разрешава моделът.
Активният маршрут тук е artists.songs
и по този начин контролерът и шаблонът ще бъдат ArtistsSongsController
и artists/songs
, съответно. Вече видяхме как шаблонът се визуализира в изхода, предоставен от artists
шаблон, за да можем да се фокусираме само върху шаблона под ръка:
(...) {{#each songs}} {{title}} {{view App.StarRating maxRating=5}} {{/each}}
Имайте предвид, че извадих кода, за да създам нова песен, тъй като тя би била абсолютно същата като тази за създаване на нов изпълнител.
songs
свойството е настроено във всички обекти на изпълнители от данните, върнати от сървъра. Точният механизъм, по който се прави, не представлява особен интерес за настоящата дискусия. Засега е достатъчно да знаем, че всяка песен има заглавие и оценка.
Заглавието се показва директно в шаблона и рейтингът се представя със звезди, чрез StarRating
изглед. Нека да видим това сега.
Рейтингът на песента пада между 1 и 5 и се показва на потребителя чрез изглед, App.StarRating
Изгледите имат достъп до техния контекст (в случая песента) и техния контролер. Това означава, че те могат да четат и модифицират свойствата му. Това е за разлика от друг градивен блок на Ember, компоненти, които са изолирани, многократно използваеми контроли с достъп само до това, което им е предадено. (Можем да използваме и компонент за оценка на звездите в този пример.)
Нека да видим как изгледът показва броя на звездите и задава рейтинга на песента, когато потребителят кликне върху една от звездите:
App.StarRating = Ember.View.extend({ classNames: ['rating-panel'], templateName: 'star-rating', rating: Ember.computed.alias('context.rating'), fullStars: Ember.computed.alias('rating'), numStars: Ember.computed.alias('maxRating'), stars: function() { var ratings = []; var fullStars = this.starRange(1, this.get('fullStars'), 'full'); var emptyStars = this.starRange(this.get('fullStars') + 1, this.get('numStars'), 'empty'); Array.prototype.push.apply(ratings, fullStars); Array.prototype.push.apply(ratings, emptyStars); return ratings; }.property('fullStars', 'numStars'), starRange: function(start, end, type) { var starsData = []; for (i = start; i <= end; i++) { starsData.push({ rating: i, full: type === 'full' }); }; return starsData; }, (...) });
rating
, fullStars
и numStars
са изчислени свойства, които обсъдихме преди с disabled
собственост на ArtistsController
. По-горе използвах така наречения изчисляван макрос на свойствата, около десетина от които са дефинирани в Ember. Те правят типичните изчислени свойства по-лаконични и по-малко склонни към грешки (за писане). Зададох rating
да бъде оценката на контекста (и по този начин на песента), докато дефинирах и двете fullStars
и numStars
свойства, така че да четат по-добре в контекста на приспособлението за оценка на звездите.
stars
методът е основната атракция. Той връща масив от данни за звездите, в които всеки елемент съдържа rating
свойство (от 1 до 5) и флаг (full
), за да се посочи дали звездата е пълна. Това прави изключително лесно преминаването през тях в шаблона:
{{#each view.stars}}
{{/each}} Този фрагмент съдържа няколко бележки:
each
помощникът обозначава, че използва свойство view (за разлика от свойство на контролера), като добавя името на свойството с префикс view
.class
атрибутът на тага на span има смесени динамични и статични класове. Всичко с префикс от :
се превръща в статичен клас, докато full:glyphicon-star:glyphicon-star-empty
нотацията е като троичен оператор в JavaScript: ако пълното свойство е вярно, трябва да се присвои първият клас; ако не, втората.setRating
действието трябва да бъде задействано, но Ембър ще го търси на изгледа, а не на маршрута или контролера, както в случая на създаване на нов изпълнител.По този начин действието е дефинирано в изгледа:
App.StarRating = Ember.View.extend({ (...) actions: { setRating: function() { var newRating = $(event.target).data('rating'); this.set('rating', newRating); } } });
Получаваме рейтинга от rating
data атрибут, който присвоихме в шаблона и след това го зададохме като rating
за песента. Обърнете внимание, че новата оценка не се запазва в задната част. Не би било трудно да приложим тази функционалност въз основа на това как сме създали художник и е оставена като упражнение за мотивирания читател.
Опитахме няколко съставки на гореспоменатата торта Ember:
за какво се използва docx
Красиво, нали?
За Ембър има много повече, отколкото бих могъл да побера само в тази публикация. Ако искате да видите поредица от скрийнкасти за това как създадох малко по-развита версия на горното приложение и / или да научите повече за Ember, можете да регистрирайте се в моя пощенски списък за да получавате статии или съвети на седмична база.
Надявам се, че съм възбудил апетита ви, за да научите повече за Ember.js и че излизате далеч отвъд примерното приложение, което използвах в тази публикация. Докато продължавате да научавате за Ember.js, не забравяйте да погледнете нашия статия за Ember Data, за да научите как да използвате библиотеката ember-data . Забавлявайте се строителство!
Свързани: Ember.js и 8-те най-често срещани грешки, които допускат разработчиците