20200219のJavaScriptに関する記事は24件です。

JavaScriptの変数宣言「var,let,const」の使い分け

JavaScriptの宣言var,let,constの違い

var,let,constとは

JavaScriptで変数名を宣言するときに使用する。
宣言時にはJavaの場合はintやstringと型を指定する必要があるが、JavaScriptではすべてvarなどで宣言できる。

大きな違いは2つ

1. 再宣言が可能か

間違って同じ名前の変数を宣言してしまうリスクがある。

2. 再代入可能か

意図せず変数を上書きしてしまうリスクがある。

詳しい例

var

再宣言可能、再代入可能

var str = '初期値';
str = '再代入';  //可能
var str = '再宣言';  //可能

//スコープ外でも使用可能
if(true){
  var scope = 'OK';
}
console.log(scope);  //OK

let

再宣言不可、再代入可能

let str = '初期値';
str = '再代入';  //可能
let str = '再宣言';  //不可能

//スコープ外では使用不可
if(true){
  let scope = 'OK';
}
console.log(scope);  //エラー

const

再宣言不可、再代入不可

const str = '初期値';
str = '再代入';  //不可能
const str = '再宣言';  //不可能

//スコープ外では使用不可
if(true){
  const scope = 'OK'; 
}
console.log(scope);  //エラー

//オブジェクトはOK
const obj = {};
obj.name = 'Qiita';  //OK

最後に

Javaを勉強してからJavaScriptを初めたらvarの使い方にものすごく悩まされました。
それと同時にvarってなんにでも使えて便利!と思ったのですがバグになりやすいことを知って驚きました...
letとconstはバグを見つけやすくなると思うのでぜひ活用してみてください。

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

Mochaによるテスト実行時に複数のレポーターを使用する方法

Mochaによるテスト実行時に、複数のレポーターを使用する方法を紹介します。

パッケージをインストール

@mochajs/multi-reporterパッケージをインストールします。

$ npm i -D @mochajs/multi-reporter

Mochaの設定ファイルを更新

Mochaの設定ファイル(.mocharc.jsonなど)にreporterを追加し、@mochajs/multi-reporterを指定します。

.mocharc.json
{
  "reporter": "@mochajs/multi-reporter"
}

mochaコマンドの--reporterオプションで指定することもできます。

package.json
{
  "scripts": {
    "test": "mocha --reporter @mochajs/multi-reporter"
  }
}

@mochajs/multi-reporterの設定ファイルを作成

@mochajs/multi-reporterの設定ファイルとして、.reporters.jsonファイルを作成します。

.reporters.json
{
  "spec": true,
  "xunit": {
    "output": "report.xml"
  }
}

設定ファイルはオブジェクト形式で、キーにはレポーター名、値には有効フラグまたはレポーターオプションを指定します。
つまり上記の設定ファイルでは、specxunitという2つのレポーターを使用し、かつxunitのレポーターオプションでoutputreport.xmlにするという指定になっています。

テストを実行

あとは普通にテストを実行します。

$ npm test

上記の設定の場合、specによって標準出力にテスト結果が表示され、xunitによってreport.xmlファイルが生成されます。
こうすることで、ヒューマンリーダブルな形式で結果を表示しつつ、XMLファイルやJSONファイルを出力してプログラムで扱うといったことが可能になります。

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

【jQuery】要素をn個ずつ表示するtoggleボタン【ぬるっと】

経緯

なが〜〜いリストをn個ずつ段階的に表示させたかった

クリックすると要素をぬるっと表示する「もっと見る」ボタンは
toggle系メソッドで簡単に出来るのですが、
そのまま使うと表示/非表示の切り替えだけだったのでアレンジ。

※今回は、要素をすべて表示しきったら「もっと見る」ボタンを消すようにしています。

間違っているところがあればご指摘下さい。

参考にさせていただいた記事

大変参考になりました。神です。

n件ずつ表示する「もっと見る」ボタンのシンプル実装

デモ

こんな感じです。

https://codepen.io/kotottt/pen/vYOKqxm

HTML/CSS

最初にいくつか表示した状態からスタートします。

HTMLはこちら
細かいところは、伝家の宝刀『割愛』です。

qiita.html
<body>
  <div class="container">
    <div class="p-tags">
      <a class="p-tag">初期表示</a>
      <a class="p-tag">初期表示</a>
      <a class="p-tag none">ぬるっと後から表示</a>
      <a class="p-tag none">ぬるっと後から表示</a>
      <a class="p-tag none">ぬるっと後から表示</a>
      <a class="p-tag none">ぬるっと後から表示</a>
      <a class="p-tag none">ぬるっと後から表示</a>
      <a class="p-tag none">ぬるっと後から表示</a>
    </div>
    <div class="p-more__tags"><a href="#">もっと見る</a></div>
  </div>
</body>

CSSはこちら

style.css
.p-tags{
  display: -webkit-box;
  display: -ms-flexbox;
  display: flex;
  -ms-flex-wrap: wrap;
  flex-wrap: wrap;
  width: 100%;
  margin:10px 0;
}

.p-tag{
  display: block;
  width: 100%;
  background: #333;
  color: #fff;
  padding: 3px;
  margin-bottom: 3px; 
}

.p-more__tags{
  width: 100%;
  background: #ff69b4;
  text-align: center;
  color: #fff;
  padding: 5px 0;
}

.p-more__tags a{
  display: block;
  color: #fff;
}

.none{
  display:none; /* 見せたくない要素は見えないように */
}

jQuery

nurutto.js
var num = 2;//何個ずつ表示させていくか指定

$('.p-more__tags').on('click', function() { 
  const hiddenTags = $('.p-tags a.none');
  //noneクラスがついてる要素を指定

  var hiddenTagsNum = hiddenTags.length; 
  //noneクラスがついてる要素の個数を取得 

    if(num > hiddenTagsNum){
    //noneクラスがついてる要素の個数より最初に指定した表示させる数が多かった時
      num = hiddenTagsNum;
      //表示させる数を残りの要素数に変更
    }

  hiddenTags.slice(0, num).slideToggle();
  //noneクラスがついている要素のうち、最初からnum個目まで表示
  hiddenTags.slice(0, num).removeClass('none');
  //表示させた要素はnoneクラスを削除

  if (num == hiddenTagsNum) {
  //表示させるものがなくなったら、ボタンをフェードアウト
      $('.p-more__tags').fadeOut();
  }
});

解説

順番前後しますが、まずToggle部分から

nurruto.js
hiddenTags.slice(0, num).slideToggle();
//noneクラスがついている要素のうち、最初からnum個目まで表示

hiddenTags.slice(0, num).removeClass('none');
//表示させた要素はnoneクラスを削除

イメージで言うと、工場のベルトコンベアですね。

流れてくる非表示状態の要素を前から2個取って、slideToggleで表示。
取った要素はnoneクラスを外してあげて、ベルトコンベアから降ろす。

そうすると、さっきは3、4番目でベルトコンベアから降ろされなかった要素が前に流れてきて事実上1、2個目に。

これを取って・表示して・降ろしてを要素が無くなるまで繰り返します。

nurruto.js
if(num > hiddenTagsNum){
//noneクラスがついてる要素の個数より最初に指定した表示させる数が多かった時
  num = hiddenTagsNum;
  //表示させる数を残りの要素数に変更
}

この処理を挟んでいるのは、上の処理をして最後に残った非表示の要素が
必ずしも2個とは限らないからですね。(割り切れない可能性がある)

もし余りが1の時、numをそのまま2にすると.slice(0, num)がエラーになってしまいます。

懸念点

display: none;を最初に見せたくない要素に指定していると、
グーグル先生いわく要素が隠しコンテンツ的な扱いになるとかならないとか。

SEOへの影響を考えると、opacity: 0;からopacity: 1;への変化がいいのかなぁ。

知見のある方いらっしゃいましたらコメント下さい。。

まとめ

ざっくりまとめると、
display: none;が指定されているクラスを付けたり外したりしてるだけです。
それならaddClassやremoveClassでいいじゃないか、
と最初は思ったのですが、
それだと、ぬるっと表示できなかったのと、
cssでアニメーションをつけるにしてもdisplay: none;と相性が悪い。
そしてめんどくさい(本音)

なのでjQuery頼みでtoggle系メソッドを使いました。

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

本格的なTwitterクローンを作る

Twitterのクローンアプリを10日間で作った話

今回、Web上で「CloudDiary」という交流サイトを作りました。
デモは以下をご覧ください。

スマホ版(GIF画像)
スマホ用

PC版
PC用

このアプリについて

主な機能はTwitterに似ており、デザインはGoogleが推奨するマテリアルUIを採用しました。言語はHTML,CSS,JavaScript,PHP,Ajax(非同期でいいね)です。メインページはSPAで、フッターのナビゲーションで画面遷移しても、ページバックした際は、前のページに戻ります。

機能一覧

  • ログイン/ログアウト
  • パスワードの不可逆暗号化(解読不可能な暗号としてDBに登録)
  • 文章投稿
  • 画像投稿
  • いいね(Ajaxで非同期処理)
  • コメント機能
  • Twitter共有
  • フォロー/フォロワー
  • アイコン画像
  • プロフィール変更

主な機能の仕組み

データベースは、usersテーブルpostsテーブルlikesテーブルrepliesテーブルfollowsテーブルの5つから成り立っております。例えば何か投稿したら、usersテーブルのpost数が更新され、postsテーブルに詳細カラムが追加されます。このようにデータベースが連携し合っています。

PHP

基礎土台はこんな感じです。これを応用すれば、ユーザーごとのいいね数,コメント内容等を取り出して表示できます。

index.php
<!--postsテーブルのnameを取り出す-->
<?= foreach($posts as $row): ?>
<?= echo $row['name']; ?>
<?= endforeach; ?>

<!--セッションがあればユーザーの名前を表示-->
<?= if(isset($_SESSION['user'])): ?>
<div class= "user-name"><?php echo $_SESSION['user']['name']; ?></div>
<?= else: ?>
<div class= "user-name">User name</div>
<?= endif; ?>

Ajax

いいねボタンが押された場合、Ajaxで非同期でlikesテーブルに新情報を挿入します。メリットとしては、例えば下にスクロールしている際にいいねボタンを押した場合、ページがリロードされてトップに戻ることがなくなります。また、感覚としては瞬時にいいねボタンを押せるため、UXが向上します

JavaScript

特定のボタンが押されたら、ポップアップ表示を出したり、部品をクリックしたときに波紋を出したりしています。

おわりに

今回はPHPを使ってこのような交流サイトを作ってみました。
何かアドバイス等あれば教えていただければと思います。
そして、この記事が皆様にお役に立てれば幸いです。

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

Reactアプリのデバッグ方法

この記事の目的

Reactの公式ページを見たがReact DevToolsを用いたデバッグ方法についての記述が無かったため(パフォーマンス最適化の項でDevToolsの紹介はあった)、Reactアプリのデバッグを行う上で最低限知っておくべき内容を整理しておく。

参考資料

公式ガイド

Reactアプリのデバッグ方法

準備

Reactアプリのデバッグはブラウザで行う。FirefoxとChromeのどちらでもデバッグ用の拡張機能(React Developer Tools)が用意されているので、普段Webアプリのデバッグで利用しているブラウザ向けのDevToolsをインストールして利用する。

ChromeのReact Developer Tools
FirefoxのReact Developer Tools

※IE用は存在しないので、IEでのデバッグは諦めてください。。。

React DevToolsの使い方概要

この資料ではChromeのDevToolsで説明する。(FirefoxのDevToolsでも基本同じはず)

アイコンの見方

ChromeのReact DevToolsをインストールすると、ブラウザの右上にReactのアイコンが表示される。
アイコンの色によって意味が異なり、以下の3種類が存在する。

アイコンの種類 image.png image.png image.png
Reactで描画されたページか否か ×(Reactで描画されたページではない) ○(Reactで描画されたページ) ○(Reactで描画されたページ)
Reactのビルドモード - Production Build Development Build
React DevToolsによるデバッグ - 不可 可能

自分が作成したReactアプリをnpm startで起動してWebページを開いた場合、一番右の赤色アイコンが表示される。

画面のコンポーネント構成を見る方法

「Chromeのメニュー」→「その他ツール」→「デベロッパーツール」を開く。
デベロッパーツールのタブメニューから「Components」を選択。
image.png

現在ブラウザで表示しているページのコンポーネント構成を確認できる。
また、対象のコンポーネントをクリックすればpropsやstateの値を確認することも可能。
image.png
多重階層になっていてお目当てのコンポーネントを探すのが大変なときは、Componentsタブ内の左上にある矢印ボタンを押して画面から対象コンポーネントを選択することも可能。
(ネストされたコンポーネントを正確にポインティングするのは難しいようなので、補助的に使ってあげてください)

ブレイクポイントを設定してステップ実行でデバッグする

React Developer ToolsでComponentsタブを開き、ブレイクポイントを設定したいコンポーネントを選択した状態で「<>」アイコンをクリックする。
image.png

コンポーネントのソースがReact Developer Toolsに表示されるので、ブレイクポイントを設定したい行番号をクリックする。ブレイクポイントを設定した行番号は緑色になる。
image.png
ブレイクポイントを設定した状態でWebブラウザ内でReactアプリを操作すると、ブレイクポイントで処理が止まる。ブレイクポイントで処理が止まると、対象の行が緑色で網掛けされる。
image.png

ブレイクポイントで処理が停止したら、以下のいずれかの実行が可能。
image.png

  • Resumu script execution
    • ブレイクポイントからそのまま処理が続行される
  • Step over next function call
    • ステップオーバーで実行
    • 今いる関数内の処理はすべて実行し、次の関数が呼ばれたタイミングで処理をブレイク(停止)する
  • Step into next function call
    • ステップインで実行
    • 今いる関数から別の関数が呼ばれるタイミングまで処理を進め、別の関数に入ったタイミングで処理をブレイク(停止)する
  • Step out of current function
    • ステップアウトで実行
    • 今いる関数内の処理はすべて実行し、今いる関数の呼び出し元に戻ったタイミングで処理をブレイク(停止)する
  • Step
    • 一行づつ順番に実行する
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Javascriptでhtml要素の順番を入れ替える。

はじめに

ちゃんとした説明を一度書いてみようと思った。

実際の動き

Jsのコマンドに、appendChildinsertBeforeというヤツらが存在する。
この2つは、特定の要素を最後尾、または要素の前に挿入する関数なのだが……

さて、問題である。既に存在する要素を再び追加すると何が起きるのだろうか。

ちなみに、初期状態は以下の通り。

1582104043515.jpg

HTMLそのもののソースはこちら。

html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="utf-8">
    <title>無題</title>
</head>
<body>

<style>
#pipimi{
    border: 2px solid black;
    padding: 10px 20px;
    margin: 0;
    max-width: 200px;
}
#pipimi p{
    font-family: serif;
    margin: 0 0 0.5em 0;
}
</style>

<div id="pipimi">
    <p>なんとも低い</p>
    <p>クオリティ</p>
    <p>まあひどい</p>
</div>

<script type="text/javascript">
var tag = document.getElementById('pipimi');
var taglist = tag.getElementsByTagName('p');

var p01 = taglist[0]; // なんとも低い
var p02 = taglist[1]; // クオリティ
var p03 = taglist[2]; // まあひどい

</script>
</body>
</html>

scriptを以下のように変えてやると……

html
<script type="text/javascript">
var tag = document.getElementById('pipimi');
var taglist = tag.getElementsByTagName('p');

var p01 = taglist[0]; // なんとも低い
var p02 = taglist[1]; // クオリティ
var p03 = taglist[2]; // まあひどい

tag.appendChild(p02);

</script>

こうなる。

1582104656370.jpg

そう、既に存在する要素を「追加」すると、元の位置からは消えて、新しい位置に追加される。つまり移動するのだ。
同じ要領で、insertBeforeのほうもためしてみよう。

html
<script type="text/javascript">
var tag = document.getElementById('pipimi');
var taglist = tag.getElementsByTagName('p');

var p01 = taglist[0]; // なんとも低い
var p02 = taglist[1]; // クオリティ
var p03 = taglist[2]; // まあひどい

tag.appendChild(p02);

tag.insertBefore(p03, p01);

</script>

結果はこのとおり。完成である。
1582105232515.jpg

おわりに

不明な点などありましたら編集リクエストおねがいします。

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

React Routerのざっくり概要

この記事の目的

公式ページのガイドを見てもらうのが一番だが、公式は英語の情報しかないためReact Routerを使用するにあたって押さえておきたいポイントをかいつまんで説明する。

参考資料

公式ガイド

React Routerのざっくり概要

React Routerとは

ReactでSPA(Single Page Application)を実現し、SPAのルーティングを管理するためのモジュール。
Reactのルーティングライブラリは過去にいくつか出ては消えてを繰り返してきたが、現在はReact Routerがデファクトスタンダードと言える。

基本的な使い方

画面の構成例

Material UIのテンプレートページで紹介されている、以下の画面構成を例にReact Routerの使い方を説明する。

image.png

画面の構成例をコンポーネント単位に分解

上記の画面は、コンポーネント単位に分解すると以下のような構成となる。

image.png

左端のDrawerコンポーネントにメニューがあり、メニューを選択するとmainタグの領域にコンテンツを表示する。
このとき、ページ遷移(HTMLのhttpリクエスト・レスポンス)は発生せず、DrawerコンポーネントとAppBarコンポーネントの表示はそのままにmain領域のコンテンツだけを入れ替える動作となる。

ページ遷移(HTMLのhttpリクエスト・レスポンス)を行わないでコンテンツの入れ替えを行うので、SPA(Single Page Application)となっている。

React Routerをコードに仕込む例

上記の画面例をReact Routerで実現する場合のコード例を示す。

コードの例

app.tsx
// ※説明に必要なステップ以外は省略している

import React from "react";
import Dashboard from "./components/Top";
import About from "./components/About";
import { BrowserRouter as Router, Switch, Route, Link } from "react-router-dom";  //ポイント1

const App: React.FC = () => {
  return (
    <Router>  //ポイント2
      // AppBar
      <AppBar>
      </AppBar>
      // Drawer
      <Drawer>
        <List>
          <ListItem button component={Link} to={/}>  //ポイント3a
            <ListItemText primary="Dashboard" />
          </ListItem>
        </List>
        <List>
          <ListItem button component={Link} to={/about}>  //ポイント3b
            <ListItemText primary="About" />
          </ListItem>
        </List>
      <Drawer>
      // main
      <main>
        <Switch>  //ポイント4a
          <Route exact path="/">  //ポイント4b
            <Dashboard />
          </Route>
          <Route exact path="/about">  //ポイント4c
            <About />
          </Route>
        </Switch>
        <main>
    </Router>
  );
};

export default App;

注目すべきポイント

ポイントは以下の4点。

  1. react-router-domをインポートする
  2. メニュー領域(この例の場合Drawerコンポーネント)とコンテンツ領域(この例の場合はmainタグ)を<Router>タグで囲む(Routerコンポーネントの一部として扱われる)
  3. メニュー領域(Drawerコンポーネント)
    1. メニューから「Dashboard」を選択した場合、/にルーティングすることを定義している
    2. メニューから「About」を選択した場合、/aboutにルーティングすることを定義している
  4. コンテンツ領域(mainタグ)
    1. コンテンツを表示する領域を<Switch>タグで囲む(Switchコンポーネントの一部として扱われる)
    2. <Route>タグでルーティングを定義する。この場合、/にルーティングしたらmainタグの領域にDashboardコンポーネントを表示するように定義している
    3. <Route>タグでルーティングを定義する。この場合、/aboutにルーティングしたらmainタグの領域にAboutコンポーネントを表示するように定義している

応用的な使い方

イベント起因でルーティング

基本的な使い方では、Linkコンポーネントを使ってボタンを押したらルーティングする方法を取ったが、ルーティングHistoryに直接パスを追加するとこでonClickイベントなどのイベント起因でルーティングすることも可能。

コードの例

order.tsx
// ※説明に必要のないステップは省略している

import React from "react";
import { withRouter } from 'react-router';  //ポイント1

class Orders extends React.Component {
  routingToAbout = () => {
    this.props.history.push('/about')  //ポイント3
  }

  render() {
    return (
      <div>
        <Button onClick={this.routingToAbout}> //ポイント2
          aboutへ移動
        </Button>
      </div>
    )
  }
}

export default withRouter(Orders)

注目すべきポイント

ポイントは以下の3点。

  1. インベント起因のルーティングではwithRouterコンポーネントを使用するので、'react-router'をインポートする
  2. onClickイベントでルーティングしたいので、onClickイベントが発火したらコンポーネント内の関数を呼ぶように設定する
  3. onClickイベントから呼ばれた関数内では、this.props.history.push('/about')でルーティングHistoryにaboutページへのルーティングを追加する。これによって、ルーティングHistoryの最後がaboutページになるので、aboutページに遷移することになる
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCodeでサーバー建ててファイル一式で納品の案

HTML/JSをサーバー動作でスタンドアロン納品したいときにVSCodeでLiveServerして簡易サーバーするメモ
XAMMPとかApache入れるなど結構大変なので。
他にいい方法あるとうれしい

VSCodeをZip形式でダウンロード

https://code.visualstudio.com/download
NoName_2020-2-19_10-12-35_No-00.png

拡張機能をダウンロード

右下の方にダウンロードできるところあり

Live Server

https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer
NoName_2020-2-19_17-20-17_No-00.png

Japanese Language Pack for Visual Studio Code

https://marketplace.visualstudio.com/items?itemName=MS-CEINTL.vscode-language-pack-ja
NoName_2020-2-19_17-20-37_No-00.png

VSCodeに拡張機能をインストール

日本語化してLiveサーバーな状態
dataフォルダつくるとPCにインストしたVSCodeの設定を読み込まなくなるので便利でした
NoName_2020-2-19_17-23-44_No-00.png

参考サイト

Visual Studio CodeをオフラインPCで使用する
https://qiita.com/mekemeke421/items/98d5a3e2f2cc7517a6ab

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

1段上のCSS設計・コーディングの概念図(HCDCモデル図)

はじめに

HTML+CSSコーディングにおいて、「どのように要素を特定してスタイリングするのか」というCSS設計上の課題に対し、「ひとつ上の視点で思考できる概念図」を紹介します。

この図を用いることで、3種類の異なるスタイリングアプローチ(OOCSS方式 / 包括要素基点方式 / BEM方式)の本質を一度に俯瞰できるため、全てを同じ枠の中で捉えられます。そして、最終的には種別や規模の異なるサイトやプロジェクトに対し、同じメソッドを使ってそれぞれ最適な設計がおこなえるようになります。

※この記事は標準化ノウハウ公開の一環として書いています。仕組みの概要や前提事項などについては「UltimateCoding 概要・前提事項」のエントリをご確認ください。

経緯 / 制作者中心のデータ分類

そもそもですが、HTMLとCSSは目的も仕様も異なる言語です。
HTML+CSSコーディングを一般的な視点で見れば「異なるものをWebページ上で融合させる」という高度な事をおこなっているわけですが、2つの言語にはエラー出力機能が無い上に、全域への影響を簡単に作成できてしまいます。この"自由さ"が現代の複雑なWeb制作においては仇となり、管理上の様々な問題を引き起こしやすい状態となっています。

従来からあるこの問題を解決しながらも高度な標準化を目指すとすれば、片方の言語視点のルールだけでは材料不十分です。では、何を基準にするのか。ですが、出てきたアイデアが「制作者はどうしたかったのかを考える」ということでした。
制作者はHTMLとCSSで何をしてきたのか・どうしたかったのか

標準化のためにHTML+CSSコーディングについて 言語化 をおこない、いつくかの設計手法を交えた行動分析と考察を経て、先にCSS側のものとして作成したのが今回の概念図(モデル図)です。
図ができるまでの過程(分析・考察・分類・正規化)の情報は、別記事として「HCDCモデル図が出来るまで/行動分析と考察による分類」に記載しています。

HCDCモデル図

以下が完成した概念図(HCDCモデル図)です。

この図は、オブジェクトに求める4つのデータ分類「 Block / Parts / Structure / Utility 」と、既知のスタイリングパターン「 A / B / C 」の関連性を1枚の図に納めることによって、「どのように要素を特定してスタイリングするのか」という課題に対し、選択可能なパターンを秩序立てて示す計器のような役割を果たします。

HCDCモデル図

左半分はレンダリングに使われるような"物質的な"感覚に近い部品で、右半分はコーディングの都合によって欲しくなるような役割・部品です。
この図の中には業界を通して斬新と言えるような語句や概念は含まれていません。相関関係さえ把握してしまえばすぐに利用を開始できるでしょう。設計時にはアプローチを俯瞰した思考用ツールとなり、コーディングの最中にはいつでも立ち戻れるマスターの概念として働きます。

以下から、図の説明をおこないます。

モデル図の要素(データ分類)

まず、主要な4つのデータ分類、BlockPartsStructureUtility についての説明です。
これらは具体的な手法やディレクトリ名を示すものではありません。「コーディングの時に、こういうのってあるよね」という制作視点での、役割に対して与えた名称とお考え下さい。

1 ・ Block(ブロック)

Blockは、機能・役割・情報属性などの基準によって、制作者が「これ以上は他と同じにできない」と判断した範囲の区画・塊です。
Blockのイメージ
具体的には、ヘッダーやフッター、ブランディングエリア、ナビゲーション、コンテンツの1セクションなど、制作者が「この範囲を塊として扱う」と定めたものが該当します。
おおよそdivに対して名前が与えられ、HTML側はある程度まとまった行数のコードになるでしょう。
「ヘッダーのBlock」の中に「ナビゲーションのBlock」が配置される等、Blockは、Block同士のネストが恒常的に発生します。

2 ・ Parts(パーツ)

Partsは、再利用する可能性の高い、もしくは制作者が再利用したいと望む中規模~小規模の要素です。
Partsのイメージ
カード、詳細リストといった中型のリピート部品や、見出しやボタン、ラジオボタン一個、テキスト一行。といったもので、レンダリングに直接関わるものが該当します。これらは粒度の概念により細分類できます。(※ 粒度の分類はここでは定めません)
Partsは、A・B・Cの3種類のスタイリング方法によってBlockの構成部品になれます。また、同様に、自身よりも大きな粒度のPartsの構成部品になれます。

3 ・ Structure(ストラクチャ)

Structureは、Blockの中のPartsをまとめたり、Blockそのものを大きな範囲でレスポンシブ制御したり、ページのカラム分割や画面分断といったような、コーディングの都合で欲しくなる補助ボックス・構造です。
Structureのイメージ
インナー・アウターや、セクションごとのヘッダー・フッター、位置決め用のレイアウトボックスなど、用途により細分類できます。(※用途の分類はここでは定めません)
大きなカラム分割の中にグリッドが入る 等、Structure同士の入れ子も考えられますが、入れ物である以上Structureがデータ表示の「主役」になる事は少ないと言えます。A・B・Cの3種類のスタイリング方法によって、Blockもしくは、粒度の大きなPartsの構造になれます。

4 ・ Utility(ユーティリティ)

Utilityは、コーディング用の便利な役割です。
Utilityのイメージ
状態変化を表すものとして利用したり、色や余白などの単一プロパティを後付けするために利用したり、要素の識別子に使うなど用途は様々です。(※用途の分類はここでは定めません)
単体で「主役」になることは少なく、コーディング都合の補助的な役割と言えます。

スタイリングのパターン

次に、ABC のスタイリングパターンについて説明します。
どのパターンも全て、命名を主役として考えます。(命名 ≒ 部品 と捉えます)

A ・ スタイル完成品を使う

パターンAは、完成させた部品をグローバルに再利用するアプローチです。
「Parts」を例にすると、ボタンを.btnと命名し、第一セレクタで完全なスタイルを与え、いたるところに同じものを配置できるようにします。
パターンA Partsの利用方法説明
「Structure」の場合も同様に、スタイルを完成させてからそのまま再利用します。
パターンA Structureの利用方法説明
「Utility」の場合は、スタイルを完成させてから、目的の対象にマルチクラスで付与する事になります。図の例では対象を「非表示」の状態にしますが、色やサイズの後付けを目的としたものもあるでしょう。
パターンA Utilityの利用方法説明
このように、グローバルに利用できる形で部品のスタイルを完成させておいて、HTMLに設置していくような使い方になるのが「パターンA」です。

B ・ スタイル無し、もしくはスタイル半完成品を使う

パターンBは、包括要素の命名を第一セレクタに指定してから対象をセレクトするアプローチです。
例えば「Parts」の場合、.btnというボタン用の命名を.boxの内部に配置して.box .btn{}といった子孫セレクタでスタイリングします。
パターンB Partsの利用方法説明
「Structure」の場合も同様に、命名(部品)を.boxの内部に配置してから、包括要素である親(先祖)要素を基点にセレクタを記述してスタイリングします。
パターンB Structureの利用方法説明
「Utility」をこのパターンで使う場合は、対象に命名をマルチクラスで付与してから、包括要素である親(先祖)要素を基点にスタイリングします。
パターンB Utilityの利用方法説明
この時の対象(例で言うと.btn.l-width.is-close、)は、第一セレクタによるグローバルなスタイルは一切与えないか、共通した最低限のスタイルのみを与えて半完成の状態で使うかのどちらかで利用します。HTML側の記述は、ネストすればパターンAと同じになるものの、CSSは異なる意味合いになっています。

このように"命名を設置"してから、先祖要素を基点にスタイリングする方法が「パターンB」です。

C ・ 命名を1つに結合して使う

パターンCは、命名の工夫(単語連結)によるアプローチです。 ※事例は全てBEMの記法で説明
例えば「Parts」の場合、.textという命名を、親となる.boxの命名と連結して、.box__textという新たな命名を作成した上で使用します。
パターンC Partsの利用方法説明
「Structure」の場合も同様に、親となる命名に連結します。
パターンC Structureの利用方法説明
「Utility」は、BEMの概念で置き換えると「Modifire」となるため以下のような連結方法になります。(※他の記法・ルールを使うのであれば、この限りではありません)
パターンC Utilityの利用方法説明
このように命名のみを連結し、親の名前による「名前空間」を作り、元の命名とは異なる部品としてスタイリングする方法が「パターンC」です。

ちなみに、BEMの概念「Block + Element + Modifire」は、HCDCモデル図の「Block + Parts(or Structure) + Utility」で再現できます(本質的な意味が同じになります)

総括

この概念図(HCDCモデル図)が全ての制作者や現場にとって適合するものとは言い切れませんが、少なくとも、ここで挙げたアプローチに関しては、整理されたパターンとして把握できるかと思います。

パターン化できれば「混沌としたもの」ではなくなり、検証もおこないやすくなります。検証がおこないやすくなれば、よりよい方法・スキームを蓄積し、独自の効率化ルールを作成しやすくなるのではないでしょうか。

既存の設計手法に意識を捕らわれることなく、制作者の意図を持って設計を自由に選択し、コードを安定的に制御するための手助けになれば幸いです。

以降の展望

この概念図は標準化された仕組みとして実用できます。
概念を実用するためには、概念と物理の間に"わだかまりのない"手法が必要となります。これは、ご自身で考案・構築することもできますし、今後紹介していく以下のような情報を利用することもできます。

標準化の方法(今後紹介する情報)

複数の制作者が認識を合わせ続けるには、ある程度の範囲の取り決め・指針となるものが必要です。以下の4つは「業務標準化」という意味において必要になると判断したガイド・手法です。

標準化のための4つの手法

上記4つは「HCDCモデル図が出来るまで」の「作業ステップ」の要所と対応する形になっており、簡単に言えば、以下のような流れを誰もが同じ精度と品質で実行するためのものです。

  1. まずは、デザインやワイヤーフレームなどの視覚情報を分解。粒度分類をおこなう
  2. つぎに、分解・分類した対象に対して一貫性のある単語で名前を検討する
  3. 単語連結規則に従ってコーディング用の正式な命名をおこなった上で
  4. 記述したCSSコード(Sassファイル)を、プロジェクトに最適なディレクトリ構造で管理する

実際のコード記述まで安定的に標準化するには、CSS側面だけではなく、HTML側の取り決めやルールも必要になりますが、これは手法よりも強い立場の「ルール・レギュレーション」で定義します。

これらは既に完成して運用中ですが、一度に記事作成できないため順次公開していく予定です。
一連の情報「Ultimate Coding」の方針や、標準化に関する考え方については「UltimateCoding 概要・前提事項」に記載していますので、ご興味があればご確認ください。

関連記事

クレジット・その他

Ultimate Coding
概要・前提事項

この仕組みは、組織所属時に業務効率化のために構築したものであり、許可を得た上で設計者本人が個人活動として公開しています。商用の制作や開発には利用していただけますが、仕組みを販売したり媒体化するなどの、制作以外での商用利用はご遠慮下さい。質問その他、何かあれば@croco_worksまでお声かけください。

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

フリマアプリ購入機能実装(pay.jp)購入編

前回の記事の続きです。

Payjp(Pay.jp)から既に顧客IDとカードIDを取得済みでの購入を想定しています。

前提条件

・devise/hamlが導入済みでログインができている
・payjpのアカウントが既に取得できていて、ユーザーとカードの登録が完了しており、cardテーブルに以下の情報が登録されている
user_id ... UserテーブルのID
customer_id ... payjpの顧客ID
card_id ... payjpのデフォルトカードID
顧客IDとデフォルトカードIDは以下の画面で顧客ごとに確認できます。
alt

1.コントローラーを作成しよう

コントローラとビュー(indexとdone)を作成するため、下記コマンドを実行します。

$ rails g controller buyers index done

コントローラーの中身を編集

app/controllers/buyers_controller.rb
class BuyersController < ApplicationController
  require 'payjp'#Payjpの読み込み
  before_action :set_card, :set_item

  def index
    if @card.blank?
      #登録された情報がない場合にカード登録画面に移動
      redirect_to new_card_path
    else
      Payjp.api_key = Rails.application.credentials[:PAYJP_PRIVATE_KEY]
      #保管した顧客IDでpayjpから情報取得
      customer = Payjp::Customer.retrieve(@card.customer_id) 
      #カード情報表示のためインスタンス変数に代入
      @default_card_information = customer.cards.retrieve(@card.card_id)
    end
  end

  def pay
    Payjp.api_key = Rails.application.credentials[:PAYJP_PRIVATE_KEY]
    Payjp::Charge.create(
      :amount => @item.price, #支払金額を引っ張ってくる
      :customer => @card.customer_id,  #顧客ID
      :currency => 'jpy',              #日本円
    )
    redirect_to done_item_buyers_path #完了画面に移動
  end

  def done
  end

  private

  def set_card
    @card = Card.find_by(user_id: current_user.id)
  end

  def set_item
    @item = Item.find(params[:item_id])
  end

end

set_card , set_itemの記述で、cardとitemの情報をもってきます
今回は、先ほど作成したindexから、form_tag内のaction: 'pay'で、controller内のpayを発動

2.購入画面と完了画面を作成しよう

購入画面

app/views/buyers/index.html.haml
    %h2.buy-content__title
        購入内容の確認
      .buy-content__item
        .buy-content__item__inner
          .buy-item-main
            .buy-item-image
              = image_tag "#{@item.images[0].url}", size: '64x64', class: 'buydetails-contet__image'
            .buy-item-detail
              .buy-item-name
                = @item.name
                %p.buy-price
                  = #{@item.price.to_s}"
                  %span.shipping-free (税込) 送料込み
      .buy-content__item
        %form.buy-form
          .buy-price-table
            .buy-price-table__left
              支払金額
            .buy-price-table__right
              = #{@item.price.to_s}"
      .buy-content__user-info
        .buy-content__user-info__inner
          %h3 支払方法
          .user-info-update
            = link_to "変更する", "#", calss:"update-btn"
          .user-info-text
          - if @default_card_information.blank?
            %br /
          - else
            = "**** **** **** " + "#{@default_card_information.last4}"
            %br
            - exp_month = @default_card_information.exp_month.to_s
            - exp_year = @default_card_information.exp_year.to_s.slice(2,3)
            = "有効期限 " + exp_month + " / " + exp_year
            %br
      .buy-content__user-info
        .buy-content__user-info__inner              
          %h3 配送先
          .user-info-update
            = link_to "変更する","#", calss:"update-btn"
          .user-info-text
            〒111-1111
            %br
            大阪府大阪市北区〇〇1-11
            %br
            山田太郎
            = form_tag(action: :pay, method: :post) do
              %button.buy-button{type:"submit"} 購入する

*exp_monthはカードの期限月、exp_yearは期限年、last4はカードの下4桁を取得
PAYJP カードオブジェクト

完了画面

app/views/purchase/done.html.haml
%h1.buy-content__attention
        %i.far.fa-clock
          発送をお待ちください
        %h2.buy-content__attention__title
          購入が完了しました
        .buy-content__item
          .buy-content__item__inner
            .buy-item-main
              .buy-item-image
                = image_tag "#{@item.images[0].url}", size: '64x64', class: 'buydetails-contet__image'
              .buy-item-detail
                .buy-item-name
                  = @item.name
                  %p.buy-price
                    %span 
                      = #{@item.price.to_s}"
                    %span
                      .shipping-free (税込) 送料込み
        .buy-content__item
          %form.buy-form
            .buy-price-table
              .buy-price-table__left
                支払金額
              .buy-price-table__right
                = #{@item.price.to_s}"
            .buy-content__user-info__submit
              = link_to "トップページへ戻る", root_path, class: 'buy-content__user-info__submit__button'

3.ルートを設定しよう

ルーティング設定ですが、商品詳細ページからitem_idを引き継ぎたかったので、下記のようにしました

config/routes.rb
resources :items do
    resources :buyers, only: [:index] do
      collection do
        get 'done', to: 'buyers#done'
        post 'pay', to: 'buyers#pay'
      end
    end
  end

購入の確認

購入が完了するとPayjpが以下の様に変わります

参考

https://qiita.com/takachan_coding/items/d21c0d2621368c9b0d9b
https://qiita.com/Ikuy_h/items/7232ba32e0b728ff77aa

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

axiosでリロードできなかったつまずき

現象

初め下のコードを書いていたところ、200が返ってきているにも関わらず、ページがリロードされない現象に悩まされました。

axios.get('/user')
  .then(location.reload())
  .catch(error => alert("失敗しました。"));

解決

結論、javascriptの書き方が違っていたようです。

axios.get('/user')
  .then(res => location.reload())
  .catch(error => alert("失敗しました。"));
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Nuxt.js Tips

※ 随時更新予定

目次

概要

Vue.jsをベースに、更に色々な機能を取り揃えたフレームワーク。
SPA、SSR、Vue Router+自動ルーティング、Vuex、デフォルトテンプレート等の機能がある。
Vue CLIよりも使いやすくなっている。簡単なSPAでもNuxtで作るのが最適解だと思う。

環境構築

create-nuxt-appを使うのがベスト!下記の手順で基本的にok

  1. npm i -g create-nuxt-app ※create-nuxt-appを準備していない時
  2. npx create-nuxt-app アプリ名 ※各設定は必要に応じて選択。基本enterでok。
  3. デフォルトの画像やコンポーネントを削除し、環境の準備完了

機能

  • vue-router標準装備で、なおかつpageディレクトリに配置したファイルを元に自動ルーティングを行ってくれる。
  • vuex標準装備で、this.$storeでアクセス可能。
  • axiosやbootstrapもインストールの時に設定することで、this.$axiosのような形で利用が簡単。
  • Express.jsの利用も視野に入れられているため、サーバサイドJSも組み込みやすい。

Tips

ルーティングのルール

pagesディレクトリ内で任意のディレクトリとindex.vueを作成することで、自動的にルーティングが作成される。

例1:) page1/index.vue を作成

npm run dev を実行
localhost:3000/page1/ にアクセスできる。

例2:) page1/_id.vue を作成

npm run dev を実行
localhost:3000/page1/(任意の値) にアクセスできる。

※ (任意の値)部分は、this.$route.params.id で取得可能
※ asyncDataで利用する場合は、 context.route.params.id

Vuex(Store)は、モジュールモード推奨

まず、Vuexとは、コンポーネント間でやり取りする共通の値や処理をまとめてあるもので、コンポーネントのどこからでも利用可能です。

モジュールモードとは、store ディレクトリ内のすべての *.js ファイルが 名前空間付きモジュール に変換されます(index はルートモジュールとして存在します。)

index内で、名前空間を分ける従来の書き方は廃止される様です。

例:)store/namaekuukantest.jsというファイルを作り、stateにアクセスする場合

this.$srore.state.namaekuukantest.プロパティ名 になります。

Vuex(Store)のモジュールのテンプレート

state, getters, mutations, actionsが基本構成です。

state → 共通で利用する値を保持する役割。
getters → stateの値を取得する役割。コンポーネントのcomputedで利用しやすい。
mutations → stateの値を変更する役割。コンポーネントから直接実行するかactionsから利用。
actions → 通信処理で値を取得したり、複雑な共通処理を行ったりする役割。

export const state = () => ({
  list: {},
  targetId: '',
})


export const getters = {
  getList(state) {
    return state.list
  },
  getTargetId(state) {
    return state.targetId
  },
  getListById(state){
    return state.list[state.targetId] || {}
  },
}


export const mutations = {
  setList(state, register) {
    state.list = register
  },
  setTargetId(state, id) {
    state.targetId = id
  },
}


export const actions = {
  async getAll({commit}) {
    const res = await this.$axios.$get('/api/register/all/')
    commit('setList', res)
  },
  async getById({commit}, id) {
    const res = await this.$axios.$get(`/api/register/${id}`)
    return res
  }
}

コンポーネントからVuexの値を参照する場合

直接参照する場合

this.$store.state.モジュール名(ファイル名).stateに記述したプロパティ

gettersを利用する場合

  • script部でvuexを読み込みmapGettersを利用する
import { mapGetters } from 'vuex'
  • computedでgettersを取得
    ※記述はgetListというゲッターをlistという名前で利用する場合
computed: {
  ...mapGetters({
    list: 'モジュール名(ファイル名)/getList'
  })
}

異なるコンポーネントで、共通のmethods等を利用したい場合

mixin を利用しましょう!

1.分かりやすい様にNuxtプロジェクトの直下に mixins というディレクトリを作成する。
2.作成したいmixinファイルを作成する。

例: mixins/InputValidation.js を作成

export const InputValidation = {
  methods: {
    isEmpty(str) {
      return str === "" || str === undefined || str === null
    },
    isLength(length, str) {
      const validStr = String(str)
      return validStr.length > length
    }
  }
}

3.mixinを利用するコンポーネントファイルのscript部でimportする

import { FileEvaluableImage } from '@/mixins/FileEvaluableImage' 

4.mixins という項目を作成し、利用するmixinを指定する。

export default {
  mixins: [ FileEvaluableImage ],
  ...
}

どのファイルからでも参照可能なutilを作成する方法

1.pluginsディレクトリにutils.jsを作成(名前はなんでもok)
2.nuxt.config.jsのpulginsに下記を記述する。

plugins: [
  '@/plugins/utils'
],

3.utils.jsに共通で利用したい処理を記述する。

const deepCopy = (obj) => {
  return JSON.parse(JSON.stringify(obj))
}

export default ({}, inject) => {
  inject('deepCopy', deepCopy)
}

4.実際に利用する時は、 this.$登録名で記述する。

this.$deepCopy(state.list)

Nuxt.jsでapiを利用する

1.nuxt.config.jsの任意の場所に下記を記述する。

serverMiddleware: [
  '~/api/'
],

2.api/にindex.jsを作成し、apiの設定を記述する。
(設定内容は、Express.jsでミニマムなローカルサーバ構成で説明)

npm run devでローカルサーバを立ち上げた際に、他の機器からAPIにアクセスする場合

nuxt.config.jsに以下を記載。

server: {
  port: (任意のポート番号),
  host: '0.0.0.0'
},

ただし、アクセスする際は、 http://localhost:ポート/api/○○ でアクセスする。

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

Javascriptで改行したら下に伸びるテキストボックスをつくる

はじめに

読んで字の通り、可変テキストボックスを実装する関数をつくってみました。
改行するごとに下へ下へと枠が伸びてゆくので、見栄えはよくなるかも。

コード

仕組みとしては、改行文字(\n)の数を数えて、改行文字 ✕ 一行分の高さ + もともとの高さ となるようにした感じ。
パソコンは改行そのものも「改行という名の文字」として認識しているので、そこを突いてやればいいのダダダダッ!というわけだ。

なお、CSSは適用されているスタイルをブラウザから自動取得してくる仕組みになっている。
わざわざJsを書き換えてやらずとも、使いまわすことができるというわけだ。

javascript
function afeed(element){
    if(element.tagName == 'TEXTAREA'){

        // 行の高さとpadding値を取得する。IEやSafariなど、ブラウザによって必要な関数が異なるので、or(||)で3パターン分取れるようにしてある。
        var stylelists = window.getComputedStyle(element, null) || element.currentStyle || document.defaultView.getComputedStyle(element, '');

        var line_height = stylelists.getPropertyValue('line-height').replace(/px/g , "");
        var line_length;

        var firstheight = Number(element.offsetHeight) - stylelists.getPropertyValue('padding-top').replace(/px/g , "") - stylelists.getPropertyValue('padding-top').replace(/px/g , "");

        var result_height;

        element.style.height = firstheight + 'px';
        element.style.resize = 'none';

        element.addEventListener('input', function(){

            // 改行文字の個数を取得
            if(element.value.match(/\n/g) === null){
                line_length = 0;
            }else{
                line_length = element.value.match(/\n/g).length;
            }

            // 見栄えの関係上、1つ以上改行があるときは文字のすぐ下に枠が来るようにする
            if(line_length === 0){
                result_height = firstheight;
            }else{
                result_height = ((line_length - 1) * line_height) + firstheight;
            }
            element.style.height = result_height + 'px';
        }, false);
    }else{
        console.log('Error: テキストエリアでないものに対して適用されています。');
    }
}

Codepenを用いたサンプル

See the Pen テキストエリア自動改行 by Yomogenium (@yomogenium) on CodePen.

おわりに

間違いやわかりにくい箇所ありましたら、編集リクエストをお願いいたします。

参考文献

https://amachang.hatenablog.com/entry/20070611/1181554170

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

量子力学の非局所性を計算で確認してみた

量子力学では非局所性ということが言われます。簡単なプログラムを作って計算で確認してみました。

プログラムで扱うための視点から量子力学や量子コンピューターの世界に入っていくのもありだと思うので、この記事では可能な限り量子力学などの物理の知識を前提としないで説明を試みます。

スピン

何らかの粒子について考えます。具体的には指定しませんが、電子や光子を想像していただいて構いません。

【参考】スピン角運動量 - Wikipedia

ここでいう「粒子」は電子やクォークなどの素粒子であっても、ハドロンや原子核や原子など複数の素粒子から構成される複合粒子であってもよい。

粒子はスピンと呼ばれる性質を持っています。今回は単純化して「特定の方向を向いている」と考えます。

image.png

測定

ある方向を指定して、スピンがそちらを向いているかを測定できます。結果は真 (true) か偽 (false) かで得られます。

イメージ
> 測定(粒子1, 右)
true
> 測定(粒子2, 上)
false

一度測定すると、スピンはその測定で得られた方向を向きます。「右」で true なら右、false なら反対の左です。

image.png

測定によって方向が確定するため、何度も同じ測定を繰り返せば同じ結果が得られます。

イメージ
> 測定(粒子1, 右)
true
> 測定(粒子1, 右)
true

スピンが横方向を向いている状態で縦方向に測定すると、結果はランダムに返ります。

イメージ
> 測定(粒子1, 右)
true
> 測定(粒子1, 上)
true または false(ランダム)

※ 確率 1/2 のランダムで、制御する方法はありません。物理乱数の生成源としても利用されます。

縦方向の測定によってスピンは上または下を向くようになります。そしてその後にまた横方向の測定をすれば結果はランダムです。

このように測定によってスピンの向きが変わるため、測定前にどちらを向いていたのかは正確には分かりません。

確率

ここまでは上下左右の四方向だけを考えましたが、もちろん斜めを向いていることもあります。

スピンがある方向を向いているときに右だと測定される確率は、直径 1(半径 1/2)の円によって図示できます。スピン方向の円周上の点(緑矢印の先端)から x 軸に垂線を下ろして、円の左端から垂線との交点までの長さが確率です(赤矢印)。スピン方向(緑矢印)が上を向いていれば 1/2、右を向いていれば 1 です。

image.png

※ よくある半径 1 の単位円ではないのに注意してください。直径 1 を確率の最大値に対応させています。

スピン方向(緑矢印)の先の座標は $(\frac{\cosθ}2,\frac{\sinθ}2)$、垂線との交点は $(\frac{\cosθ}2,0)$、円の左端から出ている測定方向の矢印(赤矢印)の長さは $\frac{1+\cosθ}2$ です。

image.png

スピン方向(緑矢印)と測定方向(赤矢印)の間の角度 θ が分かれば、どの方向でも確率が計算ができます。

なお、確率は $\cos^2\fracθ2$ と表されることも多いです。

\frac{1+\cosθ}2=\cos^2\fracθ2

実装

角度を指定して確率を求める関数を実装します。

function 確率(θ) {
  return (1 + Math.cos(θ / 180 * Math.PI)) / 2;
}

※ θ は度数法で指定します。Math.cos には弧度法(ラジアン)に変換して渡します。度数法の 180° がラジアンの $π$ に対応します。

確率に応じてランダムに 1 か 0 を返す関数を実装します。与えられた確率は 1 になる確率として扱います。

function 測定(確率) {
  if (Math.random() < 確率) return 1;
  return 0;
}

Math.random() は 0 以上 1 未満の乱数を返します。

スピンがランダムな方向を向いている 1,000 個の粒子を、測定方向を固定して測定する例です。測定方向を 0° として、スピンの角度をそのまま確率の計算に回します。

See the Pen 非局所性 (1) by 七誌 (@7shi) on CodePen.

乱数を使っているため多少の誤差は出ますが、平均して半分の 500 回は 1 が出ます。これはランダムに決めた方向が多数の繰り返しによって平均化されるためです。

エンタングルメント

1つの粒子が崩壊して、2つの同種の粒子に分裂したとします。その場合、2つの粒子は正反対の方向に飛んでいきます。

image.png

方向だけでなくスピンも正反対の向きとなります。両者を進行方向に対して同じ角度で測定すれば逆の結果が得られます。片方が 1 であれば、もう片方は 0 となります。このような関係性をエンタングルメント量子もつれと呼びます。

単純にスピンを逆方向にしただけではエンタングルメントは実装できません。

ダメな例
  for (let i = 1; i <= 5; i++) {
    let spin = Math.random() * 360;
    let 測定値1 = 測定(確率(spin));
    let 測定値2 = 測定(確率(spin + 180));
    log(i, "回目:", 測定値1, ",", 測定値2);
  }
実行結果(例)
1 回目: 0 , 0
2 回目: 0 , 1
3 回目: 1 , 1
4 回目: 1 , 0
5 回目: 0 , 0

所々 0, 01, 1 のように同じ値が出て来てしまいます。

確実に測定結果を逆にするため、片方の結果を反転させるように特別扱いします。

See the Pen 非局所性 (2) by 七誌 (@7shi) on CodePen.

非局所性

先ほどの例のように両方を同じ角度で測定した場合、分裂した時点でスピン方向と乱数に相当する何か(隠れた変数)が決まってエンタングルメントが起きるのではないかという疑いがあります。実際、そのように実装することも可能です。

これは大きな論争になりましたが(アインシュタイン=ポドルスキー=ローゼンのパラドックス)、ベルの不等式という確認方法が提案され、最終的に実験によって隠れた変数だけでは説明できない現象が確認されました。(アスペの実験)

つまり空間的に離れた 2 つの粒子の間で何らかのつながり(もつれ)があるということです。これを非局所性と呼びます。非局所性の物理的な仕組みは不明ですが、結果を再現するにはどのように実装するかを考えます。

なお、非局所性の対義語(空間的に離れた粒子のつながりがないこと)は局所性です。

CHSH 実験

非局所性を確認する実験です。アスペの実験もこれです。

左側の粒子を測定する角度を a と a' のどちらか、右側の粒子を測定する角度を b と b' のどちらかとして、4 つの角度はすべて異なるとします。粒子が分裂して測定器に到達するまでの間に、どちらの角度で測定するかをランダムに決めて θ1 と θ2 とします。

image.png

分裂した時点で測定する角度が決まっていないにも関わらず、測定値には角度差に応じた対応が見られます。後で確認しますが、空間的に隔てられたもう一方の測定に使った角度を使わなければ得られない結果です。

測定値は 確率(θ2-θ1) で逆になります。先ほど実装したエンタングルメントの例では θ1 と θ2 が等しいことから 確率(0)=1 より常に逆になります。

実装は次の通りです。spin はスピンの方向です。

非局所モデル
function 非局所(spin, θ1, θ2) {
  let 測定値1 = 測定(確率(θ1 - spin));
  let 測定値2 = 測定値1;
  if (Math.random() < 確率(θ2 - θ1)) 測定値2 = 1 - 測定値1;
  return [測定値1, 測定値2];
}

測定値2を計算するのに測定値1と θ1 を使っていますが、逆にしても構いません。確率が角度差に依存していることがポイントです。

局所モデルでは分裂時に乱数を共有していても、角度差が分からないため測定値の傾向は異なります。

局所モデル
function 局所(spin, θ1, θ2) {
  let r = Math.random();
  let 測定値1 = 0, 測定値2 = 1;
  if (r < 確率(θ1 - spin)) 測定値1 = 1;
  if (r < 確率(θ2 - spin)) 測定値2 = 0;
  return [測定値1, 測定値2];
}

どの程度異なるかを次で調べます。

CHSH 不等式

非局所モデルと局所モデルの違いを調べるのに使うのがベルの不等式です。今回はその一種の CHSH 不等式を使います。

The usual form of the CHSH inequality is

|S|\leq 2 \tag{1}

where

S=E(a,b)-E\left(a,b'\right)+E\left(a',b\right)+E\left(a',b'\right). \tag{2}
E = \frac {N_{++}  - N_{+-} - N_{-+} + N_{--}} {N_{++}  + N_{+-} + N_{-+}+ N_{--}} \tag{3}

数式の細かい説明は EMAN さんの記事に譲って、実装するのに必要な範囲で読み方を説明します。

θ1 と θ2 はランダムに選ぶため毎回変わります。今回は 4 種類の組み合わせがあるので、組み合わせごとに集計すれば傾向が調べられます。(2) の $E(a,b)$ は θ1=a, θ2=b の場合を表し、具体的な計算方法が (3) です。

(3) は測定値の組み合わせの個数から計算します。この記事では測定値を 1 と 0 としてきましたが、CHSH の不等式では +1 と -1 としており、$N_{++}$ の添え字は測定値の符号を表します。++ と -- は測定値が一致した場合、+- と -+ は一致しない場合で、次のように解釈できます。

E=\frac{\text{一致した個数}-\text{一致しない個数}}{\text{個数の合計}} \tag{3'}

一致と不一致が均衡していれば 0 に近付き、どちらかに偏れば絶対値が大きくなります。

組み合わせの相手を変えても測定値に変化がなければ S の値は (1) の範囲に収まります。絶対値記号を外して書き直します。

-2≦S≦2 \tag{1'}

もしこの範囲からはみ出すようであれば、組み合わせの相手に応じて測定値が変化することを意味します。

理論的に S の値が最小となる角度が示されています。

The settings a, a′, b and b′ are generally in practice chosen to be 0, 45°, 22.5° and 67.5° respectively — the "Bell test angles" — these being the ones for which the QM formula gives the greatest violation of the inequality.

? a = 0, a' = 45, b = 22.5, b' = 67.5

実装

CHSH の不等式を実装して S の値を確認します。一致と不一致を数えて (3') と (2) に代入します。前に見たように spin の方向は平均化されるため固定しても結果は変わりませんが、ここでは固定せずに計算します。

See the Pen 非局所性 (3) by 七誌 (@7shi) on CodePen.

非局所モデルでは -2 を超え、見事に不等式 (1') の範囲からはみ出しました(不等式を破ると表現します)。乱数を共有するだけの局所モデルでは不等式の範囲内に収まっています(たまに誤差程度に少しはみ出します)。これを確認するのが今回の目的でした。

量子の振る舞いはすぐには腑に落ちないかもしれません。今回の実装はそれほど難しいことはしていないので、色々といじって結果の変化を確認してみるのも手だと思います。

ご意見

この記事にいただいたご意見をご紹介します。

参考

出力の log() は次の実装を使っています。

確率の図示は T. M. さんのツイートが多大なヒントになりました。

CHSH 不等式の基になったベルの不等式については、やはり EMAN さんの記事が参考になります。

同じ話題について少し違ったアプローチで実装する記事です。

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

Googleカレンダー、カレンダーアプリにイベントを登録する Javascript

きっかけ

 今、予定の登録は、バックアップを兼ねて iCloud のカレンダーと、Googleカレンダーの両方に記載していて、どちらかに先に書いて、コピペしていたけど、面倒なので両方に登録するスクリプトを書こうと思い立った。
 まず、慣れている AppleScript でと思って、AppleScript で Googleカレンダーに書く方法を検索してみたら、なんということでしょう。自分が過去に公開しているページが先頭に表示された。(すっかり忘れていました ^.^;;)

Googleカレンダー、カレンダーアプリにイベントを登録する AppleScript

 https://qiita.com/ynomura/items/2468ab782500938548e7

 ともあれ、これのおかげで AppleScript で動作するものが作れました。Qiitaは備忘録ですね。
そうこうして、スクリプトエディタを使っていて気がついたのが、このエディタは Javascript にも対応しているということ。忘れている AppleScript の文法を検索すると、そのページには Javascript の記法も書いてある。

湧き上がる好奇心

 気づいてしまうと、同じ機能を Javascript でも書いてみたくなったというわけです。

つまづきの連続

 大概の所はなんとかなったのですが、やはり簡単にはいかなかった。
* displayDialog (返り値の取得方法が文法のページに無かったので試行錯誤)
* 指定のURLをブラウザーで開く方法 (window.open()などダメで試行錯誤)
* date 関数は new Date() を使う
* AppleのカレンダーアプリにEventを追加する (このサイトが参考になりました。)

スクリプトの説明

 私は、予定のところ(イベント名)に@を付けて場所も記載するようにしているので、location には何も入れるようになっていません。「イベント名@場所」と記載
 Googleと iCloud それぞれ登録するカレンダー名(ID)は冒頭に変数に直接セットしています。(自分の環境に合わせて直して使ってください)
 年も2020と固定値です。
月と日にちは一緒に「mmdd」と4桁で入力するようにしています。(時刻と分も同様に「hhmm」と)

Javascript のソース

event2cal_javascript.scpt
// イベントを Google カレンダーと、Appleのカレンダーアプリに登録する
// イベント名の後に@と場所を書く習慣があるので、カレンダーの「場所」には登録していない

var app = Application.currentApplication()
app.includeStandardAdditions = true

var GcalName = "<<登録するカレンダーI D>>@group.calendar.google.com"
var iCalName = "登録するカレンダー名"  // Apple のカレンダー名
var nen = "2020"        // 年は毎回入力が面倒なので、固定値にしている

var Summ = (app.displayDialog("イベント名@場所", {defaultAnswer: ""})).textReturned
var sYY = nen
var ans = (app.displayDialog("開始月日?(mmdd)", {defaultAnswer: ""})).textReturned
var sMM = ans.substr(0,2)
var sDD = ans.substr(2,2)
ans = (app.displayDialog("開始時分?(hhmm)", {defaultAnswer: ""})).textReturned
var sHH = ans.substr(0,2)
var sMin = ans.substr(2,2)
// 終了予定の年月日は開始予定と同じものを使うことにした
var eYY = sYY
var eMM = sMM
var eDD = sDD
ans = (app.displayDialog("終了時分?(hhmm)", {defaultAnswer: ""})).textReturned
var eHH = ans.substr(0,2)
var eMin = ans.substr(2,2)

// Googleカレンダーにイベントを登録する

var escSumm = encodeURIComponent(Summ)
var sDateTime = sYY + sMM + sDD + "T" + sHH + sMin + "00"
var eDateTime = eYY + eMM + eDD + "T" + eHH + eMin + "00"
var DateTime = sDateTime + "/" + eDateTime
ans = app.displayDialog(("Googleのカレンダーに「" + Summ + "" + DateTime + "を登録しますか?"), {
    buttons: ["しない", "する"],
    defaultButton: "する"
    })
if (ans.buttonReturned == "する") {
    var calURL =  "http://www.google.com/calendar/event?action=TEMPLATE&src=" + GcalName
    calURL = calURL + "&text=" + escSumm + "&dates=" + DateTime
    app.openLocation(calURL)
}

// iCalカレンダーにイベントを登録する
var sDate = new Date(sYY + "/" + sMM + "/" + sDD + " " + sHH + ":" + sMin + ":00")
var eDate = new Date(eYY + "/" + eMM + "/" + eDD + " " + eHH + ":" + eMin + ":00")
ans = app.displayDialog(("iCalのカレンダーに「" + Summ + "" + DateTime + "を登録しますか?"), {
    buttons: ["しない", "する"],
    defaultButton: "する"
    })
if (ans.buttonReturned == "する") {
    var iCal = Application("Calendar")
    iCal.activate()
    var Calendars = iCal.calendars.whose({name: iCalName})
    var Calendar = Calendars[0]
    var event = iCal.Event({summary: Summ, startDate: sDate, endDate: eDate})
    Calendar.events.push(event)
}

ご指導、コメント歓迎します。

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

フリマアプリ購入機能実装(pay.jp)

最終課題のチーム開発で担当した部分を自分の復習を兼ねて記録用に書きました。

商品の講入機能の実装

・クレジット登録(payjp)
  削除・再登録できる
・Payjpのコンソールからその売り上げが確認できる
・クレジット登録していないと購入できない
・購入すると商品状態が切り替わる(売り切れる)

PAY.JP クレジットカード機能

payjpとは?

PAY.JPを使うと、シンプルなAPIで
Webサービスなどにクレジットカード決済機能を簡単に導入できる。

payjpの仕組み

前提条件

hamlでの記載(gem 'haml-rails')
deviseが導入済みでログインができている
→Devise未導入や何もない状態からスタートする場合は、
『Devise導入の設定手順 ~haml使用/pay.jp導入の前準備~ (Rails)』を先に実施。

1.PAY.JPアカウントの作成

Payjpのサイトでアカウントを作成。

2.APIを確認しよう

ダッシュボードのAPIより確認ができます。
今回はテストモードでの実装なので、テスト秘密鍵とテスト公開鍵を使用。
alt

3.payjpのgemを設置しよう

下記をgemfileに記載しbundle installを実施.

gem 'payjp'

4.payjp.jsを読み込めるようにしよう

%script{src: "https://js.pay.jp/", type: "text/javascript"}を下記の通り追記します。

app/views/layouts/application.html.haml
%html
  %head
    %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
    %title payjptest
    %script{src: "https://js.pay.jp/", type: "text/javascript"}
    -# このscriptを記載
    = csrf_meta_tags
    = stylesheet_link_tag    'application', media: 'all', 'data-turbolinks-track': 'reload'
    = javascript_include_tag 'application', 'data-turbolinks-track': 'reload'
  %body
    = yield

5.テーブルを作成しよう

下記コマンドでpayjpのデータを保管するテーブルを作成します。

rails g model Card user_id:integer customer_id:string card_id:string

テーブルのカラムの紐づけは下記の通りです。

user_id ... Userテーブルのid
customer_id ... payjpの顧客id
card_id ... payjpのデフォルトカードid

※デフォルトカードidはトークンとは違う

db/migrate/20200206000000_create_cards.rb
class CreateCards < ActiveRecord::Migration[5.2]
  def change
    create_table :cards do |t|
      t.integer :user_id, null: false
      t.string :customer_id, null: false
      t.string :card_id, null: false

      t.timestamps
    end
  end
end

マイグレーションを実施

$ rails db:migrate

※カード情報そのものを保存することは禁止されている
payjpに保管されている情報を顧客idカードidで呼び出すことで情報取得や支払いなどに対応

6.コントローラーを作成しよう

app/controllers/cards_controller.rb
class CardsController < ApplicationController

  def new
    card = Card.where(user_id: current_user.id)
    redirect_to card_path(current_user.id) if card.exists?
  end


  def pay #payjpとCardのデータベース作成
    Payjp.api_key = Rails.application.credentials[:PAYJP_PRIVATE_KEY]
    #保管した顧客IDでpayjpから情報取得
    if params['payjp-token'].blank?
      redirect_to new_card_path
    else
      customer = Payjp::Customer.create(
        card: params['payjp-token'],
        metadata: {user_id: current_user.id}
      ) 
      @card = Card.new(user_id: current_user.id, customer_id: customer.id, card_id: customer.default_card)
      if @card.save
        redirect_to card_path(current_user.id)
      else
        redirect_to pay_cards_path
      end
    end
  end

  def destroy #PayjpとCardデータベースを削除
    card = Card.find_by(user_id: current_user.id)
    if card.blank?
    else
      Payjp.api_key = Rails.application.credentials[:PAYJP_PRIVATE_KEY]
      customer = Payjp::Customer.retrieve(card.customer_id)
      customer.delete
      card.delete
    end
      redirect_to new_card_path
  end

  def show #Cardのデータpayjpに送り情報を取り出す
    card = Card.find_by(user_id: current_user.id)
    if card.blank?
      redirect_to new_card_path 
    else
      Payjp.api_key = Rails.application.credentials[:PAYJP_PRIVATE_KEY]
      customer = Payjp::Customer.retrieve(card.customer_id)
      @default_card_information = customer.cards.retrieve(card.card_id)
    end
  end
end

コントローラ内のRails.application.credentials[:PAYJP_PRIVATE_KEY]は環境変数でテスト秘密鍵を設定し読み込みます。
credential.ymlファイルにAPIキーを記載

credential.yml
PAYJP_PRIVATE_KEY = 'sk_test_000000000000000000000000'
PAYJP_KEY = 'pk_test_00000000000000000000000'

(自分が他にも参考にした記事、メモ)
※where:与えられた条件にマッチするレコードをすべて返す。
https://qiita.com/nakayuu07/items/3d5e2f8784b6f18186f2
※blank?:空のオブジェクト、またはnilのオブジェクトかどうかを判定するメソッド。
( empty? || nil? と同等)
空のオブジェクト、またはnilのオブジェクトの場合は true
値が存在する場合は false
※ActiveRecordで条件に一致するデータが存在するかどうかを調べたいときは、exists?を使う。
https://qiita.com/uw9623/items/851ac5f71e316834c0fa
※メタデータとは、本体であるデータに関する付帯情報が記載されたデータです。データのためのデータ

7.カードの登録画面を作成しよう

登録画面と確認兼削除画面の2つを作成

登録画面
app/view/cards/new.html.haml
            = form_tag(pay_cards_path, method: :post, id: 'charge-form',  name: "inputForm") do
              %label カード番号
              .require 必須
              = text_field_tag "number", "", class: "number", placeholder: "半角数字のみ" ,maxlength: "16", type: "text", id: "card_number"
              %ul.signup-card-list
                %li
                  = image_tag "https://www-mercari-jp.akamaized.net/assets/img/card/visa.svg?238737266",width:"49px",height:"20px"
                %li
                  = image_tag "https://www-mercari-jp.akamaized.net/assets/img/card/master-card.svg?238737266",width:"34px",height:"20px"
                %li
                  = image_tag "https://www-mercari-jp.akamaized.net/assets/img/card/saison-card.svg?238737266",width:"30px",height:"20px"
                %li
                  = image_tag "https://www-mercari-jp.akamaized.net/assets/img/card/jcb.svg?238737266",width:"32px",height:"20px"
                %li
                  = image_tag "https://www-mercari-jp.akamaized.net/assets/img/card/american_express.svg?238737266",width:"21px",height:"20px"
                %li
                  = image_tag "https://www-mercari-jp.akamaized.net/assets/img/card/dinersclub.svg?238737266",width:"32px",height:"20px"
                %li
                  = image_tag "https://www-mercari-jp.akamaized.net/assets/img/card/discover.svg?238737266",width:"32px",height:"20px"
              %br
              %label 有効期限
              .require 必須
              %select#exp_month{name: "exp_month", type: "text"}
                %option{value: ""} --
                %option{value: "1"}01
                %option{value: "2"}02
                %option{value: "3"}03
                %option{value: "4"}04
                %option{value: "5"}05
                %option{value: "6"}06
                %option{value: "7"}07
                %option{value: "8"}08
                %option{value: "9"}09
                %option{value: "10"}10
                %option{value: "11"}11
                %option{value: "12"}12
              %span 月/
              %select#exp_year{name: "exp_year", type: "text"}
                %option{value: ""} --
                %option{value: "2019"}19
                %option{value: "2020"}20
                %option{value: "2021"}21
                %option{value: "2022"}22
                %option{value: "2023"}23
                %option{value: "2024"}24
                %option{value: "2025"}25
                %option{value: "2026"}26
                %option{value: "2027"}27
                %option{value: "2028"}28
                %option{value: "2029"}29
              %span%br
              %br
              %label セキュリティコード
              .require 必須
              = text_field_tag "cvc", "", class: "cvc", placeholder: "カード背面3~4桁の番号", maxlength: "4", id: "cvc"
              %br
              #card_token
              %br
              = submit_tag "追加する", id: "token_submit"

※参考記事がこの書き方だったのでそのまま書きましたが
 他の記事も載せておきます
【Rails】date_selectタグの使い方メモ

(自分が他にも参考にした記事、メモ)
※text_field_tagの使い方
text_field_tagヘルパーは、Viewファイルのform_withヘルパー内に記載することで、文字列入力用のテキストボックスを実現することができるViewヘルパー

text_field_tag(テキストボックス名, 初期文字列, {オプション1, オプション2,,,})
「テキストボックス名」は、テキストフィールドのidとnameに割り当てられ、フォームから値を取得するときのキー
「初期文字列」は、テキストフィールドのデフォルト値で、指定された文字列が入力された状態で表示される
オプションには、以下の4種類に加え、各種HTML属性をハッシュ形式で設定することが可能
:disable 無効化(trueにすると、入力できなくなる)
:size     表示可能文字数
:maxlength  入力可能文字数
:placeholder フィールド内にデフォルト表示される文字列。ただし、値として設定されているわけではなく、フォーカスが当たると削除される

※append()の基本的な使い方
append()メソッドは、指定した要素内の最後に引数のコンテンツを追加するメソッドです。
コンテンツにはテキストの他、HTML要素やJQueryオブジェクトが指定できる。

確認兼削除画面
app/view/card/show.html.haml
  クレジットカード情報
          .form-content
            %br
            = "**** **** **** " + @default_card_information.last4
            %br
            - exp_month = @default_card_information.exp_month.to_s
            - exp_year = @default_card_information.exp_year.to_s.slice(2,3)
            = exp_month + " / " + exp_year
            = form_tag(card_path(current_user.id), method: :delete, id: 'charge-form',  name: "inputForm") do
              %input{ type: "hidden", name: "card_id", value: "" }
              %button.delete-btn 削除する

(自分が他にも参考にした記事、メモ)
sliceでカード年数の2、3番目を取得
※type属性をhiddenにしたときの大きな特徴は、「送信したいデータがブラウザに表示されない」ブラウザ上では見えないことを利用して、ユーザーからの命令がどのような種類なのかをサーバーに送信するときに判断させるために、hiddenを使うこともある

8.Payjpにデータを送りトークンを取得しよう

jQueryを使用するので、railsに未設定の場合は設定をしてください。
設定方法はこちら

pay.jpのサンプル

app/assets/javascripts/payjp.js
document.addEventListener(
  "DOMContentLoaded", e => {//DOM読み込みが完了したら実行
    if (document.getElementById("token_submit") != null) { //token_submitというidがnullの場合、下記コードを実行しない
      Payjp.setPublicKey("pk_test_31f3ec18c086406c969b76cb"); //ここに公開鍵を直書き
      let btn = document.getElementById("token_submit"); //IDがtoken_submitの場合に取得
      btn.addEventListener("click", e => { //ボタンが押されたときに作動
        e.preventDefault(); //ボタンを一旦無効
        let card = {//カード情報生成
          number: document.getElementById("card_number").value,
          cvc: document.getElementById("cvc").value,
          exp_month: document.getElementById("exp_month").value,
          exp_year: document.getElementById("exp_year").value
        }; //入力されたデータを取得
        Payjp.createToken(card, (status, response) => {//トークン生成
          if (status === 200) { //成功した場合
            $("#card_number").removeAttr("name");
            $("#cvc").removeAttr("name");
            $("#exp_month").removeAttr("name");
            $("#exp_year").removeAttr("name"); //データを自サーバにpostしないように削除
            $("#card_token").append(
              $('<input type="hidden" name="payjp-token">').val(response.id)
            ); //取得したトークンを送信できる状態
            document.inputForm.submit();
            alert("登録が完了しました"); 
          } else {
            alert("カード情報が正しくありません。"); 
          }
        });
      });
    }
  },
  false
);

(自分が他にも参考にした記事、メモ)
※ボタンを押したタイミングで PAY.JP のサーバにクレジットカード情報を送信し、結果としてトークンなどの情報を受け取っている
※removeさせることでカード情報をparamsの値として含まれないようにしています
※HTTP レスポンスステータスコードは、特定の HTTP リクエストが正常に完了したどうかを示します。レスポンスは 5 つのクラスに分類されています。
1. 情報レスポンス (100–199),
2. 成功レスポンス (200–299),
3. リダイレクト (300–399),
4. クライアントエラー (400–499),
5. サーバエラー (500–599)
-成功レスポンス200-
リクエストが成功したことを示します。成功が意味することは、 HTTP メソッドにより異なります。
* GET: リソースが読み込まれ、メッセージ本文で転送された。
* HEAD: メッセージ本文にエンティティヘッダーある。
* PUT または POST: 操作の結果を表すリソースがメッセージ本文で送信される。
* TRACE: メッセージ本文に、サーバーが受け取ったリクエストメッセージが含まれている。

9.ルートを作成しよう

config/routes.rb
resources :cards, only: [:new, :show, :destroy] do
    collection do
      post 'pay', to: 'cards#pay'
    end
  end

(自分が他にも参考にした記事、メモ)
リソースベースのルーティングでは「index」「show」「new」「edit」「create」「update」「destroy」の7つのアクションへのルーティング自動で設定されます。これに別のアクションを呼び出すためのルーティングを追加する為collectionを記述します。

10.カードを登録してみよう

http://localhost:3000/cards/new にアクセスして登録できるか確認。

その時、テストカードで登録するようにしてください。
それ以外を打ち込んだ場合はトークンが発行できずはねられてしまいます。

購入編

参考

https://qiita.com/takachan_coding/items/f7e70794b9ca03b559dd

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

kintoneの新しいJS-SDK「kintone-rest-api-client」を使ってみた

はじめに

去年から地味に告知は出ていたのですが、kintoneの新しいJS-SDKがver.1.0.0としてついに正式リリースされました???

@kintone/rest-api-client Ver.1.0.0
https://github.com/kintone/js-sdk/tree/master/packages/rest-api-client

これからはこのSDKがJS-SDKでは主となるので、ぜひぜひ触ってみて下さい〜

ということで、
僕も触ってみたのでそのメモを書きます。いくつか注意点もあったのでそれも付け合わせとして添えます。

※ まぁ実は去年からver.0系としては公開はされていました

ツールの入れ方

今までのSDKと同様に npm で入れます。リポジトリが別なので既存のJS-SDKのアップデートでは入らないのでそこは注意してください。

npm install @kintone/rest-api-client

まぁここは何の問題もないですね!

コード書く前に

ちゃんとURLとか認証キーは .envファイル に書くようにしましょう。
※ 自分もよくコードに直書きしてしまうので、なるべく環境変数にするよう癖をつけないとです

# dotenvモジュールのインストール
npm install dotenv
.env
# kintone settings
KINTONE_URL=https://<subdomain>.cybozu.com
KINTONE_APITOKEN=XXXXXXXXXXXXXXXXXXXXXXXXXX

余談) dotenvのメモ

ちょっと調べてみると、コード内に require('dotenv').config(); と書くパターンと、実行時に引数に --require dotenv/config をつけるパターンの2種類あるらしい。

PC自体の環境変数とかAWS Lambdaの環境変数を使う場合は require('dotenv').config(); は必要ないので、そこを踏まえるとコード内に書くより 引数につける ほうが良いらしい。なるほど。

ただ、実行時に引数つけるの忘れそうだな〜

インポート・コネクションの貼り方

今までと変わっています。とても楽になりました。

const { KintoneRestAPIClient } = require('@kintone/rest-api-client');

const env = process.env;
const client = new KintoneRestAPIClient({
  baseUrl: env.KINTONE_URL,
  auth: {
    apiToken: env.KINTONE_APITOKEN
  }
});

シングルオブジェクトで URL、認証(ログイン、APIトークン、Basic) 全部指定します

?ポイント

新しいSDKは TypeScript で書かれているので、関数のパラメータなど何を書けばいいか説明が出ます。これがかなり便利!!

スクリーンショット 2020-02-19 12.50.17.png

補完されるからわざわざドキュメント読んで書き方調べる必要もなし!
スクリーンショット 2020-02-19 12.50.33.png

⚠ 注意点

わかる人はわかると思いますが、最初のインポート時の変数を KintoneRestAPIClient から他の変数名にただ変えるだけだと、候補がうまく出てこないです。
スクリーンショット 2020-02-19 12.54.29.png

基本は KintoneRestAPIClient と書けば良い気がしますが、どうしても他の変数名を使いたい場合は下記のようにすれば良いです。

const hoge = require('@kintone/rest-api-client').KintoneRestAPIClient;
const client = new hoge(...);

スクリーンショット 2020-02-19 13.41.32.png

レコード登録

補完に身を任せて書けばOK!

const { KintoneRestAPIClient } = require('@kintone/rest-api-client');

const env = process.env;
const client = new KintoneRestAPIClient({
  baseUrl: env.KINTONE_URL,
  auth: {
    apiToken: env.KINTONE_APITOKEN
  }
});

client.record.addRecord({
  app: XXXX,
  record: {
    text: {
      value: 'SDK使いやすーい!'
    }
  }
}).then(resp => console.log(resp)).catch(err => console.log(err));

ファイルアップロード

kintone APIのファイルアップロードの流れと同じく、

  1. kintone自体にファイルをアップロードして fileKey を取得
  2. fileKey をレコードに保存

と2回処理を書きます。

⚠ 注意点

現状のSDKでは ファイルパス指定 には対応していないので、ローカルにあるファイルは fsモジュール などを使ってBufferに変換してからアップロードする必要があります。まぁそこまで手間ではないですけど。

const fs = require('fs'); // ファイル操作用のモジュールをインポート
// 他は上で書いてるし割愛

client.file.uploadFile({
  file: {
    name: 'hoge.png',
    data: fs.readFileSync('<YOUR_FILE_PATH>')
  }
}).then(resp => {
  return client.record.addRecord({
    app: XXXX,
    record: {
      file: {
        value: [{
          fileKey: resp.fileKey
        }]
      }
    }
  });
}).then(resp => console.log(resp)).catch(err => console.log(err));

ファイルダウンロード

アップロードと逆の手順でやればOKです。

// この部分は割愛

client.record.getRecord({
  app: XXXX,
  id: XXXX
}).then(resp => {
  return client.file.downloadFile({
    fileKey: resp.record.file.value[0].fileKey
  });
}).then(res => {
  fs.writeFileSync('hoge.png', res);
}).catch(err => console.log(err));

おわりに

いやー、補完があるとめっちゃ便利ですね〜?
なんとなく 「次来るのはappか?recordか?」と予想もできるので、ドキュメント読まずにさくさくコーディングができます!

それでは! ≧(+・` ཀ・´)≦

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

excel, js, 長期休暇の申請について、google先生と中二病患者の答えの比較。

 4才の時に幼稚園のお遊戯が大の苦手で、(音痴な歌声を伴いながら大人の動きの真似をするというこんなつまらない儀式に偽り以外の何物でもない笑みを浮かべる周りの子たちはきっと宇宙の悪魔か何かで、自分とは明確に異なる生き物でありその違いとは男女の性差よりも手前に来るおよそ決定的なものだ。。)と悟って以来の先天性難治性中二病自覚患者(ADHD Lv5)である私が、excel, js, 長期休暇の取得方法についての疑問に対する、google先生と拙の回答を簡潔に対比してみました。

 それぞれにメリットとデメリットがあると思いますので、ケースバイケースで取捨選択してみてください。

Q1. 【excel】(c)が©️になってしまいます。

 A1_1.google先生の答え

   「オートコンプリートをオフにします。」

 A1_2.季節性中二病患者の答え

   「閉じカッコを全角にしてもバレません。」

 A1_3.先天性難治性中二病患者の答え

   ="(c"&")"

Q2.書いたjsがIEのみ動作しません。何のエラーも出ません。

 A2_1.google先生の答え

   「console.logの消し忘れに注意してください。」

 A2_2.季節性中二病患者の答え

   <script src=hoge><a href=//google.com/search?q=chrome>みんなが使ってるブラウザ</a></script>

 A2_3.先天性難治性中二病患者の答え

  function cl(s){try{console.log(s)}catch(e){}}

Q3.長期休暇を取りたいのですが、上司に切り出せずにいます。

 A3_1.google先生の答え

   「上司には早めに伝えておきましょう。仲間同士で欠員を補い合う環境を普段から作っておくのも大事です。」

 A3_2.季節性中二病患者の答え

   「『一昨年亡くなったのは生みの母の育ての母であり、昨晩救急車で運ばれたのは育ての母の生みの母です。』」

 A3_3.先天性難治性中二病患者の答え

  「『70年前に制定された日本国憲法でありますが、国民はやれ改憲だ護憲だと互いに喚き散らすばかりで、右翼、左翼ともども今目の前にある条文の中身を何一つ遵守しておりません。とりわけ男女平等の原則が盛り込まれた事は当時として画期的であったにも拘らず、現実社会は21世紀のこの期に及んで大手企業の接待費も殆どが男性役員、男性社員の為に使われ、サラリーマンの毎日のランチと比較して主婦の週一のランチを高いと申す割りにはゴルフだのノーパンしゃぶしゃぶだのにはしぶとく目を瞑り、管理職や重役の女性比率も世界最低ランクの域に成り下がっております。一国を覆わんとするこの巨悪の根元は男性の男性による男性のための論理によって共和的平等とは乖離したファシズム的平等の押しつけが罷り通る極東アジア固有の民族性にあり、その事を如実に裏付けするケースとして「長期休暇を取る者は管理職に成れない」という合理性の欠片もない悪しき慣習があります。30を超えたばかりの女性に出産か係長かの二者択一を迫るこの手の陰湿な間接的不平等はヘイトスピーチや不買運動の類いよりある意味で根深い差別問題であるばかりか、少子化問題、実力主義の否定といったの諸々の問題とも密接に結びつくものであり、愛する姪っ子がやがて大人になりリケジョとなり子育てをしながらエンジニアとして幸せに生きることを今より望み人生の糧とする下名としては到底看過できるものではなく、この際上長には率先して長期休暇を取って頂き、当部門、当社、ひいては愛する母国における男女平等の礎を築いて頂きたいところではありますが、しかしながら私めをはじめ、部下であり平社員である者共が長期休暇を取らない以上は上長もなかなか取得しづらいかと存じます。従いまして、下名が第一歩兵部隊の特攻隊員となり、あるいは開墾者として荒野の芝を刈り草を毟る事で上長の王道に花を添えんとする自らの使命を帰結し、来週の金曜より十七日間、カンボジアのアンコールワットへ嫁探しに参る事に致しました。お土産はストゥントレン県名産のコーヒー豆で宜しいでしょうか?』」

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

高さが可変のアニメーションするアコーディオンメニュー 【JavaScript】【高さ可変】【脱jQuery】

アコーディオンメニューはサイトの様々な箇所で使われるため、ページの中で複数箇所に設置でき、CSSで容易に装飾を変更できるものを作りました。
特に、アコーディオンのコンテンツの高さをcssで都度指定の必要がないことも使い回しのポイントです。

動作デモ

See the Pen アコーディオンメニュー by masato yamada (@marchin133) on CodePen.

使い方

JavaScriptを読み込み、cssに追記します。

JavaScript

別ファイルなどにして読み込んでください

acordion.js
document.addEventListener('DOMContentLoaded', function () {

    var acodionMenu = document.querySelectorAll('.acdn');
    var acodionContentHight = [];

    for (var i = 0; i < acodionMenu.length; i++) {

        acodionContentHight[i] = acodionMenu[i].nextElementSibling.clientHeight + "px";
        acodionMenu[i].nextElementSibling.style.height = acodionContentHight[i];

            if (acodionMenu[i].nextElementSibling.classList.contains('acdn_close')) {
                acodionMenu[i].nextElementSibling.style.height = '0px';
            }

        acodionMenu[i].addEventListener('click', function () {

            var clickitem = Array.prototype.indexOf.call(acodionMenu, this);

            if (this.nextElementSibling.classList.contains('acdn_close')) {
                this.nextElementSibling.style.height = acodionContentHight[clickitem];
            } else {
                this.nextElementSibling.style.height = '0px';
            }

            this.classList.toggle('acdn_open');
            this.classList.toggle('acdn_close');
            this.nextElementSibling.classList.toggle('acdn_open');
            this.nextElementSibling.classList.toggle('acdn_close');

        }, false);

    }


}, false);

html

1)アコーディオンの開閉に使う要素に、.acdn クラスをつけてください。
2)ページが表示されたときに閉じているときは.acdn_close も付与してください。逆に展開しているときは.acdn_openをつけてください。
3).acdnが付けられた要素のの要素がアコーディオンとして機能します。その要素に展開状態を示す.acdn_closeまたは.acdn_openを付与してください。
4)htmlはこれだけです。

sample.html
<div class="menu_title acdn acdn_close">アコーディオンメニュー1</div> <!-- 閉じた状態で表示させたい場合 -->
    <div class="menu_content acdn_close">
        <ul>
            <li>項目1</li>
            <li>項目2</li>
            <li>項目3</li>
            <li>項目4</li>
        </ul>
    </div>
    <div class="menu_title acdn acdn_open">アコーディオンメニュー2</div> <!-- 開いた状態で表示させたい場合 -->
    <div class="menu_content acdn_open">
        <ul>
            <li>項目1</li>
            <li>項目2</li>
            <li>項目3</li>
            <li>項目4</li>
        </ul>
    </div>
    <div class="menu_title acdn acdn_open">アコーディオンメニュー2</div>
    <div class="menu_content acdn_open">
        <ul>
            <li>項目1</li>
            <li>項目2</li>
            <li>項目3</li>
            <li>項目4</li>
        </ul>
    </div>

CSS

1).menu_title クラス名不問 アコーディオンの開閉として使用する要素です。自由に装飾してください。jsでクリックイベントが付きますので、面積があったほうがいいです。
2).menu_content クラス名不問 アコーディオンで高さを、もともとの高さ→0pxになる要素です。必要に応じて装飾ください。

overflow: hidden; この内側の要素を消す(見えなくする)ために必要です。
transition: all 0.5s ease-out; トランジションはお好きな数値に変更などしてください。設定しないとアニメーションしません。

3)要素に.acdn_closeまたは.acdn_openが付与されることを利用して、展開時や縮小時に表現を変えることができます。
例:サンプルでは押された要素(.menu_title)の背景色を変えています。

sample.css
.menu_title{
  width:400px;
  height: 50px;
}

.menu_content{
  width:400px;
  overflow: hidden;
  transition: all 0.5s ease-out;
}

.menu_title.acdn_open{
  background-color: #cccccc;
}

動作のしくみ

.acdnが付けらた要素にクリックイベントを付け、その次の要素の高さを取得しています。
一度高さを格納したあとに、.acdn_closeがついている要素については高さを0pxに設定して閉じた状態にしています。
高さが画面描画後に動的に変更されない限り、アコーディオンコンテンツの中身の高さを調べてcssに記述することなく、トランジションさせることができます。

クリックイベントが起きるたび、要素に.acdn_closeまたは.acdn_openを都度付けたり消したりすることで動きを実現しています。

コードが冗長であるなど、ご指摘いただけると嬉しいです。

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

【cakephp】FormHelperのlinkメソッド

linkメソッド

HtmlHelperのlinkメソッドは、アクションを実行したときに指定したURLへ移動するための機能

  • 書き方
$this->Html->link(タイトル, $URL, $オプション);
$this->Html->link(タイトル, ['controller' => 'コントローラ名', 'action' => 'アクション名', 引数1, $クエリ文字列オプション]);
  • 具体例
$this->Html->link( $this->Html->image('画像', array("alt" => '代替テキスト', 'width'=>'n', 'height'=>'n')), 'javascript:window.close()', array('escape'=>false));

これを実行すると画像が表示、クリックすると第二引数のjavascript:window.close()が実行されwindowを閉じることができる

javascript:という書き方ですが自分も初めて知ったので今度記事にします
簡単に説明するとjavascriptをリンクのように扱える書き方です

第三引数の'escape'=>falseはhtmlの特殊文字を変換しないようにしてます

以下のhtmlが作成されます

<a href="javascript:window.close()">
 <img src="画像" alt="代替テキスト" width="n" height="n">
</a>

参考にしたサイト
https://www.sejuku.net/blog/27174
https://book.cakephp.org/3/ja/views/helpers/html.html

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

Vue.jsで翻訳も辞書引きもできるブラウザ拡張機能を作った話

?前置き

初記事なので前置きが長いです。本編を見たい方は?要件まで飛んで下さいね。

?これはなにか

javascriptにほんの少し慣れ、なにか実用性のあるものを作ってみようとし、?辞書+?️‍?翻訳の両方ができる器用貧乏な拡張機能を自作してみた記録

こんなんができました。

o.gif

辞書データをインポートし、選択した部分の単語が辞書内に存在すれば、その結果を表示します
単語or熟語が見つからなかったらGoogle翻訳に投げて、翻訳結果を取得します

GitHubリポジトリ
Chrome ウェブストア
Firefox Addons

?誰

  • かつて趣味でジオシティーズにHTML・JS・CSSを一つのファイルに書いてホームページを作り、隠しページを仕込み、魔法のiらんどやモバゲーで小説を読んでいたような人種
  • 特にやりたい仕事もなかったので、偶然居酒屋で初対面の人になんとなく誘われ、2年半前になんとなく新卒でなんとなく孫受けCOBOLerに
  • ちょうど1年程前に鏡に映った自分を見たら死んだ目をしていたので、脱出してベンチャーへ
  • 今はpythonとjavascriptとC++とWebGLと・・・あとなんだっけ・・・を書いています
    全部ちょっとずつかじった結果まだ1ミリくらいしか分かっていない
  • javascriptだけ2ミリくらい分かった気がするので、何かを作ってみるついでにQiitaにもアウトプットしてみようとしてみんとて、するなり。

?‍?作るものを決める

  • 調べ物をしていると、英語のほうが役に立つ資料が多いが、英語が1ミリもで読めない でも読めるようになりたい
  • ものぐさなのでGoogle翻訳を開いて、文章をコピペする手間すら惜しい

→ 難易度的にもお手軽そうなので、お手軽翻訳機を作ってみYO!

?要件

  • Webページ内で、選択した部分を(できればワンクリックで)パッと翻訳したい
  • 普段のブラウジングは邪魔してほしくない
  • 単語の意味も覚えたいので辞書も引きたい
  • ドイツ語やその他の言語も自動判別して翻訳できたらしたい

(・・・どんだけわがままやねん)

?偉大な先輩方が作ったものを覗いてみる

  • Mouse Dictionary
    辞書データを読み込んで、マウスオーバーした単語の意味を爆速で表示していってくれるツール
    もうこれ使えばいいんじゃないかと思うくらい完成されている・・・すごい・・・
    この作者さんが作ったゲームを昔めっちゃやり込・・・話が脱線するのでまたの機会にします
  • Simple Translate
    今までこれ使ってました
    これに辞書機能足せたらええなーと言うのが発端でもあります

この2つを合わせた拡張機能が作れたらめっちゃ便利なのでは・・・????
(多分既にある・・・?私の調べ方が足りないだけな気がしている)

?基本的な仕組み

極力シンプルに作りました。

image.png

基本機能は2週間足らずで完成しましたが、

  • 途中から突然思い立ってvue-web-extensionを使うように変更したり、
  • 辞書データの管理にstorage APIを使ったら速度がとんでもなく遅くて無限に最適化が必要になったり、
  • ↑の挙動がFirefoxとChromeでぜんぜん違うので対応に苦労したり、
  • 権限周りのせいで拡張機能の審査に1ヶ月位待たされたり( これが一番時間かかった )

で残り1割の完成にものすごく時間がかかりました。

最初に設計をしっかりしなかったせいです。エンジニア失格ですね。

?‍?使い方

⚙️オプションを開く

オプションページはこんな感じになっています。
シンプルイズベストが信条なので、ゴチャゴチャ作り込むのはやめましためんどくさかったとも言う

image.png

  • chromeの場合
    アイコンを右クリックし、オプションを開く

image.png

  • Firefoxの場合
    アドレスバーに about:addonsと入力後、オプションを開く

image.png

?辞書データの登録

※ 現在は英辞郎データ ( 有料、https://booth.pm/ja/items/777563 )、ejdic-hand ( 無料、https://kujirahand.com/web-tools/EJDictFreeDL.php ) にのみ対応しています
需要があればJSONにも対応できればいいのですが、私自身の需要がないので未実装です

  1. 辞書データを選択します
    ※ 選択した時点で登録が開始します
  2. 暫く待つと登録が完了します
  3. 単語を選択すると、辞書に載っていた場合にその意味を表示できます

?️‍?翻訳機能

  1. Google App Scriptで翻訳スクリプトを作ります。
    Google翻訳APIを無料で作る方法
    3 分で作る無料の翻訳 API with Google Apps Script
    この辺りを参考にしてAPIを作ります。

下図はその一例です。

image.png

main.gs
function doPost(e) {
  const text = e.postData.getDataAsString();
  const translatedText = LanguageApp.translate(text, "", "ja");

  const output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.TEXT);
  output.setContent(translatedText);
  return output;
}

確認はPowerShellでやりました。
curlだったら
$ curl -L -d "this is a pen." https://script.google.com/macros/s/[生成されたURL]
みたいな感じですかね。

check
> Invoke-WebRequest -Method Post -Body 'this is a pen.' https://script.google.com/macros/s/[生成されたURL]
StatusCode        : 200
StatusDescription : OK
Content           : これはペンです。
...
  1. 実行URLをオプションページのtranslate API settingsのテキストボックスに貼り付け、
    API動作テストボタンを押下します
  2. テストが行われ、成功した場合にそのURLが登録されます
    失敗した場合はその旨が表示されます
  3. 文章を選択すると、辞書に選択部分の単語が載っていなかった場合に翻訳を行い、その結果を表示します

?機能の一時停止

ちょっと邪魔!今は出てこないで!!!
っていう時に、拡張機能のアイコンを押すと、翻訳機能のオンオフが切り替えられるようにしました。
地味だけどあるとほんの少し便利なので加えました。

?おわりに

  • こういうのをサクッと見通しよく書けるVueってやっぱりすごいですね。
    ほんとにほんとに便利な時代になりました。
  • 辞書データの文字コード自動検出は、辞書データの最初の数文字を読み込んで、そのバイト列で判断するという仕様。
    もしその数文字が全部英語だったら勝手にUTF8になる。文字コード周りはどうやら闇が深いようなので戦うのをやめましたが、いつか直したい。
  • iframeに対応してなかったり、カスタマイズ性が低かったりするのはそのうち対応します。頭の悪いコードを書いてたら石を投げてください
  • 昔の人間な上に何年もCOBOLを触っていたせいで、原始人が高度文明に触れて途方に暮れるみたいな気持ちで作っていました。
    作り上げるにあたって色んな壁にぶち当たってるので、いつかそういう技術内容の記事も書きたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Vue.jsで翻訳も辞書引きもできるブラウザ拡張機能を作りました

?前置き

初記事なので前置きが長いです。本編を見たい方は?要件まで飛んで下さいね。

?これはなにか

javascriptにほんの少し慣れ、なにか実用性のあるものを作ってみようとし、?辞書+?️‍?翻訳の両方ができる器用貧乏な拡張機能を自作してみた記録

こんなんができました。

o.gif

辞書データをインポートし、選択した部分の単語が辞書内に存在すれば、その結果を表示します
単語or熟語が見つからなかったらGoogle翻訳に投げて、翻訳結果を取得します

GitHubリポジトリ
Chrome ウェブストア
Firefox Addons

?誰

  • かつて趣味でジオシティーズにHTML・JS・CSSを一つのファイルに書いてホームページを作り、隠しページを仕込み、魔法のiらんどやモバゲーで小説を読んでいたような人種
  • 特にやりたい仕事もなかったので、偶然居酒屋で初対面の人になんとなく誘われ、2年半前になんとなく新卒でなんとなく孫受けCOBOLerに
  • ちょうど1年程前に鏡に映った自分を見たら死んだ目をしていたので、脱出してベンチャーへ
  • 今はpythonとjavascriptとC++とWebGLと・・・あとなんだっけ・・・を書いています
    全部ちょっとずつかじった結果まだ1ミリくらいしか分かっていない
  • javascriptだけ2ミリくらい分かった気がするので、何かを作ってみるついでにQiitaにもアウトプットしてみようとしてみんとて、するなり。

?‍?作るものを決める

  • 調べ物をしていると、英語のほうが役に立つ資料が多いが、英語が1ミリもで読めない でも読めるようになりたい
  • ものぐさなのでGoogle翻訳を開いて、文章をコピペする手間すら惜しい

→ 難易度的にもお手軽そうなので、お手軽翻訳機を作ってみYO!

?要件

  • Webページ内で、選択した部分を(できればワンクリックで)パッと翻訳したい
  • 普段のブラウジングは邪魔してほしくない
  • 単語の意味も覚えたいので辞書も引きたい
  • ドイツ語やその他の言語も自動判別して翻訳できたらしたい

(・・・どんだけわがままやねん)

?偉大な先輩方が作ったものを覗いてみる

  • Mouse Dictionary
    辞書データを読み込んで、マウスオーバーした単語の意味を爆速で表示していってくれるツール
    もうこれ使えばいいんじゃないかと思うくらい完成されている・・・すごい・・・
    この作者さんが作ったゲームを昔めっちゃやり込・・・話が脱線するのでまたの機会にします
  • Simple Translate
    今までこれ使ってました
    これに辞書機能足せたらええなーと言うのが発端でもあります

この2つを合わせた拡張機能が作れたらめっちゃ便利なのでは・・・????
(多分既にある・・・?私の調べ方が足りないだけな気がしている)

?基本的な仕組み

極力シンプルに作りました。

image.png

基本機能は2週間足らずで完成しましたが、

  • 途中から突然思い立ってvue-web-extensionを使うように変更したり、
  • 辞書データの管理にstorage APIを使ったら速度がとんでもなく遅くて無限に最適化が必要になったり、
  • ↑の挙動がFirefoxとChromeでぜんぜん違うので対応に苦労したり、
  • 権限周りのせいで拡張機能の審査に1ヶ月位待たされたり( これが一番時間かかった )

で残り1割の完成にものすごく時間がかかりました。

最初に設計をしっかりしなかったせいです。エンジニア失格ですね。

?‍?使い方

⚙️オプションを開く

オプションページはこんな感じになっています。
シンプルイズベストが信条なので、ゴチャゴチャ作り込むのはやめましためんどくさかったとも言う

image.png

  • chromeの場合
    アイコンを右クリックし、オプションを開く

image.png

  • Firefoxの場合
    アドレスバーに about:addonsと入力後、オプションを開く

image.png

?辞書データの登録

※ 現在は英辞郎データ ( 有料、https://booth.pm/ja/items/777563 )、ejdic-hand ( 無料、https://kujirahand.com/web-tools/EJDictFreeDL.php ) にのみ対応しています
需要があればJSONにも対応できればいいのですが、私自身の需要がないので未実装です

  1. 辞書データを選択します
    ※ 選択した時点で登録が開始します
  2. 暫く待つと登録が完了します
  3. 単語を選択すると、辞書に載っていた場合にその意味を表示できます

?️‍?翻訳機能

  1. Google App Scriptで翻訳スクリプトを作ります。
    Google翻訳APIを無料で作る方法
    3 分で作る無料の翻訳 API with Google Apps Script
    この辺りを参考にしてAPIを作ります。

下図はその一例です。

image.png

main.gs
function doPost(e) {
  const text = e.postData.getDataAsString();
  const translatedText = LanguageApp.translate(text, "", "ja");

  const output = ContentService.createTextOutput();
  output.setMimeType(ContentService.MimeType.TEXT);
  output.setContent(translatedText);
  return output;
}

確認はPowerShellでやりました。
curlだったら
$ curl -L -d "this is a pen." https://script.google.com/macros/s/[生成されたURL]
みたいな感じですかね。

check
> Invoke-WebRequest -Method Post -Body 'this is a pen.' https://script.google.com/macros/s/[生成されたURL]
StatusCode        : 200
StatusDescription : OK
Content           : これはペンです。
...
  1. 実行URLをオプションページのtranslate API settingsのテキストボックスに貼り付け、
    API動作テストボタンを押下します
  2. テストが行われ、成功した場合にそのURLが登録されます
    失敗した場合はその旨が表示されます
  3. 文章を選択すると、辞書に選択部分の単語が載っていなかった場合に翻訳を行い、その結果を表示します

?機能の一時停止

ちょっと邪魔!今は出てこないで!!!
っていう時に、拡張機能のアイコンを押すと、翻訳機能のオンオフが切り替えられるようにしました。
地味だけどあるとほんの少し便利なので加えました。

?おわりに

  • こういうのをサクッと見通しよく書けるVueってやっぱりすごいですね。
    ほんとにほんとに便利な時代になりました。
  • 辞書データの文字コード自動検出は、辞書データの最初の数文字を読み込んで、そのバイト列で判断するという仕様。
    もしその数文字が全部英語だったら勝手にUTF8になる。文字コード周りはどうやら闇が深いようなので戦うのをやめましたが、いつか直したい。
  • iframeに対応してなかったり、カスタマイズ性が低かったりするのはそのうち対応します。頭の悪いコードを書いてたら石を投げてください
  • 昔の人間な上に何年もCOBOLを触っていたせいで、原始人が高度文明に触れて途方に暮れるみたいな気持ちで作っていました。
    作り上げるにあたって色んな壁にぶち当たってるので、いつかそういう技術内容の記事も書きたい。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初心者によるプログラミング学習ログ 243日目

100日チャレンジの243日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
243日目は、

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

Azureみたいなウィザードっぽい入力フォームを作りたい Phase2 とまどいの確認画面実装編

ローカルでも動くように、ちょっとファイルを書き換えた。

以下のフォルダ構成にすることで、ローカルでも動くようにした。

index.html(本記事のソースをコピペしただけのHTMLファイル)
AdminLTE-3.0.2(フォルダ)
css
img
js
plugins

css, img, jsはAdminLTE3をダウンロードしたときにdistフォルダ直下に入ってるもの。
pluginsはAdminLTE3をダウンロードしたときにindex.htmlとかと同じフォルダに入ってるやつ。

結論から言うと、確認画面以外はそれっぽく作成できた。

ソースは以下。(クリックするとソースが表示されます)
<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <meta http-equiv="x-ua-compatible" content="ie=edge">

    <title>テストページ</title>

    <!-- Font Awesome Icons -->
    <link rel="stylesheet" href="AdminLTE-3.0.2/plugins/fontawesome-free/css/all.min.css">
    <!-- Theme style -->
    <link rel="stylesheet" href="AdminLTE-3.0.2/css/adminlte.min.css">
    <!-- Google Font: Source Sans Pro -->
    <link href="https://fonts.googleapis.com/css?family=Source+Sans+Pro:300,400,400i,700" rel="stylesheet">
</head>

<body>
<div class="split-input-form container-fluid">
    <div class="Title">分割入力フォーム</div>

    <div class="docking">
        <div class="docking-body p-3">
            <ul class="nav nav-tabs">
                <li class="nav-item">
                    <a href="#tab1-content" class="nav-link" data-toggle="tab">基本</a>
                </li>
                <li class="nav-item">
                    <a href="#tab2-content" class="nav-link" data-toggle="tab">住所</a>
                </li>
                <li class="nav-item">
                    <a href="#tab3-content" class="nav-link" data-toggle="tab">連絡先</a>
                </li>
                <li class="nav-item">
                    <a href="#tab4-content" class="nav-link" data-toggle="tab">確認</a>
                </li>
            </ul>
            <div class="tab-content">
                <div id="tab1-content" class="tab-pane active">
                    <div>
                        <span>基本の情報を入力します。</span>
                    </div>
                    <br />
                    <div class="font-weight-bold" style="margin: 10px 0px;">基本情報</div>
                    <div>
                        <span>あなたの名前と年齢を入力します。</span>
                    </div>
                    <br />
                    <div class="row form-group">
                        <label class="col-xl-1 d-flex align-items-center">
                            名前
                        </label>
                        <input type="text" class="col-xl-11 form-control" onchange="InputNameFunc(this.value)" id="InputName" placeholder="名前を入力" />
                    </div>
                    <br />
                    <div class="row form-group">
                        <label class="col-xl-1 d-flex align-items-center">
                            年齢
                        </label>
                        <input type="text" class="col-xl-11 form-control" id="InputAge" placeholder="年齢を入力" />
                    </div>
                </div>
                <div id="tab2-content" class="tab-pane">
                    <div>
                        <span>あなたの住所を入力します。</span>
                    </div>
                    <br />
                    <!-- general form elements -->
                    <div class="row form-group">
                        <label for="selectPrefectureLabel" class="col-xl-1 d-flex align-items-center">
                            都道府県
                        </label>
                        <select class="form-control" id="selectPrefecture">
                            <option>北海道</option>
                            <option>本州</option>
                            <option>四国</option>
                            <option>九州</option>
                        </select>
                    </div>
                    <br />
                    <div class="form-group">
                        <label for="selectAreaLabel" class="col-xl-1 d-flex align-items-center">
                            地域
                        </label>
                        <div class="custom-control custom-radio">
                            <input class="custom-control-input" type="radio" id="AreaRadioWest" name="AreaRadio" checked="" value="どちらかというと西">
                            <label for="AreaRadioWest" class="custom-control-label">
                                どちらかというと西
                            </label>
                        </div>
                        <div class="custom-control custom-radio">
                            <input class="custom-control-input" type="radio" id="AreaRadioEast" name="AreaRadio" value="どちらかというと東">
                            <label for="AreaRadioEast" class="custom-control-label">
                                どちらかというと東
                            </label>
                        </div>
                    </div>
                </div>
                <div id="tab3-content" class="tab-pane">
                    <div>
                        <span>希望する連絡手段を選んでください。(複数選択可能)</span>
                    </div>
                    <br />
                    <div class="form-group">
                        <div class="custom-control custom-checkbox">
                            <input class="custom-control-input" type="checkbox" id="customboxMobile" value="携帯電話" />
                            <label for="customboxMobile" class="custom-control-label">
                                携帯電話
                            </label>
                        </div>
                        <div class="custom-control custom-checkbox">
                            <input class="custom-control-input" type="checkbox" id="customboxPhone" value="固定電話" />
                            <label for="customboxPhone" class="custom-control-label">
                                固定電話
                            </label>
                        </div>
                        <div class="custom-control custom-checkbox">
                            <input class="custom-control-input" type="checkbox" id="customboxFax" value="FAX" />
                            <label for="customboxFax" class="custom-control-label">
                                FAX
                            </label>
                        </div>
                    </div>
                </div>
                <div id="tab4-content" class="tab-pane">
                    <div>
                        <span>入力した内容を確認します。以下でよろしいですか?</span>
                    </div>
                    <br />
                    <div class="form-group">
                        <div class="row">
                            <label class="col-xl-1 d-flex align-items-center">
                                名前
                            </label>
                            <p class="col-xl-11" id="InputNameResult" />
                        </div>
                        <br />
                        <div class="row">
                            <label class="col-xl-1 d-flex align-items-center">
                                年齢
                            </label>
                            <p class="col-xl-11" id="InputAgeResult" />
                        </div>
                        <br />
                        <div class="row">
                            <label class="col-xl-1 d-flex align-items-center">
                                都道府県
                            </label>
                            <p class="col-xl-11" id="InputPrefResult" />
                        </div>
                        <br />
                        <div class="row">
                            <label class="col-xl-1 d-flex align-items-center">
                                地域
                            </label>
                            <p class="col-xl-11" id="InputAreaResult" />
                        </div>
                        <br />
                        <div class="row">
                            <label class="col-xl-1 d-flex align-items-center">
                                連絡先
                            </label>
                            <p class="col-xl-11" id="InputContactResult" />
                        </div>
                    </div>
                </div>
            </div>
        </div>
    </div>
</div>

<!-- jQuery -->
<script src="AdminLTE-3.0.2/plugins/jquery/jquery.min.js"></script>
<!-- Bootstrap 4 -->
<script src="AdminLTE-3.0.2/plugins/bootstrap/js/bootstrap.bundle.min.js"></script>
<!-- AdminLTE App -->
<script src="AdminLTE-3.0.2/js/adminlte.min.js"></script>

<script>
    function InputNameFunc(value) {
         document.getElementById("InputNameResult").innerHTML = value;
    }

</script>

</body>
</html>

できていないところは、言わずもがな確認画面。
今のソースだと、[基本]タブの「名前」に入力したテキストは、[確認]タブの「名前」に反映されるようになっている。
他の項目に対しても、あとは同じようなことをやれば確認画面は作れる。ここまできたら、ぶっちゃけもうそんなに時間もかからないと思う。

けど、いちいち項目ごとに

document.getElementById("InputNameResult").innerHTML = value;

みたいなコード書くのが究極にだるい。ついでに、保守性とか考えたらどうなんだろう、という感じ。

というわけで、こういうときはAzureのソースを見てみよう!
と思い、ジロジロソースを参照。すると、なんかやたら

<!-- /ko -->

みたいなコメントがあちこちにあることに気づく。なんだこれ・・・・と思って調べてみたら、
どうやら「knockout.js」というライブラリで使う表記(コメント)であることがわかった。
(実際にAzureがこれを使用しているのかは知らないが)

なので、ちょっとコレがなんなのか調べつつ、使えそうなら取り入れてみよう!と決意したぴよぴよくんであった・・・・

つづく(たぶん)

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