20201009のJavaScriptに関する記事は16件です。

Vimium でクリックできない要素をクリック可能にする

はじめに

「クリックできない!」というケースに遭遇することがまれにあるので。

Vimium でのクリック

Vimium は、ブラウジング操作を Vim のようなキーバインドで可能にする、Chrome 及び Firefox 用の拡張機能です。

Vimium では LinkHints という方法でクリッカブルな要素をクリックできます。
LinkHints というのは、クリック可能な要素のところに文字列を表示し、その文字列がキーボードで入力されたらクリックイベントを発火させるというものです。

が、まれに、クリック可能であるにもかかわらずヒントが表示されないことが起きます。
それでは Vimium だけでクリックすることができません。

なぜクリックできないのか

Vimium は、要素のタグ名や要素の属性、CSS であてられたスタイルなどからクリック可能かどうかを判別しています。

大体の要素は正しく判別できますが、HTML の書き方があまりよろしくない場合などはクリック可能であると判別されずヒントが表示されません。

やるべきことはこれだけ

なら、ユーザースクリプトで HTML を編集し、クリック可能であることを明示してあげればよいのです。
幸い、Vimium はよくできていて、属性をちょこっといじるだけで済みます。

targetElement.setAttribute('role', 'button');

要素のrole属性をbuttonに設定しています。

role属性というのは、要素の意味付けのための属性で、WAI-ARIA というアクセシビリティをより良くするための、HTML に付け加える形の言語によるものです。
(詳しくは私もわからないのですが、buttonタグを使えなかったときに、読み上げツールなどに対し「ボタンである」と示すために使われるものだそうです)。

これで、クリック可能になります。

おわりに

HTML は正しく書こうね!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Rails,JS】コメントの非同期表示を実装する方法

この記事では、コメントを非同期で表示できる実装方法を解説します。

  • Rails_6.0.0_ を使用しています。
  • 商品が出品・購入できるアプリケーションにコメントをつけます。
  • それぞれの商品(item)の詳細ページにコメントをする場所があります。
  • コメントの保存や送信に必要なRubyのコーディングと、保存したコメントを即時に表示させるJavaScriptのコーディングを行います。
  • すでにコメント投稿機能は完成している体で、非同期機能だけ実装していきます。

画面収録 2020-10-06 -1.mov.gif

実装内容

  • channelを用いて実装を行う
  • コメントを非同期で表示
  • コメントには名前(name)、コメントされた日付(created_at)、コメント内容(text)を表示

channelとは?

channelとは?

チャネルとは、即時更新機能を実現するサーバー側の仕組みのことです。データの経路を設定したり、送られてきたデータをクライアントの画面上に表示させたりします。

channelでどんなことができる?

データの経路を設定したり、送られてきたデータを表示させるJavaScriptを記述すれば、送信したデータが非同期で表示できます。

channelのファイル作成

ターミナルで以下コマンドを実行
% rails g channel comment
(commentには自分が作成するファイルの名前を記述)
いくつかファイルができますが、今回は以下2つのファイルを使用します。

app/channel/comment_channel.rb

クライアントとサーバーを結びつけるためのファイルです。

app/javascript/channels/comment_channel.js

サーバーから送られてきたデータをクライアントの画面に表示させるためのファイルです。

comment_channel.rbの記述

class MessageChannel < ApplicationCable::Channel
  def subscribed
    stream_from "comment_channel"
  end

  def unsubscribed
  end
end

stream_from "comment_channel"を記述することでサーバーとクライアントを結びつけることができます。

comments_controller.rbの記述

コントローラーの記述です。
すでに非同期でないコメント実装機能は済んでいる体なので、非同期に関する記述以外の説明は割愛します。

データベースからJSに渡したいデータを記述する

今回JSで反映させたいデータは以下の通りです

  • ユーザーのニックネーム
  • コメントされた時間
  • コメントのテキスト
  • アイテムのid(どの商品にコメントするかを判断するために必要)

この3つの情報を、コントローラーでJSに渡してあげる必要があります。

class CommentsController < ApplicationController
  before_action :authenticate_user!
  def create
    @comment = Comment.new(comment_params)
    @item = Item.find(params[:item_id])
    @comments = @item.comments.includes(:user).order('created_at DESC')
    if @comment.valid?
      @comment.save
            ActionCable.server.broadcast  'comment_channel', content: @comment, nickname: @comment.user.nickname, time: @comment.created_at.strftime("%Y/%m/%d %H:%M:%S"), id: @item.id
    else
      render "items/show"
    end
  end

  private
    def comment_params
    params.require(:comment).permit(:text).merge(user_id: current_user.id, item_id: params[:item_id])
    end
end

今回の実装で書き足したのは以下の1文だけです。
ActionCable.server.broadcast 'comment_channel', content: @comment, nickname: @comment.user.nickname, time: @comment.created_at.strftime("%Y/%m/%d %H:%M:%S"), id: @item.id

contentusertimeidをJSで使用するので、そちらの定義をしてあげました。
content
@commentで定義している:text、Commentテーブルのtextカラム、すなわち入力したコメントのことです。
user
@commentに紐づいているuserのnicknameをとってきています。(commentとuserにアソシエーションを組んでいます。)
time
Commentテーブルのcreated_atカラムです。strftime("%Y/%m/%d %H:%M:%S")と記述することで任意の日付、時刻設定を表示できます。以下の記事を参考にさせていただきました。
strftime を憶えられない (rubyの)
item
自分が今表示しているアイテムのページだけでJSが発火する必要があるので、それを判断するために使用します。

comment_channel.rbの記述(JavaScript)

今回はapp/javascript/channels/comment_channel.jsreceived()部分に記述していきます。

app/javascript/channels/comment_channel.js
received(data) {
}

()の中にdataと記述してあげることで、コントローラーで定義した値がとってこれるようになります。receivedは、受け取るという意味なので、データを受け取ったら、この中に記述しているJSを実行してね!ということになります。
さあ、これからこの中にJSの記述をしていきます!

今開いているアイテムページだけでコメント表示できるように、条件分岐分を書きます。
app/javascript/channels/comment_channel.js
//現在開いているページのURLをゲット
let url = window.location.href
//スラッシュ(/)ごとに要素を取り出す
let param = url.split('/');
//このアプリの場合、URLの一番最後にアイテムidがきており、それをparamItemとして定義
let paramItem = param[param.length-1]
// パラメータid(URLの中に含まれているid)が、コントローラーから送った`data.id`かどうかを判断する
if (paramItem == data.id) {}

if文で、条件分岐をしてあげます。次は、処理の内容を条件分岐分の中に書いてあげます。
記述する内容は、

  • div要素を生成する
  • 生成した要素をブラウザに表示させる
  • 表示させるテキストを生成する

といった流れです。

表示させるためのdivを作る
app/javascript/channels/comment_channel.js
    //表示させる場所のdivのIDをとってくる
    const comments = document.getElementById('comments');
    // すでにあるビューファイルと同じになるようにdivを作成
    const textElement = document.createElement('div');
    textElement.setAttribute('class', "comment-display");

    const topElement = document.createElement('div');
    topElement.setAttribute('class', "comment-top");

    const nameElement = document.createElement('div');
    const timeElement = document.createElement('div');

    const bottomElement = document.createElement('div');
    bottomElement.setAttribute('class', "comment-bottom");

createElementメソッドを使用し、div要素を作成、必要なものにはそれぞれsetAttributeメソッドでclass名を与えてあげます。

ちなみに、コメントを表示するビューは以下の通り

      <div id='comments'>
      </div>
      <% @comments.each do |comment| %>
        <div class='comment-display'>
          <div class='comment-top'>
            <div><%= comment.user.nickname %></div>
            <div><%= l comment.created_at %></div>
          </div>
          <div class='comment-bottom'>
            <p><%= comment.text %></p>
          </div>
        </div>
      <% end %>

div要素を生成しましたが、まだブラウザに表示されていません。ブラウザに表示させ、かつ親子関係を作りましょう。

app/javascript/channels/comment_channel.js
      // 生成したHTMLの要素をブラウザに表示させる
      comments.insertBefore(textElement, comments.firstElementChild);
      textElement.appendChild(topElement);
      textElement.appendChild(bottomElement);
      topElement.appendChild(nameElement);
      topElement.appendChild(timeElement);

insertBefireメソッドと、appendChildメソッドを使用します。
親要素.insertBefore(追加する要素, どこに追加するのか)
親要素.appendChild(追加する要素)
で、insertBefireは任意の場所に、appendChildは親クラスの中の最後のに要素を入れることができます。

もう少し詳しく見たい方は以下をご覧ください
【JavaScript】appendChildとinsertBeforeの違い

div要素が作成できたら、次は表示させる情報をとってきましょう。

app/javascript/channels/comment_channel.js
      const name = `${data.nickname}`;
      nameElement.innerHTML = name;
      const time = `${data.time}`;
      timeElement.innerHTML = time;
      const text = `<p>${data.content.text}</p>`;
      bottomElement.innerHTML = text;

表示させる情報をそれぞれ変数に入れています。dataは、received(data) {}のdataです。コントローラーで定義した値のことです。それぞれcontent,nickname,time,を定義しましたね。
innerHTMLで、既存の要素にHTMLを上書きをします。

ここまでで表示は完了しました!
ですがこのままだと、2つ問題があります。
1. データは表示されたが、コメント入力欄にコメントが残ったままであること
2. HTMLはデフォルトでボタンが1回しか押せない仕様になっていること
これを解決しましょう!

データ送信した後にコメント入力欄のコメントを消す
app/javascript/channels/comment_channel.js
    const newComment = document.getElementById('comment_text');
    newComment.value='';

コメント入力欄のIDをとってきて、そこの値を空にする、という記述です。

何度もコメントボタンを押せるようにする
app/javascript/channels/comment_channel.js
    const inputElement = document.querySelector('input[name="commit"]');
    inputElement.disabled = false;

"コメントする"ボタンの、name属性をとってきて、そこをdisabled = falseとしてあげることで何度もクリック可能になります。

記述をまとめると以下の通りです。
app/javascript/channels/comment_channel.js
import consumer from "./consumer"

consumer.subscriptions.create("CommentChannel", {
  connected() {
  },

  disconnected() {
  },
  // ↓データを受け取ったら実行してね
  received(data) {
    let url = window.location.href
    let param = url.split('/');
    let paramItem = param[param.length-1]
    if (paramItem == data.id) {
      const comments = document.getElementById('comments');
      const comment = document.getElementsByClassName('comment-display');
      //使用する要素の作成
      const textElement = document.createElement('div');
      textElement.setAttribute('class', "comment-display");
      const topElement = document.createElement('div');
      topElement.setAttribute('class', "comment-top");
      const nameElement = document.createElement('div');
      const timeElement = document.createElement('div');
      const bottomElement = document.createElement('div');
      bottomElement.setAttribute('class', "comment-bottom");
      // 生成したHTMLの要素をブラウザに表示させる
      comments.insertBefore(textElement, comments.firstElementChild);
      textElement.appendChild(topElement);
      textElement.appendChild(bottomElement);
      topElement.appendChild(nameElement);
      topElement.appendChild(timeElement);
      // 表示するテキストを生成
      const name = `${data.nickname}`;
      nameElement.innerHTML = name;
      const time = `${data.time}`;
      timeElement.innerHTML = time;
      const text = `<p>${data.content.text}</p>`;
      bottomElement.innerHTML = text;
      //コメントを送った後、コメント欄をからにする
      const newComment = document.getElementById('comment_text');
      newComment.value='';
      //何度もボタンを押せるようにする
      const inputElement = document.querySelector('input[name="commit"]');
      inputElement.disabled = false;
    }
  }
});

おわりに

完成したと思っていましたが、これを書いている時点でいくつもミスや、ちょっとよくわからない記述を発見しました。
リファクタリングがいかに大切かよく分かりました。
正しく無い記述があるかもしれませんが、誰かのお役に立てれば幸いです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Symbol from NEMのトランザクションをパースする

Symbol from NEM で実装されるアグリゲートトランザクションによって、ブロックチェーンは信用と信用の途切れ目を無くし、さらに複雑な用途に使用できるようになります。

ただ、一つのトランザクションの内部に複数のトランザクションを詰め込むこという設計のため、そのトランザクションを解析するには注意が必要です。

今回は、アグリゲートトランザクションを他の一般のトランザクションと同列に解析する方法を説明します。

事前準備

まず、いつものようにChromeのコンソールをF12で開いてスクリプトを読み込みます。

NODE = 'https://sym-test.opening-line.jp:3001';
GENERATION_HASH = '6C1B92391CCB41C96478471C2634C111D9E989DECD66130C0430B5B8D20117CD';

(script = document.createElement('script')).src = 'https://xembook.github.io/nem2-browserify/symbol-sdk-pack-0.21.0.js';
document.getElementsByTagName('head')[0].appendChild(script);
必要なライブラリのインポート
nem = require("/node_modules/symbol-sdk");
rxjs = require("/node_modules/rxjs");

txHttp  = new nem.TransactionHttp(NODE);

解析function

これが今回の要となる解析用の関数です。

function parseTxs(txs){

    for(tx of txs){

        //アグリゲートトランザクション判定
        if(tx.type === nem.TransactionType.AGGREGATE_COMPLETE 
        || tx.type === nem.TransactionType.AGGREGATE_BONDED){

            //アグリゲートの場合、内部トランザクションを再取得
            txHttp.getTransaction(

                tx.transactionInfo.hash,
                nem.TransactionGroup.Confirmed

            ).subscribe(tx =>{

                console.log("== aggregateTx ==")
                //この関数を再帰的に呼び出し
                parseTxs(tx.innerTransactions);  //再帰呼び出し
                console.log("-----------------")
            });
        }else {
            //ここに解析する処理を記述します。
            console.log(tx);
        }
    }
}

解析ファンクションはトランザクションを配列で受け取ります。
for ループで1件ずつトランザクションを取り出し、処理していきます。
TransactionTypeがアグリゲートトランザクションでなければそのままコンソール出力して次の処理へうつります。
アグリゲートトランザクションだった場合はinnerTransactionsの中をさらに再帰的に同じ関数で処理させます。
ここで注意点としては、REST APIでトランザクションをリストで取得した場合、innerTransactionsの情報が入っていません。なので、リストで一つずつ取り出したトランザクションがアグリゲートトランザクションだった場合は、そのHASH値をキーにinnerTransactions情報も含む全情報を取り直す必要があります。innerTransactionsが取れればその関数の内部から同じ関数を呼び出せば再帰的に処理していくことができます。

呼び出しプログラム

上記解析関数を呼び出すプログラム例です。

txHttp.search({

//  type:[
//      nem.TransactionType.AGGREGATE_COMPLETE,
//      nem.TransactionType.AGGREGATE_BONDED
//  ],
    order:nem.Order.Desc,
    group:nem.TransactionGroup.Confirmed

})
.subscribe(
    page => parseTxs(page.data)
)

最新の承認済みトランザクションを新しいもの順に抽出します。
コメントアウトしている部分は検索条件としてアグリゲートトランザクションだけに絞り込みます。
検索結果はページングされているので、parseTxs関数へは内部のdata部分のみを渡します。

これで以下のように、普通のトランザクションとアグリゲートトランザクションを同列に解析することができるようになりました。

image.png

ぜひ、お試しください。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】ホイスティングとは?

はじめまして!Qiita初投稿になります。
最近JavaScriptの勉強を始めました。
折角なので学習したことをアウトプットしていきたいと思います。
基本的な内容が多いですが、私と同じ初学者方の参考になれば幸いです。

また至らない点がありましたら、ご指摘いただけると嬉しいです。

今回はJavaScriptを勉強中に、ホイスティングという耳慣れない用語が登場したので調べて見ました。

ホイスティングとは

変数や関数の定義をコードが実行される前にメモリに配置する挙動のことを指します。
その挙動から「宣言の巻き上げ」とも言われます。

言葉ではイメージしづらいので早速コードを見ていきましょう。

関数のホイスティング

f(); //f関数を実行

function f(){
    console.log("Hoisting Test"); // Hoisting Test
}

こちらのコードですが、一見すると実行文が関数の定義より前にあり、エラーになりそうですが問題なく動作しています。

これはホイスティングによってコードが実行される前に関数の定義がメモリに配置されているため、実行することができています。

これがホイスティングによる動きになります。

続いて変数のホイスティングを見てみましょう。

varを使った場合

console.log(x); // undefined

var x = 0; // 変数xの宣言

今度は変数xの宣言よりも前にconsole.logでxの値を取得しています。
こちらも一見エラーになりそうですが、undefinedが出力されます。

変数も関数同様、ホイスティングによってコード実行前に変数xのメモリを確保しているので、console.logで問題なく参照することができています。

注意点としては、行っているのはメモリの確保のみで、値は代入されていないので中身はundefined(未定義)となります。
undefinedは値が未定義の場合に自動で代入されるキーワードになります。

イメージとしては以下になります。

// 事前に変数xのメモリ確保とundefinedの代入を行ってからコードを実行!

console.log(x); // 空の中身を出力。

var x = 0; // 0を代入。

var変数ではこういった処理がコード実行前に行われるため、エラーが発生することなく変数の値を出力することができます。

let、const場合

console.log(x); // ReferenceError

let x = 0;
console.log(x); // ReferenceError

const x = 0;

let、const変数の場合は、
[Uncaught ReferenceError: Cannot access 'x' before initialization]
というエラーメッセージが表示されます。

これは変数xに値が値が入っていませんよ(初期化されていない)というエラーです。

どういうことかと言うと、let、constではvarのように自動でundefinedが代入されません。
よって、メモリ確保はされているので参照することはできますが、変数の値を取得しようとしてもエラーが発生することになります。

個人的にエラーがでてくれた方が安心感があります。

ということで、varとlet、constではホイスティングの挙動が異なるということになります。

予期せぬエラー

さて、関数や変数のホイスティングの動きを見てきましたが、この挙動を理解していないと意図していない値の取得、エラーの原因となります。

例えば、以下のコードを見てみましょう。

let x = 10; //グローバルスコープの変数x。

{
    console.log(x); // グローバルスコープの変数xを取得したい。でもエラー

    let x = 0; //後からブロックスコープで変数xを宣言。
}

グローバルスコープで宣言した変数xの値をブロックスコープ内で取得したい時、誤って後から同名の変数xをブロックスコープで宣言してしまうと、ReferenceErrorが発生してしまいます。

ホイスティングはスコープごとに発生するのですが、今回はブロックスコープ内で宣言した変数xがホイスティングにより、中身が無い状態でブロックスコープの最初に定義されるため、参照されるのはブロックスコープの変数xとなります。

そしてletの中身は空なので、値を出力しようとしてもReferenceErrorになるわけです。

イメージとしては以下になります。

let x = 10; //グローバルスコープの変数x。

{
    // x 中身が無い変数xがここにある。

    console.log(x); // 上の空のxを参照する。

    let x = 0; //ここで初めて値が入る
}

この場合、グローバル変数xの値を取得するには、ブロックスコープの変数名を変更してあげれば良いのですが、慣れないうちは予期せずこういったエラーに遭遇するかもしれません。

ホイスティングの動きを理解していれば冷静に対処できそうです。

まとめ

ホイスティングについて記述しました。ホイスティングによって変数や関数の定義が事前にメモリに配置されるということになります。慣れないうちはホイスティングによる挙動を意識してコードを記述していきたいと思います。

至らない点がありましたら、ご指摘いただけると嬉しいです。

参考

https://developer.mozilla.org/ja/docs/Glossary/Hoisting
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/var

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

BFE.dev解答記録 #88. JavaScriptでnegative indexをサポートする

https://bfe.dev/ja はFrontEnd版のLeetCode、GAFAの面接を受けるなら練習した方がいいかなと。
以下は自分の練習記録です。

スクリーンショット 2020-10-09 15.57.52.jpg

BFE.dev#88. JavaScriptでnegative indexをサポートする をみてみよう

説明をみよう

const originalArr = [1,2,3]
const arr = wrap(originalArr)

arr[0] // 1
arr[1] // 2
arr[2] // 3
arr[3] // undefined
arr[-1] // 3
arr[-2] // 2
arr[-3] // 1
arr[-4] // undefined

新しいobjetを返して、プロパティにいい感じのindexとvalueを設定するのは可能です。例えば {0:1, 1:2, 2:3, -1:3, -2:2, -3:1}

だけど下記のように元々の配列とシンクするのは不可能のようだ。

arr[-1] = 5
arr // [2,3,5]
originalArr // [2,3,5]

getter/setterを使えばsyncできそう、このように

Object.defineProperty(arr, '-1', {
  get() {
     return originalArr[2]
  },
  set(value) {
     originalArr[2] = value
  }
})

だけの-1の他、-2... -originalArr.lengthもあるので、全部いちいち変更するのは無理を感じる。

Proxy は救世主

問題の説明からでは、ある意味のProxyが求められるのはわかりました。

  1. 全ての変更は元のarrayにする
  2. negative indexのみ、戻り値をいじる

なず基本のproxyを書く

function wrap(arr) {
  return new Proxy(arr, {
    get(target, prop) {
      return target[prop]
    },

    set(target, prop, value) {
      target[prop] = value
      return true
    }
  })
}     

このproxyはただproxyするだけなので、negative indexはサポートしていない、それ以外のデータはちゃんと返している。

Alt Text

indexをいじる

propはnumberではないことを注意してください、まずnumberに変換しとこう。

get(target, prop) {
  let index = parseInt(prop, 10)
  // negative index
  if (index < 0) {
    index += target.length;
    return target[index]
  }

  return target[prop]
},

set(target, prop, value) {
  let index = parseInt(prop, 10)
  // negative index
  if (index < 0) {
    index += target.length;
    // negative index overflowed, too small
    if (index < 0) {
      throw new Error('not ok')
    }
    target[index] = value
    return true
  }

  target[prop] = value
  // don't forget to return true to indicate success
  return true
}                   

iterableではない!!

Alt Text

[...arr]は動かない。。

[...arr] は実はSymbol.Iteratorを実行している、上記のproxyではdata持っていないから、元の配列のinteratorを実行する必要があるので、Function.prototype.bind() はいいかも、このように。

get(target, prop) {
  if (prop === Symbol.iterator) {
    return target[Symbol.iterator].bind(target)
  }
  ...
}

通った!

Alt Text

上記のコードはこちらで見れます。https://bigfrontend.dev/ja/problem/support-negative-Array-index/discuss/22

もし興味あれば、 BFE.devでやってみましょう

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

冷やかしで作った翻訳コマンド(TUI)が便利過ぎるのでガチ使い!

GAS をラップした翻訳ツール (TUI command) を Go 言語で書きました。
tran_1.gif

対象読者

以下に該当する方にとっては読む価値があるかもしれません。

  • 英語(や他の外国語)が苦手で翻訳ツールが手放せない
  • ターミナルで作業する時間が長い
  • translate-shell よりシンプルで視認性の良いコマンドを探している
  • GAS (Google Apps Script) に興味がある
  • Golang (Go 言語) の TUI ツールの実装に興味がある

目次

1. はじめに

英語が苦手な僕は、ドキュメントを読むときも書くときも、辞書サイトや翻訳サイトが手放せません。
普段、ターミナルで作業をすることが多いんですが、Web ブラウザとターミナルソフトの間を行ったり来たりしているので、それはそれはストレスが溜まります。

そんなある日、GAS (Google Apps Script) を触ったことがなかった僕は、後学のためにちょっと試してみようと思い、LanguageApp をラップしたお手軽翻訳コマンドを作ってみたんです。

最初は洒落で作り始めたつもりだったんですが、意外にもこれが便利に使えることがわかったので、記事にして紹介したいと思います。

コマンドは以下のリポジトリで公開しています。

類似するものに translate-shell というツールがあります。使っている方も多いと思いますが、本稿の後半で go-tran と translate-shell の違いについても触れています。

2. go-tran の概要

対応OS

Linux と Windows で動作確認しています。
(Mac でも動くと思います)

対話型インターフェス

tran コマンドを引数なしで実行すると、対話型シェルが起動します。

翻訳する

デフォルトでは、実行環境のカレント・ロケールの言語へ翻訳されるので、あとは翻訳したい外国語を入力するだけです。
以下は、英語、フランス語、イタリア語、スペイン語、韓国語、中国語を日本語へ翻訳する例です。
tran_1.gif
上記画像ではプロンプトが :ja> となっています。
これは (Source):(Target)> の形式になっていて、変換元 (Source) の言語が自動 (Auto) で、変換先 (Target) の言語が日本語 (ja) という意味です。例えば、日本語から英語へ翻訳する場合は ja:en> と表示されます。

言語を切り替える

変換先の言語を切り替えるには、2 桁の言語コード (ISO639-1) を入力します。
以下は、ターゲットの言語を切り替えながら、日本語を英語 (en)、フランス語 (fr)、イタリア語 (it)、スペイン語 (es)、韓国語 (ko)、中国語 (zh) へ翻訳する例です。
tran_2.gif

言語コードが分からない場合

言語コードが分からない場合は l (エル) コマンド (l: Language codes) で調べられます。

l コマンドは、入力した文字列が ISO639-1 コードと同じか、ISO 言語名称 (英名) に部分一致したものの一覧を表示します。どちらにも引っかからなかった場合は、内部で英語に変換してから再検索をかけるため、例えば日本語で入力した名称でも、目的の言語コードを比較的簡単に見つけることができます。

以下は「 l コマンドで "nor" を含むもの、"nese" を含むもの、"ドイツ語"、"クルド語"、"ズールー語" を検索し、その一覧は表示できたが、"カンブリア語" は見つからなかった」という例です。
tran_3.gif

バッチ・インターフェス

コマンドライン引数にファイル名を指定したり、コマンドの標準入力へパイプやリダイレクトで繋いだ場合はバッチ・コマンドとして実行されます。

ファイルの指定

引数にファイルのパス名を渡して go-tran を実行すると、そのファイルを翻訳してくれます。これも変換先の言語(デフォルト値)は実行環境のカレント・ロケールにより決まります。
tran_5.gif

パイプやリダイレクト

標準入力がターミナルではない場合、go-tran はバッチモードで実行されます。

例えば、僕の Ubuntu 環境は man が日本語で表示されるようになっているのですが、日本語のマニュアルが用意されていないコマンドもあります。普段あまり使わないコマンドほど日本語化されていなかったりしますよね。これまでは、知らない英単語に出くわしたときはわざわざ Web ブラウザを開いて調べていました。

でもこれからは man を go-tran にリダイレクトするだけで済みます。
tran_4.gif

3. go-tran の使い方

インストール

Go の環境がある場合

install go-tran
$ go get github.com/y-bash/go-tran
$ cd $GOPATH/src/github.com/y-bash/go-tran
$ go install ./...

Go の環境がなく、ソースコードのみ取得する場合

get source code
$ git clone https://github.com/y-bash/go-tran.git

もし、Go の環境はないけどバイナリを使ってみたいという方がいたら、方法を考えますので、気軽に声をかけてくださいね。

対話型で使う

起動する

実行ファイルは tran という名前です。

$ tran
Welcome to the GO-TRAN!
...

コマンドの説明

入力した文字は概ね以下のように判断されます。

文字列 説明
1 文字 コマンドとして判断。
2 文字 Target 言語の切り替え。
3 文字以上 入力したテキストを現在のモードに従って翻訳。

コマンドの種類は以下のとおりです。

コマンド 説明
h ヘルプを表示します。
l [str] 言語コードと名称の一覧を表示します。
(言語コードは ISO639-1)
  • str を指定した場合
    マッチする言語コードの一覧を表示
  • str を省略した場合
    すべてのコード (184 件) を表示
  • コード、名称に "en" を含むもの
    l en
  • 名称に "nor" を含むもの
    l nor
  • 名称に英名 "Welsh" を含むもの
    l ウェールズ語
s [str] 変換元の言語 (Source) を切り替えます。
  • str を指定した場合
    マッチする言語へ切り替え
  • str を省略した場合
    Auto (自動モード)へ切り替え
    ※通常は Auto で問題なし
  • 英語へ
    s en
  • スペイン語へ
    s spanish
  • ポルトガル語へ
    s ポルトガル語
  • Auto(自動モード) へ
    s
[t] code

t [str]
変換先の言語 (Target) を切り替えます。
  • t を省略した場合
    2 桁のコード (ISO639-1) で
    指定した言語へ切り替え
  • t の後に str を指定した場合
    マッチする言語へ切り替え
  • t の後の str を省略した場合
    実行環境のカレント・ロケール
    へ切り替え
  • カレント・ロケールへ
    t
  • 英語へ
    t en
  • ラテン語へ
    t ラテン語
  • C 言語へ
    c
q 終了

キー操作

主なキーバインドは以下のとおりです。
(bash 互換です)

カーソル移動

キー 説明 代替キー
Ctrl-a 行頭へ移動 Home
Ctrl-e 行末へ移動 End
Ctrl-b 1文字後退
Ctrl-f 1文字前進

文字削除

キー 説明 代替キー
Ctrl-h カーソル位置の手前の文字を削除 Back
space
Ctrl-d カーソル位置の文字を削除 Delete
Ctrl-k カーソル位置から行末まで削除
Ctrl-u 行頭からカーソル位置の手前まで削除

履歴操作

入力したコマンドやテキストはヒストリに保存されるので、キー操作でそれを辿ることができます。

キー 説明 代替キー
Ctrl-p 入力履歴を遡る
Ctrl-n 入力履歴を進む
Ctrl-r 入力履歴のインクリメンタルサーチ

バッチコマンドとして使う

書式

コマンドの書式は以下のとおりです。

format
tran [options] [path...]

オプション

オプションは以下のとおりです。

option 説明
-h ヘルプを表示します。 $ tran -h
-l 言語コードと名称の一覧 (184件) を表示します。
(言語コードは ISO639-1)
$ tran -l
-s code 変換元の言語 (Source) を
言語コード(ISO639-1)で指定します。
省略した場合は自動判別。
$ tran -s en
-t code 変換先の言語 (Target) を
言語コード(ISO639-1)で指定します。
省略した場合はカレント・ロケールへ変換。
$ tran -t en

パス

path の指定方法は以下のとおりです。

path 説明
指定 指定したファイルを翻訳して標準出力へ出力します。 $ tran a.txt b.txt
省略
  • 標準入力がターミナルの場合
    対話型で起動
  • 標準入力がパイプやリダイレクトの場合
    翻訳した結果を標準出力へ出力
  • ss コマンドの man を翻訳
    $ man ss|tran|less
  • Go のコンパイルエラーを翻訳
    $ go build . 2>&1|tran
  • ファイルをリダイレクト
    $ tran < a.txt

※セル内の "|" は全角文字です。
コピペの際はご注意ください。

使用例

難しい英単語を取り急ぎ確認

$ echo "penpineappleapplepen" | tran
ペンパイナッポーアップルペン

日本語のファイルを英語にする

$ tran -t en Japanese.txt
this is a pen.

日本語のファイルをラテン語にして保存

$ tran -t la Japanese.txt > Latin.txt

自動翻訳の結果を日本語に還元して、しっくりくるか確認

$ echo "僕はりんごが大好きです" | tran -t en | tran
りんごが大好き

strings.Builder (Golang) のソースコードの全コメント行を日本語にする

$ grep '^\s*//' ./builder.go | sed -e 's/^\s*..\s*//' | tran
Copyright 2017 The GoAuthors。全著作権所有。
このソースコードの使用は、BSDスタイルによって管理されています
LICENSEファイルにあるライセンス。
Builderは、Writeメソッドを使用して文字列を効率的に構築するために使用されます。
...

カレントディレクトリ配下の全ログファイルのエラー行を日本語にする

$ find . -name '*.log' | xargs grep 'error:' | tran
./tool/vim/src/auto/config.log:conftest.c:154:12:エラー: 'GETACLCNT'が宣言されていません(この関数での最初の使用)
./tool/vim/src/auto/config.log:conftest.c:154:26:エラー: 'NULL'が宣言されていません(この関数での最初の使用)
...

4. translate-shell との比較

実は僕が translate-shell のことを知ったのは、 go-tran を作り始めた後でした。
機能もほとんど似ており、コマンド名も同じ trans だったので驚きました。
(最初は go-trans という名前だったんです)

しかも、translate-shell は品詞などの付加情報を表示してくれたり、単語に複数の意味がある場合はそれを表示してくれたりと高機能です。

でも、少し使ってみると、go-tran には go-tran の良さがあることが次第にわかってきました。

translate-shell のメリット

translate-shell は、翻訳結果だけでなく以下のような付加情報も表示してくれます。

  • 変換元の言語
  • 変換先の言語
  • 品詞(単語の場合)
  • 複数の意味(単語の場合)

translate-shell の実行例
translate-shell_1.gif

辞書のように使えるため、英作文などでは使い勝手が良いかもしれませんね。
とはいってもがっつり英文ドキュメントを書かなければならない場合はもっとしっかりした辞書が必要になると思います。

go-tran のメリット

translate-shell に対する go-tran のアドバンテージは以下のとおりです。

  • シンプル
    余計な情報を出力しないため見やすいです。
    品詞などの付加情報は時として邪魔になります。
  • カラー表示
    カラー表示なので、見たい箇所を瞬時に判別できます。
  • 言語コードの検索機能
    go-tran なら言語コードを簡単に見つけられます。
    translate-shell では言語コードを記憶しておくか、ドキュメントを見なければならないでしょう。
  • bash ライクなキーバインド
    go-tran は文字編集がしやすいです。
    translate-shell は標準入力を単に読み取っているだけのようで、文字編集に難があります。
  • ヒストリ機能
    go-tran は前に入力したコマンドやテキストを再表示し、それを編集して実行することができます。
    これも translate-shell にはない機能です。
  • Windows ネイティブ対応
    go-tran は Windows のコマンドプロンプトで実行できます。
    translate-shell では Cygwin などの UNIX 互換環境が必要なようです。

go-tran の使用イメージ
tran_1.gif

5. 作り方は簡単

GAS (Google Apps Script) で作る Web API も、Go で作るクライアントもとても簡単です。

GAS で Web API を作る

tanabee さんの大人気記事を参考にさせていただきました。

参考にさせていただいたコードに、以下の処理を追記しています。

  • json で返却
  • GET/POST に対応
  • 例外処理
  • 例外メッセージを英語で出力

コードは以下のとおりです。

Translate.gs
// 共通処理
// param e イベント
function process(e) {
    let body // レスポンスボディ
    try {
        const p = e.parameter                                       // リクエスト・パラメータ
        const s = LanguageApp.translate(p.text, p.source, p.target) // 翻訳
        body = {code: 200, text: s}                                 // レスポンス・ボディの作成
    } catch (e) { // param e 例外
        // 翻訳に失敗した
        try {
            const msg = LanguageApp.translate(e.toString(), "", "en") // エラーメッセージを英語へ
            body = {code: 400, message: msg}                          // レスポンス・ボディの作成
        } catch (e) { // param e 例外
            // これに失敗するということは内部エラー
            body = {code: 500, message: e.toString()}                 // レスポンス・ボディの作成
        }
    }
    let resp = ContentService.createTextOutput()    // レスポンス
    resp.setMimeType(ContentService.MimeType.JSON)  // MIME タイプを JSON 形式にして
    resp.setContent(JSON.stringify(body))           // レスポンス・ボディを JSON 形式の文字列に変換
    return resp;                                    // レスポンスを返却
}

// GET
// param e イベント
function doGet(e) {
    return process(e) // 共通処理の呼び出し
}

// POST
// param e イベント
function doPost(e) {
    return process(e) // 共通処理の呼び出し
}

上記コードで process(e) と 2 つの catch(e) で同名の変数を使っているのが気になる方は変数名を変更してください。

なお、スクリプトを修正した後に Project versionNew にしないとプロダクトに反映されないということを知らなかったため、これで 20 分くらい悩みました。

Go でクライアントを作る

GAS で作った Web API をラップするクライアント側の関数(Go 言語)のは以下のとおりです。

tran.go
package tran

import (
        "encoding/json"
        "errors"
        "io/ioutil"
        "net/http"
        "net/url"
        "strings"
)

const gURL = "https://script.google.com/macros/s/@@script@@/exec" // @@script@@ 要修正

// レスポンスの JSON をマップする構造体
type TransData struct {
        Code    int    `json:"code"`    // レスポンス・ステータス
        Text    string `json:"text"`    // 翻訳結果
        Message string `json:"message"` // エラーメッセージ
}

// 翻訳
func Translate(text, source, target string) (string, error) {
        // パラメータの作成
        v := url.Values{}
        v.Add("text", text)     // 変換元の文字列
        v.Add("srouce", source) // 変換元の言語 (ISO639-1)
        v.Add("target", target) // 変換先の言語 (ISO639-1)

        // 翻訳 API へ POST でリクエスト
        resp, err := http.PostForm(gURL, v)
        if resp != nil {
                defer resp.Body.Close()
        }
        if err != nil {
                return "", err
        }
        // レスポンス・ボディをバッファへ読み込む
        buf, err := ioutil.ReadAll(resp.Body)
        if err != nil {
                return "", err
        }
        // レスポンス・ボディ (JSON 形式) を構造体へマップ
        var td TransData
        if err := json.Unmarshal(buf, &td); err != nil {
                return "", err
        }
        // レスポンス・ステータスの検査
        if td.Code != 200 {
                // エラー処理 (プレフィクスの "Exception: " を取り除く)
                msg := td.Message
                prefix := "exception:"
                if strings.HasPrefix(strings.ToLower(msg), prefix) {
                        msg = string(msg[len(prefix):])
                        msg = strings.TrimSpace(msg)
                }
                return "", errors.New(msg)
        }
        // 正常処理
        return td.Text, nil // 構造体から変換後の文字列を取り出して返却
}

難しいことは何もしておらず、一番シンプルな方法で HTTP リクエスト (POST) を投げているだけなんですが、以下の点は注意してくださいね。

  • 上記コードの @@script@@@ の箇所は、それぞれの GAS プロジェクトに合わせて修正する必要があります。
  • エラーが発生した際、メッセージのプレフィクス "Exception: " を取り除く処理を行っていますが、これは状況によっては不要な処理だと思います。

これ以外のコードについては、こちら をご覧ください。

6. go-tran の課題

現時点で以下のような課題がありますが、改善はそれほど難しくないと思います。

  • レスポンス・タイム
    現在、サーバ側は GAS の無料プランを使っているということもあり、トラフィックが集中した際のアクセス制限でレスポンスが悪くなることが考えられます。
    これについては、コマンドの config 設定でユーザ固有の URL を指定できるようにしようと考えています。そうすれば、利用者が自分の Google Account に GAS スクリプトを登録する負担は増えますが、それと引き換えに他人のことを気にせずにコマンドを使うことができるようになるはずです。

  • バッチの出力形式
    ログファイルやドキュメントを翻訳する場合はやはり原文と比較しながら見たいですよね。
    これについては、変換元と変換先を行ごとに交互に出力するオプションを用意する予定です。おそらく対応は難しくないでしょう。

7. おわりに

本稿では、Go 言語で翻訳ツール (TUI コマンド) を作ったという話と、そのコマンドの使い方を紹介しました。作り方についても簡単に紹介しました。また、既に似たツールが存在することを知り、最初はがっかりするも次第に自作コマンドも悪くないと思うようになったということを書きました。

GAS の翻訳 API は簡単に使える上に、皆に知れ渡っています。
最初は、そんな API を使ったプロダクトなんて特に面白いこともできないだろうと思っていましたが、実際に作ってみると意外にも便利なものを作ることができました(オレオレ仕様なのであたりまえなのですが…)。

また自作してみることで、人気ツールのメリットとデメリットの両方が見えるようになりました。

世に知れ渡ったソリューションは星の数ほどあります。
でも今回の経験は、そういったものの中にも、

  • コモディティ化したように見えるが、まだ誰も気づいていない使い道がある
  • デファクトスタンダードになっているが、まだ誰も気づいていない改善点がある

そういったことを改めて認識する機会となりました。

それでは!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

冷やかしで作った翻訳コマンド(TUI)が便利すぎるのでガチ使い!

GAS をラップした翻訳ツール (TUI command) を Go 言語で書きました。
tran_1.gif

対象読者

以下に該当する方にとっては読む価値があるかもしれません。

  • 英語(や他の外国語)が苦手で翻訳ツールが手放せない
  • ターミナルで作業する時間が長い
  • translate-shell よりシンプルで視認性の良いコマンドを探している
  • GAS (Google Apps Script) に興味がある
  • Golang (Go 言語) の TUI ツールの実装に興味がある

目次

1. はじめに

英語が苦手な僕は、ドキュメントを読むときも書くときも、辞書サイトや翻訳サイトが手放せません。
普段、ターミナルで作業をすることが多いんですが、Web ブラウザとターミナルソフトの間を行ったり来たりしているので、それはそれはストレスが溜まります。

そんなある日、GAS (Google Apps Script) を触ったことがなかった僕は、後学のためにちょっと試してみようと思い、LanguageApp をラップしたお手軽翻訳コマンドを作ってみたんです。

最初は洒落で作り始めたつもりだったんですが、意外にもこれが便利に使えることがわかったので、記事にして紹介したいと思います。

コマンドは以下のリポジトリで公開しています。

類似するものに translate-shell というツールがあります。使っている方も多いと思いますが、本稿の後半で go-tran と translate-shell の違いについても触れています。

2. go-tran の概要

対応OS

Linux と Windows で動作確認しています。
(Mac でも動くと思います)

対話型インターフェス

tran コマンドを引数なしで実行すると、対話型シェルが起動します。

翻訳する

デフォルトでは、実行環境のカレント・ロケールの言語へ翻訳されるので、あとは翻訳したい外国語を入力するだけです。
以下は、英語、フランス語、イタリア語、スペイン語、韓国語、中国語を日本語へ翻訳する例です。
tran_1.gif
上記画像ではプロンプトが :ja> となっています。
これは (Source):(Target)> の形式になっていて、変換元 (Source) の言語が自動 (Auto) で、変換先 (Target) の言語が日本語 (ja) という意味です。例えば、日本語から英語へ翻訳する場合は ja:en> と表示されます。

言語を切り替える

変換先の言語を切り替えるには、2 桁の言語コード (ISO639-1) を入力します。
以下は、ターゲットの言語を切り替えながら、日本語を英語 (en)、フランス語 (fr)、イタリア語 (it)、スペイン語 (es)、韓国語 (ko)、中国語 (zh) へ翻訳する例です。
tran_2.gif

言語コードが分からない場合

言語コードが分からない場合は l (エル) コマンド (l: Language codes) で調べられます。

l コマンドは、入力した文字列が ISO639-1 コードと同じか、ISO 言語名称 (英名) に部分一致したものの一覧を表示します。どちらにも引っかからなかった場合は、内部で英語に変換してから再検索をかけるため、例えば日本語で入力した名称でも、目的の言語コードを比較的簡単に見つけることができます。

以下は「 l コマンドで "nor" を含むもの、"nese" を含むもの、"ドイツ語"、"クルド語"、"ズールー語" を検索し、その一覧は表示できたが、"カンブリア語" は見つからなかった」という例です。
tran_3.gif

バッチ・インターフェス

コマンドライン引数にファイル名を指定したり、コマンドの標準入力へパイプやリダイレクトで繋いだ場合はバッチ・コマンドとして実行されます。

ファイルの指定

引数にファイルのパス名を渡して go-tran を実行すると、そのファイルを翻訳してくれます。これも変換先の言語(デフォルト値)は実行環境のカレント・ロケールにより決まります。
tran_5.gif

パイプやリダイレクト

標準入力がターミナルではない場合、go-tran はバッチモードで実行されます。

例えば、僕の Ubuntu 環境は man が日本語で表示されるようになっているのですが、日本語のマニュアルが用意されていないコマンドもあります。普段あまり使わないコマンドほど日本語化されていなかったりしますよね。これまでは、知らない英単語に出くわしたときはわざわざ Web ブラウザを開いて調べていました。

でもこれからは man を go-tran にリダイレクトするだけで済みます。
tran_4.gif

3. go-tran の使い方

インストール

Go の環境がある場合

install go-tran
$ go get github.com/y-bash/go-tran
$ cd $GOPATH/src/github.com/y-bash/go-tran
$ go install ./...

Go の環境がなく、ソースコードのみ取得する場合

get source code
$ git clone https://github.com/y-bash/go-tran.git

もし、Go の環境はないけどバイナリを使ってみたいという方がいたら、方法を考えますので、気軽に声をかけてくださいね。

対話型で使う

起動する

実行ファイルは tran という名前です。

$ tran
Welcome to the GO-TRAN!
...

コマンドの説明

入力した文字は概ね以下のように判断されます。

文字列 説明
1 文字 コマンドとして判断。
2 文字 Target 言語の切り替え。
3 文字以上 入力したテキストを現在のモードに従って翻訳。

コマンドの種類は以下のとおりです。

コマンド 説明
h ヘルプを表示します。
l [str] 言語コードと名称の一覧を表示します。
(言語コードは ISO639-1)
  • str を指定した場合
    マッチする言語コードの一覧を表示
  • str を省略した場合
    すべてのコード (184 件) を表示
  • コード、名称に "en" を含むもの
    l en
  • 名称に "nor" を含むもの
    l nor
  • 名称に英名 "Welsh" を含むもの
    l ウェールズ語
s [str] 変換元の言語 (Source) を切り替えます。
  • str を指定した場合
    マッチする言語へ切り替え
  • str を省略した場合
    Auto (自動モード)へ切り替え
    ※通常は Auto で問題なし
  • 英語へ
    s en
  • スペイン語へ
    s spanish
  • ポルトガル語へ
    s ポルトガル語
  • Auto(自動モード) へ
    s
[t] code

t [str]
変換先の言語 (Target) を切り替えます。
  • t を省略した場合
    2 桁のコード (ISO639-1) で
    指定した言語へ切り替え
  • t の後に str を指定した場合
    マッチする言語へ切り替え
  • t の後の str を省略した場合
    実行環境のカレント・ロケール
    へ切り替え
  • カレント・ロケールへ
    t
  • 英語へ
    t en
  • ラテン語へ
    t ラテン語
  • C 言語へ
    c
q 終了

キー操作

主なキーバインドは以下のとおりです。
(bash 互換です)

カーソル移動

キー 説明 代替キー
Ctrl-a 行頭へ移動 Home
Ctrl-e 行末へ移動 End
Ctrl-b 1文字後退
Ctrl-f 1文字前進

文字削除

キー 説明 代替キー
Ctrl-h カーソル位置の手前の文字を削除 Back
space
Ctrl-d カーソル位置の文字を削除 Delete
Ctrl-k カーソル位置から行末まで削除
Ctrl-u 行頭からカーソル位置の手前まで削除

履歴操作

入力したコマンドやテキストはヒストリに保存されるので、キー操作でそれを辿ることができます。

キー 説明 代替キー
Ctrl-p 入力履歴を遡る
Ctrl-n 入力履歴を進む
Ctrl-r 入力履歴のインクリメンタルサーチ

バッチコマンドとして使う

書式

コマンドの書式は以下のとおりです。

format
tran [options] [path...]

オプション

オプションは以下のとおりです。

option 説明
-h ヘルプを表示します。 $ tran -h
-l 言語コードと名称の一覧 (184件) を表示します。
(言語コードは ISO639-1)
$ tran -l
-s code 変換元の言語 (Source) を
言語コード(ISO639-1)で指定します。
省略した場合は自動判別。
$ tran -s en
-t code 変換先の言語 (Target) を
言語コード(ISO639-1)で指定します。
省略した場合はカレント・ロケールへ変換。
$ tran -t en

パス

path の指定方法は以下のとおりです。

path 説明
指定 指定したファイルを翻訳して標準出力へ出力します。 $ tran a.txt b.txt
省略
  • 標準入力がターミナルの場合
    対話型で起動
  • 標準入力がパイプやリダイレクトの場合
    翻訳した結果を標準出力へ出力
  • ss コマンドの man を翻訳
    $ man ss|tran|less
  • Go のコンパイルエラーを翻訳
    $ go build . 2>&1|tran
  • ファイルをリダイレクト
    $ tran < a.txt

※セル内の "|" は全角文字です。
コピペの際はご注意ください。

使用例

難しい英単語を取り急ぎ確認

$ echo "penpineappleapplepen" | tran
ペンパイナッポーアップルペン

日本語のファイルを英語にする

$ tran -t en Japanese.txt
this is a pen.

日本語のファイルをラテン語にして保存

$ tran -t la Japanese.txt > Latin.txt

自動翻訳の結果を日本語に還元して、しっくりくるか確認

$ echo "僕はりんごが大好きです" | tran -t en | tran
りんごが大好き

strings.Builder (Golang) のソースコードの全コメント行を日本語にする

$ grep '^\s*//' ./builder.go | sed -e 's/^\s*..\s*//' | tran
Copyright 2017 The GoAuthors。全著作権所有。
このソースコードの使用は、BSDスタイルによって管理されています
LICENSEファイルにあるライセンス。
Builderは、Writeメソッドを使用して文字列を効率的に構築するために使用されます。
...

カレントディレクトリ配下の全ログファイルのエラー行を日本語にする

$ find . -name '*.log' | xargs grep 'error:' | tran
./tool/vim/src/auto/config.log:conftest.c:154:12:エラー: 'GETACLCNT'が宣言されていません(この関数での最初の使用)
./tool/vim/src/auto/config.log:conftest.c:154:26:エラー: 'NULL'が宣言されていません(この関数での最初の使用)
...

4. translate-shell との比較

実は僕が translate-shell のことを知ったのは、 go-tran を作り始めた後でした。
機能もほとんど似ており、コマンド名も同じ trans だったので驚きました。
(最初は go-trans という名前だったんです)

しかも、translate-shell は品詞などの付加情報を表示してくれたり、単語に複数の意味がある場合はそれを表示してくれたりと高機能です。

でも、少し使ってみると、go-tran には go-tran の良さがあることが次第にわかってきました。

translate-shell のメリット

translate-shell は、翻訳結果だけでなく以下のような付加情報も表示してくれます。

  • 変換元の言語
  • 変換先の言語
  • 品詞(単語の場合)
  • 複数の意味(単語の場合)

translate-shell の実行例
translate-shell_1.gif

辞書のように使えるため、英作文などでは使い勝手が良いかもしれませんね。
とはいってもがっつり英文ドキュメントを書かなければならない場合はもっとしっかりした辞書が必要になると思います。

go-tran のメリット

translate-shell に対する go-tran のアドバンテージは以下のとおりです。

  • シンプル
    余計な情報を出力しないため見やすいです。
    品詞などの付加情報は時として邪魔になります。
  • カラー表示
    カラー表示なので、見たい箇所を瞬時に判別できます。
  • 言語コードの検索機能
    go-tran なら言語コードを簡単に見つけられます。
    translate-shell では言語コードを記憶しておくか、ドキュメントを見なければならないでしょう。
  • bash ライクなキーバインド
    go-tran は文字編集がしやすいです。
    translate-shell は標準入力を単に読み取っているだけのようで、文字編集に難があります。
  • ヒストリ機能
    go-tran は前に入力したコマンドやテキストを再表示し、それを編集して実行することができます。
    これも translate-shell にはない機能です。
  • Windows ネイティブ対応
    go-tran は Windows のコマンドプロンプトで実行できます。
    translate-shell では Cygwin などの UNIX 互換環境が必要なようです。

go-tran の使用イメージ
tran_1.gif

5. 作り方は簡単

GAS (Google Apps Script) で作る Web API も、Go で作るクライアントもとても簡単です。

GAS で Web API を作る

tanabee さんの大人気記事を参考にさせていただきました。

参考にさせていただいたコードに、以下の処理を追記しています。

  • json で返却
  • GET/POST に対応
  • 例外処理
  • 例外メッセージを英語で出力

コードは以下のとおりです。

Translate.gs
// 共通処理
// param e イベント
function process(e) {
    let body // レスポンスボディ
    try {
        const p = e.parameter                                       // リクエスト・パラメータ
        const s = LanguageApp.translate(p.text, p.source, p.target) // 翻訳
        body = {code: 200, text: s}                                 // レスポンス・ボディの作成
    } catch (e) { // param e 例外
        // 翻訳に失敗した
        try {
            const msg = LanguageApp.translate(e.toString(), "", "en") // エラーメッセージを英語へ
            body = {code: 400, message: msg}                          // レスポンス・ボディの作成
        } catch (e) { // param e 例外
            // これに失敗するということは内部エラー
            body = {code: 500, message: e.toString()}                 // レスポンス・ボディの作成
        }
    }
    let resp = ContentService.createTextOutput()    // レスポンス
    resp.setMimeType(ContentService.MimeType.JSON)  // MIME タイプを JSON 形式にして
    resp.setContent(JSON.stringify(body))           // レスポンス・ボディを JSON 形式の文字列に変換
    return resp;                                    // レスポンスを返却
}

// GET
// param e イベント
function doGet(e) {
    return process(e) // 共通処理の呼び出し
}

// POST
// param e イベント
function doPost(e) {
    return process(e) // 共通処理の呼び出し
}

上記コードで process(e) と 2 つの catch(e) で同名の変数を使っているのが気になる方は変数名を変更してください。

なお、スクリプトを修正した後に Project versionNew にしないとプロダクトに反映されないということを知らなかったため、これで 20 分くらい悩みました。

Go でクライアントを作る

GAS で作った Web API をラップするクライアント側の関数(Go 言語)のは以下のとおりです。

tran.go
package tran

import (
        "encoding/json"
        "errors"
        "io/ioutil"
        "net/http"
        "net/url"
        "strings"
)

const gURL = "https://script.google.com/macros/s/@@script@@/exec" // @@script@@ 要修正

// レスポンスの JSON をマップする構造体
type TransData struct {
        Code    int    `json:"code"`    // レスポンス・ステータス
        Text    string `json:"text"`    // 翻訳結果
        Message string `json:"message"` // エラーメッセージ
}

// 翻訳
func Translate(text, source, target string) (string, error) {
        // パラメータの作成
        v := url.Values{}
        v.Add("text", text)     // 変換元の文字列
        v.Add("srouce", source) // 変換元の言語 (ISO639-1)
        v.Add("target", target) // 変換先の言語 (ISO639-1)

        // 翻訳 API へ POST でリクエスト
        resp, err := http.PostForm(gURL, v)
        if resp != nil {
                defer resp.Body.Close()
        }
        if err != nil {
                return "", err
        }
        // レスポンス・ボディをバッファへ読み込む
        buf, err := ioutil.ReadAll(resp.Body)
        if err != nil {
                return "", err
        }
        // レスポンス・ボディ (JSON 形式) を構造体へマップ
        var td TransData
        if err := json.Unmarshal(buf, &td); err != nil {
                return "", err
        }
        // レスポンス・ステータスの検査
        if td.Code != 200 {
                // エラー処理 (プレフィクスの "Exception: " を取り除く)
                msg := td.Message
                prefix := "exception:"
                if strings.HasPrefix(strings.ToLower(msg), prefix) {
                        msg = string(msg[len(prefix):])
                        msg = strings.TrimSpace(msg)
                }
                return "", errors.New(msg)
        }
        // 正常処理
        return td.Text, nil // 構造体から変換後の文字列を取り出して返却
}

難しいことは何もしておらず、一番シンプルな方法で HTTP リクエスト (POST) を投げているだけなんですが、以下の点は注意してくださいね。

  • 上記コードの @@script@@@ の箇所は、それぞれの GAS プロジェクトに合わせて修正する必要があります。
  • エラーが発生した際、メッセージのプレフィクス "Exception: " を取り除く処理を行っていますが、これは状況によっては不要な処理だと思います。

これ以外のコードについては、こちら をご覧ください。

6. go-tran の課題

現時点で以下のような課題がありますが、改善はそれほど難しくないと思います。

  • レスポンス・タイム
    現在、サーバ側は GAS の無料プランを使っているということもあり、トラフィックが集中した際のアクセス制限でレスポンスが悪くなることが考えられます。
    これについては、コマンドの config 設定でユーザ固有の URL を指定できるようにしようと考えています。そうすれば、利用者が自分の Google Account に GAS スクリプトを登録する負担は増えますが、それと引き換えに他人のことを気にせずにコマンドを使うことができるようになるはずです。

  • バッチの出力形式
    ログファイルやドキュメントを翻訳する場合はやはり原文と比較しながら見たいですよね。
    これについては、変換元と変換先を行ごとに交互に出力するオプションを用意する予定です。おそらく対応は難しくないでしょう。

7. おわりに

本稿では、Go 言語で翻訳ツール (TUI コマンド) を作ったという話と、そのコマンドの使い方を紹介しました。作り方についても簡単に紹介しました。また、既に似たツールが存在することを知り、最初はがっかりするも次第に自作コマンドも悪くないと思うようになったということを書きました。

GAS の翻訳 API は簡単に使える上に、皆に知れ渡っています。
最初は、そんな API を使ったプロダクトなんて特に面白いこともできないだろうと思っていましたが、実際に作ってみると意外にも便利なものを作ることができました(オレオレ仕様なのであたりまえなのですが…)。

また自作してみることで、人気ツールのメリットとデメリットの両方が見えるようになりました。

世に知れ渡ったソリューションは星の数ほどあります。
でも今回の経験は、そういったものの中にも、

  • コモディティ化したように見えるが、まだ誰も気づいていない使い道がある
  • デファクトスタンダードになっているが、まだ誰も気づいていない改善点がある

そういったことを改めて認識する機会となりました。

それでは!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

useEffect 初回レンダリングで走るな!!!

useEffectって初回に走るんですよ、
これが面倒で面倒で....
かつてのcomponentWillmountやらcomponentDidUpdateやらを使ったclassコンポーネントを
functionコンポーネントに置き換えようとすると悲劇が起こりやすいです。。(今朝の話)

デモンストレーション

import React, { useEffect, useState } from "react";![スクリーンショット 2020-10-09 13.19.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/441432/19f286a6-7d2f-805c-e2a2-a35fc68e7bf1.png)


export default function App() {
  const [count, setCount] = useState(0);

  useEffect(() => {
    console.log("初回だけ走るよ〜");
  }, []);

  useEffect(() => {
    console.log("カウントが押されたとき走るよ〜");
  }, [count]);

  return (
    <div>
      <span>{count}回押された</span>
      <button onClick={() => setCount(count + 1)}>カウントアップ!</button>
    </div>
  );
}

ボタンが押されるとcountが1upするよくあるやつです。
2回目に出てくるuseEffectカウントが押された時だけ走って欲しいとします。
しかし、実際にコンソールをみてみると....スクリーンショット 2020-10-09 13.10.28.png

ボタンを一度しか押していないのに、
console.log("カウントが押されたとき走るよ〜");
が二回走ってしまっています。
componentDidUpdateと全く同じ挙動を期待するなら、一手間必要です。

解決作

フラグを持たせます。
この場合は無駄なレンダリングを避けるためuseRefを使うのが適切でしょう。

- import React, { useEffect, useState } from "react";
+ import React, { useEffect, useState, useRef } from "react";

export default function App() {
  const [count, setCount] = useState(0);
+  const renderFlgRef = useRef(false)
  useEffect(() => {
    console.log("初回だけ走るよ〜");
  }, []);

  useEffect(() => {
+    if(renderFlgRef.current) {
      console.log("カウントが押されたとき走るよ〜");
+    } else {
+      renderFlgRef.current = true
+    }
  }, [count]);

  return (
    <div>
      <span>{count}回押された</span>
      <button onClick={() => setCount(count + 1)}>カウントアップ!</button>
    </div>

  );
}

スクリーンショット 2020-10-09 13.19.15.png

ちゃんとカウントアップ時のみ走らせることができました。

毎度フラグを作るのだるい~

useEffectがたくさんあるような場合は、その個数分フラグ処理が必要ですね。
正直面倒なのでカスタムフックにします。

useEffectCustom
// 絶対に初回走るな
import { useEffect, useRef } from "react";

const useEffectCustom = (func, dependencyList) => {
  const fisrtFlgRef = useRef(true);

  useEffect(() => {
    if (!fisrtFlgRef.current) {
      func();
    } else {
      fisrtFlgRef.current = false;
    }
  }, dependencyList);
};

export default useEffectCustom;
index
import React, { useEffect, useState } from "react";
import useEffectCustom from "./useEffectCustom";

export default function App() {
  const [count, setCount] = useState(0);
  useEffect(() => {
    console.log("初回だけ走るよ〜");
  }, []);

  useEffectCustom(() => {
      console.log("カウントが押されたとき走るよ〜");
  }, [count]);

  return (
    <div>
      <span>{count}回押された</span>
      <button onClick={() => setCount(count + 1)}>カウントアップ!</button>
    </div>
  );
}

これで毎度フラグ処理を実装せずに済みます!

参考資料

https://qiita.com/nishiurahiroki/items/7e9aa809abc655378682

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

rails simple formでbooleanのvalueをjQueryでgetする方法

jqueryでcheckされたかをどうやって実装するか悩んで調べましたが、あまり情報がなく自分で実装してみました

# rails form, idは"testBool"とします
= f.input :test_bool, as: :boolean

// jquery
$('#testBool').on('click', function() {
  console.log($(this)[0]['checked'])
})

-> 
false
true

この実装方法でいけば動的にtrue, falseを取得することができるのでjs側での制御がかなり楽になります

参考記事

なし

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

キャメルケースとスネークケース

キャメルケース

companyIdblogCategoryのように2語3語と単語同士をつづけた書く書き方。

  • 先頭の単語が小文字

    • 関数名は先頭小文字。例:createBlogCategory()
    • 変数名(引数名)は先頭小文字。例:companyId
  • 先頭の単語が大文字をパスカルケースまたはアッパーキャメルケースと呼ぶ

    • class名は先頭大文字。例:BlogCategory

スネークケース

company_idblog_catefgoryのように2語3語と単語同士をアンダーバーで挟んでつなげる書き方。

  • 定数はすべて大文字。例:BLOG_CATEGORY
  • データベースなどで使われます。

ケバブケース

company-idblog-catefgoryのように2語3語と単語同士をハイフンで挟んでつなげる書き方。

  • HTMLのclass名で使われます。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptの闇 thisへ挑む

はじめに

今回はJavaScriptの闇... thisについて、様々な角度で挑みたいと思います!

【YouTube動画】JavaScriptの闇 this
JavaScriptの闇 this

thisとは

thisは関数の呼び出し元へのリンクです。

例えば、以下の場合test.getName()はtaroを返します。

thisは関数getNameで呼び出されています。その関数getNameはtestオブジェクトの関数なので、this = testオブジェクトになります。
関数getName()のthisをtestオブジェクトに変え、test.name = taroとなります。

const test = {
  name: 'taro',
  getName: function() {
    return this.name;
  }
}

グローバルなthis

関数の読み出し元を指定しない場合はWindowになります。
それを確認してみましょう。

以下2つを定義しておきます。

まず、obj.sayThis()を考えます。
sayThis()はobjオブジェクトから呼び出されているので、関数sayThis内のthisはobjオブジェクトになります。
ということで、obj.sayThis()は { name: 'taro' } を返します

次に、sayThis()を考えます。
こちらは特に呼び出しているオブジェクトがないため、Windowになります。
ちなみに、strictモードで実行すると、undefinedになります。

// メソッド
const sayThis = function() { console.log(this) }

// オブジェクト
const obj = { name: 'taro' }

コンストラクタでのthis

コンストラクタではnewでオブジェクトを作成でき、呼び出し元が定まります。

以下のように定義しておきます。

yamadaの方ではnew Personによりオブジェクトが作成されるので、yamada.nameはtaroを返します。
しかし、tanakaの方はオブジェクトが作成されていないので、name of undefinedエラーになります。

const Person = function(hoge) { this.name = hoge; }

const yamada = new Person('taro')
const tanaka = Person('taro')

プロトタイプメソッド内でのthis

以下のように定義しておきます。
ここで、yamada.getName()とすると、taroが返ります。
ちなみに、Person.getName()の場合はPerson.getName is not a functionになります。

const Person = function(hoge) { this.name = hoge; }
Person.prototype.getName = function() { return this.name; }

const yamada = new Person('taro')

対して、次のようにprototypeをなくした場合は、yamada.getName is not a functionになります。
また、Person.getName()の場合、Personが返ります。

const Person = function(hoge) { this.name = hoge; }
Person.getName = function() { return this.name }

const yamada = new Person('taro')

入れ子関数でのthis

入れ子関数にすると、定義元はどうなるでしょう。

以下を定義して、test.sayHello()を実行すると、上から { sayHello: f }, Windowになります。
最初のthisの呼び出し元はsayHelloで、2番目のthisは呼び出し元が明示されていないためです。

const test = {
  sayHello: function() {
    console.log(this)
    const func = function() {
      console.log(this)
    }
    func()
  }
}

arrow関数でのthis

入れ子関数でもarrow関数にすると、thisの呼び出し元が定まります。

そのため以下の場合、test.sayHello()は、{ sayHello: f }, { sayHello: f } になります。

const test = {
  sayHello: function() {
    console.log(this)
    const func = () => {
      console.log(this)
    }
    func()
  }
}

まとめ

今回はJavaScriptで迷いやすいthisについて解説していきました!
なるほど分からんと思った方、ぜひコンソール画面などで遊んでみてください!

twitteryoutubeでのコメントもお待ちしています!

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

pixi.jsからAdobe Animateで作ったコンテンツを使いたい

はじめに

皆さんはゲームを作る際、デザイナーが作ったUI・アニメーションをどのような形で実装しているでしょうか?

Unityのようにそのあたりの実装機能を持つIDEが内蔵されていればいいんですが、
スクリプトのみでやらなければいけない場合、目コピしたりプロパティ変化を見てみたりと
わりと面倒だったりするんですよね。

私はHTML5ゲームを作るのに、主に pixi.js を使用しているんですが、
シンプルなゲームであればまだしも、アプリ然としたゲームになってくるにしたがって
このアニメーション実装時間かかるよ問題が重くのしかかってくるように。1

そんなこんなで

pixi.jsからAdobe Animateでパブリッシュしたコンテンツを使用できるプラグイン
Pixim-animate-container

を作ってみました。

諸注意

  • このPixim-animate-containerですが、
    名前の頭にもあるようにPixim.js用のプラグインとして制作しています。
    Pixim.jsというのは、pixi.jsをラップして使いやすくしたものと思ってください。
    pixi.js用のビルドもありますが、もし興味があればPixim.jsも触ってみていただけると嬉しいです。
    ※以降はpixi.js向けビルドで説明します。

  • Adobe Animate 20.02/20.5.1で動作を確認しています。(2020/10/09時点)
    書き出されるコンテンツの構造に互換性があれば、前後のバージョンでも動作するかもしれません。


ドキュメントとサンプル

https://tawaship.github.io/Pixim-animate-container/

使いかた

用意するもの

  • CreateJS 1.0.0
  • pixi.js 5.3.2
  • pixi-animate-container 2.0.1
  • Adobe Animateでパブリッシュしたコンテンツ一式

ファイルの読み込み

<script src="https://code.createjs.com/1.0.0/createjs.min.js"></script>
<script src="pixi.5.3.2.min.js"></script>
<script src="pixi-animate-container.min.js"></script>
<script src="content/content.js"></script> <!-- Adobe Animateでパブリッシュしたコンテンツ一式のjsファイル -->
  1. 最初にpixi.js及びCreateJS(順不同)
  2. 次にpixi-animate-container
  3. 最後にcontent/content.js

という順で読み込むようにしてください。

実装

アプリケーションを作る

const app = new PIXI.animate.Application({
    useSynchedTimeline: true,
    useDeltaTime: false
}, {
    width: 450,
    height: 800,
    antialias: true,
    backgroundColor: 0xFFFFFF
});
  • 第一引数はcreatejsをどのように再生するかのオプション。
  • 第二引数はPIXI.Applicationの第一引数として使用するオプション。

コンテンツの準備をする

const prepare = app.prepareAsync({
    id: [conposition id],
    basepath: [content directory path],
    options: {
        crossOrigin: false
    }
});
  • 引数は使用するコンテンツのデータオブジェクト。
    使用するコンテンツが複数あれば、データオブジェクト配列を指定することも可能。

    • id
      content.js内でlib.properties.idとして定義されている32文字くらいの文字列。
    • basepath
      「Adobe Animateでパブリッシュしたコンテンツ一式ディレクトリ」の HTMLから見た相対パス、または絶対パスのいずれか。
    • options.crossOrigin
      アセットの読み込みがcrossOriginになるかどうか。
      basepathに絶対パスを使用、かつオリジンが異なる場合はtrueにする。

使用する

prepare.then(function(lib) {
    class Root extends PIXI.animate.Container {
        constructor() {
            super();

            this.addCreatejs(new lib.game());
        }
    }

    app.stage.addChild(new Root());
    document.body.appendChild(app.view);
});
  • PIXI.animate.Application#prepareAsyncは、指定されたコンテンツのlibで解決される。
    仮に複数のコンテンツが指定されている場合は、libの配列で解決される。

  • PIXI.animate.Container#addCreatejsの引数に、libに定義されている任意のクラスを指定する

補足

PIXI.animate.Container#addCreatejsの戻り値は、引数に指定したcreatejsインスタンスそのものです。
ですので例えば

  • 'lib.game'クラスインスタンスのinstance_3プロパティに格納されているインスタンスを0フレームで止める
this.addCreatejs(new lib.game()).instance_3.gotoAndStop(0);
  • lib.game'クラスインスタンスのalphaを0.5にする
this.addCreatejs(new lib.game()).alpha = 0.5;

のような処理が可能です。
またこれらの処理によって描画内容が変化する場合、もちろん見た目(pixi.jsで作ったcanvasの表示)に反映されます。

おわりに

みんなもpixi.js使ってね!


  1. pixi.jsはspineが使えるので、そっちに行くのもありなんですが、
    AnimateないしFlashに慣れ親しんだデザイナーが多いという背景もあり… 

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Microsoft Teams 開発の初心者向けガイド ? その1: Tabs

みなさんこんにちは。まだまだコロナの余波はありますが皆さんはいかがでしょうか。私の住んでいるサンフランシスコでは、アメリカ歴史上一番酷い規模の山火事も重なったこともあり、まだしばらく前のような生活に戻れません。冬眠したいところなのですが、何かみなさんが新しいことを学ぶために時間を活用しようと思います ??
1-tabs-cover-1000x420.png

さて、このチュートリアルは、Microsoft Teams 開発のための初心者シリーズの私の第一弾。いままで仕事などで Microsoft Teams を使用て、Teams で動くアプリを書いてみたいなと思っている方や、Slack のような他のプラットフォームでアプリを公開たことがあり次は Teams のサポートも付け、より多くのユーザーを獲得したいプロダクト開発者にとって適しています。
また、このチュートリアルを試すにおいて、マイクロソフトのクラウド テクノロジについての前知識や経験は必要のないように書いてみました。

Teams アプリの作成方法を学ぶにはさまざまな方法やツールがありますが、このチュートリアルでは、最小限のコードと最小限のツールセットを使用して視覚的なアプローチを使用します。また、本格的なアプローチで先に進みたい場合は、Microsoft ドキュメントのチュートリアルを試してください。

さて、この初心者シリーズの第一弾として、ちょっと簡単に「タブ」の機能が何であるかを説明してみましょう。

チームの機能: 「タブ」

Teams アプリケーション開発で使える機能には、メッセージング拡張機能、会話型ボットなどのさまざまな機能がありますが、そのひとつに、任意のビジュアル インターフェイスで任意の情報を表示できる タブ があります。
MS Teams tabs
技術的には、タブは単なる iframe ですので、要は任意のウェブページを自由に表示できる枠のようなもの。必要なのは、Teams 上で正しく表示するように構成することです。

ここでは、個人用 パーソナルタブ (インストールしたユーザーのみが表示可能) と チーム タブ (チーム レベルのタブ) の 2 種類のタブを作成する方法を紹介します。

このチュートリアルでやってみること

  1. App Studio でアプリを構成する
  2. オンライン IDE で個人用タブを作成し、そこから直で起動
  3. チャンネル/グループタブに変更するように再構成する
  4. チームタブの機能を追加する
  5. ダークモードのサポートを追加する

最終的な結果は次のようになります。
client-tabs-to-be-opened.gif

? 先に必要な条件

Teams にアプリをインストールできるようにするには、組織の管理者がアクセス許可を付与する必要があります。
それ以外の場合は、無料のMicrosoft 365 開発者プログラム に登録しましょう。このプログラムでは、開発者テナントのサンドボックス、サンプル データ パックに付属しているモック ユーザーデータなどが使え、サブスクリプションはなどでもリニューアルできます。

  • 開発の許可がある Teams テナント、または開発専用テナント (M365 開発者プログラムにサインアップしよう)
  • App Studio - チームクライアントのアプリメニューからアプリを探し、テナントのワークスペースにインストールしてね
  • JavaScript の基礎知識

1. Building a Personal Tab

? コード サンプルの取得

このチュートリアル シリーズでは、プロジェクトのコードのホスティングと実行を簡略化するために、サードパーティのツール、Glitchを使用しています。

Glitch は、node.jsコードを記述して実行できるWebベースのIDEなので、少なくとも今のところ、localhostのトンネル、デプロイを気にすることなく、Teamsアプリ開発の概念と基本を学ぶことに集中できます。(今後ゆくゆく Azure も含め、しっかりカバーしていきたいと思います。)

サンプル コード ページに移動し、プロジェクトを「リミックス」します。リミックスとは GitHub で言うところのリポジトのをフォークのようなようなもので、自分用のプロジェクトのコピーが生成されるため、元のコードに影響なく自分用に好きなように変更できます。
glitch-remix.png
さて、自分専用プロジェクトのリポを取得すると、自動的に独自のWebサーバーURLを取得します。たとえば、生成されたプロジェクトは、3単語ほどでできたランダムな語で構成されています。たとえばそのプロジェクト名が achieved-diligent-bell だったら、ウェブサーバーのURLは https://achieved-diligent-bell.glitch.me になります。必要に応じて名前をカスタマイズすることもできます。
glitch-remix-project-name.png

⚙️ App Studio を使ってアプリ マニフェストを作成

Teams 用のアプリを作成する場合は、Teams クライアントにインストールするアプリ パッケージを作成する必要があります。パッケージには以下が含まれます。

? your-app-package
    └── ? manifest.json
    └── ? color.png (192x192)
    └── ? outline.png (32x32)

残りの、アプリに必要なコードと画像などのアセットは、Web サーバーでホストする必要があります。(このチュートリアルでは、 Glitch からアプリを自動的にランするので特別なサーバの必要はありませんが、将来的に自分でアプリを作成する場合は用意してください。)
現時点ではマニフェスト ファイルを手動で作成するのではなく、App Studio というビジュアル ツールを使用してチーム クライアントに直接アプリ パッケージを作成、インストールできるようにします。

? Using App Studio

チームクライアントで App Studio アプリをインストールしたら、起動させてください。左側のメニュー バーの … をクリックして App Studio をクリック。
teams-open-app-studio.png
次に、上部の Manifest Editor(マニフェスト エディター) タブをクリックし、Create a new app(新しいアプリの作成])を選択します。
teams-new-app-studio.png
アプリ名や説明など、必要なフィールドをすべて入力します。
tabs-appstudio-app.png
 App URLs セクションで、プライバシーと利用規約の Web ページ URL も入力します。この例では、プレースホルダ URL である、 https://example.com を使用していますが、公開するアプリを開発する場合は、フェイクではない URL が必要になってきます。

? Personal tab の設定

左側のメニューから、 Capabilities > Tabs をクリックしてください。
tabs-appstudio-tabs.png
Add a personal tab (個人用タブを追加) の下の Add ボタンをクリックし、情報を入力します。

Content URL で、いまから埋め込むWeb ページの URL を入力します。 (https://[自動生成されたプロジェクト名].glitch.me/index.html といったようなURL になります。)
tabs-app-studio-tab1.png
現在、index.html ファイルには、このように、静的な HTML コード (およびいくつかの CSS) の数行しか含めありません。

<h1>Hello world! </h1>
<p>This is the bare-minimum setting for MS Teams Tabs. </p>

index.html の内容は自由に調整してください。これが、Teams クライアントに表示されるコンテンツになります。

? マニフェスト パッケージの作成を完了する

つぎに、Finish > Test and distribute へ移動します。
tabs-appstudio-install.png
エラーが発生した場合は、それに応じて修正してください。エラーがなければ、Install ボタンをクリックします。

できました!これで左のメニューにパーソナルタブが加わりました?
tabs-final.png
正しくインストールした後に問題が発生した場合は、コンテンツ URL が正しく設定されているか確認してください。 https://[your-project-name].glitch.me/index.html のように、リミックスした自分の Web サーバー URL を使用していますか。 

さて、ここで終了してもいいのですが、次の手順ではもう一歩ふみこんで、チーム用のタブを作っていきます。

2. Building a Channel/Group Tab

それでは、これをチャンネル/グループタブに変換してみましょう。これにはいくつかの追加の手順が必要です。

? Glitch からコード サンプルを取得

今回は、この リミックス リンクをクリックして、コード サンプルを直接リミックスできます。もしくは、前のコード サンプルをそのまま続行して、自分でコードを変更してください。

このコード サンプルには、先ほどのサンプルにはない config.html が含まれています。

⚙️ アプリの設定を調整

App Studio に戻り、作成したアプリを開いて、タブの設定を変更します。(または、別の新しいアプリをしたい場合は、新規アプリを作成しましょう。この場合、また新しいアプリの詳細を入力する必要があります。)

? Team tab の設定

Capabilities > Tabs に移動します。以前に作成したパーソナルタブをそのままにしておいてもいいし、削除してもかまいません。

Team Tab の下の "Add" ボタンをクリックし、情報を入力します。

Configuration URL に Web URL を入力します。 ( https://[your-project-name].glitch.me/config.html のような URL です)。 構成可能なコンテンツがない場合でも、このフィールドは必須です。
tabs-team-app-studio-teamtab.png

タブに JavaScript SDK を使用してコンテンツを読み込む

単純な静的 HTML である個人用タブとは異なり、チーム タブでは、SDK を使用してメイン コンテンツを読み込みます。
config.html には、次のコード行が含まれます。

<script src="https://statics.teams.cdn.office.net/sdk/v1.6.0/js/MicrosoftTeams.min.js"></script>

注: この例では SDK バージョン 1.6.0 を使用しています。コードが期待どおりに動作しない場合は、使用しているバージョンを確認してください。

microsoftTeams.initialize();
microsoftTeams.settings.setSettings({
  contentUrl: 'https://msteams-teamtab-minimum.glitch.me/index.html',
  });
microsoftTeams.settings.setValidityState(true); 

getContext() メソッドを使用して、ユーザー ロケール情報のような動的な値を追加できます。チーム SDK の詳細については、SDK のドキュメントを参照してください。

次に、Finish > Test and distribute へ行き、アプリをインストールします。
すべてがうまくいけば、アプリはクライアントの上部のタブに表示されまーす?

client-tabs-to-be-opened.gif

? ダークモードとハイコントラストモードのサポート

さて、もうひとつ大事なことがあります。もし、アプリのユーザーのクライアントがダークモードになっている場合はどうなるのでしょうか? iframe の中身はクライアントが自動的に処理するでしょうか?
残念ながら答えは?‍♀️(oh no!)。なので、今からそれを修正しましょう。

HTML ファイルの 1 つを見てみましょう。Theme の検出 (および theme 変更イベント) を処理する JavaScript があることに注目してください。
この例では、デフォルトのライト・モードでは、既定のテキストの色 (通常は黒) が使用されます。

microsoftTeams.getContext((context) => {
  if(context.theme !== 'default') {
    document.body.style.color = '#fff';
  }
});
microsoftTeams.registerOnThemeChangeHandler((theme)=> { 
  if(theme !== 'default') {
    document.body.style.color = '#fff';
    document.body.style.color = 'inherit';
  }
});

それでは、テーマを切り替えて、変更がどのように反映されているかを見てみましょう。
クライアントテーマの色を変更するには、右上のアバターから設定メニューを表示します。
ダーク モードまたはハイコントラスト モードでは、フォントの色は白になります。
tabs-dark-mode.png
?
うっひょ〜い!
これで、Hello world から一歩踏み出た Teams アプリ開発を始めてみたいなと思ったり、アプリのアイデアがでてくれたらな、と願っています。

このチュートリアルでは、 Microsoft Teams アプリまたはマイクロソフト・サービス一般の開発の経験ゼロの方ができるだけ簡単に行えるように、推奨ツールセットの使用など述べませんでしたが、実際には、VS Codeを使用した本格的なアプリを作成し、おそらくビルドマネージャーを使ったり、Reactのようなフロントエンドフレームワークを使ったり、Azureにデプロイしたりすることになると思います。これらのチュートリアルもそのうち書いていきたいと思います。お楽しみに。

くわしくは、以下のリンクをご覧ください。

次のチュートリアルでは、対話型ボット、その後はメッセージ拡張機能を作成する方法を紹介します。じゃあ、また ?

?? この記事を英語で読みたいという方は dev.to のリンクからどうぞ!


? Learn more

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[Slick]最初のスライダーから最後のスライダーへ移動すると高速で一周する現象を直したい

なりゆき

Slickのバグかと思って検索しまくったのですが、同一の現象が全く無く困り果てたので(唯一あった同じ現象の対処法は直ったは直ったが理想の動きにはならず、使えなかった)メモ書き+誰かのエラーに刺されば・・・と書き留めます。

原因

CSSに記載していた、

* {
-webkit-transition: all .6s;
transition: all .6s;}

コイツが原因でした。
競合してたのか何なのか、私はこれをcommon.cssで全部に効くようにしていたので、Slickのdivやらbuttonやらなにやらすべてにかかってしまっていたみたいです。

解決法

原因でほぼ解決法はお分かりかと思いますが、
まるっと削除するか、適用範囲をSlickの部分だけ除かせる。

* {
-webkit-transition: all .6s;
transition: all .6s;}

これを削除するか、

*:not(.slick-track) {
-webkit-transition: all .6s;
transition: all .6s;}

:notでslick範囲を除くか。

しかしながら、少しカクツキが残るので理想とは言えない。
どうしたもんかなぁ

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript データ型

JSデータ型

データ型とは、データの種類のことである

データ型一覧

  • 数値型
  • 文字列型
  • 真偽型
  • シンボル型
  • 特殊型
  • 配列
  • オブジェクト
  • 関数

JSはデータ型に寛容である

・ 最初文字列を格納していた変数に数値を格納しても問題ない。
・ 一方JavaやC#などは一つの変数に別のデータ型を格納できない。

JavaScriptの開発において、開発者がデータ型を強く意識しないといけない場面は多くはないが、厳密な演算や厳密な比較を行う局面では、データ型を念頭に置く必要がある。

JavaScriptのデータ型は基本型と参照型の2つに分類できる

両者の違いは、値を変数に格納する方法である


基本型

変数に値そのものが格納される

参照型

変数に参照値を格納する(値を実際に格納するメモリ上のアドレス)

リテラルについて

リテラルとは、データ型に格納できる値そのもの、または値の表現方法である。

それぞれのデータ型に応じたリテラルの表現方法を以下に示す

数値リテラル

数値リテラルは整数リテラルと、浮動小数点リテラルに分類され、さらに整数リテラルは以下のように分類される

  • 数値リテラル
    • 整数リテラル
      • 10進数リテラル
      • 16進数リテラル
      • 8進数リテラル
      • 2進数リテラル
    • 浮動小数点リテラル

以上のように数値リテラルにはさまざまな表現方法があるが、JavaScriptにとっては(0b10010:2進数)(0o22:8進数)(0x12:16進数)(1.8e1:指数)(18:10進数)もすべて同じ。

どの表記を選ぶかはその時どきでの読みやすさに応じて決めるべきである

文字列リテラル

文字列リテラルは'(シングルクォーテーション)"(ダブルクォーテーション)のいずれかで囲む

const str = "hello";

文字列リテラルは、特殊な意味を持つ文字(キーボードから直接表現できない文字)などを\ + 文字で表現することができる。(このような文字のことをエスケープシーケンスという)

// 文字列の中に開業を入れる
alert("こんにちは。\nいい天気だね")

テンプレート文字列を使って、文字列への変数の埋め込みと、複数行の文字列を表現する

const name = "太郎";
// 複数行に渡る文字列に、変数を埋め込む
const str = `こんにちは、${name}さん。 
いい天気ですね!`;

配列リテラル

配列とは、データの集合のことである
配列では一つの変数に複数の値を格納することができ、値の一つ一つを要素という。

配列リテラルは、以下のようにカンマ区切りの値をブラケット[]で囲って表現する。

['dog', 'cat']

各要素にはインデックス番号でアクセスする。

const data = ['dog', 'cat'];
console.log(data[0]);
// >> dog
const data2 = ['dog', ['cat', 'cat2']];
console.log(data2[1][0]);
// >> cat

オブジェクトリテラル

オブジェクトとは名前をキーとして要素にアクセスできる配列のこと。(ハッシュ、連想配列)などとも呼ばれる
通常の配列がインデックス番号しかキーにできないのに対して、オブジェクトでは文字列をキーにアクセスできるため、データの視認性が高い。

オブジェクトの各要素はプロパティという
プロパティには、文字列、数値などの情報はもちろん、関数を格納することもできる。関数が格納されたプロパティのことを特別にメソッドという

オブジェクトリテラルは、以下のように中括弧{}で囲った中に、キー:値がセットになったプロパティをカンマ区切りで記述し表現する。

const data = { 'name':"太郎", 'age':14 }

プロパティへのアクセスは、ドッド演算子による方法と、プラケット構文による方法がある

const data = { 'name':"太郎", 'age':14 }
// ドッド演算子による方法
console.log(data.name);
// >> "太郎"
// ブラケット構文による方法
console.log(data['age']);
// >> 14

関数リテラル

関数リテラルは長くなるので、別ページにまとめます。

未定義値(undefined)

未定義値(undefined)は、ある変数の値が定義されていないことを表す値で、以下のようなケースで返される。

  • ある変数が宣言済みであるものの、値を与えられていない。
  • 未定義のプロパティを参照しようとした。
  • 関数で値が返されなかった。

null

JavaScriptにはもう一つ、該当する値がないことを意味するnullがある。
undefinedとの違い

  • undefinedは定義されていない(そもそも参照することを想定していない)
  • nullは空であることを明示的に表現する(参照されることを想定している)
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【実務未経験】初学者が0から学ぶTypeScript②

参考:TypeScriptの型入門, TypeScript Deep Dive 日本語版
前回:【実務未経験】初学者が0から学ぶTypeScript①
※もし私の記事に誤りがありましたら、ご教示いただけますと幸いです。

オブジェクト型とinterface構文

interfaceという構文を使うことで、オブジェクトとプロパティが持つにも型をつけれる。

interface :接合部分、接触面、接点、仲立ち、連絡(係)、橋渡し(役)

今回は、色々な生き物の、種類(name)体長(size)オスかどうか(male)の値をプロパティとしてもつCreatureDataというオブジェクト型を作ってみました。

interface CreatureData {
  name: string;
  size: number;
  male: boolean;
}

体長100cmのオスのマグロならこんな感じ

interface CreatureData {
  name: string;
  size: number;
  male: boolean;
}
const tuna: CreatureData {
  name: "tuna",
  size: 100,
  male: true,
}

性別を持たない体長5cmのかたつむりだと・・・
(かたつむりって性別がないんだって)

interface CreatureData {
  name: string;
  size: number;
  male: boolean;
}
const snail: CreatureData {
  name: "snail",
  size: 5,
}
//定数snailにはmaleプロパティが無い為型エラー

今度はオスかメスかわからない生き物にも型をつけたいので、CreatureData2というオブジェクト型も追加してみました。

さらに最初にいたマグロと同じ体長のマグロをもう1尾発見したのですが、今度のは損傷が激しく性別が判断できませんでした。このマグロはCreatureData2型をもつ定数damageTunaとしてtunaオブジェクトを代入して扱ってみましょう。

interface CreatureData {
  name: string;
  size: number;
  male: boolean;
}

interface CreatureData2 {
  name: string;
  size: number;
}

const tuna: CreatureData {
  name: "tuna",
  size: 100,
  male: true,
}
const damageTuna: CreatureData2 = tuna;

負傷したマグロ定数damageTunaCreatureData2型なんだからCreatureData型の定数tunaを代入したら型エラーになるんじゃ?(かたつむりの例でダメって言ってなかった?)

TypeScriptは構造的部分型っていう仕組みを採用していて、この場合CreatureDataCreatureData2部分型として問題なく動作するんだって!!

代入する定数の型が、新しく宣言した定数の部分型に当たる場合はこんな使い方ができるみたい。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む