2019年10月15日火曜日

iOSアプリから Amazon ConnectとLambdaで電話をかける。


こんにちは、オフィス狛 モバイル開発担当 Aika-yuy です。
今回の投稿は、iOSアプリから AmazonConnectとLambdaで任意の番号に電話をかける方法をご紹介します。


前回までのLambdaからAmazon Connectで電話をかける方法です

①Amazon Connectで電話番号、お問い合わせフローの作成
②IAMでロールを作成
③Lambda関数の作成
④実行!!


今回の手順

①Amazon CognitoでiOSアプリからアクセス権限を作成
②CognitoのロールにLambdaのアクセス権限を追加
③iOSアプリにCognitoとLambda呼び出しの設定


①Amazon CognitoでiOSアプリからアクセス権限を作成

・TOP画面からCognitoを検索して選択





・Identity Poolを作成します









・新しいIDプールの作成









・任意の名前をつける
・認証なしのユーザーを許可にチェックして作成









・許可を押すと自動でロールが作成されます








・サンプルコードをコピーします(後ほどAppDelegateに貼り付け)















②CognitoのロールにLambdaのアクセス権限を追加

・IAMに移動します
・サイドメニューのロールを選択
・先ほど自動作成されたロールが一覧に表示されています
・Unauthの方をクリックして下さい








・インラインポリシーの追加を押して下さい







・サービスはLambdaを選択
・アクションはInvokeFunctionにチェックを入れて下さい
・ポリシーの確認ボタンを押します







・ARNの追加で以前に作った、LambdaFunctionのARNをペーストして下さい







・任意の名前をつけ、ポリシーの作成ボタンを押して下さい







・ポリシーが追加されていれば、完了です
・次はiOSアプリを作成していきます

③iOSアプリにCognitoとLambda呼び出しの設定

・今回は以下をpod installしました

pod 'AWSMobileClient', '~> 2.6.13'  # For AWSMobileClient
pod 'AWSCognito'
pod 'AWSLambda'

AppDelegateに先ほどのサンプルコードをそのまま貼り付けます

import AWSCognito//インポートを忘れずに

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {
    var window: UIWindow?
    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        let credentialsProvider = AWSCognitoCredentialsProvider(regionType:.APNortheast1,identityPoolId:"identityPoolId")
        let configuration = AWSServiceConfiguration(region:.APNortheast1, credentialsProvider:credentialsProvider)
        AWSServiceManager.default().defaultServiceConfiguration = configuration
        return true
    }

viewControllerにボタンを押したらLambdaFunctionを叩くようにします

import AWSLambda//インポートを忘れずに

class ViewController: UIViewController {

    override func viewDidLoad() {
        super.viewDidLoad()
    }
    @IBAction func callButtanTapped(_ sender: Any) {
        let lambdaInvoker = AWSLambdaInvoker.default()
        
        lambdaInvoker.invokeFunction("testCall", jsonObject: nil).continueWith(block: {(task:AWSTask) -> Any? in
            if let error = task.error {
                print("Error: \(error)")
                return nil
            }
            return nil
        })
    }
}

ボタンを押すと電話がかけられるようになりました!!!

,

2019年9月30日月曜日

node.jsでの日付時刻処理(moment.jsを使ってみる) その2


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

前回、moment.jsを記事にしたのですが、一部機能しか紹介できなかったので、便利だなと思った機能を追加でご紹介します。

日付時刻の比較、差分、タイムゾーンの変換は、前回の記事を参照ください。
node.jsでの日付時刻処理(moment.jsを使ってみる)

※下記の例は、moment.jsのバージョン「2.22.2」で取得した結果になります。
バージョンにより、メソッド名やオブジェクト名などが異なる場合がありますので、ご注意ください。
公式ドキュメント

【フォーマット(format)】

// フォーマットを指定
console.log(moment('2019-08-28 01:05:07').format('YYYY年MM月DD日(ddd) HH時mm分ss秒'));  // 2019年08月28日(Wed) 01時05分07秒

// 曜日を日本語表示する場合、事前にロケールを設定します
moment.locale("ja");
console.log(moment('2019-08-28 01:05:07').format('YYYY年MM月DD日(ddd) HH時mm分ss秒'));  // 2019年08月28日(水) 01時05分07秒

// フォーマットを指定して、momentオブジェクトが作成可能です
const date1 = moment("2019年08月28日(水) 01時05分07秒", "YYYY年MM月DD日(ddd) HH時mm分ss秒");
console.log(moment(date1).format('YYYY/MM/DD(ddd) HH:mm:ss'));  // 2019/08/28(水) 01:05:07

// フォーマットが英語表記の場合は、事前にロケールを変更します
moment.locale("en");
const date2 = moment("Fri Aug 28 01:05:07 am 2019", "ddd MMM DD HH:mm:ss a YYYY");
console.log(moment(date2).format('YYYY/MM/DD(ddd) HH:mm:ss'));  // 2019/08/28(Wed) 01:05:07

【加減算(add、subtract)】

// 加算
console.log(moment('2019-08-30').add(2, 'days').format('YYYY年MM月DD日(ddd)'));  // 2019年09月01日(日)

// 減算
console.log(moment('2019-08-01').subtract(2, 'days').format('YYYY年MM月DD日(ddd)'));  // 2019年07月30日(火)

// 加減算する値は、オブジェクトで指定可能です(x年xヶ月x日後など)
console.log(moment('2019-08-30').add({year :2, month :3, day :5}}).format('YYYY年MM月DD日(ddd)'));  // 2021年12月05日(日)

【開始・終了(startOf、endOf)】

// 年の開始の日
console.log(moment('2019-09-04').startOf('year').format('YYYY/MM/DD(ddd) HH:mm:ss'));  // 2019/01/01(火) 00:00:00

// 週の最後の日
console.log(moment('2019-09-04').endOf('week').format('YYYY/MM/DD(ddd) HH:mm:ss'));  // 2019/09/07(土) 23:59:59

【月の日数(daysInMonth)】

// 月の日数
console.log(moment("2020-02", "YYYY-MM").daysInMonth()) // 29

// 月末の日を取得しても同じです
console.log(moment("2020-02", "YYYY-MM").endOf('month').get('date'));  // 29

【期間内判定(isBetween)】

// 期間内の場合、trueを返します(デフォルトでは、指定した開始、終了と一致する場合、falseを返します)
console.log(moment('2019-10-19').isBetween('2019-10-19', '2019-10-25'));  // false
console.log(moment('2019-10-20').isBetween('2019-10-19', '2019-10-25'));  // true
console.log(moment('2019-10-25').isBetween('2019-10-19', '2019-10-25'));  // false

// 第3パラメータで判定基準(年、月など)を指定できます
console.log(moment('2019-10-20').isBetween('2019-09-19', '2020-11-25', 'month'));  // true
console.log(moment('2019-10-20').isBetween('2019-09-19', '2020-11-25', 'year'));  // false

// 第4パラメータで、指定した開始、終了を含む「[]」、含まない「()」を指定できます
console.log(moment('2019-10-19').isBetween('2019-10-19', '2019-11-25', null, '[]'));  // true
console.log(moment('2019-10-19').isBetween('2019-10-19', '2019-11-25', null, '(]'));  // false

【うるう年判定(isLeapYear)】

// うるう年の場合、trueを返します
console.log(moment("2020", "YYYY").isLeapYear());  // true
console.log(moment("2019", "YYYY").isLeapYear());  // false

【オブジェクトのコピー(clone)】

// 値渡しでコピーされます(参照渡しではないです)
const date1 = moment('2019-08-28 01:05:07');
const date2 = date1.clone();
// コピー元を更新
console.log(date1.year(2020).format('YYYY/MM/DD(ddd) HH:mm:ss'));  // 2020/08/28(Fri) 01:05:07
// コピー先は変更されない
console.log(date2.format('YYYY/MM/DD(ddd) HH:mm:ss'));  // 2019/08/28(Wed) 01:05:07

【ロケールをカスタマイズする】

ロケールを新たに定義して、カスタマイズすることが可能です。
// ロケール「ja」を継承して新規ロケール「my_ja」を定義
moment.defineLocale('my_ja', {
  parentLocale: 'ja',
});

// 定義を更新
moment.updateLocale('my_ja', {
  // 月(MMMM:1月、2月・・・)の定義を旧暦の月で更新
  months : ["睦月", "如月", "弥生", "卯月", "皐月", "水無月", "文月", "葉月", "長月", "神無月", "霜月", "師走"],
  // 週の最初を月曜日に更新
  week : { dow: 1 }
});

// 「my_ja」にロケールを変更
moment.locale("my_ja");

// 9月を旧暦で、周の最初を表示
console.log(moment('2019-09-04').startOf('week').format('YYYY年 MMMM DD日(ddd)'));  // 2019年 長月 02日(月)

// 2番目の引数としてnullを渡すと、定義したロケールを削除します
moment.updateLocale('my_ja', null);


日付時刻処理のライブラリは他に「dayjs」、「date-fns」、「luxon」などありますが、
「moment」は一番人気のライブラリなので、ぜひ使ってみてください。

,

2019年9月27日金曜日

ワード、エクセル、パワーポイントファイル内に貼り付けられている画像を保存する。

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

「複数ある画像ファイルをエクセルに貼り付けたものが素材画像として送られてきて困った!」
「このパワーポイントに貼り付けられたロゴを保存できたらいいんだけどなあ……」
こんな風にエクセルやワードやパワーポイントに貼り付けられた画像を書き出したい、しかし方法がわからないという風に悩んだことありませんか?
今回はエクセル、ワード、パワーポイントに貼り付けられた画像の保存方法を解説します。

保存したい画像が少なめの場合は「図として保存」という機能を使うと簡単です。
保存したい貼り付けられてる画像の上で右クリックします。 出てきたメニューから「図として保存」を選択しましょう!
保存名、フォルダ、形式を選択し、保存ボタンを押せば保存完了です。


また、保存したい画像が数枚ある場合は以下の方法が便利です。
(なお、下記の手順はMacOSでの手順です)
まず、保存したい画像のあるファイルを別名コピーします。ファイルの名はどのようなものでも大丈夫です。
コピーしたファイルの名前を変更して拡張子をzipにします。(警告メッセージが出ますが「".zip"を使用」を押してください)
こうして出来上がったzipを解凍し、
解凍したファイル内に、元のファイルごとに作成されるディレクトリがあり(*1)、その中の『media』というディレクトリに画像ファイルがまとめられています。

*1)
「元ファイルがエクセルの場合は『xl』」、
「元ファイルがワードの場合は『word』」、
「元ファイルがパワーポイントの場合『ppt』」

しかし、稀にアーカイブユーティリティ(Macの場合ダブルクリックでzip解凍するとこのソフトで解凍してくれます)で解凍すると「.cpgz」という拡張子の謎のファイルが出来てしまいます。
cpgz形式のファイルをダブルクリックで解凍すると、zip形式のファイルが出来、それを解凍するとcpgz形式のファイルが……といった具合に無限ループを繰り返し、正しく解凍されません。

 調べたところ、cpgz形式のファイルが出来てしまうzipファイルはターミナルで「unzip」というコマンドを使って解凍するときちんと解凍できる、とのことだったので試してみました。
(UNIXコマンドと普段戯れていない私のような非エンジニアの方々は、デスクトップに解凍できないファイルを置いておくことを推奨します。【】の中のコマンドをC⌘でコピーして⌘Vでペーストすればなんとかなりますので……!)

デスクトップに解凍したいzipファイルを置いたので【cd Desktop/】とターミナルに入力し、エンターキーを押してデスクトップに遷移します。
(きちんと遷移したか不安な時は【ls】とターミナルに入力してみてください。 デスクトップに保存してあるファイル名がずらずらとターミナルに表示されるはずです!)
遷移後、【unzip (解凍できないファイル名).zip】(unzipとファイル名の間の半角スペースは消さないでください!)とターミナルに入力し、エンターキーを押すと処理が走るので少し待ちます。
少し待つと解凍されたファイルがデスクトップにできています! 手間はかかりましたが、どうにか保存できました!やったー!

zip解凍がうまくいかない場合にはターミナルでUNIXコマンドをぽちぽち打たなくてはいけないので、きちんとできてるのかが分かりにくいかったりと少しデザイナーにはハードルが高いですが、これら二つの方法でどうにか保存できます!
困った際は試してみてください!

, ,

2019年9月25日水曜日

ASP.NetとSqlServerでローカル環境を作ってみる。


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

最近ASP.Netを主に触れているのですが、いつもテストサーバー(実際のWindowsServerのIIS上)で動作確認をしていました。が、いまさらながらローカル環境を作ってみようと思い、Dockerとwindows IISを利用して作ってみました。
基本的なプロジェクトの構成としてはローカルIISのwebサーバーとDockerの仮想DBとのデータのやり取りを実現するという内容になります。
ちなみに開発はweb画面からのアクセスではなくAPI開発で、下記のようにJSONでHTTPリクエストを行うという流れで今回やりました。

■環境

webサーバー:windows 10 IIS
DB:SqlServer(Docker)

■ローカル(winsows10)のIISを有効化する

まずはローカルのIISを有効化されているか確認する必要があります。
有効化されていない場合は下記手順で有効化します。
  1. ①コントロールパネルを開く
  2. ②プログラムを選択する
  3. ③「プログラムと機能」のWindowsの機能の有効化または無効化を選択
  4. ④「Windowsの機能」の一覧のインターネットインフォメーションサービスにチェックを入れてOKを選択
  5. ⑤デスクトップ左下のWindowsボタンを右クリック⇒コンピュータの管理を選択選択
  6. ⑥サービスとアプリケーションに「IISマネージャー」があることを確認する
  7. ⑦ブラウザにhttp://localhost/ とURLを入力し、Enterを押すと以下画面が見れる

■DockerでローカルDB環境を作る

※Dockerが既にインストールされていることが前提となります
まずはdockerを起動しておきます。
$ docker-machine start

次にdocker-compose.ymlファイルを作成し、使うDBの内容を記載します。
今回はSqlServerを使って開発したいので、下記のような記載になります。

・docker-compose.yml

version: '3'
services:
  db:
    environment:
      - MSSQL_SA_PASSWORD=password
      - ACCEPT_EULA=Y
      - MSSQL_PID=Developer
    image: microsoft/mssql-server-linux:2017-latest
    ports:
      - "1433:1433"
    volumes:
      - ./mssql/:/c/var/opt/mssql/

あとは、このdocker-compose.ymlファイルがあるディレクトリで
$ docker-compose up -d
で仮想ローカルDBを作成します。
あとはSSMS等のツールを利用し、テーブル作成等細かな設定を行います。

■IISのルートディレクトリにビルドしたファイルを設置する

visual studioでビルドしたファイルを下記IISのルートディレクトリに設置します。
C:\inetpub\wwwroot

■IISのサイトを作成する

デスクトップ左下のWindowsボタンを右クリック⇒コンピュータの管理を選択し
サービスとアプリケーションの「IISマネージャー」を選択します。

※サイトの作成方法についてはいろいろなサイトで説明があるのでここでは割愛します。

■IISサイトのサービスを開始する

サイトの「Default Web Site」で右クリック→「Webサイトの管理」→「開始」をクリックしてサイトを起動します。

あとは、作成したサイトにPOSTMAN等のツールを利用して、HTTPリクエストを投げて、正常にHTTPレスポンスが返ってくればローカル環境完成となります。

細かな部分は省いたのでかなりざっくりとした記載になってしまいましたが、ローカルIISのwebサーバーとDockerの仮想DBでローカル環境作る場合に参考になればと思います。


,

2019年9月4日水曜日

CSSで「要素が空の時の擬似セレクタ」を使う。

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

先日webシステムのコーディングをする際に、「ここの要素の中身が空のときにCSSの内容を変えたいんだよなー」と考えることがありました。
jQueryを書けばいいのですがCSS3だけで出来たらいいなー、CSS3の擬似セレクタってなんでも出来そうだし……(雑なイメージ)と思ったので、できるか調べた所「要素が空の時の擬似セレクタ」が二つも見つかったので「要素が空の時の擬似セレクタ」のあれこれのメモ書き記事です。

※このブログ執筆時(2019/09/02)時点での情報です。



Mozilla Developer Centerなどで調べた所、要素が空の時の時のみ適用されるセレクタには「:empty」「:blank」の二種類があると分かりました。

:empty」は完全に要素の中が空(スペース、改行もない状態)な時のみ適用される(コメントアウトした文字は要素中に入っていても大丈夫なようです)擬似クラスで、
:blank」はスペースと改行以外の要素がない場合適用される擬似クラスであるという違いがあるそうです。

:empty」は改行やスペースがあると使えないのか……。ちょっと不便。
少しエンジニアさんに説明するの面倒だし「:blank」使おうかなと思ったのですが、まだ主要ブラウザで未実装なようです。悔しい……。
つまり現在時点では「:empty」を使わざるをえないわけですね。

See the Pen ZEzXbEN by sato (@officekoma_sato) on CodePen.


試しに「:empty」を使ってみました。(わかりにくいデモですが、最初の<div>のみ「:empty」を適用しています。
コメントアウトしている所に何か要素を入れると「:empty」を適用していない下の<div>で括った部分と背景色が同じになります。)


かなり扱いが難しいのでなかなか使いにくいですが、もしCSS3だけで要素の中身が空の時のみCSSの内容を変えたい時は擬似クラス「:empty」を思い出してください。

2019年8月29日木曜日

ASP.NETのweb.configで定数設定する

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

個人的に最近はASP.NETを使った開発を多くやっているのですが、そこでC#プログラム内の定数を外部から変更したいという要望がありました。
基本的にプログラム内の定数はビルドを行わない限り変更することはできませんが、プロジェクト内のweb.configというファイルに値を設定することにより、いちいちビルドをしなくても値を変更できるということで、その設定をやってみたのでご紹介したいと思います。

■web.configに値を記載する

まずweb.configに設定したい値を下記のようにappSettingsセクションを追記します。
keyに変数名、valueに値というように記述します
  <appSettings>
    <add key="name" value="apple" />
    <add key="count" value="3" />
  </appSettings>
設定は上記の記載だけで完了です。形式もkey=valueの記載なのでわかりやすく簡単ですね。

■プログラム内でweb.configの設定値を呼び出す

ConfigurationManagerクラスを使う必要があるので、まずusing設定を行います。
using System.Configuration;

※上記追記だけで読込ができない場合があるので、その場合は下記のようにプロジェクトの参照から追加してみてください。

【参照追加方法】

  1. ①「ソリューションエクスプローラー」→「参照」右クリック→「参照の追加」
  2. ②アセンブリ:フレームワーク内にある「System.Configuration」にチェックを入れて「OK」

ここまで整ったら後は設定を呼ぶだけです。
string fruitsName = ConfigurationManager.AppSettings["name"];
int count = int.Parse(ConfigurationManager.AppSettings["count"]);
ちなみにweb.configの設定値は常にstringなので、数値を使いたい場合は上記のようにint型にParseする必要があります。

上記のようにとても簡単に設定できました。
例えば下記のようにDBの接続先情報等を設定しておいて、接続先情報だけ変更したい場合にはいちいちビルドする必要がないので楽だと思います。
  <appSettings>
    <add key="connectionDB" value="Data Source=XX.XX.XX.XX; Initial Catalog=testdb; User Id=userid; Password=password; "/>
  </appSettings>

※web.config内の値を変更しても、すぐにプログラム内に適用される訳では無いのでご注意ください。
IIS的には、接続が無くなった時点で、自動でサイト再起動が発生し、値が適用される事になります。


,

Xcode10.2以降にアップデート後にクラッシュ「This coder requires that replaced objects be returned from initWithCoder」が発生した時の対応方法


こんにちは、オフィス狛 モバイル開発担当 Aika-yuy です。
今回の投稿は、 Xcode10.2以降にアップデートした後にクラッシュする機能があったので、
解決方法を備忘録として残しておこうと思います。

・発生した事象

・Xcode10.1で作られていたアプリがあり、改修を行う事になったのですが、Xcodeのアップデータがあったので、10.2へアップデートした。
・ビルドは問題なく通ったが、一部の機能を実行すると、アプリが落ちるようになった。

という事で、修正前のコードです。こちらを実行したところ落ちてしまい下記のエラーが出ました。

 public init(_ coder: NSCoder? = nil) {
        if let coder = coder {
            super.init(coder: coder)!
        }
}
public override init(frame:CGRect) {
        super.init(frame:frame)
        self.initialize()
}

Terminating app due to uncaught exception 'NSGenericException', reason: 'This coder requires that replaced objects be returned from initWithCoder

・前提条件

・Objective-CとSwiftでは、呼び出し時の型が違う。
・型を合わせる為に、「@objc」を付ける

・エラーになる理由

Xcode10.2になって、コンパイラーの仕様が変更され、 今まで@objc推論が行われていたが、行われなくなったのでエラーになってしまったようです。

・対応方法

「@objc」が自動で付かなくなったので、必要ならば明示的に付けないと、Objective-CからSwiftをCallする時に落ちてしまいます。
該当する箇所を修正しました。
@objc public init(_ coder: NSCoder? = nil) {
        if let coder = coder {
            super.init(coder: coder)!
        }
}
@objc public override init(frame:CGRect) {
        super.init(frame:frame)
        self.initialize()
}

@objcをつけることで、使用することができました。


, ,

2019年8月16日金曜日

node.jsでの日付時刻処理(moment.jsを使ってみる)


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

node.js(JavaScript)では日付時刻の扱いが難しく、特に文字列の時刻などを比較や計算するときはいつも調べているので、備忘録を兼ねてブログに書いておこうと思います。
また、日付時刻操作ではmoment.jsという便利なライブラリがあるので、同じ処理をmoment.jsでも書いてみます。

① 時刻(文字列)の比較と差分

【ライブラリを使用しない場合】
// Dateオブジェクトを生成
const dateTime1 = new Date('2019-08-01 09:00');
const dateTime2 = new Date('2019-08-01 14:00');

// 大小確認
if (dateTime1 < dateTime2) {
    処理;
}

// 一致確認の場合は「getTime()」でミリ秒形式に変換する
if (dateTime1.getTime() === dateTime2.getTime()) {
    処理;
}

// 差分算出の場合も「getTime()」でミリ秒形式に変換して計算、結果のミリ秒を欲しい単位に計算する
const diffTime = dateTime2.getTime() - dateTime1.getTime(); // ミリ秒で差分を算出
const diffSecond = Math.floor(diffTime / 1000 / 60); // 結果のミリ秒を分に変換
console.log(diffSecond); // 300(分)


【moment.jsを使用した場合】
const moment = require('moment');

// momentオブジェクトを生成
const dateTime1 = moment('2019-08-01 09:00');
const dateTime2 = moment('2019-08-01 14:00');

// 比較(<:isBefore  >:isAfter  <=:isSameOrBefore  >=:isSameOrAfter)
if (moment(dateTime1).isBefore(dateTime2)) {
    処理;
}

// 一致(第2パラメータで年から比較したい単位までを指定可能)
if (moment(dateTime1).isSame(dateTime2, 'minute')) { // 年~分までの一致確認
    処理;
}

// 差分(第2パラメータで取得したい差分の単位を指定可能)
const diffTime = dateTime2.diff(dateTime1, 'minutes'); // 差分を分で取得
console.log(diffTime); // 300(分)


② UTCからJSTに変換

【ライブラリを使用しない場合】
文字列の日時はUTCの時間と仮定しています。
// Dateオブジェクトを生成
const dateTime1 = new Date('2019-08-01 09:00'); // UTCの日時と仮定しています
// ミリ秒にして9時間(32,400,000ミリ秒)足す
dateTime1.setTime(dateTime1.getTime() + 1000 * 60 * 60 * 9);
console.log(dateTime1);  // Thu Aug 01 2019 18:00:00 GMT+0900 (日本標準時)


タイムゾーンを扱う場合は、「moment-timezone」を使います。
【moment.jsを使用した場合】
const momentTimezone = require('moment-timezone');

// UTCのmomentオブジェクトを生成
const dateTimeUtc = momentTimezone.tz('2019-08-01 09:00:11', 'UTC');
// JSTに変換(フォーマットも併せて変換)
const dateTimeJst = momentTimezone(dateTimeUtc).tz('Asia/Tokyo').format('YYYY/MM/DD HH:mm:ss');
console.log(dateTimeJst); // 2019/08/01 18:00:11



ご紹介したmoment.jsの機能はほんの一部ですので、日付時刻操作で悩んだらぜひ使ってみてください。


, ,

2019年8月3日土曜日

テンプレート内のclass属性を動的に変える(Angular)


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

今回はAngularの軽い小ネタを紹介します。

テンプレート(html)内のclass属性を動的に変えたい、という事はよくあると思います。
特に、入力項目にエラーがあった場合のエラーメッセージ、などがそうですね。

(1)ngIfを使った方法

まずは、ngIfを使った方法から。

<div *ngIf="loginIdInvalid; else loginIdValidBlock" class="form-label alert alert-danger">
使用出来ないログインIDです。
</div>
<ng-template #loginIdValidBlock>
 <div class="form-label correct correct-info">
 使用可能なログインIDです。
 </div>
</ng-template>

「loginIdInvalid」がコンポーネント側(TypeScript側)で定義している変数で、エラーの場合、trueになると思ってください。
これをclass属性を動的に変えているか、と言われると微妙ですが、結果的にclass属性を使い分けている事になります。
困った時の「ngIf」ってやつですね。

(2)[class.xxxxx]を使った場合

次は、[class.xxxxx]を使った場合です。

<div class= "form-label"
       [class.alert]="loginIdInvalid"
       [class.alert-danger]="loginIdInvalid" 
       [class.correct]="!loginIdInvalid"
       [class.correct-info]="!loginIdInvalid">
{{ message }}
</div>

[class.xxxxx]の「xxxxx」にクラス名を指定し、そのクラスを使用するかどうかが、イコールの後ろの条件式(trueかfalseが返却されるもの)となります。
より簡潔にする為、メッセージは変数にして、コンポーネント側(TypeScript側)で設定するようにしました。
「form-label」は正常でもエラーでも共通して使うので、htmlのclass属性として切り出しました。
ちょっと冗長な気もしますが、良く使用されている手法かと思います。

(3)[ngClass]を使った場合

続いて、[ngClass]を使った場合です。

<div class= "form-label"
       [ngClass]="{'alert alert-danger' : loginIdInvalid, correct correct-info' : !loginIdInvalid}">
{{ message }}
</div>

[ngClass]は、クラスを複数一気に指定できます。連想配列形式で「{'xxxx' : '1111', 'yyyy' : '2222'}」のように設定します。
個人的には一番良く使うのですが、正直、こうして他と比べると、ちょっと見難いと言うか、テンプレート側が複雑になるのは、あまり良くないなぁ、と感じています。

(4)[class]を使った場合

最後に、[class]を使った場合です。
[class]を使う場合、コンポーネント側(TypeScript側)にも少しロジックを書く必要があります。
まずはテンプレート側の設定です。

<div [class]="loginIdClass">
{{ message }}
</div>

続いて、コンポーネント側(TypeScript側)の設定です。
get loginIdClass() {
  return this.loginIdInvalid
    ? 'form-label alert alert-danger'
    : 'form-label correct correct-info';
}

テンプレート側はだいぶスッキリしました。実はこの方式は個人的にはあまり使っていなくて、
今回、ブログにする為に使ってみたのですが、割と良いですね。
ただ、htmlのclassタグを同時に使用出来ないので、共通的のクラス(form-label)の記述を敢えて複数回記載することの面倒さと、本来はhtml、つまり画面表示に関わるclass属性内の文字列をコンポーネント側(TypeScript側)に記載している、と言う気持ち悪さは少し残ります。

以上となります。

それでは、良いAngularライフを!


2019年8月2日金曜日

Illustratorのトレース機能で出来るだけ楽にロゴをトレスする。


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

今回は、Illustratorを使ってjpgやpngのロゴなどの簡単な画像を簡単にトレースする方法をご紹介したいと思います。(Illustrator使ってる方はみんな知ってる気でいたんですが意外と知らない方も多いようなので)
調節は必要ですが、ペンツールでトレースするよりも早くトレースできるので時短になりますよ!


今回使用する画像です。(架空のサービスのロゴマークです。)

まずこの画像をイラレで読み込みます。
画像を選択した後、コントロールパネルの「画像トレース」をクリックします。


トレース処理が終了後、コントロールパネルの「プリセット」を変えたりしていい感じのプリセットを探します。
一番元画像に近いプリセットを選択後、コントロールパネルの「拡張」ボタンをクリックすればパスにしてくれます!

単純な画像ならそのまま調整せずに綺麗にトレースしてくれます。


複雑な画像で「画像トレース」機能を使う場合は「ウインドウ」>「画像トレース」を選択して画像トレースパネルで調整しましょう!仕上がりを見ながら作業できるため便利です。


昔フォトショップで描いた鶏の絵をトレースしてみました!
絶対ペンツールでトレースしたくないような無駄にテクスチャなど使った絵を細かく再現してくれましたー!ありがたすぎる。


この方法を使うとアナログのラフスケッチを簡単にパス化したり自分で撮影した写真をトレースしたものを元にアイコンを作れたりして便利です!ぜひこの機能を使ってみてください!

2019年7月30日火曜日

踏み台サーバー(EC2)経由でSQLServer(RDS)にSSH接続する方法(ポートフォワーディング)・MacOS編


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

以前、弊社のメンバーが、踏み台サーバー(EC2)経由でSQLServer(RDS)にSSH接続する方法(ポートフォワーディング)・Windows編という記事を書きました。

今回は、MacOSでのやり方について記載しようと思います。
(「ポートフォワーディングとは?」の説明については、Windows版の記事をご覧ください。)
今回の接続イメージは下図の通りです。


前回のWindows版は、踏み台サーバーから直接データベースへ接続していましたが、今回は、踏み台からさらにWebサーバーを経由してデータベースへ接続する事にします。
手順は以下となります。
(1)Webサーバー(EC2)へポートフォワーディングする設定で、踏み台サーバー(EC2)へSSH接続
(2)データベース(RDS)へポートフォワーディングする設定で、(1)のポートフォワーディング状態でSSH接続
(3)上記までのポートフォワーディングした状態でSQLServerにログインしDB操作
※各SSH接続で使う鍵はローカルに持っている事を前提とします。

手順(文章)だけだと、分かり難いですね・・・・ということで、具体的な手順を書いていきます。


(1)Webサーバー(EC2)へポートフォワーディングする設定で、踏み台サーバー(EC2)へSSH接続

まずは、踏み台経由でWebサーバー(EC2)へ接続します。
イメージ図で言うと、下記の赤字の「(1)」の部分になります。

まず、mac標準ツールのターミナルを開き、以下のコマンドを入力し、実行します。
ssh -L 2200:10.0.1.1:22 -i key-hoge-bastion.pem bastionuser@xx.yy.zz.001

それぞれ、説明をしていきます。

「ssh -L 2200:10.0.1.1:22」:
ローカルとリモートを繋ぐ設定になります。この場合、最終的に「localhost:2200」が「10.0.1.1:22」へと繋がる事になります。
localhost側のポートは、使用していないものであれば、何でも構いません。
最終的に接続する先のIP(今回は「10.0.1.1」)はプライベートIPである事に気を付けてください。

「-i key-hoge-bastion.pem bastionuser@xx.yy.zz.001」:
これは踏み台への接続設定です。「-i」の後に、踏み台の鍵を指定し、その後ろに、「接続ユーザー名@パブリックIP or ドメイン」を指定します。

コマンド実行後、ターミナルは踏み台に繋がった状態になっています。
ですので、さらにポートフォワードする場合には、ターミナルを新たに起動する必要があります。

※「ssh -fNL」とする事で、バックグラウンドでコマンドが実行されるので、そのまま同じターミナルを使う事が可能です。ですが、今、どこにポートフォワードしているのか分からなく可能性がありますし、タスクをkillするのも面倒だったりするので、最初の内は、ポートフォワードする単位でターミナルを開くことをお勧めします。


(2)データベース(RDS)へポートフォワーディングする設定で、(1)のポートフォワーディング状態でSSH接続

続いて、Webサーバー(EC2)経由でデータベース(RDS)へ接続します。
イメージ図で言うと、下記の青字の「(2)」の部分になります。


ssh -L 1433:hoge.z6vbrfgt5st.ap-northeast-1.rds.amazonaws.com:1433 -i key-hoge-web.pem webuser@localhost -p 2200

それぞれ、説明をしていきます。

「ssh -L 1433:hoge.z6vbrfgt5st.ap-northeast-1.rds.amazonaws.com:1433」:
ローカルとリモートを繋ぐ設定になります。この場合、最終的に「localhost:1433」がRDSのエンドポイント「hoge.z6vbrfgt5st.ap-northeast-1.rds.amazonaws.com:1433」へと繋がる事になります。
localhost側のポートは、使用していないものであれば、何でも構いませんが、後のことを考えて、1433にしました。

「-i key-hoge-web.pem webuser@localhost -p 2200」:
これは、Webサーバー(EC2)への接続設定です。「-i」の後に、Webサーバー(EC2)の鍵を指定し、その後ろに、「接続ユーザー名@localhost -p (1)で設定したポート番号」を指定します。
ポイントは、接続するのはあくまで「ポートフォワード設定をしたlocalhost」と言うことです。

コマンド実行後、ターミナルはWebサーバー(EC2)に繋がった状態になっています。
今回はこれ以上はポートフォワード設定を追加しないので、ターミナルはこのままにしておきます。


(3)上記までのポートフォワーディングした状態でSQLServerにログインしDB操作

ここまで来たら後はデータベースへの接続のみです。
今回は、Azure Data Studioを使って、接続します。

すでに、「localhost:1433」は、「踏み台→Webサーバー」を経由して、「endpoint:hoge.z6vbrfgt5st.ap-northeast-1.rds.amazonaws.com」に繋がっている状態なので・・・


上記のように、接続先を「localhost」(Azure Data Studioはデフォルト1433ポートを使用するので、1433は省略可能)として、ユーザー名とパスワードを入力すれば、無事に接続出来ると思います。

以上となります。今回はSQLServerに限定しましたが、他のDBサーバーでも、基本的な流れは変わらないと思います。


, , , , ,

2019年7月29日月曜日

C#でListの多次元化をやってみる。


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

C#で開発中に、ある処理で値を多次元で持たせる必要があり、最初は配列で多次元化を考えたのですが、処理内容的にListを使っているので、Listで多次元化を行ってみました。
php等で配列の多次元はよく行いますが、C#でかつListで行ったことがなかったので、いろいろ調べつつ、自分への備忘録も兼ねて今回記載しようと思います。

まず基本的なListでの動きですが、通常Listに値をセットする場合等は以下のように記述すると思います。
var list = new List<string>();
list.Add("みかん");
list.Add("りんご");
list.Add("バナナ");

上記は1次元Listなので、そのまま
list[0]=>"みかん"
list[1]=>"りんご"
list[2]=>"バナナ"
というような感じで値が入ります。

で、多次元Listですが、基本的には2次元Listができればあとは応用という感じなので、一旦わかりやすく2次元Listを作ってみたいと思います。
例えば以下のようなphpの多次元配列のような感じでデータを持ちたい場合があるとします。
    [0]=>{
        [0]=>"みかん"
        [1]=>"orange"
    }
    [1]=>{
        [0]=>"りんご"
        [1]=> "apple"
    }
    [2]=>{
        [0]=>"バナナ"
        [1]=> "banana"
    }

上記を2次元Listにするには以下のように記述します。
var list = new List<List<string>>()
list.Add(new List<string>());
list[0].Add("みかん");
list[0].Add("orange");
list[1].Add("りんご");
list[1].Add("apple");
list[2].Add("バナナ");
list[2].Add("banana");
結果上記でいくと例えば
list[0][1]=>"orange"
list[2][0]=>"バナナ"
がデータとしてセットされていることになります。

Listの<>内にはクラスも入れる事ができるということなので、多次元化ができるということらしいですね。
なので、3次元、4次元としたい場合(そんなにないと思いますが)、さらにListの<>を入れ子にしていけば可能になります。

あと1次元Listと違って少し注意したいのが、Listに追加するときにはListの中もnewしてインスタンスする必要があります。自分はここで少しハマりました。。

以上のような感じで、Listを多次元化して処理を行う場合に参考になればと思います。

2019年7月26日金曜日

ローカルにあるhtmlファイルでChromeの拡張機能を使う設定。

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

今回は、ほんの小技なのですが、ローカルにあるhtmlファイルをChromeで開いた際に、拡張機能を使う方法(設定)です。

何の設定もしていない状態だと、Chromeでローカルファイルを開いた時には拡張機能が使えないので、少々不便です。
私はコーディング中のページ全体を拡張機能でスクリーンショットを撮りたい時があるので、結構困っていました。

コーディング中でも拡張機能が使いたいと思う方は、私以外にもいらっしゃると思いますので設定方法をご紹介します。

拡張機能のアイコンバーを右クリックして、「拡張機能を管理」をクリックし、詳細設定画面に遷移します。

「ファイルの URL へのアクセスを許可する」という項目があるのでONにするとローカルファイルをChromeで開いた際も拡張機能を使えるようになります。

ただ、セキュリティ的に不安のある拡張機能では使わないようにした方が良いかと思います。
(ですので「ファイルの URL へのアクセスを許可する」をONする際は、拡張機能のセキュリティ要件を確認し、慎重に判断してください)

2019年7月5日金曜日

Amazon ConnectとLambdaで電話をかける ③Lambda関数の作成と、④実行


こんにちは、オフィス狛 モバイル開発担当 Aika-yuy です。
今回の投稿は、 AmazonConnectとLambdaで任意の番号に電話をかける方法をご紹介します。









AmazonConnectはコールセンターをメインとして、簡易に作成できるというものなので、
受電する記事をよく見かけますが、架電方法の記事は少なかったので記事にしました。




難しそうに見えますが、簡単で短時間で作成できますので試してみてください。
こちらの4ステップで紹介していきます。

①Amazon Connectで電話番号、お問い合わせフローの作成
②IAMでロールを作成
③Lambda関数の作成← 今回はこちら
④実行!!← 今回はこちら

 

③Lambda関数の作成

今回はいよいよ、Lambda関数からスマフォに電話をかけてみます! とっても簡単なので、あと少し頑張りましょう。

〜必要なもの〜

1、①で作成した電話番号
2、①で作成したお問い合わせフローのインスタンスID、リージョン名、コンタクトフローID
3、②で作成した、ロール
4、任意の電話番号


AWSにログイン後、Lambdaを選択します。
ページ遷移後、関数の作成ボタンを押してください。
下の画像のように入力選択していきます。
1.関数名に任意の関数名をつけてください。
2.実行ロールを既存のロールを使用するを選択してください。
3.②IAMでロールを作成で、作成したロールを選択してください。
※作成したロールがすぐに反映されないことがあるので、作成したロールが見つからない場合は少し時間をおいてからもう一度試してみてください。
4.関数を作成ボタンを押す

それではいよいよ関数を作っていきましょう!

exports.handler = (event) => {
     
    // import entire SDK
    var AWS = require('aws-sdk');
 
    var connect;
    // create amazon connect object
    // ①で作成したお問い合わせフローのリージョン名を入力してください。
    connect = new AWS.Connect({apiVersion: '2017-08-08', region:"ap-northeast-1"});
 
    // create API request parameter
    var params = {
      // ①で作成したお問い合わせフローのコンタクトフローID
      ContactFlowId: "XXXXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
      // 今回かける先の電話番号
      DestinationPhoneNumber: "XXXXXXXXXXXXXX",
      // ①で作成したお問い合わせフローのインスタンスID
      InstanceId: "XXXXXXXXXXXXXXXXXXXXXX",
      // かける元の電話番号(作成した電話番号)
      SourcePhoneNumber: "+8XXXXXXXXXXX"
    };
  
  // call API with parameter
  console.log("開始!!");
  var calling = connect.startOutboundVoiceContact(params, function(err, data) {
    if (err) {
      console.log(err);
    } else {
      console.log("終了!!電話を切る");
    }
  });
  return calling;
};


これで完成です!

④実行!!

それではいよいよ電話をかけてみます。
Lambdaのページの一番上、右にあるテストボタンを押してください。

任意の電話番号に、電話がかかれば成功です。

意外と簡単に作成できましたね。
アレクサから電話したり色々できそうで、夢が広がりますね。
次回はアプリ(ios)から電話をかけるをやっていきます。

, , ,

2019年6月30日日曜日

AngularのdetectChanges()で「ViewDestroyedError: Attempt to use a destroyed」が発生した時の対応方法。


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

私の担当がいつの間にかAngular専任になっていますが・・・・今回もやっぱりAngularネタです。

Angularで、NgRxなどでAPIから値を取得した場合、そのままView(template・html)側にデータを流し込んであげれば、取得したデータは問題なく表示されます。
ところが、component(TypeScript)側で取得したデータをstore.selectで取得すると、View(template・html)側に表示データが反映されない場合があります。

うーん、言葉だけで説明するのが難しい。
この辺はいつかNgRxの使い方とかで詳しく説明したいですが、前者の場合、ストリーム(川の流れ)は繋ぎっぱなしですが、
後者は、一度ストリーム(川の流れ)から、データ取っているので、流れが止まっている、という事ですね。

強制的に画面に反映させる為には、「changeDetectorRef の detectChanges() 」メソッドを使います。

では、ここで使い方の例を一つ。
import { Component, ChangeDetectorRef, OnInit } from '@angular/core';
// (中略)
export class HogeKomaComponent implements OnInit {
// (中略)
  constructor(
    public store: Store,
    private changeDetectorRef: ChangeDetectorRef
  ) {}
 
  ngOnInit() {
    this.hogeKomaSubscription.push(
      this.store
        .select(fromHoge.getKomaList)
        .pipe(skip(1))
        .subscribe(result => {
            //
            // ここで取得したデータを編集し、Template側の変数へ再設定(これだけだと、画面は更新されない)
            //
 
            // 画面の表示を更新する(これを実施する事で画面が更新される)
            this.changeDetectorRef.detectChanges();
    }));
 
    // データ取得
    this.store.dispatch(new KomaListActions.GetKomaList());
  }

で、ここからが本題なのですが、「detectChanges()」を使っていると、以下のようなエラーが発生する事があります。
    common.96cfb6ba445916612966.js:1 ERROR Error: ViewDestroyedError: Attempt to use a destroyed view: detectChanges
    at pm (main.35927d7a5d9b4fa62e08.js:1)
    at Object.m_ [as updateDirectives] (main.35927d7a5d9b4fa62e08.js:1)
    at jv (main.35927d7a5d9b4fa62e08.js:1)
    at D_ (main.35927d7a5d9b4fa62e08.js:1)
    at Object.i_ [as checkAndUpdateView] (main.35927d7a5d9b4fa62e08.js:1)
    at n.detectChanges (main.35927d7a5d9b4fa62e08.js:1)
    at e._next (10.ab643854ddb1ed074304.js:1)
    at e.__tryOrUnsub (main.35927d7a5d9b4fa62e08.js:1)
    at e.next (main.35927d7a5d9b4fa62e08.js:1)
    at e._next (main.35927d7a5d9b4fa62e08.js:1)

エラーメッセージは、
ViewDestroyedError: Attempt to use a destroyed view: detectChanges
という事ですが、要は、「画面表示を行おうとしたけど、もう対象のViewが存在しない」という事ですね。

こういう場合は、
  if (!this.changeDetectorRef['destroyed']) {
    this.changeDetectorRef.detectChanges();
  }

上記のように、既にViewが破棄されていないか確認する事で回避出来ます。

今回も内容の割には長くなってしまいました・・・・
(エラーメッセージと対応内容書けば、ある意味終了なんですけどね。)

こんな内容でも、誰かの役に立つ事を願って・・・・

では、より良いAngularライフを!