Сегодня расскажу о способе создания пользовательского списка для Qlik Sense на основе Angular и RxQ. Статья основана на материалах Джастина Скаггса (Justin Skaggs), который работает в западной консалтинговой компании и специализируется на Qlik-разработке. Итак, создавать listbox будем через Engine API.
НА ЗАМЕТКУ! Engine API позволяет управлять элементами интерфейса приложения не через пользовательский интерфейс, а через доступ по API. Среди возможных действий через API:
- Создание листов приложения.
- Создание объектов листа.
- Создание закладок.
- Изменение скрипта загрузки.
- Перезагрузка приложения.
- Применение и очистка выборок и многое другое.
Итак, приступим к созданию простого элемента управления в виде списка с использованием Angular(ng2) в части работы с интерфейсом, а RxQ для работы с Engine API.
Listbox: инструкция создания
Мы не будем создавать полностью проект с нуля, а воспользуемся уже готовыми наработками, а именно angular-seed, который будет отличным помощником в нашем деле. Фактически, angular-seed — это шаблон готового приложения AngularJS. Если хотите параллель с Qlik, то он схож с шаблоном приложения QVF. Такой шаблон позволяет стартовать новое приложение в разы быстрее, на основе уже написанных строк кода, необходимых для старта работы.
НА ЗАМЕТКУ! Прежде чем начать разработку, установите NodeJS.
1. Копирование файлов и создание проекта. Команда ниже откопирует репозитарий с angular-seed, даст новое название папке и изменит его на новую для нашего проекта:
1 |
git clone https://github.com/mgechev/angular-seed.git && mv angular-seed listbox-ng2-example && cd listbox-ng2-example |
2. Установите зависимости (это может занять несколько минут…)
1 |
npm install |
3. Перечислите скрипты проекта
1 |
npm run |
Для запуска скрипта используйте такую команду:
1 |
npm run ${script name} |
4. Начните развертывание сервера:
1 |
npm run start |
Так, в браузере должна открыться вкладка по адресу http://localhost:5555 :
Структура проекта
Прежде чем перейти к созданию Listbox, давайте посмотрим, какая у нас структура проекта:
Создание модуля Listbox
Создадим новый модуль с помощью angular-cli, он запоминает правильный импорт или копирует те части проекта, которые работоспособны.
Вот, что должно быть импортировано для нашего списка:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
listboxes.module.ts… import { NgModule } from ‘@angular/core’; import { ListboxesComponent } from ‘./listboxes.component’; import { ListboxesRoutingModule } from ‘./listboxes-routing.module’; import { SharedModule } from ‘../shared/shared.module’; @NgModule({ imports: [ListboxesRoutingModule, SharedModule], declarations: [ListboxesComponent], exports: [ListboxesComponent], providers: [] }) export class ListboxesModule { } listboxes.component.ts… import { Component, OnInit } from ‘@angular/core’; /** * This class represents the lazy loaded ListboxesComponent. */ @Component({ moduleId: module.id, selector: ‘listboxes’, templateUrl: ‘listboxes.component.html’, styleUrls: [‘listboxes.component.css’], }) export class ListboxesComponent implements OnInit { constructor() {} /** * OnInit — Setup will go here */ ngOnInit() {} } listboxes-routing.module.ts… |
НА ЗАМЕТКУ! Для каждого модуля работает своя ссылка:
Домашний экран — http://localhost:5555/
О приложении — http://localhost:5555/about
Список — http://localhost:5555/listboxes
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
import { NgModule } from ‘@angular/core’; import { RouterModule } from ‘@angular/router’; import { ListboxesComponent } from ‘./listboxes.component’; @NgModule({ imports: [ RouterModule.forChild([ { path: ‘listboxes’, component: ListboxesComponent } ])], exports: [RouterModule] }) export class ListboxesRoutingModule { } |
listboxes.component.html — шаблон HTML-кода для списка. Здесь хранится основная информация о div-блоке со списком.
1 |
<div>Listboxes will go here!</div> |
listboxes.component.css… — а так выглядит таблица стилей для списка
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
:host { display: block; padding: 0 16px; margin-top: 15px; } ul { list-style-type: none; padding: 0 0 0 8px; } |
Затем импортируем модуль Listboxes в модуль определения нашего приложения.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 |
app.module.ts… import { NgModule } from ‘@angular/core’; import { BrowserModule } from ‘@angular/platform-browser’; import { APP_BASE_HREF } from ‘@angular/common’; import { HttpModule } from ‘@angular/http’; import { AppComponent } from ‘./app.component’; import { AppRoutingModule } from ‘./app-routing.module’; import { AboutModule } from ‘./about/about.module’; import { HomeModule } from ‘./home/home.module’; import { ListboxesModule } from ‘./listboxes/listboxes.module’; import { SharedModule } from ‘./shared/shared.module’; @NgModule({ imports: [BrowserModule, HttpModule, AppRoutingModule, AboutModule, HomeModule, ListboxesModule, SharedModule.forRoot()], declarations: [AppComponent], providers: [{ provide: APP_BASE_HREF, useValue: ‘<%= APP_BASE %>’ }], bootstrap: [AppComponent] }) export class AppModule { } |
Теперь запускаем сервер. Ссылка на список — http://localhost:5555/listboxes.
Подключение к Qlik Sense
Прежде чем добавить список на экран, нам нужно подключиться к серверу Qlik Sense. В примере мы используем библиотеку RxQ. То же самое можно сделать, используя enigma или qsocks.
Чтобы добавить библиотеку на страницу, нужно вставить в index.html src/client/index.html строку скрипта:
1 |
<script src=”https://opensrc.axisgroup.com/rxq/rxq.js"></script> |
Теперь создайте новую папку в src/client/app/shared, “qlik”.
index.ts…
export * from ‘./qlik.service’;
qlik.service.ts — соединение с Qlik Sense Engine и открытие приложения (в нашем случае “AirBnB Data”).
Для подключения к серверу Qlik Playground, войдите в приложения в Qlik Playground и скопируйте ваш apiKey в файл qlik.service.ts.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 |
import { Injectable } from ‘@angular/core’; import { Observable } from ‘rxjs/Observable’; /** * This class provides the Qlik service with methods to read names and add names. */ @Injectable() export class QlikService { private engine$: any; private app$: any; /** * Creates a new QlikService, do any setup here * @constructor */ constructor() { // connect to engine this.engine$ = RxQ.connectEngine({ host: “playground.qlik.com”, prefix: “/playground/”, port: “443”, isSecure: true, rejectUnauthorized: false, apiKey: “” // ADD YOUR APIKEY HERE }, ‘warm’); // open application — AirBnB QVF this.app$ = this.engine$.qOpenDoc(‘2b10add1–472f-4192-aac9–44a99125825c’); } getApp() { return this.app$; } } |
Для доступа к службам загрузим модуль совместимости:
shared.module.ts…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import { NgModule, ModuleWithProviders } from ‘@angular/core’; import { CommonModule } from ‘@angular/common’; import { FormsModule } from ‘@angular/forms’; import { RouterModule } from ‘@angular/router’; import { ToolbarComponent } from ‘./toolbar/toolbar.component’; import { NavbarComponent } from ‘./navbar/navbar.component’; import { ListboxComponent } from ‘./listbox/listbox.component’; import { NameListService } from ‘./name-list/name-list.service’; import { QlikService } from ‘./qlik/qlik.service’; /** * Do not specify providers for modules that might be imported by a lazy loaded module. */ @NgModule({ imports: [CommonModule, RouterModule], declarations: [ToolbarComponent, NavbarComponent, ListboxComponent], exports: [ToolbarComponent, NavbarComponent, ListboxComponent, CommonModule, FormsModule, RouterModule] }) export class SharedModule { static forRoot(): ModuleWithProviders { return { ngModule: SharedModule, providers: [NameListService, QlikService] }; } } |
Теперь идем дальше и создаем список, который будет:
1. Показывать имя поля
2. Показывать значения полей
3. Выбранные значения полей
Если вы раньше не работали с компонентами, то можете представить, что это своего рода расширение синтаксиса HTML. Так, нам будет не нужно каждый раз прописывать div для каждого списка.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
<div class=“lb-container”> <div class=“lb-title”>$Title</div> <div class=“scrollable”> <ul> <li>$value 1</li> <li>$value 2</li> <li>$value 3</li> <!-- etc --> </ul> </div> </div> |
Определяем название поля и отображаемое имя ($Title)…
1 |
<listbox field=”Employee Dept” name=”Department”></listbox> |
Далее в src/client/app/shared создаем папку “listbox” для нового компонента.
listbox.component.ts — здесь создадим ListObject.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 |
import { Component, OnInit, Input } from ‘@angular/core’; import { Observable } from ‘rxjs/Observable’; import { QlikService } from ‘../qlik/qlik.service’; @Component({ moduleId: module.id, selector: ‘listbox’, templateUrl: ‘listbox.component.html’, styleUrls: [‘listbox.component.css’] }) export class ListboxComponent implements OnInit { @Input() field: string; @Input() name?: string; obj$: any; // Qlik Sense Object source$: any; // Transformed qLayout objectPath: string = ‘/qListObjectDef’; constructor(private qlikService: QlikService) { } ngOnInit() { // Grab a reference to the QVF that was opened in the QlikService Constructor this.obj$ = this.qlikService.getApp() // create a session object with the given prop .qCreateSessionObject({ “qInfo”: { “qType”: “ListObject” }, “qListObjectDef”: { “qDef”: { “qFieldDefs”: [this.field], “qSortCriterias”: [{ “qSortByState”: 1, “qSortByAsci”: 1 }] }, “qInitialDataFetch”: [{ “qTop”: 0, “qHeight”: 10000, “qLeft”: 0, “qWidth”: 1 }] } }); // qLayouts will automatically update when the object is invalidated. // This method maps the qMatrix into a minimal array that // the view will use to render values/states. this.source$ = this.obj$.qLayouts() .pluck(‘qListObject’, ‘qDataPages’, 0, ‘qMatrix’) .map(m => { return m.map(n => ({ idx: n[0].qElemNumber, value: n[0].qText, s: n[0].qState === ‘S’, // s = Selected x: n[0].qState === ‘X’ // x = Excluded })); }); } // this method will select a listbox value by index using qElemNumber select(index: number) { return this.obj$ .qSelectListObjectValues(this.objectPath, [index], true, false) .take(1) .subscribe(); } } listbox.component.css… :host { display: block; } .scrollable { max-height: 270px; overflow-y: scroll; overflow-x: hidden; margin-bottom: 10px; } .title { color: #717575; font-size: 14px; } ul { list-style-type: none; -webkit-padding-start: 0; padding: 0; margin: 0; } li { height: 20px; margin: 5px; border-bottom: 1px solid #D8E4E6; } li span { white-space: nowrap; } /* Qlik States */ .s { color: #2D8C94; font-weight: bold; } .x { color: #BDBDBD; font-style: italic; } |
listbox.component.html — здесь используется модификатор async pipe, который автоматически распределяет поток данных из source$ observable.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
<div class=”filter-container”> <div class=”title”>{{ name || field }}</div> <div class=”scrollable”> <ul> <li *ngFor=”let row of source$ | async” [ngClass]=”{ ‘s’: row.s, ‘x’: row.x }” (click)=”select(row.idx)”> <span>{{ row.value }}</span> </li> </ul> </div> </div> |
shared.module.ts — добавляем еще один компонент списка.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 |
import { NgModule, ModuleWithProviders } from ‘@angular/core’; import { CommonModule } from ‘@angular/common’; import { FormsModule } from ‘@angular/forms’; import { RouterModule } from ‘@angular/router’; import { ToolbarComponent } from ‘./toolbar/toolbar.component’; import { NavbarComponent } from ‘./navbar/navbar.component’; import { ListboxComponent } from ‘./listbox/listbox.component’; import { NameListService } from ‘./name-list/name-list.service’; import { QlikService } from ‘./qlik/qlik.service’; /** * Do not specify providers for modules that might be imported by a lazy loaded module. */ @NgModule({ imports: [CommonModule, RouterModule], declarations: [ToolbarComponent, NavbarComponent, ListboxComponent], exports: [ToolbarComponent, NavbarComponent, ListboxComponent, CommonModule, FormsModule, RouterModule] }) export class SharedModule { static forRoot(): ModuleWithProviders { return { ngModule: SharedModule, providers: [NameListService, QlikService] }; } } |
Теперь обновляем наш шаблон модуля списка, чтобы создать единый список.
listboxes.component.html…
1 |
<listbox field=”property_type”></listbox> |
После обновления страницы у вас появится property_type со списком элементов.
НА ЗАМЕТКУ! Названия поля можно заменить на другое, более удобное пользователю, при помощи простого тега:
1 |
<listbox field=”property_type” name=”Property Type”></listbox> |
Этот компонент можно использовать для нескольких списков:
1 2 3 4 5 |
<listbox field=”property_type” name=”Property Type”></listbox> <listbox field=”City”></listbox> <listbox field=”state” name=”State”></listbox> |
Поскольку мы использовали оператор qLayouts, все списки будут обновляться автоматически.
Итоги
Это была вводная статья по работе с элементами управления с помощью Angular (ng2) и RxQ.
Итоговое соображение в завершении статьи – теперь мне необходимо более глубокое изучение реактивного программирования. Так, открывается возможность управлять любыми потоками, которые используются в переменных, пользовательском вводе, свойствах, кеше, структуре данных и многом другом.
На этом все на сегодня. Удачных вам разработок с Qlik!
Свежие комментарии