スポンサーリンク

2015年10月15日

[Elixir+Phoniex]JavaScriptへ値を渡す方法まとめ

Goal

Phoenix-FrameworkでのJavaScriptへの値の渡し方を習得する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V7.1, OTP-Version 18.1
Elixir: v1.1.1
Phoenix Framework: v1.0.3
PostgreSQL: postgres (PostgreSQL) 9.4.4

Wait a minute

この記事ではJavaScriptへ、
Phoenix-Frameworkで使っている変数の値を渡す方法を記述しています。
後半の方は知的好奇心を満たすためだけのものですので、ご注意下さい。
Ruby on RailsでJavaScriptへ渡している方法と同じなので、
そちらの方法をご存知の方は見る必要がないと思います。

Index

From Phoenix to JavaScript you pass a value
|> Before you start
|> Pass a value
|> Using content_tag/3
|> Little experiment
|> Extra

Before you start

検証を行うためのプロジェクトを準備します。
既に作成している適当なプロジェクトがあれば、そちらで試しても構いません。

Example:

>cd path/to/create/directory

>mix phoenix.new pass_by_value

>cd pass_by_value

>mix ecto.create

>mix phoenix.server
Ctrl+C
準備終わり。

Pass a value

早速、JavaScriptへ値を渡してみましょう。
デフォルトで生成されているPageコントローラのindexアクションで適当な変数を渡します。

File: web/controllers/page_controller.ex

defmodule PassByValue.PageController do
  use PassByValue.Web, :controller

  def index(conn, _params) do
    render conn, "index.html", hoge: "hoge"
  end
end
indexテンプレートを以下のように編集して下さい。

File: web/templates/page/index.html.eex

<script language="JavaScript">
function sample() {
  var e = document.getElementById("hoge");
  var value = e.getAttribute("data-hoge");
  window.alert(value)
}
</script>

<div id="hoge" data-hoge="<%= @hoge %>">
  <a onclick="sample()">hoge</a>
</div>
動作的には、aタグのリンクをクリックすると渡している値をアラートで表示するだけです。
HTML5にある独自データ属性を利用しています。
(data-*で記述される属性です。)

Using content_tag/3

Phoenix.HTMLのcontent_tag/3関数を利用して、書き換えてみます。

File: web/templates/page/index.html.eex

<script language="JavaScript">
function sample() {
  var e = document.getElementById("hoge");
  var value = e.getAttribute("data-hoge");
  window.alert(value)
}
</script>

<%= content_tag(:div, raw("<a onclick=\"sample()\">hoge</a>"),
                  id: "hoge", data: [hoge: @hoge]) %>
少し、ごちゃごちゃしたような気が…まぁいいや。

Little experiment

幾つか思いついたことを実験してみます。

Q: Elixirの埋め込みコード使えるの?

A: 使えます。

記述しているのはEExテンプレートに対してですので、
こういう風にElixirコードの埋め込みでも書けます。

Example:

<script language="JavaScript">
function sample() {
  window.alert("<%= @hoge %>")
}
</script>

<a onclick="sample()">hoge</a>
ただ、個人的にはあんまりよくない書き方な気がする。
JavaScriptのコードに対して、埋め込みとはいえ別言語の記述が混じるわけですから。
最終出力は、結局HTMLやJavaScriptになるとしても、
プログラムをする時に、複数の言語が混在するわけですから…

Q: マップやリストの値を渡したらどうなるか?

A: 実行時エラーになる。

渡す値のデータ形式がElixirのマップやリストだったらどうなるでしょうか?
まずは、content_tag/3を使って渡してみた。
実行時エラーとなりました。
正確に書くなら、content_tag/3の引数(属性)を解釈する段階で失敗する。
属性の解釈ができないと書いた方が良いかもしれない。
関数の問題なのでどうにもならん。
(詳しく知りたければ、Phoenix_HTMLのソース見て下さい。)
おそらく、マップを渡しても同様のエラーになるのが簡単に予想できるので、
content_tag/3を使わないで渡してみる。

Example:

defmodule PassByValue.PageController do
  use PassByValue.Web, :controller

  def index(conn, _params) do
    render conn, "index.html", hoge: ["hoge", "huge", "foo", "bar"]
  end
end

<div id="hoge" data-hoge="<%= @hoge %>">
  <a onclick="sample()">hoge</a>
</div>
実行して動作を確認したが、リストの内容が一つの文字列(?)になって表示される。
[“hoge”, “huge”, “foo”, “bar”] → hogehugefoobar、といったように。
とりあえず、実行はできるようなのでマップの方も渡してみる。

Example:

defmodule PassByValue.PageController do
  use PassByValue.Web, :controller

  def index(conn, _params) do
    render conn, "index.html", hoge: %{"foo" => "bar"}
  end
end
実行エラーで落ちますね。
エラーの内容を読むと、EEx(HTML)テンプレートで解釈できるデータ形式じゃないからっぽいですね。
そりゃそうか…ってことはEEx(HTML)テンプレートをこう変えてあげれば問題ないですね。

Example:

<div id="hoge" data-hoge="<%= @hoge["foo"] %>">
  <a onclick="sample()">hoge</a>
</div>
こうすれば、key:”foo”の値がでますね。
しかしそうすると、Ectoでよく使うデータ形式である、
リストマップ([%{…}, %{…}])で複数のデータを渡したい時はどうしましょうね。
もう少し、実験してみましょう。

Q: EctoでDBを検索して返ってくる値を渡せないの?

A: JSON形式に変換して渡す

この方法が正しいのか分かりません。
ただ、複数の値を渡すための共通のデータ形式が、JSONしか思いつかなかったんです。
なので、一例としてって程度で…
これは動かない…

Example:

defmodule PassByValue.PageController do
  use PassByValue.Web, :controller

  def index(conn, _params) do
    render conn, "index.html", users: [%{name: "hoge"},
                                      %{name: "huge"},
                                      %{name: "foo"},
                                      %{name: "bar"}]
  end
end

<%= for user <- @users do %>
  <div id="user" data-user-name="<%= user.name %>">
    <a onclick="sample()"><%= user.name %></a>
  </div>
<% end %>
いっそのこと、JavaScriptの関数の引数で渡してみましょうか。
そうすれば、JavaScriptの関数内に直接埋め込みコードを記述するわけではないので、見苦しくはならないはず…
しかし、マップは参照してあげないとEExで解釈できない。
よし!JSONにエンコードしたデータを渡そう!!(妙案)
これで、一応実行できる。

Example:

defmodule PassByValue.PageController do
  use PassByValue.Web, :controller

  def index(conn, _params) do
    users = Poison.encode!([%{name: "hoge", age: 20},
                            %{name: "huge", age: 21},
                            %{name: "foo", age: 23},
                            %{name: "bar", age: 25}])
    render conn, "index.html", users: users
  end
end

<script language="JavaScript">
function sample(users) {
  console.info(users)
  window.alert(users)
}
</script>

<a onclick="sample(<%= @users %>)">users</a>
しかし、視覚的にはConsoleからしか確認できないため、
画面に出力できるように少し修正する。

Example:

<script language="JavaScript">
function sample(users) {
  console.info(users)
  for(var i in users) {
    $("#output").append("<li>" + users[i].name + "(" + users[i].age + ")" + "</li>")
  }
}
</script>

<a onclick="sample(<%= @users %>)">users</a>
<ul id="output"></ul>
usersリンクをクリックすると各ユーザのデータがliタグで表示されます。
段々と脱線してきたので、これで実験終わりにします。
動かなかったら、多分jQueryが必要かも…?

Speaking to oneself

お好きな方法をお使い下さい。
どれを使うかの判断は、お任せします。
Phoenix-FrameworkでFullCalendarを使っているサンプルにて、参考になるソースコードを作成しています。
Github: https://github.com/darui00kara/full_calendar_sample

Bibliography

HTML5.jp: http://www.html5.jp/tag/attributes/data.html
JavaScript プログラミング講座: http://hakuhin.jp/js/json.html
JavaScriptのデバッグが捗る!コンソールにログを出力する方法: http://www.hp-stylelink.com/news/2014/01/20140112.php
設計力に学ぶデザインドリル jQuery入門: http://www.jquerystudy.info/index.html

2015年10月10日

[Elixir+Phoenix]FullCalendar with Phoenix-Framework

Goal

Phoenix-Framework上でFullCalendar(js)を動作させる。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V7.1, OTP-Version 18.1
Elixir: v1.1.1
Phoenix Framework: v1.0.3
PostgreSQL: postgres (PostgreSQL) 9.4.4
FullCalendar: v2.4.0

Wait a minute

FullCalendar(js)を使って、カレンダーを表示してみます。

Index

FullCalendar with Phoenix-Framework
|> Before you start
|> Placement of files
|> Let’s run!!

Before you start

プロジェクトの準備とFullCalendarのダウンロードを行います。
プロジェクトの準備。

Example:

>cd path/to/create/project
>mix phoenix.new full_calendar_sample
>cd full_calendar_sample
>mix ecto.create
>mix phoenix.server
Ctrl+C
FullCalendarのダウンロード。

以下の公式サイトよりダウンロードして下さい。

FullCalendar Download: http://fullcalendar.io/download/

私がダウンロードしたのは、v2.4.0です。

Placement of files

FullCalendarのファイルをPhoenix-Frameworkへ配置します。
配置するファイルは5つです。
  • fullcalendar-2.4.0/fullcalendar.js
  • fullcalendar-2.4.0/fullcalendar.min.css
  • fullcalendar-2.4.0/lib/jquery.min.js
  • fullcalendar-2.4.0/lib/moment.min.js
  • fullcalendar-2.4.0/lang/ja.js (任意、日本語表示したい場合)
プロジェクトに以下のように配置して下さい。

Example:

full_calendar_sample
|
priv
  |
  static
    |
    |-css
    |  |-fullcalendar.min.css
    |
    |-js
       |-fullcalendar.js
       |-jquery.min.js
       |-moment.min.js
       |-ja.js (任意、日本語表示したい場合)
これで配置完了です。

Let’s run!!

お待ちかねのカレンダーを出してみましょう。
cssとjsの読み込みをレイアウトテンプレートで行います。

web/templates/layout/app.html.eex

<!DOCTYPE html>
<html lang="en">
  <head>
    ...
    <link rel="stylesheet" href="<%= static_path(@conn, "/css/fullcalendar.min.css") %>">

    <script src="<%= static_path(@conn, "/js/jquery.min.js") %>"></script>
    <script src="<%= static_path(@conn, "/js/moment.min.js") %>"></script>
    <script src="<%= static_path(@conn, "/js/fullcalendar.js") %>"></script>
    <script src="<%= static_path(@conn, "/js/ja.js") %>"></script>
  </head>

  ...
</html>
デフォルトで配置されているindexテンプレートを以下のように変更します。

web/templates/page/index.html.eex

<script language="JavaScript">
$(document).ready(function() {
  $('#calendar').fullCalendar({

  });
});
</script>

<div id="calendar"></div>
基本的な使い方は公式サイトのUsageを見て下さい。
参考: http://fullcalendar.io/docs/usage/
試しにカレンダーでイベント(予定)を表示してみます。

web/templates/page/index.html.eex

<script language="JavaScript">
$(document).ready(function() {
  $('#calendar').fullCalendar({
    events: [
        {
          title: 'サンプル1',
          start: '2015-10-15'
        },
        {
          title: 'サンプル2',
          start: '2015-10-16',
          end: '2015-10-18'
        },
        {
          title: 'サンプル3',
          start: '2015-10-18',
          url: "http://www.google.co.jp"
        },
        {
          title: 'サンプル4',
          start: '2015-10-01',
          end: '2015-10-07'
        }
      ]
  });
});
</script>

<div id="calendar"></div>

Speaking to oneself

割かし簡単に動いた。
取り急ぎ最初の使い方まで。

Bibliography

2015年10月2日

[Elixir]マクロだってテストしたい!

とある錬金術師の万能薬(Elixir)

Goal

ExUnitからのマクロのテスト方法を習得する。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V7.1, OTP-Version 18.1
Elixir: v1.1.1

Wait a minute

マクロをはどうやってExUnitでテストしたらいいのでしょうか?
丁度良く、友人から強だゲフンゲフン・・・快くお貸し頂いたMetaprogramming Elixirにある方法を、試してみようと思います。

Index

Macro test with ExUnit
|> Before you start
|> Macro test with ExUnit

Before you start

始める前に準備をしましょう!

Example:

>cd path/to/project
>mix new macro_sample
>cd macro_sample
>mix test
よし、ではやっていきましょう!!

Macro test with ExUnit

自作したマクロをExUnitでテストするにはどうすればいいのでしょうか?
結論から言えば、別に難しいことはないです。
難しかったら、誰も使わない気がしますが(笑)
まずは、テスト用の適当なマクロを作成してしまいましょう。

File: lib/macro_sample.ex

defmodule MacroSample do
  defmacro sample(string) do
    quote do
      unquote(string)
    end
  end
end
続いてテストコードです。

File: test/macro_sample_test.exs

ExUnit.start
Code.require_file("lib/macro_sample3.ex")

defmodule MacroSampleTest do
  use ExUnit.Case
  import MacroSample

  test "Ensure loaded?" do
    assert Code.ensure_loaded?(MacroSample3)
  end
end
Code.require_file/2で対象のファイルをrequireしています。
その後、importしています。
Code.ensure_loaded?/1では、読み込んだモジュールがロードされているか確認しています。
ロードされていればtrueを返しています。
テストを実行してみましょう。

Example:

>mix test
.

Finished in 0.09 seconds (0.08s on load, 0.01s on tests)
1 test, 0 failures

Randomized with seed 738000
最初のテストはパスしました。
それでは、本題のマクロをテストコードで使ってみましょう。
といっても、既に読み込みもできているのが確認できているので、後は普通の関数と変わりませんね。
テストコードを追加します。

File: test/macro_sample_test.exs

...

defmodule MacroSampleTest do
  ...

  test "Macro test sample" do
    assert MacroSample.sample("hoge") == "hoge"
  end
end
テスト用に作ったマクロは、渡した引数をそのままunquoteしているだけです。
なので文字列:hogeを渡したら、文字列:hogeが返ってくるはずです。
テストを実行しましょう!

Example:

>mix test
..

Finished in 0.07 seconds (0.07s on load, 0.00s on tests)
2 tests, 0 failures

Randomized with seed 622000
テストが通過しました。

Speaking to oneself

もっと難しいマクロのテストをどうするんだ?とかとか色々とまだあるのですが、
最初の足掛かりとしてはこれで良いです。
気が向いたら続きの記事作ります。

Bibliography

2015年10月1日

[Elixir]alias, require, import, useの使い方を学ぶ

とある錬金術師の万能薬(Elixir)

Goal

alias、require、import、useの使い方を学ぶ。

Dev-Environment

OS: Windows8.1
Erlang: Eshell V6.4, OTP-Version 17.5
Elixir: v1.0.5

Wait a minute

今更ですが、alias、require、import、useの違いを学びます。
それぞれ、どんな機能なのか把握すれば特段難しいこともないですね。

Index

alias, require, import, use
|> alias
|> import
|> require
|> use

alias

モジュールへ別名を付ける時に使います。

Exmaple:

defmodule AliasSample.Sample.Hoge do
  ...
end

defmodule AliasSample.Sample.Huge do
  alias AliasSample.Sample.Hoge, as: Hoge
end

Note:

asオプションをなしで別名定義をすると、モジュール名の最後の部分が自動的に設定されます。
以下は同じ意味になります。
alias AliasSample.Sample.Hoge, as: Hoge == alias AliasSample.Sample.Hoge

import

他のモジュールにある関数やマクロを修飾名をつけないで呼び出す時に使います。

Exmaple:

iex> count([1,2,3])
** (RuntimeError) undefined function: count/1
iex> import Enum, only: [count: 1]
nil
iex> count([1,2,3])
3

Note:

onlyオプションを使えば特定の関数だけ対象にできる。
ない場合は、importしたモジュール全ての関数が対象になる。

require

マクロを展開させる時に使います。

Exmaple:

defmodule RequireSample do
  defmacro sample(arg1, arg2) do
    quote do
      ...
    end
  end
end

iex> RequireSample.sample(arg1, arg2)
エラーが発生し、requireしろとメッセージが表示される。
iex> require RequireSample
nil
iex> RequireSample.sample(arg1, arg2)
正常に実行される。

Note:

マクロを使うためには、コンパイル時にそのモジュールと実装が用意されている必要がある。

use

モジュールのusingを展開させます。

Exmaple:

defmodule UsingSample do
  defmacro __using__(_options) do
    quote do
      ...
      import unquote(__MODULE__)
      ...
    end
  end

  ...
end

defmodule UseSample do
  use UsingSample
end
上記の場合、UsingSample.using/1がUseSampleのuseしている場所で展開されます。

Speaking to oneself

散々ここら辺の機能を使っておいて…よく分かってなかったので、
簡単にまとめてみました。

Bibliography

人気の投稿