my story blog

JavaScriptとかRubyの技術的なことを書きたい

Rails command generetor を作ってみた

angularJsとbootstrapの練習を兼ねてRailsのgenerate modelのコマンドを出力するサービス(ツール?)を作ってみた。

Rails command Generator - http://rails-command-generator.herokuapp.com/

サービスそのものにはあまり意味はなく、個人的なプログラミングの練習ということで。(個人的にはコマンドをよく忘れるので重宝しますがw)

他のコマンドは時間があれば作りたいな。

以降は使った技術の話

続きを読む

angularJSにtypeahead.jsを組み込む

autocomplete機能を実現するライブラリにtwitter社製typeaheadというライブラリがあります。これをangularJSのプラグインとして組み込み・利用する方法を書いてみたいと思います。

プラグイン

探してみるといくつかプラグインがあり、どれも大体同じようなものですが、 今回は Siyfion/angular-typeahead - Github を使います。

インストール

bowerでインストール

$ bower install angular-typeahead

bowerが利用できない場合は、 Githubのプロジェクトからangular-typeahead.jsまたはangular-typeahead.min.jsをダウンロードし、適当な位置に配置します。

本家typeahead.jsとjQuery(1.9+)も利用するので同様に配置します。

<script src="angular.min.js" type="text/javascript"></script>
<script src="jquery.js" type="text/javascript"></script>
<script src="typeahead.js" type="text/javascript"></script>
<script src="angular-typeahead.min.js" type="text/javascript"></script>
<script src="app.js" type="text/javascript"></script>

基本的な使い方

1.モジュールを生成するときにプラグインを読み込む(DIする)
//app.js
var app = angular.module('App', ['siyfion.sfTypeahead']);

 

2.htmlおよびNgControllerに組み込む

入力部分となるinputタグに. sfTypeahead, datasets, NgModelを設定する

//index.html
<input type="text" class="sfTypeahead" datasets="exampleData" ng-model="foo">

 

3.コントローラに候補データをセットする
//app.js
app.controller('MyController', ['$scope',
  function($scope) {
    $scope.exampleData = {
      name: 'items',
      local: ['java', 'javascript', 'c++', 'ruby', 'perl', 'python' ]
    };
  }
]);

指定する値の詳細は以下のページに書いてあるものと同じです。 twitter/typeahead.js - Github

応用編

上記の例はマニュアルに載っているのそのままなので、もう少し応用した例。

「リストにならんだDOMの中にinputタグがあり、その中にtypeaheadを表示したい。」という場合、どのように実装するか。

 

まずはHTML。リストデータとなるusersを追加し、NgRepeatでループさせてユーザ一覧を表示。

//index.html
<div ng-repeat="u in users">
    <useritem user="u" languages="languages" />
</div>

 

Controllerは前項の構成に、リストデータのusersを追加。

//app.js
app.controller('MyController', ['$scope',
  function($scope) {
    $scope.users = [{
      name: "bill",
      language:""
    }, {
      name: "luis",
      language:""
    }];

    $scope.languages = {
      name: 'items',
      local: ['java', 'javascript', 'c++', 'ruby', 'perl', 'python' ]
    };
  }
]);

 

NgRepeatで出力されたDomに対して、directiveを利用してhtmlを出力していきます。

//app.js
app.directive('useritem', function($compile) {
  var linker = function(scope, element, attr) {
    element.html('<span>{{user.name}}</span>' +
      '<input type="text" class="sfTypeahead" ' +
      '  datasets="languages" ng-model="user.language">');
    $compile(element.contents())(scope);
  };

  return {
    restrict: 'E',
    replace: true,
    link: linker,
    scope: {
      user: '=',
      languages: "="
    }
  };
});

 

ポイントとしてはauto completeの候補データlanguages<useritem>に渡し、directive内で各elementに対してのdatasetsとして設定しているところです。当初directiveのscopeへの値の渡し方が理解できず結構悩みました。

今回作ったDemo - http://embed.plnkr.co/Qalhe2oG6gpSr3njF4PQ/preview


angularJSすぐハマるけど、わかってくるとホントによく出来たライブラリだなぁ思います。

Rubyで全角文字列を2バイトとして数えたい

Ruby(1.9以上)でエンコードUTF-8として利用している場合、 Stringのbytesizeメソッドは基本的にマルチバイトを3バイトして返します。*1

"abc123){*~".bytesize #=>10
"あイ冬".bytesize #=>9

やりたいこと

等幅のフォントを利用して、文字列と空白を組み合わせ固定長のような形式の 文字列にしたい。 そのため、全角文字は2(半角2文字分なので)、半角文字は1として文字列をカウントしたい。

例えば

  • "漢字"という文字列は4
  • "ab"という文字列は2
  • "全角space"という文字列は9

というような具合です。

コード

以下の様なget_exact_sizeメソッドを作成しました。

https://gist.github.com/7541358

というような方法で 全角→2、半角→1という方法で取得出来ました。

count_multi_byteはおまけで作りました。

もっといい方法があればコメント下さい。

参考:マルチバイト文字を含む文字列の表示桁数を合わせる - 紅孔雀

*1:一部4バイトとして返す文字列もあります : http://www.softel.co.jp/blogs/tech/archives/596

bowerでインストールしたファイルの配置を設定するにはgrunt-bower-taskが便利

bowerのファイル構成。デフォルトのbowerでinstallするだけだと、 (デフォルトの設定で)bower_compornentにそのライブラリのディレクトリとファイルが配置されるだけ。

プロジェクトでjs/cssを使いたい場合はその中から特定のファイルを取り出して、プロジェクト用のディレクトリに配置してそこから呼び出すようにしたい。

そこでgruntとgrunt-bower-taskを使っていい感じにjs/cssファイルを配置する方法を調べてみました。

grunt-bower-task インストールと設定

grunt-bower-taskのインストールはこちらのGithubのREADMEを参考にしてください。

bower.jsonGruntfile.jsを作成し、以下のコマンドを実行することで bowerでのインストールと指定したディレクトリへの配置を自動で行ってくれます。

$ grunt bower:install

grunt-bower-taskのoptions.layout

本題はここからで、optionのlayoutによって targetDir以下のディレクトリ・ファイルの構成を指定ます。

マニュアルにも書いてあるのですが、 種類はbyTypebyComponent独自のfunctionの3つがあります。

今回bowerでインストールするライブラリはangularJSとbootstrapとし、bootstrapに依存したjqueryもインストールされます。 bower.jsonを以下のようになりました。

{
//中略
  "dependencies": {
    "angular": "1.2.0",
    "bootstrap": "‾3.0.2"
  },
  "exportsOverride":{
    "angular":{
      "js": "**/angular.min.js"
    },
    "bootstrap":{
      "js": "**/bootstrap.js",
      "css": "**/bootstrap.css"
    },
    "jquery":{
      "js": "**/jquery.js"
    }
  }
}

byType

layoutをbyTypeにするとbowerで設定されているtypeが親ディレクトリになり、その下に各ライブラリごとのディレクトリが作成されます。

typeは各ライブラリのbower.jsonのexportsOverrideによって設定されたものです。

f:id:ky0h:20131114015115p:plain

byComponent

byComponentは単純に親ディレクトリが各ライブラリのルートになり、その下に各タイプ別のディレクトリが生成され、その下にファイルが作成されます。

f:id:ky0h:20131114015014p:plain

独自のfunction

ただ、今回やりたい構成は以下の様なjsファイルとcssファイルをまとめてディレクトリの下に置きたいというもの。

f:id:ky0h:20131114015231p:plain

この場合はfunctionを使って独自にレイアウトを定義することができます。

Gruntfile.jsのlayoutの部分を以下の用にします。

layout : function (type, component) {
  if (type === 'css') {
    return 'css';
  } else {
     return 'js';
  }
},

functionの引数のtypeは上記のtypeを、componentは各ライブラリ名になっています。いずれもString型になっています。

戻り値はtargetDir以下のディレクトリ名になります。/(スラッシュ)を含んだ場合parseされ、ディレクトリ階層になります。

この状態で実行すると以下ようにjs/cssがまとまったフォルダ構成になります。

f:id:ky0h:20131114015231p:plain

※exportsOverrideでうまく設定しないと、 js以外のファイルもjsディレクトリに入ってきてしまうので、トライアンドエラーでその辺は調整していきます。

まとめ

一つのコマンドを実行するだけで、js/cssファイルのダウンロード→配置までやってくれるのですごく便利ですねー。 grunt-bower-taskには他にも便利なoptionが色々とあるので是非お試しあれ。

今回作ったgithubはこちら kyohei8/grunt-bower-task_Demo · GitHub