2018年10月4日木曜日

CSSでテキストの1文字目を大きく表示する。


こんにちは、オフィス狛 デザイン部のSatoです。

先日、ドロップキャップをウェブサイトで表現する必要があったのですが調べるまで正しいやり方がわからなかったのでメモしたいと思います。
(テキストの1文字目のみを大きく表示する表現のことをドロップキャップって言うんですね。実は知らなかったです。)

最初大きくしたい1文字目の部分を<span>で囲んでclassで…と思ったのですが、実は「:first-letter」という擬似要素で簡単に実現できます。
いちいち<span>で囲まなくてもいいんです。
この擬似要素はブロックレベル要素の最初の文字にのみスタイルが適用されるので、ドロップキャップを実現できるのですね。


「p:first-letter」にfloat設定しp要素でfloat解除するといい感じになりました。


2018年10月1日月曜日

ESLint のエラー「Use object destructuring prefer-destructuring」に対応する。


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

Node.js + ESLint で開発を行っていると、良く出てくるエラーが、
「Use object destructuring prefer-destructuring」です。

直訳すると、「オブジェクトの非構造化の使用」なのですが、初見だと、何をどう修正すれば良いのか分からないんですよね・・・

例えば、以下のようなプログラムがあったとします。(あくまで例なので、意味の無いプログラムになっています)
  const response = await hogeService.getUser();
  if (response.id) {
    const userId = response.id;
  } else if (response.name) {
    const userName = response.name;
  }

「hogeService.getUser()」は、DBやAPIからデータを取得するイメージです。
「hogeService.getUser()」の戻り値の中には、「{id: 1234, name: "test"}」のような構造化されたデータが入っている事が前提で、そのデータと構成を「response」に設定している、という感じです。

これでESLintのチェックに掛けると、見事に
error Use object destructuring prefer-destructuring
が出ます。

「オブジェクトの非構造化の使用」を促されているので、問題なのは構造化されている「response」だと分かります。
(正確に言うと、構造化された後の使い方ですが)

と言う事で、
  const { id, name } = await hogeService.getUser();
  if (id) {
  } else if (name) {
  }

と直す事で、ESLintのエラーが出なくなります。
「const { id, name }」とする事で、構造化から解放されていますね。

初見だと分かり難いですが、
お決まりのパターンの修正なので、慣れるのが一番ですね。


,

2018年9月26日水曜日

Moment.jsでDeprecation warning(value provided is not in a recognized RFC2822 or ISO format)が発生した場合の対応方法(Angular,Pipe)


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

前回、AngularのPipeを使ってhtml(View)の表示を共通化する。と言う記事を書きましたが、実は実装をしている時に一つハマったことがあります。

最終的な実装の前に記載していたプログラムは以下の通りです。
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

@Pipe({
  name: 'changeDateFormat'
})
export class ChangeDateFormatPipe implements PipeTransform {

  transform(value: string, args?: any): any {
    const postDate = moment(value).format('YYYYMMDD');
    const today = moment().format('YYYYMMDD');
    const yesterday = moment().subtract(1, 'days').format('YYYYMMDD');

    let displayDate: string = value;

    if (postDate === today) {
      displayDate = '今日';
    } else if (postDate === yesterday) {
      displayDate = '昨日';
    }

    return displayDate;
  }

}

上記で実行すると・・・・
Deprecation warning: value provided is not in a recognized RFC2822 or ISO format. moment construction falls back to js Date(), which is not reliable across all browsers and versions. Non RFC2822/ISO date formats are discouraged and will be removed in an upcoming major release. Please refer to http://momentjs.com/guides/#/warnings/js-date/ for more info.
Arguments: 
[0] _isAMomentObject: true, _isUTC: false, _useUTC: false, _l: undefined, _i: 2018/09/01, _f: undefined, _strict: undefined, _locale: [object Object]
Error

とエラーになってしまいます。
momentに引き渡す日付は、ISO形式ではなくていけないようです。 ただ、そもそもの値が文字列なので、文字列操作でISO形式にフォーマットするのが面倒・・・ と言う事で、前回の実装では、
    const postDate = new Date(value);
    const postDateISO = moment(postDate.toISOString()).format('YYYYMMDD');
上記のように一度日付に戻してから、「toISOString()」を使うようにしました。
元々、トラブルの多い「new Date()」を使いたく無いので、momentにしたのですが・・・

と言う訳で、最終的な実装は、前回も記載した通り、
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

@Pipe({
  name: 'changeDateFormat'
})
export class ChangeDateFormatPipe implements PipeTransform {

  transform(value: string, args?: any): any {
    const postDate = new Date(value);
    const postDateISO = moment(postDate.toISOString()).format('YYYYMMDD');
    const today = moment().format('YYYYMMDD');
    const yesterday = moment().subtract(1, 'days').format('YYYYMMDD');

    let displayDate: string = value;

    if (postDateISO === today) {
      displayDate = '今日';
    } else if (postDateISO === yesterday) {
      displayDate = '昨日';
    }

    return displayDate;
  }

}

となります。
上記実装を参考にされる方は、タイムゾーンに注意して下さいね。


,

2018年9月25日火曜日

AngularのPipeを使ってhtml(View)の表示を共通化する。


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

Angularのhtml(View)で変数を展開する場合、中括弧を二つ(double curly braces)で変数を囲みます。
<div>{{ targetDate }}</div>

さて、例えば、この「targetDate」変数に「2018/09/01」のような日付が設定されていて、
その日付が本日であれば『今日』、昨日だったら『昨日』と表示し、それ以外の時だけ、日付をそのまま表示するとします。
そんな時、どんな実現方法があるでしょうか?

「*ngIf」を使う?そもそもTypeScript側で判定しておく?色々とありますが、「*ngIf」を使うと煩雑になって、他に同じような表示箇所があったら、その度に記載する必要がありますし、APIから取得した値などは、html(View)側でリストのまま繰り返し処理を行う、
<ng-container *ngFor="let item of list">
  <div class="hoge">{{item.targetDate}}</div>
</ng-container>
という方法を使う事が多いので、TypeScript側で編集はしないんですよね。

まあ、『そんなのAPI側の責務でしょ』と言われたらそれまでですが、
表示内容に特化しているなら、フロントエンドでやるのも悪手では無いと思います。

さて、話が逸れてしまいましたが、こんな時に役に立つのが、AngularのPipe機能です。

簡単に言うと、htmlに表示する時に同時に変換処理も行い、表示内容を変えてしまう、と言う機能です。

早速使ってみましょう。
angular-cliを利用しているならば、
ng g pipe [path/name]
と言うコマンドでテンプレートを作成出来ます。

今回は、[path/name]の部分を pipe/change-date-format に変えます。
すると、以下のようなファイルが生成されます。
import { Pipe, PipeTransform } from '@angular/core';

@Pipe({
  name: 'changeDateFormat'
})
export class ChangeDateFormatPipe implements PipeTransform {

  transform(value: string, args?: any): any {
    return null;
  }

}

この状態で、実装を行なっていきます。
今回、日付の編集・判定には、「moment」を使用します。
momentのインストールと使用方法は省きますが、実装結果は以下のようになります。
import { Pipe, PipeTransform } from '@angular/core';
import * as moment from 'moment';

@Pipe({
  name: 'changeDateFormat'
})
export class ChangeDateFormatPipe implements PipeTransform {

  transform(value: string, args?: any): any {
    const postDate = new Date(value);
    const postDateISO = moment(postDate.toISOString()).format('YYYYMMDD');
    const today = moment().format('YYYYMMDD');
    const yesterday = moment().subtract(1, 'days').format('YYYYMMDD');

    let displayDate: string = value;

    if (postDateISO === today) {
      displayDate = '今日';
    } else if (postDateISO === yesterday) {
      displayDate = '昨日';
    }

    return displayDate;
  }

}

処理は単純で、引数に送られてきた値を日付に変換、形式の統一、そして判定を行い、その日付が本日であれば『今日』、昨日だったら『昨日』、それ以外は日付をそのまま return しています。

Pipeの使い方は、
<div>{{ targetDate | changeDateFormat }}</div>
となります。@Pipe の name をvertical lineで変数に繋げて(Pipe)記載するだけです。簡単ですね。

大規模なプロジェクトになればなるほどPipeを上手く使うと、開発効率が上がります。
それでは、より良いAngularライフを!


2018年9月21日金曜日

Amazon Linux 2 にnginxをインストールする。(Amazon Linux Extrasを使う)


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。
今回は完全に自分用の備忘録です。
(毎回同じ問題にぶちあたり、同じ情報を調べるので・・・・)

さて、早速ですが、AWS EC2 のAmazon Linux 2 に、nginxをyumでインストールしようとすると・・・
$ sudo yum install -y nginx
読み込んだプラグイン:extras_suggestions, langpacks, priorities, update-motd
パッケージ nginx は利用できません。
エラー: 何もしません

nginx is available in Amazon Linux Extra topic "nginx1.12"

To use, run
# sudo amazon-linux-extras install nginx1.12


と、エラーになってしまいます。
・・・まあ、メッセージに解決策があるのですが、
念の為、説明しておくと、Amazon Linux 2には、
Amazon Linux Extras という仕組みがあって、良く使われるソフトウェアの最新バージョンについて、関連パッケージを含めて簡単にインストールすることが出来ます。
AWS自体が提供している仕組みだけあって、OSの安定性を保ったインストール・利用が可能になります。

実際にどんなものを提供しているのか見てみましょう。「amazon-linux-extras」というコマンドを実行してみます。
$ amazon-linux-extras
  0  ansible2                 available  [ =2.4.2 ]
  1  emacs                    available  [ =25.3 ]
  2  memcached1.5             available  [ =1.5.1 ]
  3  nginx1.12                available  [ =1.12.2 ]
  4  postgresql9.6            available  [ =9.6.6  =9.6.8 ]
  5  postgresql10             available  [ =10 ]
  6  python3                  available  [ =3.6.2 ]
  7  redis4.0                 available  [ =4.0.5  =4.0.10 ]
  8  R3.4                     available  [ =3.4.3 ]
  9  rust1                    available  \
        [ =1.22.1  =1.26.0  =1.26.1  =1.27.2 ]
 10  vim                      available  [ =8.0 ]
 11  golang1.9                available  [ =1.9.2 ]
 12  ruby2.4                  available  [ =2.4.2  =2.4.4 ]
 13  nano                     available  [ =2.9.1 ]
 14  php7.2                   available  [ =7.2.0  =7.2.4  =7.2.5 ]
 15  lamp-mariadb10.2-php7.2  available  \
        [ =10.2.10_7.2.0  =10.2.10_7.2.4  =10.2.10_7.2.5 ]
 16  libreoffice              available  [ =5.0.6.2_15 ]
 17  gimp                     available  [ =2.8.22 ]
 18  docker=latest            enabled    [ =17.12.1  =18.03.1 ]
 19  mate-desktop1.x          available  [ =1.19.0  =1.20.0 ]
 20  GraphicsMagick1.3        available  [ =1.3.29 ]
 21  tomcat8.5                available  [ =8.5.31 ]
DBやWebコンテナ、言語、フレームワークなど、色々ありますね。

さて、nginx1.12 が利用可能(available)になっているので、インストールしてみます。
sudo amazon-linux-extras install nginx1.12

これでインストール完了です。もう一度「amazon-linux-extras」というコマンドを実行してみます。
 $ amazon-linux-extras
  0  ansible2                 available  [ =2.4.2 ]
  1  emacs                    available  [ =25.3 ]
  2  memcached1.5             available  [ =1.5.1 ]
  3  nginx1.12=latest         enabled    [ =1.12.2 ]
  4  postgresql9.6            available  [ =9.6.6  =9.6.8 ]
  5  postgresql10             available  [ =10 ]
  6  python3                  available  [ =3.6.2 ]
  7  redis4.0                 available  [ =4.0.5  =4.0.10 ]
  8  R3.4                     available  [ =3.4.3 ]
  9  rust1                    available  \
        [ =1.22.1  =1.26.0  =1.26.1  =1.27.2 ]
 10  vim                      available  [ =8.0 ]
 11  golang1.9                available  [ =1.9.2 ]
 12  ruby2.4                  available  [ =2.4.2  =2.4.4 ]
 13  nano                     available  [ =2.9.1 ]
 14  php7.2                   available  [ =7.2.0  =7.2.4  =7.2.5 ]
 15  lamp-mariadb10.2-php7.2  available  \
        [ =10.2.10_7.2.0  =10.2.10_7.2.4  =10.2.10_7.2.5 ]
 16  libreoffice              available  [ =5.0.6.2_15 ]
 17  gimp                     available  [ =2.8.22 ]
 18  docker=latest            enabled    [ =17.12.1  =18.03.1 ]
 19  mate-desktop1.x          available  [ =1.19.0  =1.20.0 ]
 20  GraphicsMagick1.3        available  [ =1.3.29 ]
 21  tomcat8.5                available  [ =8.5.31 ]

nginx1.12 が使用出来る状態(enabled)になっています。

念の為、コマンドを実行してみます。
 $ nginx -v
nginx version: nginx/1.12.2

無事にインストール出来ているようです。

以上、本当に備忘録でした。


, , ,

2018年9月19日水曜日

knex.js の transaction でなぜか delete されないときに注目すること。


オフィス狛 技術部 CTOの Taka-yamです。

knex.jsは、Node.jsでActiveRecordを用いてSQLを発行するライブラリです。
fluentにSQLを構築でき、大変便利だなと感じる一方で、
トランザクション中にSELECT分を使用する際にちょっとした落とし穴があり注意が必要です。

例えば、booksというidとnameを保存するシンプルなテーブルがあるとして考えます。
id |  name  |
---+-------------+
 1 |  我輩は猫である|
 2 | こころ       |
 3 | 三四郎       | 

例えば、このテーブルのid1のデータを 削除->存在チェック->登録と実行します。
// knexの定義
const knex = require('../db/knex');

knex.transaction(trx => {
    
    // 全て削除
    knex.from('books').del();

     // 消えているか確認(存在チェック)
    const user = knex.from('books') . where('id', 1).first();

    if (typeof user !== 'undefined') {
        throw new Error(' データがあるよ')
    }

    // 登録
    const books = {id: 1, name: '銀河鉄道の夜'}
    knex.insert(books).into('books');
}

上記の メソッドは存在チェック後のifでエラーを投げます。

なぜか?

これは selectがクロージャ内に渡されているKnex.Transactionインスタンスを使用していない ため、
トランザクションとは別に処理を走らせてしまっているためです。

クロージャ内にいるので一見すると同じトランザクション として扱われるのかなと思ってしまいますが、実際には下記のようにしないといけません。
knex.transaction(trx => {
    
    // 全て削除
    trx.from('books').del();

     // 消えているか確認(存在チェック)
    const user = trx.from('books').where('id', 1).first();

    if (typeof user !== 'undefined') {
        throw new Error(' データがあるよ')
    }

    // 登録
    const books = {id: 1, name: '銀河鉄道の夜'}
    trx.insert(books).into('books');
}

selectに関しては下記のようにも 書けます。
const user = knex.transactioning(trx).from('books') . where('id', 1).first();

例えば処理を共通化して別メソッドとして切り出したりしていると、
思わぬところでトランザクションが途切れてしまっていることがあり、
「???」になることがあります。

また、個人的にですが、Realmなどのライブラリですと、
クロージャ内がトランザクション領域として扱われるので
勘違い して同じように使用してはまってしまったという経緯もありました。

もし同じような現象で困っている方がいらっしゃいましたら、
上記のような可能性も考えてみてください。

,

2018年9月3日月曜日

Illustratorでカラーバリエーションのあるようなアイコンやイラストをできるだけ楽に作る。


こんにちは、オフィス狛デザイン部のSatoです。

今回はカラーバリエーションのあるようなアイコンをできるだけ楽に作る方法をご紹介したいと思います。
アプリ内の画像などを作っている時に背景色によってアイコンの色が変わったり、状態によってアイコンカラーが変わったりするときの画像がすこーし簡単に作れるようになります。


Illustratorで描いたイラストです。
パーツが少し多いのでもうちょっと一部の色を明るくしたいなと思ったり、カラーバリエーションのある画像が欲しいと思うとちょっと大変ですよね。
こういう時に使える機能が「オブジェクトを再配色」機能です。


まず色を変えたいオブジェクト全体を選択し、Illustratorメニューバーの「編集」の「カラーの編集」から「オブジェクトを再配色」を選ぶかツールバーのオブジェクトを再配色アイコン」クリックします。

下のようなオブジェクトを再配色パレットが出てきます。

編集ボタンを押すと色相環のような画面になるので右下の「ハーモニーカラーをリンク」ボタンをクリックしておきます。(これをクリックするとまとめて色相や彩度が変わります)
色相環のような図の中の丸い色つきの丸を移動させると色相が変わるのでこれで一気に全体的な色が変えられます!

色を変えてみました。


一部だけ色が変えたい場合は「ハーモニーカラーをリンク」ボタンを押さずに変えたい部分の色のみの位置を動かしたり、指定画面(編集ボタンの横にある「指定」ボタンで画面が戻ります)から色を変えたりできます。

一部色を変えてみました。

簡単に絵のカラーパターンができました!

グラデーションで使われている色などもこの機能で変えられるので、カラーバリエーションのある画像がササッと数枚作れます。
使う機会が多そう&時短になる機能なので、ぜひ使ってみてください。


2018年8月30日木曜日

AngularでyoutubeAPI(Iflame Player API)を使ってyoutube動画を埋め込み表示する。

オフィス狛 技術部のHammarです。

いろんなサイトでyoutube動画が見れるように工夫しているところが多いですが、今回はAngularでyoutubeAPI(Iflame Player API)を利用して、サイトにyoutube動画を埋め込み表示する方法を書いてみたいと思います。

■実装環境

・Angular
・ngx-youtube-playerモジュールを利用

今回使用するngx-youtube-playerモジュールはインストール済みとします。
まずモジュールのimportを記述します。
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { YoutubePlayerModule } from 'ngx-youtube-player';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppComponent } from './app';

@NgModule({
  imports: [BrowserModule, YoutubePlayerModule],
  declarations: [AppComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule);

次にyoutubeを埋め込みたい画面のファイルに下記のように記述します。
このあたりの記述はnpmjs.comのサンプルの記載とほぼ同じ感じです。
import { Component } from '@angular/core';
@Component(
  selector: 'app',
  template: `
        <youtube-player
      [videoId]="id"
      (ready)="savePlayer($event)"
      (change)="onStateChange($event)"
    ></youtube-player>
    `
})

export class AppComponent {
  player: YT.Player;
  private id: string = 'qDuKsiwS5xw';

  savePlayer(player) {
    this.player = player;
  }
}

これで基本的な下準備は完了で、
private id: string = 'qDuKsiwS5xw';
の部分で対象動画のyoutubeのidをセットすれば動画の埋め込みは完了です。
すごく簡単ですね!

で、カスタマイズも結構簡単にできますので、いくつか紹介したいと思います。

■動画が完了したタイミングで何か処理をする 

上記のcomponent.tsファイルに
  onStateChange(event) {
    if (event.data === 0) {
        // 動画終了してからの処理
    }
  }
のように追記して動画終了してから自動で何か処理をさせることもできます。
動画のステータスは
    -1 – 未開始
    0 – 終了
    1 – 再生中
    2 – 一時停止
    3 – バッファリング中
    5 – 頭出し済み
で判定することができますので、ステータスが変わったタイミングでなにかの処理を入れたりすることができます。

■プレイヤーのコントロールを別ボタンでやる

たとえば動画の再生や停止をyoutube内部のコントローラーではなく、プレイヤー外部にボタンを新たに設定してそこで制御したい場合などは下記のように追記するだけで実装可能です。
まずhtml側に下記のようなボタンを作成しておきます。
<button (click)="playVideo()" ><p>再生</p></button>

component.tsファイルに下記関数を設定します。
// 再生ボタン
playVideo() {
    this.player.playVideo();
}
これだけで制御が可能になります。
上記の要領で停止や音量の変更、ミュートやシークも簡単に制御ができます。

■動画終了後の表示のカスタマイズ

動画が終了すると関連の動画も勝手に表示されたりして、表示動画とは全く違うジャンルの動画リスト等もデフォルトで表示され、サイトによっては余計な情報も表示されてしまうので、この表示をいくつか消したいときがあると思います。
このあたりの表示内容についてもカスタマイズ可能です。
動画終了後の関連動画を非表示にする場合、component.tsファイルのtemplate部分を下記のように記述します。
@Component({
  selector: 'app',
  template: `
        <youtube-player
      [videoId]="id"
      (ready)="savePlayer($event)"
      (change)="onStateChange($event)"
      [playerVars]="{'rel': 0}"
    ></youtube-player>
    `
})

上記の[playerVars]="{'rel': 0}"という記述が関連動画を非表示にするパラメータになります。
上記以外にも、動画の再生が始まる前に動画のタイトルやアップロードしたユーザーなどの情報カスタマイズや、動画のプレーヤーコントロールを表示するかどうかを指定したりすることができます。

上記で紹介したのはほんの一部で、下記公式サイトにいろいろなカスタマイズのリファレンスやプレイヤーパラメータの詳細が載っていますので、ぜひ参考にしてみてください。

・Iflame Player APIリファレンス
https://developers.google.com/youtube/iframe_api_reference?hl=ja#Playback_controls
・youtubeプレイヤーパラメータ
https://developers.google.com/youtube/player_parameters?playerVersion=HTML5&hl=ja

,

2018年8月26日日曜日

AngularでChart.jsを使った時の「Failed to create chart: can't acquire context from the given item」に対応する。


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

Chart.jsは、『グラフと言えばChart.js』ぐらいに有名なライブラリです。
Abgularで使用する場合、npm install で簡単に取り込む事が出来ます。

インストールは本家のGitHub、 Angularで使う場合はこちらのサイト
に非常に詳しい説明があります。

さて、上記を参考にいざ使用してみると・・・・

ブラウザのコンソールに
Failed to create chart: can't acquire context from the given item
が出る事が分かりました。
グラフ自体は問題なく動いているんですけどね。

で、色々と試行錯誤した結果、問題ある箇所は、先のサイトで言うところの
<div *ngIf="chart">
  <canvas id="canvas">{{ chart }}</canvas>
</div>

である事が分かりました。これを・・・
<div [hidden]="chart === null">
  <canvas id="canvas">{{ chart }}</canvas>
</div>

多分、
[hidden]="!chart"
でも大丈夫だと思います。(と言うか、上記の方が良いかも)

要は、「*ngIf」を使ってしまうと、falseになった時に、レンダリングされないので、
chart.jsから「id="canvas"」という要素が見つけられないのでしょうね。

ちょっとハマったので、
同じようなエラーが出た方は、上記で回避してください。


,

2018年8月22日水曜日

windowやdocumentのイベントをAngularで使う。(HostListener)


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

Angularは、自コンポーネント内の項目についてのイベントは簡単に定義出来ます。

[View側]
<button class="button_close" type="button" (click)="close()">閉じる</button>

[Component側]
  close() {
    // ここに処理を記載
  }

しかし、自コンポーネント以外のイベントを取るとなると、ちょっと工夫が要ります。

例えば、下記のような要素があったとします。

「Company」リンクをクリックすれば、吹き出しが出るのは簡単に実装出来ますが、
『吹き出し以外をクリックしたら、吹き出しを閉じる』はどうやって実装したら良いでしょうか?

こんな時に、「HostListener」を使用します。
その名の通り、HostのイベントをListen出来ます。

import {
  Component,
  ChangeDetectionStrategy,
  OnInit,
  HostListener,
  ElementRef
} from '@angular/core';
import { Router } from '@angular/router';

@Component({
  selector: 'move-homepage-header',
  changeDetection: ChangeDetectionStrategy.OnPush,
  templateUrl: './homepage-header.component.html'
})
export class HompageHeaderComponent implements OnInit {

  isShownCompanyPopUp = false;

  constructor(
    private eRef: ElementRef,
    private router: Router
  ) {}

  ngOnInit() {}

  @HostListener('document:click', ['$event'])
  onClickOut(event) {
    if (!this.eRef.nativeElement.contains(event.target)) {
      this.isShownCompanyPopUp = false;
    }
  }

  showCompanyPopup() {
    this.isShownCompanyPopUp = !this.isShownCompanyPopUp;
  }

  linkCompanyInfo() {
   this.isShownCompanyPopUp = false;
    this.router.navigateByUrl('/home/companyinfo');
  }

  linkCompanyMap() {
   this.isShownCompanyPopUp = false;
    this.router.navigateByUrl('/home/companymap');
  }

  linkPrivacyPolicy() {
   this.isShownCompanyPopUp = false;
    this.router.navigateByUrl('/home/privacypolicy');
  }
}

HostListenerのimportは忘れないように記載して下さい。
さて、肝心の部分を抜粋すると・・・・
  @HostListener('document:click', ['$event'])
  onClickOut(event) {
    if (!this.eRef.nativeElement.contains(event.target)) {
      this.isShownCompanyPopUp = false;
    }
  }

今回は、「documentのclickイベント」を使用しています。
ただ、これだと、吹き出しの中のクリックイベントまで対象になってしまうので、
自コンポーネント内は除外し、『自コンポーネント内以外をクリック』のイベントを取る事を実現しています。

いかがでしょうか?
この方法で、windowのresizeイベントも取れると思います。


2018年8月20日月曜日

タブの選択状態をAngularで簡単に実装する。


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

画面上のタブに選択状態を表示する・・・下図のような感じの事をやりたい時があります。


Angularなので、Component側でフラグ(変数)を持って、それをView側で判断・・・という事を思い付きますが、もっと簡単な方法があります。

    <div class="header-nav">
      <ul class="header-nav-tabs">
        <li class="nav-tab" routerLinkActive="nav-tab_active"><a class="nav-link" routerLink="/home/test1">テストタブ1</a></li>
        <li class="nav-tab" routerLinkActive="nav-tab_active"><a class="nav-link" routerLink="/home/test2">テストタブ2</a></li>
      </ul>
    </div>

上記のように「routerLinkActive」と「routerLink」の組み合わせで実現出来ます。

routerLink」は該当のhtml要素(今回の場合は「a」タグ)をクリックした際の遷移先を指定します。

routerLinkActive」は、『現在表示している画面(URL)が「routerLink」で指定されているものと一致した場合、指定したクラスを適用する』
という動きをします。

今回の場合は、「nav-tab_active」というCSSクラスに選択状態のスタイルを記述すれば良いわけです。

これでViewでの「*ngIf」地獄から少しは解放されます。


2018年8月6日月曜日

Xcodeでよく使うキーボードショートカット一覧


オフィス狛 技術部 CTOの Taka-yamです。

Xcodeを使用していると複数画面でコードを表示したり、
画面やメニュー間を移動すること多いと思いますが、
その時にキーボードショートカットを知っているとだいぶ時間短縮になり、
作業が楽になります。(と個人的には思っています。)


今回は普段よく使いそうなキーボードショートカットをまとめてみました。

パッと見られるように画像にして印刷できるようにしています。

チートシート的な役割としてお役に立てれば幸いです。

ショートカットを駆使してトラックパッドレス、マウスレスなXcodeライフを!

2018年8月1日水曜日

CSSで背景画像のみぼかして表示したりする際のメモ。

こんにちは、オフィス狛 デザイン部のSatoです。

iOSのパスコード入力時やWebサイトの背景などでよく見る「背景画像が全体的に表示されていてぼかしがかかっている」状態をCSSで再現したかった際にうまくいかなかったりした所があったのでここに記載しておきます。

最初、画像をcssでぼかす事が「filterという画像の見た目を変えることができるクラスで実装できたはず」という知識はぼんやりあったのでとりあえず親要素に「filter: blur(5px);」を指定してみました。
予想はできていましたが、囲った要素全部にぼかしがかかってるし、なぜか画面の端に滲みが出てかっこ悪くなってしまいました。
これが最初の状態です。中央の白い丸の中は完全に読めなくなっていますね……。


調べた所、全体的にぼかしがかかるのも白い滲みもリストの最初に記号を置いたりする時によく使う「::before」という擬似要素で解決できるそうです。
参考にさせて頂いたサイト
背景を画面いっぱいに表示するためのclassで、参考サイトに記載されている背景を綺麗にぼかすためのCSSを囲ってみた所うまくいきました。


背景を画面いっぱいに表示するための部分とぼかすための部分を分けて指定しないと、画像が表示されている部分のみで画面の高さを計算してしまうようで、フッターの部分が下がってしまいスクロールしないと見えなくなってしまうので気をつけてください。

2018年7月31日火曜日

multer-s3を使ってaws S3へファイルアップロードする時の注意点(Node.js)


オフィス狛 技術部のHammarです。

今回もまた開発をしていく最中にちょっとハマったことについて書こうと思います。
今回は「ファイルアップロード」についてです。
よくあるファイルアップロードの機能ですが、ある条件のときにちょっとしたことなんだけどうまくいかずにハマりました。
まずファイルアップロード機能実装でやろうとしたことと、その環境をザックリ簡単に記します。

■やりたいこと

・AWS S3に動画ファイルをアップロード
・アップロードしたファイルをwebサイトで視聴できるようにする
・バックエンドのnode.jsにS3アップロードのAPIを作成する

■実装環境

・node.js(Express)
・multer-s3モジュールを利用

とりあえずS3にファイルアップロードするためのaws-sdkをインストールしたり、アップロード用のモジュールをもろもろインストールしますが、今回のハマったところはこの先なのでこの辺のインストール方法等は省略します。

さて、上記環境は整っている状態で実際にアップロード処理を作成します。
アップロード処理について今回は「multer-s3」というモジュールをつかって下記のように処理を書きました。 npmjs.comのサンプルの記載とほぼ同じ感じです。

■S3アップロード処理サンプル

var aws = require('aws-sdk')
var express = require('express')
var multer = require('multer')
var multerS3 = require('multer-s3')

var app = express()
var s3 = new aws.S3({ /* ... */ })

var upload = multer({
  storage: multerS3({
    s3: s3,
    bucket: 'some-bucket',
    metadata: function (req, file, cb) {
      cb(null, {fieldName: file.fieldname});
    },
    key: function (req, file, cb) {
      cb(null, ${req.body.fileName})
    }
  })
})

app.post('/upload', upload.array('photos', 3), function(req, res, next) {
  res.send('Successfully uploaded ' + req.files.length + ' files!')
})
上記をroutesディレクトリに任意のファイルを作成して記述します。

ここまでで基本的なアップロード機能は完成です。
で、実際に動かしてみます。今回アップロードしたときにそのファイル名とファイルIDも使いたかったので、requestのbodyにファイルとファイル名、ファイルのIDもつけて上記のAPIになげます。
動作の確認方法はいろいろあると思いますが、個人的にPostmanというツールを使ったほうが楽なのでこちらを使います。

■postmanでリクエスト作成

上記のような感じでリクエストを作成します。
  ①POSTを選択しAPIのURLを入力
  ②ファイルをアップロードするので「form-data」を選択
  ③アップロードするファイルと同時にリクエストするフィールド(key、value)を入力
  ④右上のsendボタンを押下
これで先ほど作成したAPIにリクエストが送られ、ファイルがアップロードされます。

で、ここからがハマったポイント
S3にファイルが上がっているか確認したところファイルは問題なく上がっています。ただ、なぜか②で一緒にbodyに記載していたファイル名やファイルIDがとれていない。そのためS3にアップロードしたファイル名が「undefined」となっています。アップロード自体はエラーとなっていませんでした。

    key: function (req, file, cb) {
      cb(null, ${req.body.fileName})
    }
上記のfileNameの部分でS3に上がったときのファイル名となる予定でしたが、なぜかfileNameがうまく取得できていないようです。
おかしい、同じbodyに入力していて、ファイルはアップロードできているのにファイル名がとれないなんて。。。

いろいろ調べてやってみても原因がわからず。。。
そこで最初のファイルがうまくいっているなら1番目の項目だけうまくいくのかと思い順番を下記のようにかえて再度アップロード。

なんと、ファイル名取れました!
ファイル自体もアップロードできている!
1番目も2番目の項目もうまくっているので1番目の項目だけってことではなさそうだけど、あれ?こんどはファイルIDが取れていない。
ということは、アップロードファイルより上の項目は取得できて、アップロードファイルより下の項目は取得できていないようです。

再度bodyの順番を下記のように並び替えてアップロードしてみます。
うまくいきました!
今度はファイル名、ファイルIDもちゃんと取れていました。

ということで、ファイルアップロード時のrequestのbodyはアップロードファイルの順番を最後にしておかないと、その他の項目が取得できないということがわかりました。

恐らく「multer-s3」というモジュールの特性や、記述の仕方もあるかもしれませんが、「multer-s3」でS3にアップロードするという実装に関しては上記のようなことが発生しますので注意が必要です。

またほかのnpmモジュールを使う際も少し注意したほうがよさそうで、「busboy」というモジュールも処理の組み方次第では同様の事象に注意が必要なようです。
multipartのアップロードストリームをYoutube投稿ストリームに受け流す

, ,

2018年7月8日日曜日

SVGの色をCSS上で変えてしまう方法


こんにちは、オフィス狛デザイン部のSatoです。

先日、アイコンをSVGで作成した際に、マウスオーバーすると色が変わったりするようなことがしたいなぁ……と思うことがありました。
普段なら商用フリーでCDNのアイコンフォントをアイコンにしてしまったりするのですが、今回は少し特殊なアイコンでそれもできず……。
SVGの色をテキストのようにCSSで変えたりできないものかと思って調べたらできる様子!
試してみた所、なんとか自分でもできたのでやり方をメモしたいと思います。

やり方は簡単。<svg>か親要素のCSSにfill: カラーコード;を追加するだけです。
ちなみに値を「currentColor」にするとなんとcolor設定されている色になります。
HTML
<a href="#"><svg>(本来はここにインラインで記載)</svg>Button</a>
CSS
a{color: #3ab28f;}
a:active{color: #3ddaa3;}
svg{fill: currentColor;}
こんな感じですね。
ただし、<img>タグや<object>タグではこの方法では色を読み込んでくれないようです。
最初自分はそこで躓いたのでご注意ください。

正直、自分は今までSVGのことを「どんなに拡大しても画像が荒れないすごい画像形式」くらいにしか思っていなかったのですがこんな事もできるんですね!びっくりしました。


,

2018年7月2日月曜日

Amazon S3 で サイト公開(HTTPS)する。(3)問い合わせフォーム準備編その2


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

前回は、Webサイトの問い合わせフォームから問い合わせされた内容をS3バケットへテキスト形式で保存するところまで行いました。
今回は、S3に保存されたら、その内容をメールで送信したいと思います。

※メールを送信する為のAWS SES(Simple Email Service)については説明を省きます。SESの設定が終わった状態で下記の手順を行って下さい。

1)ポリシー(Policy)の作成

まずは、AWSコンソール上のIAMのページへ遷移し、左側のメニューから「Policies」を選択。
その後、左上の「Create policy」をクリックします。


Serviceは、「CloudWatch Logs」
Actionは、「CreateLogStream」、「CreateLogGroup」、「PutLogEvents」
Resourceは「all resources」を選択して、
右下の「Review policy」をクリックします。


最後にNameとDescriptionを入力し、「Create policy」を選択します。


作成に成功しました。


2)ロール(Role)の作成

続いてロールを作成していきます。先程と同じくAWSコンソール上のIAMのページへ遷移し、
左側のメニューから「Roles」を選択します。


Select type of trusted entityは「AWS service」を選択。
Choose the service that will use this roleは「Lambda」を選択して、
右下の「Next: Permissions」をクリックします。


まずはFilterで「Policy type」を選択します。
続いてAttach permissions policiesでは「AmazonSESFullAccess」をチェックします。
(検索ボックスで「SES」と入力すると、フィルターが掛かって選択が楽です)


次にFilterで「Customer managed」を選択し、先程作成したポリシーにチェックを入れます。
(検索ボックスの中身を消さないと一覧に出て来ないのでご注意を)
右下の「Next: Review」をクリックします。


最後、Role nameを入力して、右下の「Create role」をクリックします。


作成に成功しました。


3)Lambda Functionの作成

AWSコンソール上のLambdaのページへ遷移し、「Create a function」を選択。


「Author from scratch」を選択し、Name等の必要な情報を入力します。
Runtimeは「Node.js」を選択して下さい。
Roleは「Choose an existing role」を選択し、Existing roleは、先程作成したロールを選択します。


作成直後の状態です。
ここで、左側の「Add triggers」から、「S3」を選択します。


対象のバケットを選択し、Event typeは「PUT」を選択します。 「Add」をクリックします。

続いて、Node.jsのプログラムを貼り付けます。


貼り付けるプログラムは以下の通りです。
'use strict';

console.log('Loading function');

const aws = require('aws-sdk');
const s3 = new aws.S3({ apiVersion: '2006-03-01' });
const ses = new aws.SES({ apiVersion: '2010-12-01', region: 'us-east-1' });

exports.handler = (event, context, callback) => {
    const bucket = event.Records[0].s3.bucket.name;
    const key = decodeURIComponent(event.Records[0].s3.object.key.replace(/\+/g, ' '));
    const params = {
        Bucket: bucket,
        Key: key
    };

    s3.getObject(params, (err, data) => {
        if (err) {
            console.log(err);
            const message = `Error getting object ${key} from bucket ${bucket}. Make sure they exist and your bucket is in the same region as this function.`;
            console.log(message);
            callback(message);
        } else {
            const json = JSON.parse(data.Body.toString());
            const message = `
                名前: ${json.name}           
                MAIL: ${json.email}
                問い合わせ日時:
                ${json.date}
                問い合わせ内容:
                ${json.comments}
                `
            const params = {
                Destination: {
                    ToAddresses: [process.env.TOADDRESS]
                },
                Message: {
                    Body: {
                        Text: {
                            Charset: "UTF-8",
                            Data: message
                        }
                    },
                    Subject: {
                        Charset: "UTF-8",
                        Data: "フォームからのお問い合わせ"
                    }
                },
                Source: process.env.SOURCEADDRESS
            };
            ses.sendEmail(params, function (err, data) {
                if (err) console.log(err, err.stack);
                else console.log(data);
            });
        }
    });
};

プログラムに環境変数を使っているので、値を設定します。
SOURCEADDRESSが送信元(=from)アドレスで、
TOADDRESSが送信先(=to)アドレスです。


以上でLambdaの設定が完了となります。
Lambda作成後、前回作成した問い合わせフォームから、問い合わせを行うと・・・
この後、S3側のトリガー設定などが残っているのですが、以降は、独自ドメインの設定を行った後にやって行こうと思います。
と言う事で、WebサイトへのアクセスはS3のURLを利用しているので、 次回はRoute53を使用して、独自ドメインでの運用を可能にしていきたいと思います。

次回:Amazon S3 で サイト公開(HTTPS)する。(4)Route 53を使用した独自ドメイン運用
前回:Amazon S3 で サイト公開(HTTPS)する。(3)問い合わせフォーム準備編その1


, , ,

2018年6月26日火曜日

Amazon S3 で サイト公開(HTTPS)する。(3)問い合わせフォーム準備編その1


オフィス狛 技術部のKoma(Twitterアカウントの中の人&CEO)です。

前回からだいぶ時間が経ってしまいました。
さて、前回までで、S3へサイトを移管する事が出来ましたが、サイトによっては問題が発生します。
そう、S3は単にストレージに過ぎないので、サーバサイドのプログラム(PHPなど)は動かすことが出来ません。
弊社のホームページですと、「問い合わせフォーム」が該当します。

旧サイトでは、「問い合わせフォーム」から問い合わせ内容をPOSTし、Webサーバ側のPHPファイルを実行し、メール送信処理を行っていました。
けれど、S3ではPHPを実行する事は出来ません。

という事で、代替案として、以下の方法を取ろうと思います。
・問い合わせフォームにて入力された内容を専用のS3バケットへテキストファイルとして保存
・S3バケットへの保存をトリガーに、Lambdaファンクションでメール送信

早速、実施して行きましょう。

1)identity poolの作成

まずは、AWSコンソール上のAmazon Cognitoのページへ。

・Step 1:Create identity pool
「Identity pool name」に適当な名前を設定し、「Enable access to unauthenticated identities」にチェックを付け、「Create Pool」を押します。

・Step 2:Set permissions
IAM Roleを選択します。
このタイミングで作成する事も可能なので、今回は、「Create a new IAM Role」を選択し、
Roleの名前を付けて、「Allow」を押します。

※Cognitoについては、こちらを参照。

そうすると、identity pool id が発行されるので、メモっておきます。

ここでやっている事は、『S3へファイルをアップロードする為の認証情報を作成している』、と思って頂ければ良いと思います。

2)IAM Roleへ権限付与

AWSコンソールで、IAM Roleを見ると、先程新規で作ったRoleが見れると思います。

次はこのRoleにS3へのファイルアップロード権限を付与します。
先程の画面でRoleをクリックすると、下記の画面に遷移しますので、
「Add inline policy」をクリックします。

・Visual editorでポリシー作成
JSONを直接貼り付けて説明しているサイトが多いのですが、それだと意味を理解しないで使ってしまうと思うんですよね。
と、言う訳で、視覚的にポリシー作成を行なって行きます。
「Choose a service」をクリックします。

まずはサービスの選択ですが、S3と検索ボックスに入力すると、S3が選べるので、クリックします。

続いてアクションですが、S3にファイルを格納(Put)する必要があるので、
「PutObject」と「PutObjectAcl」をチェックします。
(他にもたくさんのアクションがあるので、ここでも検索ボックスに「PutObject」と入力しておくと楽です。)

次に、対象となるバケットを選択します。
「Specific」を選択してから、「Add ARN」をクリックします。

表示されたポップアップでバケット名をします。(バケットは前もって作っておいて下さい)
バケット内のオブジェクトは全てを対象とします。

バケットを選んだ後はこんな感じになります。
確認後、「Review policy」をクリックします。

最後、ポリシーに名前を付けて、「Create policy」でポリシーを作成します。

出来上がったポリシーのjsonはこんな感じです。
結構な手順記載しましたが、内容自体はこれだけ。

3)CORSの設定

サイトがあるドメイン(S3バケット)と問い合わせ内容を保持するドメイン(S3バケット)は違うので、
CORS(Cross-Origin Resource Sharing)を設定する必要があります。
これを設定しないと、後に用意するjavascriptプログラムから問い合わせファイルをアップロードすることが出来ません。

では、AWSコンソール上で、問い合わせ内容を保存するS3バケットを選択し、
Permissionsタブから、「CORS configuration」を選択し、編集を行います。

<?xml version="1.0" encoding="UTF-8"?>
<CORSConfiguration xmlns="http://s3.amazonaws.com/doc/2006-03-01/">
<CORSRule>
    <AllowedOrigin>http://officekoma.co.jp.xxxxxxxxxxxxxxxxxx</AllowedOrigin>
    <AllowedMethod>PUT</AllowedMethod>
    <MaxAgeSeconds>3000</MaxAgeSeconds>
    <AllowedHeader>*</AllowedHeader>
</CORSRule>
</CORSConfiguration>
「AllowedOrigin」には、問い合わせフォームがあるサイトのドメイン(S3バケットURL)を指定します。
これを「*」にしてしまうと、先程のidentity pool idを知っていれば、そこら中のドメインからファイルがアップロード出来てしまうので要注意です。

4)問い合わせフォームとアップロードプログラムの作成

後は問い合わせフォーム(html)とアップロードプログラム(javascript)の作成です。

弊社サイトの問い合わせフォームは以下のようになっています。

・問い合わせフォーム(html)
<!doctype html>
<html lang="ja">
<head>
  <!-- 中略 -->
  <script src="https://sdk.amazonaws.com/js/aws-sdk-2.200.0.min.js"></script>
  <script src="../js/s3Upload.js"></script>
  <!-- 中略 -->
</head>
<body>
  <!-- 中略 -->
  <h1>お問い合わせはこちらからお願いします。</h1>
  <p>フォームからの送信に失敗する場合、お手数ですが <a href="mailto:info@officekoma.co.jp">info@officekoma.co.jp</a> まで直接ご連絡ください。<br>
  ※商品・サービスの売り込み、その他営業活動・勧誘などには返信できかねますのでご遠慮ください</p>
  <script type="text/javascript" src="../js/form-validation.js"></script>
  <div class="form_control setting_center">
    <form id="contactForm" action="#">
      <fieldset>
        <p>お名前または会社名(Name)<span class="required">※必須</span></p> <input name="name"  id="name" type="text" class="form_box" autocomplete="name" required />
        <p>メールアドレス(Email)<span class="required">※必須</span></p><input name="email"  id="email" type="text" class="form_box" autocomplete="email" required />
        <p>問い合わせ内容(Comments)<span class="required">※必須</span></p>
        <textarea name="comments" id="comments" class="form_box" rows="4" cols="40" ></textarea>
        <div class="input_submit"><input type="button" value="送信 " name="submit" id="submit"  /></div>
      </fieldset>
      <p id="error" class="warning">送信に失敗しました。恐れ入りますが、再度入力をお願いします。</p>
    </form>
    <p id="success" class="success">お問合わせを送信いたしました。ありがとうございました。内容を確認後、早急にご返信させていただきます。もし数日中に返事が無い場合は、正しく受信できなかった可能性がありますので、恐れ入りますが再度のご連絡をお願いします。</p>
  </div>
  <!-- 中略 -->
</body>
</html>

・アップロードプログラム(javascript)
「contactBucketName」、「bucketRegion」、「IdentityPoolId」をそれぞれの環境に合わせて変更して下さい。
jQuery(document).ready(function($) {
    var contactBucketName = 'homepage-contact';
    var bucketRegion = 'hogehoge';
    var IdentityPoolId = 'hogehoge:xxxxxxxxxxxxxxxxxxxxxxxxxxx';

    AWS.config.update({
        region: bucketRegion,
        credentials: new AWS.CognitoIdentityCredentials({
            IdentityPoolId: IdentityPoolId
        })
    });

    var s3 = new AWS.S3({
        params: {Bucket: contactBucketName},
    });

    // hide messages 
    $("#error").hide();
    $("#success").hide();

    // on submit...
    $("#contactForm #submit").click(function() {
        $("#error").hide();
        
        //name
        var name = $("input#name").val();
        if(name == ""){
            $("#error").fadeIn().text("名前を入力してください。(Name required.)");
            $("input#name").focus();
            return false;
        }
        
        // email
        var email = $("input#email").val();
        if(email == ""){
            $("#error").fadeIn().text("メールアドレスを入力してください。(Email required.)");
            $("input#email").focus();
            return false;
        }

        var now = new Date();
        var obj = {"name":$("input#name").val(), "email":$("input#email").val() ,"comments":$("#comments").val(), "date": now.toLocaleString()};
        var blob = new Blob([JSON.stringify(obj, null, 2)], {type:'text/plain'});
        s3.putObject({Key: now.getTime() + ".txt", ContentType: "text/plain", Body: blob, ACL: "public-read"},
        function(err, data){
            if(err !== null){
                $("#error").fadeIn();
            }
            else{
                $("#success").fadeIn();
                $("#contactForm").fadeOut();
            }
        });
    });

    return false;
});

5)動作確認

実際に問い合わせを行うと、下記のようにファイルがアップロードされます。


ちょっと長くなってしまいましたが、これでPHP無しでも、サイトからの問い合わせの内容を確認する事が出来るようになりました。

とは言うものの、これだと、毎回能動的にS3のバケットを確認しないと問い合わせが確認出来ないので、現実的はありません。
次回は、問い合わせ内容をメールで送信する方法を記事にします。
(なんだか本質とは離れていっていますが、一応「サイトのhttps化」の一環です。)

次回:Amazon S3 で サイト公開(HTTPS)する。(3)問い合わせフォーム準備編その2
前回:Amazon S3 で サイト公開(HTTPS)する。(2)S3での公開準備編


, , , ,