ぽっちぽちにしてやんよ

技術ネタとかアプリに関する話とか

VoiceText Web APIのNode.jsライブラリ作った

GitHub: https://github.com/pchw/node-voicetext

npm: https://www.npmjs.org/package/voicetext

前説

HOYAのVoiceText Web APIというのが公開されていたのでNode.jsから叩くライブラリを作った.

VoiceText Web API(β版)はショウ君で有名なVoiceTextがスタンドアロンのソフトじゃなくてWebで公開されたやつ.

しかも今のところ無償で利用出来る.

Voice Text Web API自体はHTTPSのPOSTでデータを投げつけたらwavファイルを送り返してくるゴキゲンなやつ.

はじめにやること

VoiceText Web APIを使うには,まず利用登録が必要. 利用登録画面 から名前``メールアドレスなどその他入れて登録すればすぐAPIキーが書かれたメールが来る.

Node.jsからVoiceText Web APIを使う

おもむろにnpm installする

1
$ npm install voicetext

require('voicetext')したらVoiceTextクラスが返ってくるからnewしつつAPIキーをブチ込む

1
2
VoiceText = require('voicetext');
voice = new VoiceText('APIキーをここにコピペする');

あとは色々パラメータをいじりつつメソッドチェーンしてspeakする. 基本的にメソッドは公式ドキュメントのパラメータに合わせるようにした.

1
2
3
4
5
6
7
8
9
voice
.speaker(voice.SPEAKER.HIKARI)
.emotion(voice.EMOTION.HAPPINESS)
.emotion_level(voice.EMOTION_LEVEL.HIGH)
.pitch(200)
.speed(400)
.volume(200)
.speak('きょうもいちにちがんばるぞい', function(e, buf){
})

コールバックの第二引数にwavが詰まったBufferインスタンスが返ってくるので, fs.writeFileとかしてwavに書き出せばもうすぐに再生できる形だ!

たぶんnode-wavとか使ったらそのまま再生出来るかも.

というわけでNode.js版のVoiceTextライブラリのリポジトリはhttps://github.com/pchw/node-voicetext

chatopsなところはBOTとかに喋らせるといいんじゃないかな.

[Synth] ルーティングを増やしてみる

前説

前回でAngularだけではなく自分の好きなクライアントサイドMVCフレームワークが使えるようになったところで, 次はサーバサイドでAPIの追加が必要になってくる頃合いなのでその辺りを触ってみる.

synthは初回にも少し触れたが,back/resources以下がURLにマッピングされている感じになっている. そのため,back/resources以下にディレクトリを掘って,その中のファイルにexports.XXXXX的なのを書いておけば, そのままURLのルーティングに反映される.

実際の手順を追ってみる.

backend側の改修

back/resources にディレクトリ追加する.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
$ synth install -b bluebird
$ mkdir back/resources/messages
$ cat back/resources/messages/filename_you_want.coffee
Promise = require 'bluebird'
sample = ->
  new Promise (resolve)->
    setTimeout ->
      resolve 'something'
    , 3000

exports.getIndex = (req, res)->
  sample()
  .then (d)->
    something: d

synthはback/resources内の.jsまたは.coffeeのファイルのexportsを見るため, ファイル名はなんでもいい, 分かりやすいのを付けるのがいいらしい.

サーバサイドはPromiseを返さないといけないらしいので,bluebirdを使ってPromiseを返してる.

プリロードテンプレートの改修

front/html内もbackend側に合わせて変える必要がある. (preloadHTMLが必要な場合)

front/html内はback/resources内と良く似ているが,ディレクトリに入れるファイル名は規則がある. exports.getIndexとかだとgetIndex.jadeまたはgetIndex.htmlというファイル名にする必要がある. backend側のAPIと合わせなければいけない.

1
2
3
$ cat front/html/messages/getIndex.jade
h2 Messages
p 

frontend側の改修

今frontend側にはroutingを入れてなかったので,/messagesにアクセスしてもVue側でエラーになる. v-viewVue.componentを使ってルーティングするロジックを入れる.

まずはindex.jadeの方にv-view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
$ cat front/index.jade
doctype html
html
  head
    meta(charset='utf-8')
    meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    title= appName

    for cssFile in cssFiles
      link(rel="stylesheet", href=cssFile)

    // Preloaded Data
    script.
      window.preloadedData = !{data};
    for jsFile in jsFiles
      script(src=jsFile)
  body
    h1= appName

    div#content
      div(v-view="currentView")

    if preloadHTML
      script.template(type="v-template", id="template")
        != preloadHTML

次にfront-app.coffeeの方にURLによってv-viewcurrentViewをcomponentで置換するように書く.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
$ cat front/js/front-app.coffee
$ ->

  routes = ['tweets', 'messages']

  getRoute = ->
    path = location.hash.replace(/^#!\?/, '') or 'tweets'
    if routes.indexOf(path) > -1 then path else 'tweets'
    
  Vue.filter "formatDate", (v) ->
    v.replace /T|Z/g, " "

  Vue.component 'tweets',
    template: '#template'
    created: ->
      @$data.newTweet = "write some tweet."
      if typeof window.preloadedData isnt "undefined"
        @$data = window.preloadedData
        window.preloadedData = null
      else
        $.ajax
          url: "/api/tweets"
          success: (data) =>
            @$data.tweets = data?.tweets
    methods:
      publish: ->
        $.ajax
          type: "POST"
          url: "/api/tweets"
          data:
            content: @$data.newTweet

          dataType: "json"
          success: (tweet) =>
            @$data.tweets.unshift tweet
            @$data.newTweet = ""

    Vue.component 'messages',
      template: '#template'
      created: ->
        if typeof window.preloadedData isnt "undefined"
          @$data = window.preloadedData
          window.preloadedData = null
        else
          $.ajax
            url: "/api/messages"
            success: (data) =>
              @$data.something = data?.something

  app = new Vue
    el: "#content"
    data:
      currentView: do getRoute
      routes: routes

アクセスしてみる

http://localhost:5000/messages で something的な文字が出るページが表示されて, http://localhost:5000/tweets で今までのTweets一覧が出るページが表示されるようになる

まとめ

  • synthでルーティングを追加してみた
  • back/resource以下にディレクトリを増やしてファイルを突っ込むだけ
  • 後から見た時にURLとディレクトリ構造が一致しているので分かりやすい
  • front側もプリロードのテンプレートがfront/html以下に同じディレクトリ構造があるので分かりやすい

[Synth] SynthとVue.jsを組み合わせてみた

前回紹介したAPI-firstなフレームワークSynthVue.jsを組み合わせて使ってみる.

Synthの$ synth new my_appで生成されるコードはAngular.jsを使うようになっている. Angular.js派ではないのでVue.js版に書き換えてみる. ついでにcoffee-script派なので,coffee-scriptで動くようにする.

ここまでのコードは前回参照.Synth公式のチュートリアルを済ませた時点のコードです.

モジュールの準備

Vue.jsインストール

まず,Vue.jsを使うため,インストールする.

1
2
$ synth install -f vue
$ synth install -f jquery

Angularのアンインストール

Angularは使わないので抜く.$ synth uninstall -f angularとやりたいが,未実装っぽい. あと$ synth install -f時に.bower.jsonmainキーがないとfront/js/jsFilesに記載されないっぽい

1
2
3
$ cd front
$ bower uninstall angular
$ bower uninstall angular-route

front/bower.json からも消しておく.

1
2
3
4
5
6
7
8
9
$ cat front/bower.json
{
  "name": "my_app",
  "private": true,
  "dependencies": {
    "vue": "~0.10.5",
    "jquery": "~2.1.1"
  }
}

front/js/jsFilesから不要になったファイルを削除する

1
2
3
4
$ cat front/js/jsFiles 
../bower_components/jquery/dist/jquery.js
../bower_components/vue/dist/vue.js
front-app.js

テンプレートの修正

DirectivesをAngular用からVue用に書き換える. dateのmediumはVueでは無いので後でformatDateを定義することにする.

1
2
3
4
5
6
7
$ cat front/html/tweets/getIndex.jade
textarea(v-text="newTweet")
button(v-on="click: publish()", v-attr="disabled: newTweet.length == 0") Publish
ul.tweet-timeline
  li.tweet(v-repeat="tweet : tweets")
    .content 
    .date 

front/index.jade もVue用に色々修正

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
doctype html
html
  head
    meta(charset='utf-8')
    meta(http-equiv='X-UA-Compatible', content='IE=edge,chrome=1')
    meta(name='viewport', content='width=device-width, initial-scale=1.0')
    title= appName

    for cssFile in cssFiles
      link(rel="stylesheet", href=cssFile)

    // Preloaded Data
    script.
      window.preloadedData = !{data};
    for jsFile in jsFiles
      script(src=jsFile)
  body
    h1= appName

    div#content

    if preloadHTML
      script.template(type="v-template", id="template")
        != preloadHTML

分からなかった点

1
script(type="text/ng-template", id="#{preloadHTMLPath}")

とかなっててngRouteのtemplateUrlらへんでバインドしてた方法のVue版が分からなかったから普通にid指定にしてしまった.

Vueのバインディングとcoffee-script化

front/front-app.jsを消してfront/front-app.coffeeにしてVue版にする

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
$ ->
  Vue.filter "formatDate", (v) ->
    v.replace /T|Z/g, " "

  content = new Vue
    el: "#content"
    template: "#template"
    data:
      newTweet: "write some tweet."

    created: ->
      if typeof window.preloadedData isnt "undefined"
        @$data = window.preloadedData
        window.preloadedData = null
      else
        $.ajax
          url: "/api/tweets"
          success: (data) =>
            @$data.tweets = data?.tweets


    methods:
      publish: ->
        $.ajax
          type: "POST"
          url: "/api/tweets"
          data:
            content: @$data.newTweet

          dataType: "json"
          success: (tweet) =>
            @$data.tweets.unshift tweet
            @$data.newTweet = ""

合わせてfront/js/jsFilesも書き換え

1
2
3
4
$ cat front/js/jsFiles 
../bower_components/jquery/dist/jquery.js
../bower_components/vue/dist/vue.js
front-app.coffee

image これでめでたくTweetが表示されて,投稿できるようになった.

まとめ

  • synthのAngularで動いてたのをVue版に書き直してみた
  • そんなに変更は難しくなかった.これから新しいクライアントサイドのライブラリが出てきてもサッと変えてテスト出来そう.
  • よく考えたら当たり前だけどbackend側全然いじってない
  • API増やしたり,色々するとまたbackend側のディレクトリ構成とかfrontのhtml以下のディレクトリ構成とかハマるポイントが有るけどそれは次の話にする.

[Synth] Synthを試してみた

synth

tl;dr

  • SynthっていうAPI-firstなWebフレームワークがある
  • Turoialを回してみた
  • RESTfullなAPIとディレクトリ構造,exportsのI/Fを合わせる規約で見通し良い
  • プリロードの仕組みで初めからレンダリングできるよ!
  • まだ超beta.

前説

Synth はNode.js製のAPI-firstなWebフレームワーク. back/resourcesディレクトリに.js/.coffeeを配置して,exports.get = ...とかexports.getIndex =とかexports.<method><optional: ActionName>な形で書いておくと,myapp/back/resources/memoesGET /api/memoesとかに答えてくれる.

ひとまず,Tutorial を回して,Angular以外を使う道を探ろう.

あと超betaらしいから地雷踏む勇気が無い人はProductionに使えない.

インストールとセットアップ

1
2
3
$ npm install -g synth
$ $ synth new my_app
Successfully created a new synth app in my_app

ディレクトリ構造

1
2
3
4
5
6
7
$ cd my_app
$ ls
back       front      synth.json
$ ls front/
bower.json css        html       images     index.jade js         misc
$ ls back/
back-app.js       generateTweets.js package.json      resources

こんな感じになっている.

frontはクライアントサイドのやつが詰まってる. backはサーバサイドのやつが詰まってる.

synth newはTwitterクローンが生成されるみたい.

パッケージインストール

1
2
$ synth install -b
$ synth install -f

でそれぞれfrontのbower installとbackのnpm installが走る.

起動

1
2
$ synth server -p 5000
synth (in development mode) is now listening on port 5000

http://localhost:5000 にアクセスすると表示される. Synth Appとか出て何も表示されない.Tweetを生成してあげないといけないらしい.

1
$ node back/generateTweets.js

そしてリロードするとTweetが出た.

処理の追加

このままだとただのTweet表示君なので,Tweetする機能を付ける.

backend側

back/resources/tweets/.jsファイルを置いてexports.post = ...を書けばいい.

1
$ touch back/resources/tweets/createTweets.js
1
2
3
4
5
6
7
8
9
10
$ cat back/resources/tweets/createTweets.js 
  exports.post = function(req, res) {
    if (!req.body.content) {
      throw 422;
    }
    return req.db.collection('tweets').insert({
      content: req.body.content.slice(0, 140),
      created_at: new Date
    });
  };

ちなみにback/resources/tweets/createTweets.coffeeを置いたら読んでくれなかった. きっと全部.coffeeにしないとダメ.とりあえず先に行く.

frontend側

/front/js/controllers/tweets.js に処理を追記.

1
2
3
4
5
6
7
8
9
10
11
$ cat front/js/controllers/tweets.js 
angular.module('my_app')
.controller('tweetsController', function ($scope, $http, data) {
  $scope.tweets = data.tweets;
  $scope.publish = function () {
    $http.post('/api/tweets', { content: $scope.newTweet })
    .success(function (tweet) {
      $scope.tweets.unshift(tweet);
    });
    $scope.newTweet = '';
  };

こうする.

HTMLの方もボタンを付けたり,textareaを付けたりしないといけない.

1
2
3
4
5
6
7
$ cat front/html/tweets/getIndex.jade
textarea(ng-model="newTweet")
button(ng-click="publish()", ng-disabled="newTweet.length == 0") Publish
ul.tweet-timeline
  li.tweet(ng-repeat="tweet in tweets")
    .content 
    .date 

こうする.

確認

もう一度 http://localhost:5000 にアクセスすれば,textareaとpublishボタンがある. hogeとか書いてpublishすればめでたくTweetが追加される.

Preload

Synthの強みとしてプリロードの機能があるらしい. backend側でpromiseで返せば,front側で初めからデータを持った状態でレンダリング出来る. rendr みたいなものか.

front/index.jadeを確認してみると,

1
2
3
// Preloaded Data
script.
  var preloadedData = !{data};

とか

1
2
3
if preloadHTML
  script(type="text/ng-template", id="#{preloadHTMLPath}")
    != preloadHTML

ここらへんがプリロードの鍵らしい.

ソース を覗いてみると,

1
2
3
4
5
6
7
8
9
10
11
var renderData = {
  appName: req.appName || 'Synth App',
  jsFiles: assets.jsFiles,
  cssFiles: assets.cssFiles,
  data: prepareData(preload.data),
  preloadHTML: preload.html,
  preloadHTMLPath: htmlPath
};
for (var key in res.renderData) {
  renderData[key] = res.renderData[key];
}

がres.renderに渡ってるっぽい.

1
2
3
4
5
exports.get = function(req, res) {
  :
  res.renderData[hoge] = 'fuga'
  :
}

とかbackend側でやってあげれば,front側にデータを渡せそう.

Angular以外で使ってみる

時間が来たから,また夜にでも続き書く.

Coffee-scriptでpower-assertを使ったテストを書く

tl;dr

  1. @t_wadaさん神
  2. coffee-scriptでpower-assert使うときはespower-coffeeを使おう

あらすじ

ちょっと前に話題になっていたpower-assertを使ってみようとしました.

こんな感じのを用意しました.

1
2
3
4
5
6
7
8
9
10
assert = require 'power-assert'

describe 'array', ->
  beforeEach ->
    @arr = [1,2,3]
  describe '#indexOf()', ->
    it 'should return index when the value is present', ->
      zero = 0
      two = 2
      assert(@arr.indexOf(zero) is two)

んで

1
$ mocha --require intelli-espower-loader test/power-assert.coffee

みたいに実行したら

1
2
3
4
5
6
7
8
9
10
  array
    #indexOf()
      1) should return index when the value is present


  0 passing (4ms)
  1 failing

  1) array #indexOf() should return index when the value is present:
     AssertionError: false == true

あれ,,,ログがショボい,,,

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  array
    #indexOf()
      1) should return index when the value is present


  0 passing (10ms)
  1 failing

  1) array #indexOf() should return index when the value is present:
     AssertionError: # /Users/pochi/Documents/development/(snip)/test/power-assert.js:16

assert(this.arr.indexOf(zero) === two)
            |   |       |     |   |   
            |   |       |     |   2   
            |   -1      0     false   
            [1,2,3]                   

[number] two
=> 2
[number] this.arr.indexOf(zero)
=> -1

こんな感じのを期待したのに!!

とか言ってたら@yosuke_furukawaが色々助言をくれて,こうなって

さらに中をみていくと

さらに色々調べてたら数カ月前の@mizchiの発言が

という感じで動作しない感じでした.

TypeScriptでの前例があるように

のようにgruntを使ってjsにコンパイルしてそれを食わせるという方法しか無いように思えた時に @t_wada 降臨.

そしてその翌日

espower-coffee が公開されてました!

使い方

1
2
$ npm install espower-coffee --save-dev
$ mocha --require 'espower-coffee/guess' test/**/*.coffee

grunt-mocha-testを使っているなら

1
2
3
4
5
6
7
8
9
mochaTest:
    options:
      reporter: 'spec'
      require: 'espower-coffee/guess'
      colors: true
      timeout: 10000
    src: [
      'test/power-assert.coffee'
      ] 

こんな感じ.compilers: 'coffee:coffee-script'は要らないみたいです.

すると

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
  array
    #indexOf()
      1) should return index when the value is present
 
 
  0 passing (10ms)
  1 failing
 
  1) array #indexOf() should return index when the value is present:
     AssertionError: # /Users/pochi/Documents/development/(snip)/test/power-assert.coffee:15
 
assert(this.arr.indexOf(zero) === two)
            |   |       |     |   |   
            |   |       |     |   2   
            |   -1      0     false   
            [1,2,3]                   
 
[number] two
=> 2
[number] this.arr.indexOf(zero)
=> -1

こんなのが得られて,coffee-scriptでpower-assertが使えました!

ちなみに他のファイルとかはこんな感じでやりました.

注意点

  • coffee-scriptは1.7.1以上を要求します
  • coffee-errorsなどを併用するとショボいログに戻ります
  • AssertionErrorの行数が実際の.coffeeの行数ではありません.(多段SourceMap対応するまでは仕様とのこと)

Backbone.jsでView.renderを書く場合のセオリー

こんにちは、ぽちです. みなさんBackbone.js使ってますか?Marionetteですか?Chaplinですか?それともngが付いちゃうアレですか?

さて今日は、Backbone.jsのお話です.

タイトルは「Backbone.jsでrenderした時に空divを無くす方法」とかにしようと思ったのですが,なんだかそれに限らない気もしたのでブチあげました.

時間がない人へのまとめ

忙しい人のために,一言でまとめるとこうです.

1
2
3
4
5
6
7
8
9
Class HogeView extends Backbone.View
  initialize: ->
    @listenTo @model, ‘change’, render
    do @render
  render:->
    $oldel = @$el
    $newel = $(<render HTML by template engine>)
    @setElement $newel
    $oldel.replaceWith $newel

次の段落からは簡単な形から流れに沿って最終系になるように説明していきます.

ベーシックなよくある書き方

1
2
3
4
5
6
7
8
9
10
11
class HogeView extends Backbone.View
  el: '.hoge'
  initialize: ->
    do @render
  render:->
    @$el.html ich.hoge_tmpl()

<hoge.jade>
.hoge
script#hoge_tmpl(type="text/html")
  p this is hoge

よくあるのだと、こういう書き方しますよね. この場合、elを指定しているので、既に存在する要素にViewを紐付けています.

ここで使っているテンプレートエンジンはICanHaz.jsです.

要素を作成する場合

次は,既存のの要素ではなく,あるボタンをクリックして 要素を作成するケースを考えてみてください. クリック時に始めてclassがnewされ,それによってDOMに追加されるケースです.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
Class HogeView extends Backbone.View
  initialize: ->
    do render
  render:->
    @$el.html(<HTML rendering by template engine>)

Class ButtonView extends Backbone.View
  el: '.add-btn'
  events:
    ‘click': 'append'
  append:->
    v = new HogeView()
    @$el.append(v.el)

<hoge.jade>
a.add-btn add
script#hoge_tmpl(type="text/html")
  .hoge
    p this is hoge 

こんな感じですかね.

ここで問題が. Hogeviewにはelを指定していないので,new時にデフォルトで新しい空divがelになります. そのあと,renderで.htmlするので空divの中にテンプレートの内容が入ったものがelになります.

1
2
3
4
5
<div>
  <div class=“hoge”>
    <p>this is hoge</p>
  </div>
</div>

className/tagNameの導入?

これを回避するには,classNametagNameといったpropertyを使うのが一つの方法です.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HogeView extends Backbone.View
  className: '.hoge'
  initialize: ->
    do @render
  render:->
    @$el.html ich.hoge_tmpl()

class ButtonView extends Backbone.View
  el: '.add-btn'
  events:
    'click': 'append'
  append:->
    v = new HogeView()
    @$el.append v.el

<hoge.jade>
a.add-btn add
script#hoge_tmpl(type="text/html")
    p this is hoge 

ただし,classNameを用いるためにテンプレートの一番親の要素(.hoge)をHogeViewに持ってきたため見通しが悪い.

setElementを使う

そこでsetElementを使います.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
class HogeView extends Backbone.View
  initialize: ->
    do @render
  render:->
    @setElement ich.hoge_tmpl()

class ButtonView extends Backbone.View
  el: '.add-btn'
  events:
    'click': 'append'
  append:->
    v = new HogeView()
    @$el.append v.el

<hoge.jade>
a.add-btn add
script#hoge_tmpl(type="text/html")
  .hoge
    p this is hoge 

とすると空divに挟まずに設定できます.

Modelの更新をトリガーにViewを更新する場合

この方法でも,以下のケースのように一度setElementをしたものを更にsetElementで置き換えるようなコードでは正しく動作しません.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
class HogeModel extends Backbone.Model
  defaults:
    index: 0
  initialize: ->
    @listenTo Backbone, 'inc', @inc
  inc: ->
    i = @get 'index'
    @set 'index', i+1
class HogeView extends Backbone.View
  initialize: ->
    @index = 0
    @listenTo @model, 'change', @render
    do @render
  render:->
    @setElement ich.hoge_tmpl
      index: @index++

class ButtonView extends Backbone.View
  el: '.add-btn'
  events:
    'click': 'render'
  initialize: ->
    v = new HogeView
      model: new HogeModel()
    @$el.append v.el
  render:->
    Backbone.trigger 'inc'

<hoge.jade>
script#hoge-tmpl(type=“text/html“)
  .hoge
    p this is hoge

最終系

これを解決するにはこのようにしてあげれば良いです.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
class HogeModel extends Backbone.Model
  defaults:
    index: 0
  initialize: ->
    @listenTo Backbone, 'inc', @inc
  inc: ->
    i = @get 'index'
    @set 'index', i+1
class HogeView extends Backbone.View
  initialize: ->
    @index = 0
    @listenTo @model, 'change', @render
    do @render
  render:->
    $oldel = @$el
    $newel = ich.hoge_tmpl
      index: @index++
    @setElement $newel
    $oldel.replaceWith $newel

class ButtonView extends Backbone.View
  el: '.add-btn'
  events:
    'click': 'render'
  initialize: ->
    v = new HogeView
      model: new HogeModel()
    @$el.append v.el
  render:->
    Backbone.trigger 'inc'

<hoge.jade>
script#hoge-tmpl(type=“text/html“)
  .hoge
    p this is hoge

Conclusion

1
2
3
4
5
6
7
8
9
Class HogeView extends Backbone.View
  initialize: ->
    @listenTo @model, ‘change’, render
    do @render
  render:->
    $oldel = @$el
    $newel = $(<render HTML by template engine>)
    @setElement $newel
    $oldel.replaceWith $newel

のようにすることで,テンプレートの構造そのままをViewに適用することが出来て,正しく表示も更新されます.

refs

http://stackoverflow.com/questions/11594961/backbone-not-this-el-wrapping

Githubの二段階認証を設定しよう

Githubがログインアタック受けたみたいですね.Weak passwords brute forced そんなときにハラハラしないために2段階認証(two-factor authentication)を入れましょう.

Githubにログインして右上の工具マーク(Account Setting)から設定画面に行きます. 2013-11-22 10 56 33

Account Settings を選んで,Two-factor authenticationSet up two-factor authenticationを選択します. 2013-11-22 10 56 47

SMSかアプリか選べと言われるのでSet up using appを選びます. 2013-11-22 10 57 09

すると,右側にQRコードが表示された画面になるので,Google Authenticator でQRコードを読み込ませます. すぐに6桁のコードが表示されるので,2. Enter the 6-digit code that the application generatesに入力してEnableします. 2013-11-22 10 57 21

これで2段階認証の設定が完了です!

エジプトからのアクセスでファラオに呪いをかけられる心配しなくてよくなりましたね!

MongoDBのShellを使いやすくする方法

最近DBはMongoDBしか使ってないぽちです,おはようございます!

Node.js + MongoDBの組み合わせで使うことがほとんどなので,スクリプトからはmongooseを使うのですが,ちょっとDBの中身を確認したりするのでmongodbのshellを使うことも多いです.

普段mongooseを使っていると,mongodbのshellが貧弱で困ることがあります.

1
2
> db.users.findOne({_id:"<存在するObjectId>"})
null

みたいな.

正しくはこう書かねばなりません.

1
2
3
4
5
> db.users.findOne({_id:ObjectId("<存在するObjectId>")})
{
  "_id":ObjectId("<存在するObjectId>")
  :
}

めんどくさい!ObjectIdと毎回タイプするのがめんどくさい!

1
> db.users.findOneById("<存在するObjectId>")

と書ければ楽だなーと思って探したら,

1
> DBCollection.prototype.findOneById = function(id){return this.findOne({_id:ObjectId(id)});};

とかやってあげると

1
2
3
4
5
db.users.findOneById("<存在するObjectId>")
{
  "_id":ObjectId("<存在するObjectId>")
  :
}

楽ちんだ!

findOneByIdとか長い!

1
2
3
4
5
6
> DBCollection.prototype.findOneById = DBCollection.prototype.fbi = function(id){return this.findOne({_id:ObjectId(id)});};
db.users.fbi("<存在するObjectId>")
{
  "_id":ObjectId("<存在するObjectId>")
  :
}

楽ちんだ!!!!さすがFBI!!!連邦捜査局!!!

はい,でもこれを毎回打つのが超めんどくさい.

そういう時は,~/.mongorc.jsを配置しましょう.

1
2
3
4
5
6
7
$ echo "DBCollection.prototype.findOneById = DBCollection.prototype.fbi = function(id){return this.findOne({_id:ObjectId(id)});};" > ~/.mongorc.js
$ mongo hoge
> db.users.fbi("<存在するObjectId>")
{
  "_id":ObjectId("<存在するObjectId>")
  :
}

楽ちん!

上ではfindOneByIdだけですが,どんどん追加できるので,決まったaggregateがあるならパラメータを渡す簡単構文でaggregateしたりmongooseでいうpopulate的な動作も出来ると思います.

meshというmongo shellに色々ライブラリを読み込んでくれるのもあるのですが,欲しいのがなかったので自分で作って公開するといいよ!

まとめ

  • ~/.mongorc.jsにmongo shellを拡張するスクリプトを置くと便利!

Backbone.jsのイベントをデバッグ出力するやつをモジュール化した

土日で艦これイベント海域E-4をなんとか突破したぽちです,おはようございます.

今日は島風轟沈の日らしいですね.

ktty1220 さんのBackbone.jsで全イベントをconsoleに出力するデバッグ用スクリプト

が便利だったので使ってたのですが, 毎回backbone.debug.jsをプロジェクトにコピーして回るのが大変だったので bowerでサクッとインストール出来るようにしてみました.

backbone-event-logger

使い方

1
$ bower install backbone-event-logger

でインストールされます.

あとは

1
2
<script src="/bower_components/backbone/backbone-min.js"></script>
<script src="/bower_components/backbone-event-logger/backbone-event-logger.min.js"></script>

みたいに読みこめば,ガンガンログが出ます.

オリジナルとの違い

オリジナルと変更した部分としては

  • Backbone[<ModelとかRouterとか>]::initializeで自動的にログ出力を有効化
  • デフォルトで@constructor.nameをログに出すようにした
  • coffee化
  • 名前を.debugから.event-loggerへ(デバッグっていうよりロガーだなと思ったので)
  • AMD/RequireJSでも読み込めるようにしてみた(けど試してない)

こんな感じで出ます

1
23:04:42 Model:ContentModel  remove [> [ContentModel, Contents, Object]

ContentModel って書いてる部分が@constructor.nameで出してるところ.

イマイチなところ

オリジナルにあったスタイルの変更等はbower installした後に bower_components内を変更しないといけなくて,悩んでいるところ. (自動適用にしたので外部から設定する口を用意してもイマイチかなと)

ところでbower installした時ってbackbone-event-logger.min.jsbackbone-event-logger.js 以外のファイルは配置されるべきなんだろうか?ルールがわからない. (backbone.jsのbowerも配置されてた)

まとめ

iOS7のパララックス効果で左右に動く壁紙を自作する

iPhone 5s/5cを買うのを諦めたぽちです,こんばんは!

そんなわけでとっととiPhone4SにiOS7をいれて使ってます.

今まで使ってた壁紙が使えなくなって悲しみの極みですが, 嘆いていても仕方がないので,新しく作るしかないです.

iOS7からは左右に振ると壁紙が左右にキモい動きをする機能が入ってるっぽいです. 検索したらキモい動きを切る方法ばっかり出てきたので,作り方を書いておくです.

実は超カンタンで,

1
2
3
4
iPad 2 and iPad mini: 1,424 x 1,424
iPad 3 and iPad 4: 2,448 x 2,448
iPhone 4S: 1,360 x 1,040
iPhone 5: 1,536 x 1,040

のサイズで作ればいいだけでした.

この機能の呼び名がパララックス効果壁紙(parallax effect wallpapers)とか ダイナミック壁紙(dynamic wallpapers)とか色々言われてるっぽいですけど,どれが一般的な呼び名なんでしょうね.