javascript

Angular c Clarity Design System от VmWare

  • пятница, 27 октября 2017 г. в 03:13:26
https://habrahabr.ru/post/339294/
  • Разработка веб-сайтов
  • Node.JS
  • JavaScript
  • AngularJS




Поработав с Angular Material 2, в какой то момент пришел к выводу, что продукт сыроват для полета фантазии и некоторые вещи (badge, vertical tabs, data-grid) либо реализованы с минимальным функционалом, либо In progress, planned.

Вечером, придя домой, начал искать что нибудь, что мог бы предложить тимлиду как альтернативу для следующего проекта. Тут то я и заметил, что angular.io разжился табом Resources. Это было пару месяцев назад.
Там среди прочих довольно таки полезных вещей, команда разработчиков Angular, добавила продукт от не менее известной компании, чьи разработки я уважаю и с щенячьим восторгом всегда рад лишний раз поковырять — VmWare. Ребята сделали весьма и весьма достойный продукт — Clarity.

Решил для себя, написать статью на тему Clarity, но просто писать обзор — не хотелось, решил сделать что то вроде starter-kit на случай если кому то понадобится быстро сделать админку. Также, будет активно использоваться Highcharts, Angular Flex-layout и i18n библиотека ngx-translate

Для нетерпеливых: github, demo

Говнокода много, и если встретится подобное, просьба понять, простить и написать комментарий, буду премного благодарен :)

Не буду углубляться в процесс установки Node.js, angular-cli и положу под спойлер инструкцию, на случай если кто-то только начал знакомство с Angular.

установка node.js и angular/cli
Качаем отсюда node.js и ставим.
Выполняем команду для установки angular-cli:

npm install -g @angular/cli

после чего генерируем наш проект:

ng new ngClarity


Итак, у нас уже есть Hello World, далее необходимо подготовить среду, установить пакеты и сконфигурировать наш angular:

Установка


Ставим пакет иконок:

npm install clarity-icons --save

Пакет с полифиллами:

npm install @webcomponents/custom-elements@1.0.0 --save

Добавляем все установленное в конфиг файл .angular-cli.json

      "styles": [
        "styles.css",
        "../node_modules/clarity-icons/clarity-icons.min.css"
      ],
      "scripts": [
        "../node_modules/@webcomponents/custom-elements/custom-elements.min.js",
        "../node_modules/clarity-icons/clarity-icons.min.js"
      ],

Дополняем наш Angular модулем clarity-ui:

npm install clarity-ui --save

Лезем обратно в в конфиг файл .angular-cli.json и дописываем путь к стилям clarity-ui:

      "styles": [
        "styles.css",
        "../node_modules/clarity-icons/clarity-icons.min.css",
        "../node_modules/clarity-ui/clarity-ui.min.css"
      ],

Ставим пакет clarity-angular:

npm install clarity-angular --save

Добавляем импорт в наш app.module.ts:

import { ClarityModule } from "clarity-angular";

И объявляем в imports:

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    ClarityModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})

Для тех кто привык обращаться только к официальной документации, есть ссылка.

i18n


Далее, думаю стоит описать установку и настройку модуля i18n(используется библиотека ngx-translate), тут ничего необычного, но все же уделим внимание, возможно кому то понадобится:

Ставим либу:

npm install @ngx-translate/core --save
npm install @ngx-translate/http-loader --save

идем в наш app.module.ts и дополняем его импортами:

import {TranslateModule, TranslateLoader} from '@ngx-translate/core';
import {TranslateHttpLoader} from '@ngx-translate/http-loader';

имплементация http фактории, которая будет подгружать наши translations файлы:

export function HttpLoaderFactory(http: HttpClient) {
  return new TranslateHttpLoader(http, './assets/i18n/', '.json');
}

создаем два файла, en.json и ru.json в директории /assets/i18n/

и ставим точку в этом деле, добавляя модуль в imports нашего @NgModule:

TranslateModule.forRoot({
 loader: {
 provide: TranslateLoader,
 useFactory: HttpLoaderFactory,
 deps: [HttpClient]
 }
})

В итоге, мы имеем начальную конфигурацию ngx-translate, и теперь можем объявить в app.component.ts наш TranslationService, который будет подгружать json файлы из директории /assets/i18n

import {Component, OnInit} from '@angular/core';
import {TranslateService} from "@ngx-translate/core";

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements OnInit{
  constructor(public translate: TranslateService){}


  ngOnInit(){
    this.translate.addLangs(["en", "ru"]);
    this.translate.setDefaultLang('en');
    let browserLang = this.translate.getBrowserLang();
    if (browserLang.match( /en|ru/ )) {
      this.translate.use( browserLang );
    } else {
      this.translate.use( 'en' );
    }
  }
}

Роутинг


Cоздаем роутинг сервис для нашего проекта. В директории /app создаем файл routing.module.ts и конфигурируем наш AppRoutingModule:

import {NgModule}from '@angular/core';
import {Routes, RouterModule}from '@angular/router';

const appRoutes: Routes = [
  {
    path: '',
    redirectTo: '/',
    pathMatch: 'full',
  }
];

@NgModule({
  imports: [
    RouterModule.forRoot(
      appRoutes
    )
  ],
  exports: [
    RouterModule
  ]
})
export class AppRoutingModule {}

для инициализации, в app.module.ts, добавляем импорт AppRoutingModule в наш @NgModule.

Компоненты


Создаем три пустых пока что компонента: DashboardComponent, SettingsComponent и PageNotFoundComponent. Внутри app создаем директорию pages, и из под нее запускаем:

ng generate component dashboard
ng generate component settings
ng generate component pageNotFound

наш angular/cli создаст три директории с указанными именами, создаст внутри директорий все необходимое для компонента и обновит app.module.
Иногда angular/cli может неправильно резолвить пути до файлов, и в случае возникновения ошибок, нужно перепроверить импорты и пути к файлам в app.module.

Далее, заходим в наш routing.module.ts и редактируем роуты для новоиспеченных компонентов:

const appRoutes: Routes = [
  {
    path: 'dashboard',
    component: DashboardComponent,
  },
  {
    path: 'settings',
    component: SettingsComponent,
  },
  {
    path: '',
    redirectTo: 'dashboard',
    pathMatch: 'full'
  },
  {
    path: '**',
    component: PageNotFoundComponent
  }
];

Cтроим каркас нашего приложения. Заходим в app.component.html и начинаем:

<clr-main-container>
  <clr-header class="header-3">
    <div class="branding">
      <a href="..." class="nav-link">
        <span class="title">ngClarity</span>
      </a>
    </div>
    <div class="header-actions">
      <clr-dropdown>
        <button class="nav-icon" clrDropdownTrigger>
          <clr-icon shape="world"></clr-icon>
          <clr-icon shape="caret down"></clr-icon>
        </button>
        <clr-dropdown-menu *clrIfOpen clrPosition="bottom-right">
          <a *ngFor="let lang of translate.getLangs()" (click)="translate.use(lang)" clrDropdownItem>{{lang}}</a>
        </clr-dropdown-menu>
      </clr-dropdown>
      <clr-dropdown>
        <button class="nav-icon" clrDropdownTrigger>
          <clr-icon shape="user"></clr-icon>
          <clr-icon shape="caret down"></clr-icon>
        </button>
        <clr-dropdown-menu *clrIfOpen clrPosition="bottom-right">
          <a href="..." clrDropdownItem>{{ 'profile' | translate }}</a>
          <a href="..." clrDropdownItem>{{ 'logout' | translate }}</a>
        </clr-dropdown-menu>
      </clr-dropdown>
    </div>
  </clr-header>
  <div class="alert alert-app-level"></div>
  <div class="content-container">
    <div class="content-area">
      <router-outlet></router-outlet>
    </div>
    <clr-vertical-nav [clrVerticalNavCollapsible]="true" [clr-nav-level]="1">
      <a clrVerticalNavLink routerLink="/dashboard" routerLinkActive="active" class="nav-link">
        <clr-icon clrVerticalNavIcon shape="dashboard"></clr-icon>{{ 'dashboard' | translate }}
      </a>
      <a clrVerticalNavLink routerLink="/settings" routerLinkActive="active" class="nav-link">
        <clr-icon clrVerticalNavIcon shape="cog"></clr-icon>{{ 'settings' | translate }}
      </a>
    </clr-vertical-nav>
  </div>
</clr-main-container>


Останавливаться на каждом из директив не будем, для этого есть хорошо написанная документация. Каркас более менее готов, далее можно будет уже работать с компонентами.

Highcharts


Устанавливаем труд товарища cebor, который создал ангуляровскую директиву для Highcharts: angular-highcharts

npm install --save angular-highcharts highcharts

Далее, идем в наш app.module добавляем импорт и объявляем наш модуль, добавив его в imports:

import { ChartModule } from 'angular-highcharts';

@NgModule({
  imports: [
    ChartModule
  ]
})

Flex-Layout


Теперь возьмемся за flex-layout:

npm install @angular/flex-layout --save

и по традиции, в app.module.ts

import { FlexLayoutModule } from '@angular/flex-layout';

@NgModule({
  imports: [
    FlexLayoutModule
  ]
})

Теперь, идем в наш dashboard.component.ts объявляем импорты:

import * as Highcharts from 'highcharts';
import * as HichartsExporting from 'highcharts/modules/exporting';
import * as Hicharts3d from 'highcharts/highcharts-3d.js';

HichartsExporting(Highcharts);//объявляем модуль exporting
Hicharts3d(Highcharts);//объявляем модуль 3d

В dashboard.component.html рисуем наши блоки, где разместим графики:

<div style="padding: 1em;" fxLayout="row" fxLayout.xs="column" fxLayout.sm="column" fxLayoutWrap fxLayoutGap="1rem" fxLayoutAlign="center">
  <div class="card" fxLayout="column" fxLayout.xs="column" fxFlex="49" fxLayout.sm="column" style="padding: 5px;">
    <div class="card-block" id="chart1"></div>
  </div>

  <div class="card" fxLayout="column" fxLayout.xs="column" fxFlex="49" fxLayout.sm="column" style="padding: 5px;">
    <div class="card-block" id="chart2"></div>
  </div>
</div>

и код самого компонента, в dashboard.component.ts:

import { Component, OnInit } from '@angular/core';
import * as Highcharts from 'highcharts';
import * as HichartsExporting from 'highcharts/modules/exporting';
import * as Hicharts3d from 'highcharts/highcharts-3d.js';

HichartsExporting(Highcharts);
Hicharts3d(Highcharts);

@Component({
  selector: 'app-dashboard',
  templateUrl: './dashboard.component.html',
  styleUrls: ['./dashboard.component.css']
})
export class DashboardComponent implements OnInit {

  constructor() { }

  ngOnInit() {
    Highcharts.chart('chart1', {
      chart: {
        type: 'column'
      },
      title: {
        text: 'Schedules work progress'
      },
      credits: {
        enabled: false
      },
      xAxis: {
        categories: ['line 1', 'line 2', 'line 3', 'line 4'],
        labels: {
          skew3d: true,
          style: {
            fontSize: '16px'
          }
        }
      },
      yAxis: {
        allowDecimals: false,
        min: 0,
        title: {
          text: 'Total count',
          skew3d: true
        }
      },
      tooltip: {
        headerFormat: '<b>{point.key}</b><br>',
        pointFormat: '<span style="color:{series.color}">{series.name}</span>: <b>{point.y}</b> ({point.percentage:.0f}%)<br/>'
      },
      plotOptions: {
        column: {
          stacking: 'normal',
          depth: 40,
          dataLabels: {
            enabled: true,
            color: (Highcharts.theme && Highcharts.theme.dataLabelsColor) || 'white'
          }
        }
      },
      series: [
        {name: 'Left', data: [10, 58, 23, 8]},
        {name: 'Done', data: [27, 98, 44, 65]},
        {name: 'Alert', data: [8, 4, 65, 78]}
      ]
    });

    Highcharts.chart('chart2', {
      chart: {
        type: 'pie',
        options3d: {
          enabled: true,
          alpha: 45,
          beta: 0
        }
      },
      title: {
        text: 'Browser market shares at a specific website, 2014'
      },
      credits: {
        enabled: false
      },
      tooltip: {
        pointFormat: '{series.name}: <b>{point.percentage:.1f}%</b>'
      },
      plotOptions: {
        pie: {
          allowPointSelect: true,
          cursor: 'pointer',
          depth: 35,
          dataLabels: {
            enabled: true,
            format: '{point.name}'
          }
        }
      },
      series: [{
        type: 'pie',
        name: 'Browser share',
        data: [
          ['Firefox', 45.0],
          ['IE', 26.8],
          {
            name: 'Chrome',
            y: 12.8,
            sliced: true,
            selected: true
          },
          ['Safari', 8.5],
          ['Opera', 6.2],
          ['Others', 0.7]
        ]
      }]
    });
  }

}

Сейчас уже можно будет увидеть, что в итоге получилось: demo

На этом, первая часть завершается. Вторая часть статьи будет более детально разбирать компоненты Clarity и реализовывать их в проекте, исходники которого здесь: github