読者です 読者をやめる 読者になる 読者になる

CentOS Dockerコンテナの日本語ロケール設定方法

開発部の高橋です。

今回はCentOSのDockerコンテナで日本語ロケール設定方法について紹介します。

CentOSでのロケール設定方法

以下Dockerfileのサンプルです。

FROM centos:centos7

RUN rm -f /etc/rpm/macros.image-language-conf && \
    sed -i '/^override_install_langs=/d' /etc/yum.conf && \
    yum -y reinstall glibc-common && \
    yum clean all

ENV LANG="ja_JP.UTF-8" \
    LANGUAGE="ja_JP:ja" \
    LC_ALL="ja_JP.UTF-8"

CMD bash

補足

これまでは以下の様なDockerfileでもロケールの設定ができていたのですが、Bug 1392855 – build-locale-archive: build-locale-archive.c:301: fill_archive: Assertion `used < head->namehash_used' failed.で報告されている不具合によって、去年末頃からロケールの設定ができなくなっています。

FROM centos:centos7

RUN yum -y update && \
    yum -y reinstall glibc-common && \
    yum clean all

ENV LANG="ja_JP.UTF-8" \
    LANGUAGE="ja_JP:ja" \
    LC_ALL="ja_JP.UTF-8"

CMD bash
# コンテナ内でlocale -aを実行してもCロケールとPOSIXロケール以外が存在しない。コマンド実行する度にwarningログが出力される。
$ locale -a
/bin/sh: warning: setlocale: LC_ALL: cannot change locale (ja_JP.UTF-8)
locale: Cannot set LC_CTYPE to default locale: No such file or directory
locale: Cannot set LC_MESSAGES to default locale: No such file or directory
locale: Cannot set LC_COLLATE to default locale: No such file or directory
C
POSIX

そのため、glibc-commonを再インストールする前にrm -f /etc/rpm/macros.image-language-confsed -i '/^override_install_langs=/d' /etc/yum.confを実行することで不具合を回避しています。

intra-martのエラー発生時にSlackに投稿する方法の紹介

開発部の高橋です。

今回はlogbackの機能を使って、intra-martでエラーが発生した時にSlackにエラー内容を投稿する方法を紹介します。

Slackの設定

SlackのIncoming Webhooks機能を使って、 特定のチャンネルにintra-martのエラー内容を投稿するためのWebhook URLを発行します。 (手順の紹介は省きます)

自社ではエラーが発生すると、:imp:の絵文字で投稿するように設定しています。

f:id:gsol-dev:20170123201536p:plain

Appenderの実装

Appenderによって、エラーログをSlack APIの仕様に基づいたJSON形式に変換し、Webhook URLに対してPOST送信します。 長文のエラーログをSlackに投稿できるように、attachments属性にエラーログを設定しています。

以下Appenderのソースです。

package jp.co.gsol.logback.appender;

import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.Layout;
import ch.qos.logback.core.UnsynchronizedAppenderBase;

import java.io.IOException;
import java.io.OutputStream;
import java.io.StringWriter;
import java.net.HttpURLConnection;
import java.net.URL;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.arnx.jsonic.JSON;

public class SlackAppender extends UnsynchronizedAppenderBase<ILoggingEvent> {

    private static final String PRETEXT = "【社内システム】iAPエラーログ";
    private String url;
    private String channel;
    private Layout<ILoggingEvent> layout;
    private int timeout = 10000;

    @Override
    protected void append(final ILoggingEvent event) {
        try {
            final URL apiUrl = new URL(url);
            final StringWriter w = new StringWriter();
            w.append("payload=").append(URLEncoder.encode(createAttachmentsStr(layout.doLayout(event)), "UTF-8"));
            final byte[] bytes = w.toString().getBytes("UTF-8");

            final HttpURLConnection con = (HttpURLConnection) apiUrl.openConnection();
            con.setRequestMethod("POST");
            con.setDoOutput(true);
            con.setConnectTimeout(timeout);
            con.setReadTimeout(timeout);
            con.setFixedLengthStreamingMode(bytes.length);
            con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");

            final OutputStream os = con.getOutputStream();
            os.write(bytes);

            os.flush();
            os.close();
        } catch (IOException e) {
            addError(channel + "投稿時にエラーが発生しました", e);
        }
    }

    /**
     * ログフォーマットをJSON形式に変換
     * @param text
     * @return
     */
    private String createAttachmentsStr(final String text) {
        List<Map<String, Object>> attachments = new ArrayList<>();
        Map<String, Object> attachment = new HashMap<>();
        attachment.put("pretext", PRETEXT);
        attachment.put("text", text);
        attachments.add(attachment);
        Map<String, Object> map = new HashMap<>();
        map.put("attachments", attachments);
        return JSON.encode(map);
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(final String url) {
        this.url = url;
    }

    public String getChannel() {
        return channel;
    }

    public void setChannel(final String channel) {
        this.channel = channel;
    }

    public Layout<ILoggingEvent> getLayout() {
        return layout;
    }

    public void setLayout(final Layout<ILoggingEvent> layout) {
        this.layout = layout;
    }

    public int getTimeout() {
        return timeout;
    }

    public void setTimeout(int timeout) {
        this.timeout = timeout;
    }
}

im_logger.xmlの設定

im_logger.xmlにSlackAppenderの設定を行います。 intra-martのログ仕様書に基づいて、 ログ出力したい情報を<layout>タグに設定します。<url>タグにはSlackのWebhook URLを設定します。

以下、自社のim_logger.xmlの設定の抜粋です。

    <appender name="SLACK" class="jp.co.gsol.logback.appender.SlackAppender">
        <channel>#in-house-error-log</channel>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}]
thread: [%thread]
level: [%-5level] logger: [%logger{255}]
tenant id: [%X{tenant.id}] log id: [%X{log.id}] request id: [%X{request.id}]
user type: [%X{user.type}] authenticated: [%X{authenticated}] user cd: [%X{user.cd}]
log message code: [%X{log.message.code}]    message: %msg%n
%ex{full}
            </pattern>
        </layout>
        <url>https://hooks.slack.com/services/XXXXXXX/YYYYYYY/ZZZZZZZZZZZZZZZZZZZZZ</url>
    </appender>

    <appender name="ASYNC_SLACK" class="ch.qos.logback.classic.AsyncAppender">
        <appender-ref ref="SLACK" />
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
        </filter>
    </appender>

    <root>
        <level value="info" />
        <appender-ref ref="ASYNC_SLACK" />
    </root>

Slackに投稿される内容

intra-martでエラーが発生すると、Slackに↓の様な投稿が自動で行われます。

f:id:gsol-dev:20170126195917p:plain

投稿を展開するとExceptionの詳細が見れます。

f:id:gsol-dev:20170126195941p:plain

Exceptionが長すぎると、途中で省略されてしまうので、いつか修正したいです。

以上、intra-martのエラー発生時にSlackに投稿する方法の紹介でした。

JavaScriptを使った開発に便利な定番ライブラリ

javascript

開発部の佐野です。

JavaScriptの定番ライブラリについて紹介します。

lodash.js

非常に幅広く便利な機能を提供してくれるライブラリです。 主にコレクションやオブジェクトに関しての機能が中心で、かゆいところに手が届くようなユーティリティをたくさん備えています。

Moment.js

日付や時刻に関する機能を提供するライブラリです。 JavaScript標準の機能だと、日付の操作に不便なことが多々あります。 このライブラリは、そんな日付関係の処理を補ってくれるため、非常に便利です。

この2つで、細かな使い勝手がかなり改善されます。 画面構築と操作に関しては、jQueryやAngular.js、最近ではReact.jsなど様々な選択肢がありますが、この2つのライブラリはどの場合でも役に立つと思います。

当然使い方を覚える必要はありますが、それを補って余りあるメリットがあると思うので、ぜひ活用してみてください。

docker-compose runでport mappingされず小一時間悩む

docker

開発部の高橋です。

docker-composeを使っていたらタイトル通りの内容で悩みました。結論としてはドキュメントをよく読んで使おうって話なだけなのですが。。。

経緯

以下の様なdocker-compose.ymlを作って、dockerコンテナ上でNode.jsプロジェクトのインストール、起動すると、コンテナ内で起動しているサーバーにアクセスできずに悩みました。。。

# docker-compose.yml
version: '2'
services:
  graphql:
    image: node:alpine
    volumes:
      - .:/usr/src
    ports:
      - "3000:3000"
    privileged: true
# Node.jsプロジェクトをcheckout
git clone https://github.com/kadirahq/graphql-blog-schema.git
cd graphql-blog-schema
git checkout build-schema

# 作成したdocker-composeでインストール、起動
docker-compose run --rm graphql npm install --prefix=/usr/src
docker-compose run --rm graphql npm start --prefix=/usr/src

# 別ターミナルでコンテナ内のサーバーにアクセスするとConnection refused
curl http://localhost:3000
curl: (7) Failed to connect to localhost port 3000: Connection refused

原因と解決

調べてみると(というかリファレンスに書いてありました)、docker-compose run --service-portsとしないと、docker-compose.ymlファイルのport mappingが無視されるようです。以下引用です。

The second difference is the docker-compose run command does not create any of the ports specified in the service configuration. This prevents the port collisions with already open ports. If you do want the service’s ports created and mapped to the host, specify the --service-ports flag:

docker-compose run --service-portsを指定して無事動きました。

docker-compose run --rm --service-ports graphql npm start --prefix=/usr/src

# curlでコンテナ内のサーバーにアクセスできる
curl http://localhost:3000
<html>
  <head>
    <title>Sample App</title>
    <link rel="stylesheet" type="text/css" href="/static/graphiql.css">
  </head>
  <body>
    <div id='root'>
    </div>
    <script src="/static/bundle.js"></script>
  </body>
</html>

以上、ドキュメントをよく読んで使おうという話でした。

JavaScriptのアロー関数について

javascript

開発部の佐野です。

JavaScriptのECMAScript6で追加されたアロー関数を紹介します。

これは、要は関数を定義する構文です。 従来のfunctionと書く定義とは少し挙動が違います。 それに加えて、より短く簡単に関数を定義できるというのもメリットになります。

ただ、このアロー関数は括弧を省略できたりして、意外と覚えにくい機能です。 特にES6の新しい構文に慣れていない人は混乱しがちです。

例えば、以下の書き方は全て同じ結果になります。

name => 'Hello ' + name;
name => { return 'Hello ' + name; };
(name) => 'Hello ' + name;
(name) => { return 'Hello ' + name; };

左右どちらの括弧も省略できるため、結果的に4パターンの書き方があります。 引数が複数の場合や、処理が複数行の場合は省略できません。

アロー関数はES6の構文で書く場合は必須となる構文なので、覚えると将来役に立つでしょう。 また、ES6で書かれたJavaScriptが読めない人の原因は、大抵このアロー関数が理解できない場合が多い気がします。 逆にこれさえ分かれば、敬遠しがちだった最新のJavaScriptソースコードもグッと理解しやすくなると思います。

以上、アロー関数の紹介でした。

JavaScriptのテンプレート文字列について

javascript

開発部の佐野です。

今回は、JavaScriptのテンプレート文字列について紹介します。

これはECMAScript6で組み込まれた新しい機能です。 他にも様々な機能が追加されていますが、テンプレート文字列は汎用性が高く、様々な場面で使える機能です。

「`」(バッククォート)で囲うと利用できます。 内部に変数などを埋め込んで表示できるのが特徴です。

例えば、以前は文字列の途中で変数を入れるにはこのように書いていました。

var name = 'GSOL';
console.log('Hello ' + name + ' !!');

これをテンプレート文字列にすると、

var name = 'GSOL';
console.log(`Hello ${name} !!`);

と書けます。

地味ですが、きれいに書けるので開発者的には嬉しい機能です。

以上、簡単ですがテンプレート文字列の紹介でした。

markdownエディタの紹介

開発部の佐野です。

今回は、markdownエディタについて紹介します。

markdownは書きやすくで便利なので、よく使っています。 しかしローカル上のmarkdownファイルを編集するエディタは、意外と定番のものが無い気がします。 なので個人的に使ってみたmarkdownエディタを紹介してみます。

Haroopad

非常にシンプルで使いやすいと思います。 メモ帳にmarkdownのプレビュー画面が追加されたような感じです。 後述するAtomなどは多機能でタブなども使えますが、もっとシンプルに使うならこちらのほうが良いと思います。 すこしフォントが微妙ですが、設定で変えることができます。

Atom

markdown専用のエディタではなく、様々なプログラミング言語の編集に使えるエディタです。 多機能でカスタマイズも柔軟にでき、様々な機能の追加パッケージが公開されています。

Typora

markdownかつWYSIWYGというユニークなエディタです。 markdownの記法を書くと、自動的に表示が更新されていきます。 普段直接markdownを書くとテーブルなどが少し書きづらいのですが、Typoraはテーブルを作ると専用の操作メニューが表示されて、非常に簡単に編集ができるようになっています。 いきなり表示が更新されることなどから、若干操作しにくい感じはありますが、おもしろいエディタだと思います。

markdownエディタの紹介は以上です。

markdownはシンプルで覚えやすく、日常のメモ書きから開発資料まで幅広く役立ちます。 このブログも含めてmarkdownで書けるサービスも数多くありますので、試してみてはいかがでしょうか。

自社githubアカウントの紹介

開発部の高橋です。

自社githubアカウントについて簡単に紹介しようと思います。

自社githubアカウント

github.com

github上で公開しているリポジトリ

自社intra-martソリューション関係

IMBoxデスクトップ通知、iACスケジュール連携ソリューションのユーティリティやプラグインgithub上に公開しています。

PeerJS

PeerJSからForkして一部修正を加えてます。 Web会議ソリューションのSignalingサーバー使用しています。

Dockerfile

社内の開発等に使用しているDockerfileを公開しています。 Docker Hubでimageも公開してます。自社Docker Hubのページ

以上、自社github(とDocker Hub)アカウントの簡単な紹介でした。

react.jsの入門書を購入しました

React

開発部の高橋です。

以前から探していたReactの入門書が先日発売したので、 会社の経費で1冊購入しました。 (お願いしたら二つ返事で購入してくれる会社に感謝です)

WebデベロッパーのためのReact開発入門 JavaScript UIライブラリの基本と活用

WebデベロッパーのためのReact開発入門 JavaScript UIライブラリの基本と活用

Reactって何?って開発メンバーもまだいるような状況なので、 この本でReactの基本を固めてサービス開発を進めていきたいと思います。

現在WantedlyでReactを使ってサービスの開発したい方を募集してますので、 興味がある方はぜひ応募してみてください。 上で書いたように社内は現在React勉強中なので、 Reactに興味があって少し触ったことのあるレベルの方でも大歓迎です。

www.wantedly.com

HTML5の新機能紹介 ~WebStorage~

javascript

開発部の真崎です。

今回は、HTML5で導入された新機能の1つ、WebStorageについての紹介です。

WebStorageとは

WebStorageとは、HTML5で導入されたブラウザでデータの永続化を行う仕組みのことです。 特徴としては、 * 比較的大容量の文字列データを保存できる * Key-Value型のシンプルなインターフェイス * 保存/取得処理に通信を伴わない などがあります。 今までは、毎回サーバにアクセスし、保存/取得処理されていたブラウザ固有のデータをブラウザ内で完結させることができます。 また、最近使われている大多数のブラウザで使用できる(ChromeFireFoxSafariOpera、IE8~)ので、安心して使えます。

Cookieとの違い

WebStorageと似た技術で、今まではCookieがありました。違いについて、簡単にまとめてみました。 (localStorageとsessionStorageの違いは、後で説明しています)

WebStorage Cookie
Origin毎の保存容量 5MB (仕様上) 4KB
有効期限 なし (localStorage) あり
通信 jsで実装する必要がある 勝手に送信される
個数制限 なし 20個 (仕様上)
pathの設定 Originで共通 できる
標準API 比較的簡単 煩雑な文字列操作
ウィンドウ間でのデータの共有 できる (localStorage) できる
スコープ Origin単位 host/path単位

有効期限を設定したい場合や常にデータを送信したい場合、特定のpath以下のみのアクセスを許可したい場合など、 Cookieでしかできないこともありますが、データ保存に関しては、WebStorageに利点が多いです。

WebStorageの種類

WebStorageには、用途に応じて2種類のストレージが用意されています。違いについて、簡単にまとめてみました。

locakStorage sessionStorage
有効期限 なし(削除されるまで) ウィンドウ(タブ)を閉じるまで
ウィンドウ(タブ)間での
データの共有
できる できない

基本的な機能については、共通です。1点だけ異なっており、
localStorageは、Origin(scheme://hostname:port/)にスコープしており、
sessionStorageは、更にTop-level browsing context(ウィンドウ、タブなど)にスコープした Storageである点が差異です。(※sessionStorageは、Webアプリなどのログインセッションとは同期してないです)

用途としては、localStorageはデータの永続化、sessionStorageは一時データの保存になると思います。

WebStorageの使い方

コードを示しながら、説明します。 (localStorageもsessionStorageも同じインターフェイスを備えてます。ここでは、sessionStorageをサンプルとします。)

// sessionStorageが使えるかどうか
if (typeof sessionStorage === 'undefined')
    alert('sessionStorage is not found');

// 文字列データの操作は、hashを操作するように行えます。
sessionStorage['key'] = 'value';
console.log(sessionStorage['key']); // => 'value'
// 次の書き方もできます
//sessionStorage.setItem('key', 'value');
//console.log(sessionStorage.getItem('key'));
//sessionStorage.key = 'value';
//console.log(sessionStorage.key);

// Objectの保存には、JSON.stringifyなどで、シリアライズする必要があります。
sessionStorage['objKey'] = JSON.stringify({key: [1, 2, 3]});
// 取得時には、デシリアライズしてください。
console.log(JSON.parse(sessionStorage['objKey'])); // => {key: [1, 2, 3]}

// データを消したいとき、次のようにできます。
//sessionStorage.clear(); // 全件削除(Originに紐づく全件が削除されるので注意)
sessionStorage.removeItem('key'); // 'key'で登録されているデータが削除されます
// OriginでStorageが共有されているので、次のようにnamespaceを分けると削除しやすいです。
sessionStorage['prefix.key'] = 'value'; // 一意なprefixを付加
Object.keys(sessionStorage)  // -> ['objKey', 'prefix.key']
      .filter(function(k) { // prefixが一致するデータをfilter
          return k.split('.')[0] === 'prefix';
      }).each(function(k) { // filterされたデータを削除
          sessionStorage.removeItem(k);
      });

// その他のStorage interface
// n番目のkeyを戻します
sessionStorage.key(0); // -> 'objKey'
// Storageに保存されている件数(正確には、Storage自体のinterfaceではないですが)
sessionStorage.length; // -> 1
// 登録されているkeyの一覧
Object.keys(sessionStorage); // -> ['objKey']

// 他のタブからStorageの変更があった場合、eventが発火します
// (sessionStorageは仕様上、発火しないので、localStorageをサンプルにします)
localStorage['key'] = 'old';
// listenerを設定
window.addEventListener('storage', function(e) {
    console.log(e);
});
// 他のタブから
// localStorage['key'] = 'new';
// 登録、更新、削除など、Storageに変更があれば、eventが発火します
// => key: 'key', newValue: 'new', oldValue: 'old', ...

※Storageのデータは、Originで共有されるので、複数の機能で使用する場合、削除や上書きに気をつけてください。

使い分け

Cookie、localStorage、sessionStorageの違いを簡単に説明してきました。ここでは、それぞれの用途について考察してみたいと思います。

Cookie

容量が少なく、常にサーバとデータを共有しているCookieはログインセッションの管理が主な用途になると思います。

localStorage

ブラウザ毎にデータを永続的に保存できるので、設定の保存目的や、大容量なのを活かし、下書き保存などに使えると思います。 また、Storage変更時に、eventが発火するので、タブ間の同期などにも使えます。

sessionStorage

タブ毎にスコープが分かれているので、タブで行った作業結果の一時保存や、タブを閉じたあと必要がない閲覧情報などの保存に使えると思います。

まとめ

HTML5で導入されたWebStorageについて、Cookieとの比較を行い説明しました。 また、使い方について、サンプルコードを元に説明を行いました。 サーバに送らなくてもいいデータなどの保存に使用すると、画面遷移の高速化が期待できます。 サーバとの無駄な通信量が多い時などいかがでしょうか?

複数のバージョンのjQueryを読んでしまったときの共存のさせ方

javascript jQuery

新人の真崎です。今週もよろしくお願いします。

今回は、複数のバージョンのjQueryを読まないといけないときの対処方法について書きたいと思います。1.7系でしか動かないコードがあるけど、1.10系も使ってみたい場合やajaxで取得したページにjQueryを再取得してしまってる場合などにお試しください(※推奨できる方法ではないので、実装を見直すべきですが)

2個目のjQueryを読む前に処理したい場合

古いjQueryを別の変数に退避させて、新しいjQueryを読む方法です。

<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>
<script type="text/javascript">
$old = jQuery;
</script>

<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script type="text/javascript">
console.log($old().jquery); // => 1.7.2
console.log($().jquery); // => 1.10.2
console.log(jQuery().jquery); // => 1.10.2
</script>

2個目のjQueryを読んだあとに処理したい場合

新しいjQueryを読んだあとに、noConflict([removeAll])で古いjQueryを復活させる方法です。removeAllのオプションにtrueを渡すと$オブジェクトだけではなく、jQueryのオブジェクトも元に戻されます。

<script type="text/javascript" src="http://code.jquery.com/jquery-1.7.2.min.js"></script>

<script type="text/javascript" src="http://code.jquery.com/jquery-1.10.2.min.js"></script>
<script type="text/javascript">
$new = jQuery.noConflict(true);
console.log($().jquery); // => 1.7.2
console.log(jQuery().jquery);
// removeAll is true => 1.7.2
// removeAll is false => 1.10.2
console.log($new().jquery); // => 1.10.2
</script>

まとめ

複数のバージョンのjQueryを読まないといけないときの対処方法について紹介しました。どうしても複数のバージョンのjQuery共存させないといけないときなどにご活用ください。

javascriptで日付を扱うときに便利なmoment.jsの紹介

javascript moment.js

新人の真崎です。 このアドベントカレンダーもなんとか1周間続けることができました。残り3週間がんばっていきたいと思います。

今回は、javascriptで日時を扱うときに便利なライブラリの紹介を行いたいと思います。 javascriptにもDateオブジェクトはあるのですが、使いづらく簡単な処理であればStringで代用してしまったりします。そこで、試してみたいのが、今回紹介するmoment.jsです。

momentオブジェクト生成

日時情報を保持するオブジェクトです。任意のフォーマットパターンで日時文字列をparseすることができます。

moment();
// => 現在日時のオブジェクト

moment("2013/12/8", "YYYY/M/D");
moment("Dec 8, 2013", "MMM D, YYYY");
moment("8 12 2013", "D M YYYY");
// => 2013年12月8日のオブジェクト

フォーマット出力

任意のフォーマットパターンでStringを出力できます。

moment().format("MMM D YYYY ddd, hh:mm:ss a Z");
// => "Dec 8 2013 Sun, 09:05:04 pm +09:00"

日本語化

langs付きのmomentなら、lang("ja")で日本語に出力してくれます。また、langの設定を別に行うこともできます。

moment.lang("ja", {
    weekdaysShort: ["日","月","火","水","木","金","土"]
});
moment.format("ddd");
// => 日

まとめ

javascriptで日時を扱うときに便利なライブラリであるmoment.jsの簡単な紹介を行いました。 moment.jsには、他にも日付のマニピュレーションの機能が豊富にあったりするので、日付関係で困ったときは試してみてはいかがでしょう。

javascriptでもpythonのdecoratorを使いたい

javascript python

新人の真崎です。 学生時代は、pythonをいじってたりしたので、今回はpythonの機能からjsで実装できそうなものを作ってみたいと思います。

pythonのdecorator

関数の処理の前後に任意の処理を飾り付けれる機能のことです。いわいるデコレータパターンだと思います。

def deco(opt):
    def wrapper(func):
        def impl(*args, **kwargs):
            print args, kwargs, opt
            print "before"
            r = func(*args, **kwargs)
            print "after"
            return r + opt
        return impl
    return wrapper

@deco(1)
def f(a):
    print a
    return a
f(2)
=> 3

javascriptでの実装

var deco = function() {
    var args = arguments;
    var wrapper = function(fn) {
        var impl = function() {
            console.log("decorator arg", args, "wraper arg", arguments);
            console.log("preprocess");
            var r = fn.apply(this, Array.prototype.slice.call(arguments));
            console.log("postprocess");
            return r + ":" + Array.prototype.join.call(args);
        }
        return impl;
    }
    return wrapper;
};

var f = deco(1)(function() {
        console.log(arguments);
        return Array.prototype.join.call(arguments);
f(2, 3);
=> "2,3:1"

まとめ

javascriptpythonのdecoratorを実装例を示してみました。 デコレータを使うことで、定型的な前処理・後処理をまめることができると思います。

CSSでくるくる回るアニメーション

CSS

新人の真崎です。 ネタ探しが大変です… 今日もCSSについて書こうと思います。 CSSアニメーションが簡単にアニメーションを表現できそうで興味があるので、試しに簡単なインジケータをCSSの@keyframesを使って作ってみたいと思います。

@keyframes

ある時点での状態を指定したキーフレームを複数用意することでアニメーションを表現します。

@keyframes 名前 {
    0% {}
    100% {
        transform: rotate(360deg);
    }
}

くるくる回るインジケータ

<html>
<head>
<style type="text/css">
@-webkit-keyframes anime {
    0% {}
    100% {
        -webkit-transform: rotate(360deg);
    }
}
.indicator {
    display: inline-block;
    position: relative;
    background: pink;
    margin-left: 50px;
    width: 20px;
    height: 100px;
    -webkit-border-radius: 5px;
    -webkit-animation: anime 3s infinite linear;
}
.indicator:before, .indicator:after{
    content: "";
    width: 20px;
    height: 100px;
    position: absolute;
    background: pink;
    opacity: 0.3;
    -webkit-border-radius: 5px;
    -webkit-transform: rotate(-60deg);
}
.indicator:after {
    opacity: 0.7;
    -webkit-transform: rotate(60deg);
}
</style>
</head>
<body>
    <div class="indicator"></div>
</body>
</html>

単調にオブジェクトが1回転するアニメーションを定義し、それを3秒で1回行うように設定しました。残像を表現したくて、バーの前後に薄いバーを追従させるようにしてみたのですが、同じスピードで回してるからか、残像ぽくないですね…(色をピンクにしたら、花が回ってるようでかわいいので、これはこれでありですが) ※Chrome以外で試すときは、ベンダープリフィックス(-webkit-)を各ブラウザのものに書き換えてください。

まとめ

CSS3の@keyframesを使うことで、CSSだけでアニメーションを実装しました。

:before擬似要素と:after擬似要素

CSS

新人の真崎です。 毎日ネタ考えるの大変ですね。

jsの話が続いてるので、今日はCSSの話にしてみたいと思います。

:before擬似要素と:after擬似要素

対象の要素の前後に擬似的な内容を追加してくれる擬似要素です

このような形で指定すると、「beforePafter]のように表示されます。

<html>
<head>
<style type="text/css">
p:before {
    content: "before";
}
p:after {
    content: "after";
}
</style>
</head>
<body>
    <p>P</p>
</body>
</html>

contentには、文字列の他に、URL(画像)、要素の属性値、カウンタが指定できます。 例えば、リンクの前にアイコンを表示させたり、リンクの後ろにURLを表示させたり、タイトルの前後に飾りをつけたりするのに使えます。

簡単Tooltip

:hover擬似クラスといっしょに使うと簡単にTooltipが実装できたりします。

<html>
<head>
<style type="text/css">
.content {
    display: inline;
    position: relative;
}
.content:hover:after {
    content: attr(data-content);
    background: yellow;
    position: absolute;
    top: 50%;
    left 50%;
}
</style>
</head>
<body>
   <p class="content" data-content="content">P</p>
</body>
</html>

要素にマウスをかざすと、説明が要素の上に表示されます。

まとめ

:before擬似要素と:after擬似要素の簡単な紹介を行いました。 簡単なTooltipの作り方の紹介を行いました。