JavaScript (TypeScript) クラス、型の判定
class Foo {} class Bar extends Foo {} const foo = new Foo(); const bar = new Bar(); console.log(typeof foo); // -> 'object' console.log(foo.constructor === Foo); // -> true console.log(foo instanceof Foo); // -> true console.log(foo instanceof Object); // -> true console.log(bar.constructor === Foo); // -> false (継承元はわからない) console.log(bar.constructor === Bar); // -> true console.log(bar instanceof Foo); // -> true console.log(bar instanceof Bar); // -> true
これをこうした (Viewのリファクタ)
命名、フォルダ構造、Viewと機能(性質)をごっちゃにしないのは大切ですよ、という反省。
フォルダ構造
├── components │ ├── baz │ │ ├── abstract │ │ ├── a │ │ └── b │ ├── bazes-container │ ├── main │ │ └── bg │ ├── qux │ │ ├── abstract │ │ ├── a │ │ └── b │ ├── quxes-container │ └── utils.ts ├── container.ts └── services
<main> <!-- bg --> <main-bg> </main-bg> <!-- bazes --> <bazes-container> <baz-a ng-repeat="..."></baz-a> <baz-b ng-repeat="..."></baz-b> </bazes-container> <!-- quxes --> <quxes-container> <qux-a></qux-a> <qux-b ng-repeat="..."></qux-b> </quxes-container> </main>
変更前
フォルダ構造
├── components │ ├── a-baz │ ├── a-bazes-container │ ├── a-qux │ ├── bazes-container │ ├── main │ ├── b-baze │ ├── b-bazes-container │ ├── qux │ └── quxes-container ├── container.ts └── services
<main> <bazes-container> <!-- a(s) --> <a-bazes-container> <a-baz ng-repeat="..."></a-baz> <a-qux></a-qux> </a-bazes-container> <!-- b(s) --> <b-bazes-container> <b-baze ng-repeat="..."></b-baze> <quxes-container> <qux ng-repeat="..."></qux> </quxes-container> </b-bazes-container> </bazes-container> </main>
Hubotを使ってGoogle AnalyticsのデータをSlackに流す
1年ほど前にまとめたものです。
初botつくりです。
環境準備
(公式ページ通りですが)
hubot, coffee-script, yo, generator-hubotをグローバル環境にインストールします。
npm install -g hubot coffee-script yo generator-hubot
yoでベースをつくります。
yo hubot
Bot adapterをslack
にします。
? Bot adapter slack
起動
起動の前にHubot用のSlack Tokenが必要です。 SlackのIntegrationsからHubotを追加してトークンを取得してください。
起動は以下コマンドで。
HUBOT_SLACK_TOKEN=xoxb-1234-5678-91011-00e4dd ./bin/hubot --adapter slack
Slackでbotのステータスがオンラインになれば、ひとまず成功です。
いくつか試してみたい場合はscripts/example.coffee
が便利です。
例えば以下部分のコメントアウトをはずします。
robot.hear /badger/i, (msg) -> msg.send "Badgers? BADGERS? WE DON'T NEED NO STINKIN BADGERS"
badgerと話しかけると、返信してくれるようになります。
Herokuで動かす
Gitの初期化がまだの場合、git init
しておきます。
create
heroku create my-company-slackbot
config
heroku config:add HUBOT_HEROKU_KEEPALIVE_URL=https://my-company-slackbot.herokuapp.com heroku config:add HUBOT_SLACK_TOKEN=xoxb-1234-5678-91011-00e4dd
push
git push heroku master
これで完了です。
うまく動かない場合は、Procfileを確認してみてください。
web: bin/hubot -a slack
HUBOT_HEROKU_KEEPALIVE_URL
configで設定しているこの項目は、HubotをHeroku上で起動させ続けるために必要になります。
hubot-scripts/hubot-heroku-keepalive: A hubot script that keeps the hubot Heroko web dyno alive
Google Analyticsの情報を取得
GoogleのNode.js client libraryを利用します。
install
npm install googleapis --save
OAuth 2.0を利用した認証と各種APIの利用が可能です。 今回はサーバー上で認証をおこなうため、JWT (Service Tokens) を利用します。
Google Developersの設定
googleapis
を利用するためにGoogle Developersの設定をします。
Google Developers Console から以下2つの設定を行います。 (「プロジェクト」がない場合は、先に作成を済ませてください。)
1. APIを有効にする
[API Manager] –> [概要] からAnalytics APIを有効にします。
2. サービス アカウント キーを作成する
[API Manager] –> [認証情報] から新しいサービス アカウント キーを作成します。キーのタイプはJSONにします。
認証
まだAnalyticsにアクセスはできませんが、認証処理がうまくいくか先に確認してみます。
var google = require('googleapis'); var key = require('path/to/key.json'); var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, ['https://www.googleapis.com/auth/analytics'], null); jwtClient.authorize(function(err, tokens) { if (err) { console.log(err); return; } console.log(tokens) });
key.json
はアカウントキー作成時にダウンロードしたJSONファイルです。
認証処理がうまくいけば、tokensの情報が確認できます。
補足: Scopeについて
['https://www.googleapis.com/auth/analytics']
の部分には必要なScopeを設定します。
Google Analyticsを利用するだけならこのままで大丈夫です。
OAuth 2.0 Scopes for Google APIs | Google Identity Platform | Google Developers
Analyticsの設定
Analytics側の設定を確認します。
1. ビューIDの確認
[アナリティクス設定] –> [ビュー] –> [ビュー設定] から「ビュー ID」の確認をします。
2. メールアドレスの追加
[アナリティクス設定] –> [ビュー] –> [ユーザー管理] から「表示と分析」が可能なメールアドレスを追加します。
Developer Consoleから取得したJSONのclient_email
を利用します。
Analyticsのデータを取得
ここまで準備ができたら Core Reporting API を利用してデータの取得が可能です。
analytics.data.ga.get
メソッドを利用してデータのリクエストを行います。
var google = require('googleapis'); var key = require('path/to/key.json'); var jwtClient = new google.auth.JWT(key.client_email, null, key.private_key, ['https://www.googleapis.com/auth/analytics'], null); var analytics = google.analytics('v3'); var viewId = 'xxx'; // 設定画面で確認した「ビューID」 function authTask() { return new Promise((resolve, reject) => { jwtClient.authorize((err, result) => { if (err) { console.log('authTask/reject'); reject(err); } else { console.log('authTask/resolve'); resolve(result); } }); }); } function gaTask(token, viewId) { return new Promise((resolve, reject) => { analytics.data.ga.get({ 'ids': 'ga:' + viewId, 'start-date': '7daysAgo', 'end-date': 'today', 'metrics': 'ga:pageviews', 'dimensions': 'ga:pagePath', 'sort': '-ga:pageviews', 'access_token': token }, (err, result) => { if (err) { console.log('gaTask/reject'); reject(err); } else { console.log('gaTask/resolve'); resolve(result); } }); }); } module.exports = robot => { robot.respond(/hi$/i, msg => { authTask().then(result => { return gaTask(result.access_token, viewId); }) .then(result => { console.log(JSON.stringify(result)); }) .catch(err => { console.log(err); }); }); };
botに対してhiと話しかけると、過去7日間のページビューランキングを取得します。
ページビューランキング以外のデータもanalytics.data.ga.get
を利用して取得します。下記リンク先を参考にしてみてください。
- Core Reporting API - 一般的なクエリ | アナリティクス Core Reporting API | Google Developers
- Core Reporting API - リファレンス ガイド | アナリティクス Core Reporting API | Google Developers
- Dimensions & Metrics Explorer | アナリティクス Core Reporting API | Google Developers
その他
取得した情報を特定のルームに流す
robot.send({ room: 'roomname' }, message);
#roomname
のように#
をつけると、3系からはエラーになります。
自動実行させる
z-indexとスタック文脈
重なり順の制御をしているときに、スタック文脈というものを理解していなかったことに気づきました。 以下の記事がわかりやすかったです。MDNにはいつもお世話になります。ありがたい。
- z-index なしのスタック : デフォルトのスタック規則
- スタックとフロート : フロート要素の扱われ方
- フロート(浮遊)ブロックでは、積み重ね順が少し違います。フロートブロックは位置指定されていないブロックとされているブロックの間に置かれます:
- ルート要素の背景とボーダー
- 通常フローに乗る子孫要素。HTML 内の出現順
- 浮遊ブロック
- 通常フローに乗るインラインの子孫要素
- 位置指定された子孫要素。HTML 内の出現順
- z-indexの追加 : z-index を使ってデフォルトのスタックを変える
- スタックの文脈 : スタック文脈についての覚書
- スタック文脈の内部で、子要素は前に説明したルールに従って積み重なります。重要なのは、子要素の z-index 値は、その親要素に対してのみ意味を持つということです。スタック文脈は、その親のスタック文脈では不可分な一つの固まりとして扱われます。
- スタック文脈の例 1 : 2レベルの HTML 階層構造、最終レベルで z-index を使う
- スタック文脈の例 2 : 2レベルの HTML 階層構造、すべてのレベルで z-index を使う
- スタック文脈の例 3 : 3レベルの HTML 階層構造、2番めのレベルで z-index を使う
(WIP) Pub-Sub
(1年前に途中までまとめたもの。こっちに転載。)
AngularJS: Notifying about changes from services to controllers - codelord.net を参考にStoreっぽいものを実装しようとした時のメモ
// component class FooController { constructor( private $scope: ng.IScope, private NotifyingService: NotifyingService, ) { } public $onInit() { this.NotifyingService.subscribe(this.$scope, () => { this.doSomething(); }); } private doSomething() { // do something // } } const FooComponent: ng.IComponentOptions = { template: '...' controller: FooController, bindings: { // ... } }; // service const EVENT_NAME: string = 'notifying-service-event'; class NotifyingService { $data: any; constructor(private $rootScope: ng.IRootScopeService) { } public initialize() { // this.$data = new Data(); } public subscribe(scope: ng.IScope, callback: (any) => void) { const handler = this.$rootScope.$on(EVENT_NAME, callback); scope.$on('$destroy', handler); } public update(data, noti: boolean = true) { const prev = angular.copy(this.$data); angular.extend(this.$data, data); if (!angular.equals(prev, this.$data)) { if (noti) { this.notify(); } } } private notify() { this.$rootScope.$emit(EVENT_NAME); } } angular.module('app', []) .component('foo', FooComponent) .service('NotifyingService', NotifyingService);
$destroy
がある$emit
しか利用しない- eventの挙動は隠蔽されている
簡易版
angular.module('app').controller('TheCtrl', function(NotifyingService) { NotifyingService.subscribe(function somethingChanged() {}); }); angular.module('app').factory('NotifyingService', function() { var onChanges = []; return { subscribe: function subscribe(callback) { onChanges.push(callback); }, notify: function notify() { onChanges.forEach(function(cb) { cb(); }); } }; });
- 「変更した」というアクションに限定
$scope
,$rootScope
が不要- React • TodoMVC に似ている
- 複数のCtrlから利用するときに、個別リスナー解除ができない
実際にやろうとして失敗したこと
(構成)
(例: destory時の流れ)
- ui-viewがまだコンポーネント対応していなかった
- inputs/outputsにほうがシンプルでよかったかも
- storeの並列が一番無理があった
- 両方で同じようなデータを利用するとき
- 両方に持つのも、主従にするのも…
- 初期化のタイミング、更新順
- 両方で同じようなデータを利用するとき
- containerとstoreのデータ被り
- templateで利用する必要があるため、多数の変数をcontainer上で展開
- (これは
$data: FooData;
みたいにすればよかったのかもしれない↓)
class FooController { $data: FooData; constructor( private $scope: ng.IScope, private FooStateService: ParentStateService ) { } public $onInit() { this.FooStateService.initialize(); this.FooStateService.subscribe(this.$scope, () => { this.$data = this.FooStateService.data; }) this.$data = this.FooStateService.data; } public handleUpdateBar($event: { value: number }) { this.ParentStateService.update({ bar: $event.value }); } }
マルチバイト文字, fromCharCode, charCodeAt, Unicode
マルチバイト文字かどうか確認
/** * UTF-8における、1Byte以外の文字かどうか * -> 0-127 (ASCII 文字セット) 以外 */ function hasMultibyte(str) { return /[^\u0000-\u007f]/.test(str); } hasMultibyte('a'); // -> false hasMultibyte('>'); // -> false hasMultibyte('ÿ'); // -> true hasMultibyte('あ'); // -> true
fromCharCode
String.fromCharCode() - JavaScript | MDN
returns a string created by using the specified sequence of Unicode values
String.fromCharCode(0x3e) // -> '>' String.fromCharCode(62) // -> '>' (62).toString(16) // -> '3e'
charCodeAt
String.prototype.charCodeAt() - JavaScript | MDN
returns an integer between 0 and 65535 representing the UTF-16 code unit at the given index
'>'.charCodeAt(0) // -> 62 '\u003E'.charCodeAt(0) // -> 62 '>'.charCodeAt(0).toString(16) // -> '3e'