Робимо круті Single Page Application на basis.js — частина 3. Клієнт для ВКонтакте


Всім доброго часу доби.
Продовжую цікавий цикл статей про створення просунутих Single Page Application на basis.js.
В минулий раз ми навчилися працювати з колекціями і реалізували повноцінний інтерактивний список.
В цей раз ми почнемо створювати повноцінний клієнт для ВКонтакте.
А саме: реалізуємо авторизацію, завантаження новин, друзів і музики.

Меню і навігація

Для початку, реалізуємо просту сторінку із заголовком і меню.

При кліці по пунктам меню (меню), відбувається навігація на різні url і виділення активного пункту меню.
Давайте подивимося на код головного файлу нашого майбутнього додатки:
let Node = require('basis.ui').Node;
let Header = require('app.ui.header.component');
let Menu = require('app.ui.menu.component');

require('basis.app').create({
title: 'VK Client by Basis.JS',
element: new Node({
template: resource('./template.tmpl'),
binding: {
header: 'satellite:',
menu: 'satellite:'
},
satellite: {
header: Header,
menu: Menu
}
})
});

Підключаємо необхідні модулі: Node, а так само компоненти заголовка і меню (які розглянемо нижче).
Далі створюється програма, за допомогою методу basis.app.create(). Можна, звичайно, обійтися без нього і робити так, як ми робили раніше — створювати новий Node і поміщати його в який-небудь елемент на сторінці.
Але в basis.js є хелпер basis.app, який інкапсулює деяку логіку, пов'язану з заголовком сторінки і розміщенням базового компонента на сторінці.
Так само, подивимося на шаблон нашого додатка:
<div class="container">
<!--{header}-->
<!--{menu}-->
<hr/>
</div>

Заголовок і меню є сателлитами кореневого компонента нашої програми.
На даний момент, компонент заголовка дуже простий:
let Node = require('basis.ui').Node;

module.exports = Node.subclass({
template: resource('./template.tmpl') // <h1>Ласкаво просимо!</h1>
});

Його завдання — виводити привітання. Пізніше ми його покращимо.
А ось компонент меню представляє для нас особливий інтерес:
let Node = require('basis.ui').Node;
let Value = require('basis.data').Value;
let router = require('basis.router');
let currentPage = Value.from(router.route(':page').param('page'));

module.exports = Node.subclass({
template: resource('./template.tmpl'), // <div class="btn-group btn-group-lg"/>
childClass: {
template: resource('./item.tmpl'),
selected: currentPage.compute((node, page) => node.url == page),
binding: {
title: 'title'
},
action: {
click() {
router.navigate(this.url);
}
}
},
childNodes: [
{title: 'Новини', url: 'news'},
{title: 'Друзі', url: 'friends'},
{title: 'Музика', url: 'audio'}
]
});

Давайте відразу звернемо увагу на вміст змінної currentPage.
Тут завжди буде зберігатися актуальний маршрут, зміни якого ми можемо відстежувати.
Це значення ми використовуємо властивості selected пунктів меню.
Тобто активність конкретного пункту меню залежить від поточного маршруту.
Якщо url поточного пункту меню збігається з поточним маршрутом, то в " цього пункту меню властивість selected = true.
Таким чином, в один момент часу буде обраний тільки один пункт меню.
При кліці на конкретний пункт, відбувається перехід до зазначеного url.
Більш детально про роутері, вбудованому в basis.js можна почитати у відповідному розділі документації.

Тепер подивимося на шаблон пункту меню:
<b:define name="active" from="selected" type="bool"/>

<button type="button" class="btn btn-default {active}" event-click="click">
{title}
</button>

Кожен пункт меню — це кнопка. Якщо selected пункту дорівнює true, додаємо до кнопки клас активний, в іншому випадку — прибираємо.

От і все. Меню з навігацією — готово.
Тепер, при натисканні на пункти меню, буде відбуватися перехід по відповідному url.
Залишилася невелика дрібниця — маршрут за замовчуванням.
Якщо просто відкрити наш додаток, без вказівки маршруту, то жоден пункт меню вибраний не буде.
Давайте виправимо це таким чином, щоб маршрутом типові Новини.
Модифікуємо основний файл нашого додатка:
// ...
let router = require('basis.router');
let defaultRoute = 'news';

require('basis.app').create({
title: 'VK Client by Basis.JS',
element: new Node({
// ...
})
}).ready(() => {
router.route('*page').param('page').as(page => page || router.navigate(defaultRoute, true));
});

Як тільки програма буде проинициализировано, починаємо відслідковувати зміну маршруту.
Якщо маршрут не вказано, то перебрасываем користувача на маршрут за замовчуванням.

Авторизація

Тепер задіємо ВКонтакте API і реалізуємо з його допомогою авторизацію.
Подивіться на обгортку над VK API (далі просто API). Ми не будемо розглядати її повністю, а подивимося лише на ключові моменти.
Зверніть увагу, що сам API є спадкоємцем від basis.data.Value.
Це означає, що у неї, як і у будь-якого джерела даних, є стани:
  • UNDEFINED коли користувач не має права
  • PROCESSING під час авторизації
  • READY після успішної авторизації
  • ERROR у разі помилки
Подивимося на те, як реалізована зміна станів моделі. Для цього звернемося до методів login() та logout):
login() {
this.setState(STATE.PROCESSING);
this.isLoggedIn().then(
() => this.setState(STATE.READY),
() => {
global.VK.Auth.login(response => {
this.setState(response.session ? STATE.READY : STATE.UNDEFINED);
}, config.perms);
}
);
},
logout) {
global.VK.Auth.logout);
this.setState(STATE.UNDEFINED);
}

Викликаючи login(), API буде переведений в стан PROCESSING.
Далі йде перевірка, якщо користувач вже авторизований, то відразу переводимо API в стан READY. Якщо ні, то авторізуємось за допомогою методу VK.Auth.login() з VK API. Процес авторизації через VK API зводиться до того, що вам показується вікно з пропозицією ввести логін і пароль.

Коли вікно буде закрито (авторизація пройшла успішно чи була скасована), буде викликаний переданий callback, в якому буде встановлено кінцевий стан нашої моделі: READY, у разі успішної авторизації UNDEFINED, у разі скасування авторизації.
Викликаючи logout), знищуємо сесію методом VK.Auth.logout) і переводимо API в стан UNDEFINED.
Тепер подивимося на інший важливий метод — callApi():
callApi(method, params = {}) {
return this.isLoggedIn()
.catch(e => Promise.reject(new Error('Помилка авторизації!')))
.then(
() => {
return new Promise((resolve, reject) => {
basis.object.complete(params, {v: config.version});

global.VK.api(method, params, response => {
if (response.error) {
reject(new Error(response.error.error_msg));
} else {
resolve(response.response);
}
});
});
},
e => {
this.setState(STATE.ERROR, e.message);
throw e;
}
);
}

Суть даного методу — відправити запит через VK API. Перед виконанням кожного запит перевіряємо наявність авторизації. Якщо авторизації немає (наприклад ми відкрили наш додаток в двох вкладках браузера і в одній з них натиснули вийти), то викидаємо помилку і переводимо API в стан ERROR. Якщо з авторизацією все добре, то виконуємо запит. Якщо сервер, у відповідь на запит, повідомляє нам про помилку — викидаємо помилку і переводимо API в стан ERROR. В іншому випадку — повертаємо результат.

За рахунок цього, ми можемо абстрагуватися від нюансів роботи з VK API і оперувати лише станами моделі:
let STATE = require('basis.data').STATE;
let Value = require('basis.data').Value;
let Node = require('basis.ui').Node;
let router = require('basis.router');
let Header = require('app.ui.header.component');
let Menu = require('app.ui.menu.component');

let vkApi = require('app.vkApi');
let apiState = Value.state(vkApi);
let defaultRoute = 'news';

require('basis.app').create({
title: 'VK Client by Basis.JS',
element: new Node({
// активний компонент програми, поки авторизований користувач
active: apiState.as(state => state == STATE.READY),
// компонент програми заблокований в процесі авторизації
disabled: apiState.as(state => state == STATE.PROCESSING),
template: resource('./template.tmpl'),
binding: {
header: 'satellite:',
menu: 'satellite:',
// буде містити текст помилки в разі її виникнення
error: apiState.as(state => state == STATE.ERROR && state.data)
},
satellite: {
header: Header,
menu: Menu
},
action: {
// обробка кнопки "авторизуватися"
login() {
vkApi.login();
}
}
})
}).ready(() => {
router.route('*page').param('page').as(page => page || router.navigate(defaultRoute, true));
// намагаємося авторизуватися відразу після ініціалізації додатка
vkApi.login();
});

Тепер застосуємо ці властивості в шаблоні:
<div class="container">
<div b:show="{active}">
<!--{header}-->
<!--{menu}-->
<hr/>
</div>
<div class="jumbotron text-center" b:hide="{active}">
<h1>
VK Client
<small> powered by basis.js</small>
</h1>
<div class="alert alert-danger" b:show="{error}">
{error}
</div>
<button class="btn btn-primary btn-lg" event-click="login" disabled="{disabled}">Авторизація</button>
</div>
</div>

Показуємо екран привітання поки не авторизований користувач, в іншому випадку — показуємо меню і заголовок.
Кнопка Авторизація буде заблокована в процесі авторизації.
Так само, додамо кнопку вийти в головне меню:
<div>
<div{childNodesElement} class="btn-group btn-group-lg"/>
<button class="btn btn-primary btn-lg pull-right" event-click="logout">вийти</button>
</div>

І, в компоненті меню, опрацюємо натискання цієї кнопки:
let vkApi = require('app.vkApi');
// ...

module.exports = Node.subclass({
// ...
action: {
logout) {
vkApi.logout);
}
}
// ...
});


Відмінно! Тепер у нас є програма, яка може авторизуватися на ВКонтакте, а так само зручний механізм відстеження станів моделі. Рухаємося далі.

Старніцах

В рамках цієї статті ми реалізуємо три сторінки: новини, друзі та аудіозаписи.
При перемиканні між пунктами меню, повинна відображатися відповідна сторінка.

Для початку, створимо спільну сторінку, від якої будемо наслідувати всі інші:
let Value = require('basis.data').Value;
let Expression = require('basis.data.value').Expression;
let STATE = require('basis.data').STATE;
let Node = require('basis.ui').Node;

module.exports = Node.subclass({
active: basis.PROXY,
binding: {
loading: Value.query('childNodesState').as(state => state == STATE.PROCESSING),
error: Value.query('childNodesState').as(state => state == STATE.ERROR && state.data),
empty: node => new Expression(
Value.query(node, 'childNodesState'),
Value.query(node, 'childNodes.length'),
(state, itemCount) => !itemCount && state == STATE.READY
)
},
handler: {
activeChanged() {
if (this.active) {
this.dataSource.deprecate();
}
}
}
});


<div>
<div class="alert alert-info" b:show="{loading}">завантажується...</div>
<div class="alert alert-warning" b:show="{empty}">список порожній</div>
<div class="alert alert-danger" b:show="{error}">{error}</div>
<div{page} b:hide="{loading}"/>
</div>

Так вже вийшло, що всі три сторінки мають загальну логіку роботи:
  • завантажити список чого-небудь
  • при завантаженні показувати напис завантажується
  • у випадку помилки вивести текст помилки
  • якщо завантажено порожній список, то показувати напис список порожній
Щось схоже ми вже робили минулого разу.
Щоб не дублювати код в компонентах всіх трьох сторінок, виносимо його в окремий файл.
Давайте докладніше розглянемо що там відбувається:
Наша абстрактна сторінка — це всього лише Node з певними биндингами і ще парою деталей.
Я не буду зараз зупинятися на цих биндингах, так як вони детально розбиралися в минулий раз.
Зараз нас більше инеересует інше.
Що таке active: basis.PROXY?
Минулого разу ми з'ясували, що набір даних починає синхронізацію тільки коли його стан UNDEFINED або DEPRECATED і у нього є активний споживач. Виконання цих двох умов обов'язково для того, щоб запустити процес синхронізації набору. Зараз нас більше цікавить частину про "коли є активний споживач".
Споживач — це сутність (спадкоємець basis.data.AbstractData), якій потрібні дані (актуальні), представлені в іншому об'єкті.
Активний споживач — це споживач з властивістю active = true.

За промовчанням, коли для Node призначається dataSource, Node автоматично стає споживачем даного набору.
Відмінно, споживач є. Але активний він?
Знову ж таки, за замовчуванням, Node не є активним потребитетем (властивість active = false).
"А давайте просто додамо active: true опис Node і проблема буде вирішена" — можете запропонувати ви.
Не все так однозначно. Адже ми робимо розумне застосування? А значить набір повинен не просто один раз синхронізуватися при запуску програми, а ще й оновлювати свої дані у міру необхідності.
У нас є три сторінки і три набори під кожну з них (новини, друзі та аудіозаписи). Будемо запускати синхронізацію набору тільки тоді, коли переходимо на вкладку, яка потребує в цьому наборі. Таким чином, ми не тільки реалізуємо механізм актуалізації даних, але ще й додамо "ліниву" синхронізацію даних. Тобто синхронізацію тільки при необхідності.
Виходячи з цього, при переході на будь-яку вкладку, ми повинні переводити стан відповідного набору у DEPRECATED.
Але як дізнатися, що ми переключилися на яку-небудь вкладку?
Швидше за все ви вже почали думати, що ми все далі віддаляємося від початкового питання.
Але це не так. Ще трохи і ви побачите, як всі сюжетні лінії зіллються воєдино, прояснивши загальну картину.
Отже, як дізнатися, що ми переключилися на яку-небудь вкладку?
Як і у випадку з dataSource, Node автоматично стає споживачем всіх своїх сателітів, а так само їх власником. Ми зробимо так, що при перемиканні між вкладками, відповідна сторінка буде ставати сателітом кореневого Node нашої програми.
Значить можна змусити сторінку зреагувати в той момент, коли вона стане сателітом і в цей момент перевести стан свого набору у DEPRECATED:
// ...

module.exports = Node.subclass({
// ...
handler: {
ownerChanged() {
if (this.owner) {
this.dataSource.deprecate();
}
}
}
});

Відмінно! В той момент, коли сторінка стане сателітом базового компонента нашого додатка, набір даних перейде в стан DEPRECATED.

Але давайте ще раз проговоримо: "Набір даних починає синхронізацію тільки коли його стан UNDEFINED або DEPRECATED і у нього є активний споживач."
З перемиканням стану набору і наявністю споживача ми розібралися. Але як бути з активністю? Якщо просто додати до сторінки active: true, то вона буде завжди активна і її джерело даних спробує синхронізувати дані відразу при створенні, незалежно від того, чи потрібні нам зараз ці дані чи ні.
Нам це не зовсім підходить, адже є випадки, коли синхронізація просто неможлива. Наприклад, коли ми ще не пройшли процедуру авторизації або відключений інтернет.
Щоб не заоморачиваться обробкою цих кейсів в самій сторінці, додамо їй властивість active: basis.PROXY, яке переведе Node в особливий режим, в якому Node буде активний тільки тоді, коли у нього самого є активний споживач.
Знаючи це, немає необхідності відстежувати ownerChanged, а краще підписатися на activeChanged. Таким чином будемо застравлять набір синхронізувати дані тільки в момент появляения активного споживача.
Погляньте ще раз на підсумковий код компонента сторінки:
let Value = require('basis.data').Value;
let Expression = require('basis.data.value').Expression;
let STATE = require('basis.data').STATE;
let Node = require('basis.ui').Node;

module.exports = Node.subclass({
active: basis.PROXY,
binding: {
loading: Value.query('childNodesState').as(state => state == STATE.PROCESSING),
error: Value.query('childNodesState').as(state => state == STATE.ERROR && state.data),
empty: node => new Expression(
Value.query(node, 'childNodesState'),
Value.query(node, 'childNodes.length'),
(state, itemCount) => !itemCount && state == STATE.READY
)
},
handler: {
activeChanged() {
if (this.active) {
this.dataSource.deprecate();
}
}
}
});

Таким чином, сторінка буде активна тільки коли у неї є активний споживач.
Як було сказано вище — потреителем сторінки буде компонент нашого додатка, а він, якщо пам'ятаєте, активний тільки коли користувач авторизований.
У момент активації сторінки, стан її джерела даних переходить у DEPRECATED.
Так ми делегували обов'язки по обробці різних кейсів основного додатком.

Тепер картина повинна прояснитися:
  • є три сторінки (новини, друзі, аудіозаписи)
  • у кожній старніцах свій власний набір даних, який вміє отримувати дані за допомогою VK API
  • при перемиканні між вкладками, сателітом основного компонента буде ставати відповідна вкладці сторінка
  • сторінка активна тільки коли у неї є активний споживач


Тепер перейдемо до створення спадкоємців розглянутого компонента сторінки.
Почнемо з новин:
let Page = require('../Page');

module.exports = new Page({
template: resource('./list.tmpl'),
childClass: {
template: resource('./item.tmpl'),
binding: {
text: 'data:',
date: Value.query('data.date').as(format('%D.%M.%Y %H:%I:%S'))
}
}
});

Шаблон залишаю на ваш розсуд.
Код решти двох сторінок аналогічний.
Сторінки все ще немає джерела даних. До цього питання ми ще повернемося, а поки подивимося на те, як відображати сторінку, яка відповідає відкритій вкладці.
Модифікуємо головний файл нашого додатка:
// ...
let pageByName = {
news: resource('./ui/pages/news/component.js'),
friends: resource('./ui/pages/friends/component.js'),
audio: resource('./ui/pages/audio/component.js')
};

require('basis.app').create({
title: 'VK Client by Basis.JS',
element: new Node({
// ...
binding: {
// ...
page: 'satellite:'
},
satellite: {
// ...
page: router.route(':page').param('page').as(page => pageByName[page])
}
})
})
// ...

Сателітом з ім'ям page буде компонент, який відповідає поточним маршрутом на підставі карти у змінній pageByName. Тепер потрібно додати використання цього сателіта в шаблон:
<div class="container">
<div b:show="{active}">
<!--{header}-->
<!--{menu}-->
<hr/>
<!--{page}-->
</div>
...
</div>

Тепер, якщо б у сторінки було джерело даних, то наше додаток початок би працювати.

Джерело даних

Вище було показано обгортка над VK API. Крім іншого, там є методи для отримання списку новин, друзів і аудіозаписів. В якості джерела даних, будемо використовувати basis.entity — типізовані сутності.
Опишемо тип новин:
let STATE = require('basis.data').STATE;
let entity = require('basis.entity');
let vkApi = require('app.vkApi');

let News = entity.createType('News', {
text: String,
date: Date
});

News.extendReader(data => data.date *= 1000);
News.all.setSyncAction(() => vkApi.news().then(News.all.set));

module.exports = News;

Кожна новина складається з двох полів — тексту і дати.
Зауважте, що ми розширюємо reader. Дана можливість використовується в тих випадках, коли необхідно модифікувати дані перед тим, як вони стануть екземпляром типу.
Так само, у кожного типу є властивість all, яке є набором всіх створених об'єктів даного типу.
Де б ми не створили екземпляр типу News, він буде поміщений в набір News.all.
Для даного набору ми визначаємо syncAction, тобто метод, який буде викликатися в разі необхідності синхронізації.
Все що нам потрібно зробити — отримати дані з ВКонтакте і передати їх методом News.all.set(), який замінить існуючі екземпляри типу News на нові.
Зауважте, що немає необхідності явно вказувати контекст методу таким чином: News.all.set.bind(News.all).
Даний метод вже має прив'язку до контексту News.all для зручності використання.
Так само зауважте, що, якщо метод, зазначений у syncAction повертає промис, то стан набору даних визначатиметься автоматично, в залежності від стану промиса.

Тепер News.all може бути переданий в якості джерела даних для сторінки новин. Відповідно, в момент активації сторінки, стан News.all буде переведено у DEPRECATED і почнеться процес синхронізації, описаний у syncAction набору

News.all.
Подібним чином опишемо два типи:
Friends entity
let entity = require('basis.entity');
let vkApi = require('app.vkApi');

let Friends = entity.createType('Friends', {
photo: String,
first_name: String,
last_name: String
});

Friends.extendReader(data => data.photo = data.photo_100);
Friends.all.setSyncAction(() => vkApi.friends().then(Friends.all.set));

module.exports = Friends;

Audio entity
let entity = require('basis.entity');
let vkApi = require('app.vkApi');

let Audio = entity.createType('Audio', {
artist: String,
title: String,
duration: Date
});

Audio.extendReader(data => data.duration *= 1000);
Audio.all.setSyncAction(() => vkApi.audio().then(Audio.all.set));

module.exports = Audio;

Тепер вкажемо News.all в якості джерела даних для сторінки:
let Value = require('basis.data').Value;
let Page = require('../Page');
let News = require('app.type.news');
let format = require('basis.date').format;
let dataSource = News.all;

module.exports = new Page({
template: resource('./list.tmpl'),
dataSource: dataSource,
childClass: {
template: resource('./item.tmpl'),
binding: {
text: 'data:',
date: Value.query('data.date').as(format('%D.%M.%Y %H:%I:%S'))
}
}
});

Аналогічним чином, зазначимо відповідні набори інших сторінок.

Friends page
let Page = require('../Page');
let Friends = require('app.type.friends');
let dataSource = Friends.all;

module.exports = new Page({
template: resource('./list.tmpl'),
dataSource:dataSource,
childClass: {
template: resource('./item.tmpl'),
binding: {
photo: 'data:',
first_name: 'data:',
last_name: 'data:'
}
}
});

Audio page
let Value = require('basis.data').Value;
let Page = require('../Page');
let Audio = require('app.type.audio');
let format = require('basis.date').format;
let dataSource = Audio.all;

module.exports = new Page({
template: resource('./list.tmpl'),
dataSource: dataSource,
childClass: {
template: resource('./item.tmpl'),
binding: {
artist: 'data:',
title: 'data:',
duration: Value.query('data.duration').as(format('%I:%S'))
}
}
});

Розмітка або на ваш розсуд, або за посиланням на репозиторій в кінці статті.
Зауважте, що для представлення зовсім не важливий вид розмітки, вона може бути будь-якою.

Все готово, але давайте додамо ще трохи поліпшень.
Додамо сторінку 404. Для цього, модифікуємо наш головний файл:
// ...
let pageByName = {
news: resource('./ui/pages/news/component.js'),
friends: resource('./ui/pages/friends/component.js'),
audio: resource('./ui/pages/audio/component.js'),
notFound: resource('./ui/pages/404/component.js')
};

require('basis.app').create({
title: 'VK Client by Basis.JS',
element: new Node({
satellite: {
header: Header,
menu: Menu,
page: router.route(':page').param('page').as(page => pageByName[page] || pageByName.notFound)
}
// ...
})
})
// ...

Все що ми зробили — додали новий маршрут в мапу маршрутів і модифікували відстеження зміни маршруту. Якщо потрібний маршрут не знайдений в картці маршрутів, то використовувати маршрут notFound.

До речі, ви помітили, що компоненти підключаються через resource а не через require?
resource дозволяє реалізувати ліниву ініціалізацію компонентів.
Тобто компонент буде ініціалізованим першим не відразу, а тільки в той момент, коли він знадобиться в перший раз.
Детальніше про ресурси можна почитати у відповідному розділі документації.

І ще один момент. Справа в тому, що на вашій стіні ВКонтакте можуть потрапляти не тільки текстові новини, але ще й відео/фото без тексту. Обробкою цих кейсів ми займемося в інший раз, а поки давайте просто відфільтруємо новини таким чином, щоб відображати тільки ті новини, у яких є текст. Для цього модифікуємо компонент новин:
let Page = require('../Page');
let Value = require('basis.data').Value;
let News = require('app.type.news');
let format = require('basis.date').format;
let Filter = require('basis.data.dataset').Filter;
let textOnlyNews = new Filter({
source: News.all,
state: Value.query('source.state'),
rule: 'data.text',
deprecate() {
this.source.deprecate();
}
});

module.exports = new Page({
template: resource('./list.tmpl'),
dataSource: textOnlyNews,
childClass: {
template: resource('./item.tmpl'),
binding: {
text: 'data:',
date: Value.query('data.date').as(format('%D.%M.%Y %H:%I:%S'))
}
}
});

Все що ми зробили — підмінили джерело даних сторінки новин фільтром, який відкине всі новини без тексту.

І останнє… оживимо компонент заголовка:
let Node = require('basis.ui').Node;
let STATE = require('basis.data').STATE;
let DataObject = require('basis.data').Object;
let vkApi = require('app.vkApi');

let dataSource = new DataObject({
data: {
firstName: ",
lastName: "
},
syncAction() {
return vkApi.me().then(me => {
this.update({ firstName: me.first_name, lastName: me.last_name });
});
}
});

module.exports = Node.subclass({
active: basis.PROXY,
представник: dataSource,
template: '<h1>Ласкаво просимо {firstName} {lastName}!</h1>',
binding: {
firstName: 'data:',
lastName: 'data:'
}
});

Таким чином, компонент заголовка навчився отримувати і відображати ім'я і прізвище власника аккаунта.

Ув'язнення, але не кінець

Отже, сьогодні ми створили повноцінний клієнт для Вконтакті basis.js. Додаток вміє авторизуватись і синхронізувати дані з сервером.
Зверніть увагу на те, що, як і минулого разу, ми йдемо по шляху FRP і основний акцент робимо на роботу з даними. Іншими словами: ми вибудовуємо потік даних таким чином, щоб програма виконувала свої задачі. При цьому, специфіка basis.js така, що клієнтський код виходить досить лінійним через відсутність циклів і великої кількості розгалужень. Принаймні визначені завдання можна вирішити без них.
У наступних статтях ми будемо покращувати наш клієнт і нарощувати його функціонал.
Дякуємо за інтерес до basis.js!

Величезна подяка lahmatiy за безцінні поради ;)

Кілька корисних посилань:

Джерело: Хабрахабр

0 коментарів

Тільки зареєстровані та авторизовані користувачі можуть залишати коментарі.