20190711のJavaScriptに関する記事は30件です。

JavaScriptを使い、CSS3を使ったanimationの内容を変える方法

はじめに

CSSでは、 ainmation プロパティと keyframes を使ってアニメーションができます。

1つのオブジェクトに対して1つのアニメーション設定をするだけなら、それほど難しいことはありません。
しかし、ボタンの入力内容などに応じてアニメーション内容を切り替えたいという事もあります。

このページでは JavaScriptを使い、CSSのアニメーションを切り替える方法を書いてみました。

まずは1回だけのCSSアニメーション

まずは、ボタンが押されたらアニメーションを1回だけおこなうサンプルを作ってみます。
ここでは、div要素の中にあるspanで作った赤色の箱(id="box")を、transform:translateX で横移動をさせてみます。

<div class="parent">
 <span class="child" id="box"></span>
</div>

<p>
<input type="button" onclick="move();" value=" move! " />
</p>
div.parent {
    position: relative;
    border: 1px solid black;
    width : 400px;
    height: 100px;
}
span.child {
    position: absolute;
    left: 100px;
    top: 25px;
    width : 50px;
    height: 50px;
    background-color: red;
}
@keyframes move {
    to {
        transform:translateX(200px);
    }
}
.moveToRight {
    animation-name: move;
    animation-duration: 1s;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}
function move() {
    const objBox = document.getElementById("box");
    objBox.classList.add('moveToRight');
}

keyframes として 200px 右に動く動きを move という名前で定義しておき、この move を1秒間で1回だけアニメーションさせる定義が .moveToRight です。ボタンが押されたら JavaScript で moveToRight を box の classList に追加してやることでアニメーションが動き出します。

CSSで「animation-fill-mode: forwards」としているので、box は移動終了した場所で止まっています。

アニメーション内容を変更する方法

上のサンプルでは、アニメーションは1回だけしか動作しません(ボタンは何回でも押せますが、押しても何も変化しません)。
もし何回も同じアニメーションをおこなわせたければ、アニメーション終了時に classList から前回のアニメーションを取り除いてやる必要があります。

では、ボタンが押されたら、今度は元の位置までアニメーションで戻るようにしたい場合はどうすれば良いでしょうか?
この場合もアニメーション終了時に classList から前回のアニメーションを取り除いた上で、今度は逆向きに移動する transform の keyframes を使ったアニメーションを定義し、それを classList に追加してやれば良さそうですね。
しかし、それだけでは上手くいきません。classList から取り除いた時点で transform の影響が消えるので、box は瞬間的に元の位置に戻ってしまいます。このため、left 位置に transform 後の値を加味した値を書いておく必要があります。left や transform の値は JavaScript からは読めないので、自分で計算して値を書かなければいけません(レンダリングエンジンが知っているはずの値を自分でも別途管理しなければいけないというのはイマイチな感じがしますね。何か良い方法はありますか?)。

あと、classList にアニメーションを追加したり削除したりするっていうのは意外に面倒くさいですね。動かしたいオブジェクトが沢山あると、だんだんと管理できなくなってきます。
この問題は、実は簡単に解決できます。classListは使わず、単にオブジェクトの style.animationName を書けば良いだけなのです。

ボタンを押す度に、box (span.child) が右に行ったり左に戻ったりするサンプルを以下に示します。(html は最初のサンプルと一緒です)

div.parent {
    position: relative;
    border: 1px solid black;
    width : 400px;
    height: 100px;
}
span.child {
    position: absolute;
    left: 100px;
    top: 25px;
    width : 50px;
    height: 50px;
    background-color: red;
    animation-duration: 1s;
    animation-iteration-count: 1;
    animation-fill-mode: forwards;
}
@keyframes moveRight {
    to {
        transform:translateX(200px);
    }
}
@keyframes moveLeft {
    to {
        transform:translateX(-200px);
    }
}
let direct = 0; // 移動方向。0以外なら右へ、0なら左へ

function move() {
    const objBox = document.getElementById("box");
    objBox.style.transform = '';    // 前回移動アニメのtransform を消す

    direct ^= 1;
    if (direct !== 0) {
        objBox.style.left = '100px';    // 移動開始位置を設定する必要がある
        objBox.style.animationName = 'moveRight';
    } else {
        objBox.style.left = '300px';
        objBox.style.animationName = 'moveLeft';
    }
}

最初のサンプルと違い、アニメーション設定は予め span の中に書いてしまっています(アニメーション設定だけの CSS 記述 .moveToRight が無くなりました)。
こうすることで、classList を使う事無く、ボタンが押されたら style.animationName を書くだけでアニメーションの切り替えが出来ます(もちろん、duration とかを変化させても良いです)。
また、classList の削除に相当する transform の初期化は、ボタンが押されてからおこなえばOKです。これは空文字をセットするだけで実現できます。

今回の例では横移動だけのアニメーションですが、色や透明度を変化させる場合も考え方は一緒です。

応用例

応用例として、上下左右のボタンに対して、押された方向へアニメーションするサンプルを作ってみました。
このページのサンプルとちょっと違うのは、同じ方向に何回も移動できるようになっている事です。これを実現するために、アニメーション終了時に style.animationName をクリアしています。style.animationName は、値の変化があった時だけアニメーションが開始します。同じ方向へのアニメでは同じ名前をセットしてしまうので、一度クリアしないとアニメが動かないのです。

このことから、style.animationName への代入は、代入に見せかけた演算子のオーバーロードでは無いことが分かります。style.animationName に限らず、left とか transform の値は、JavaScript でセットしてもすぐに状態が反映される訳では無く、描画直前に前回との差分を見て一気に反映しているらしい事が想像できます。

あと余談ぽくなりますが、moveRight と moveLeft の keyframes を作らずに、1つの keyframes でanimationDirection を normal と reverse を JavaScriptで切り替える方法も可能です。ただし reverse では加速の仕方も逆になってしまうので、animationTimingFunction が linear の時以外は使いにくいです。

最後に

ここまでやるんなら、CSS を使ったアニメーションではなくて、全て JavaScript でやった方が制御しやすいのでは? とも思いますね。
しかし、JavaScript ではインターバル割り込みによって処理する関係上、描画フレーム毎に処理してくれる CSS に比べると、どうしても動きがカクカク見えてしまいます。
当たり判定が不要な演出とかは、このページの内容を参考にして動かしてみるのも良いかもしれません。

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

PHP で Google reCAPTCHA v3 付きのお問い合わせフォームを作る (2)

PHP で Google reCAPTCHA v3 付きのお問い合わせフォームを作る (1) の続きです。
前回までで、 Google reCAPTCHA v3 スコア算出とそれによる条件分岐を作成しました。
ここからは Swift Mailer でのメール送信処理を書いていきます。

メール送信処理

日本語メールを扱うための設定

Swift Mailer の公式ドキュメントにもある、日本語メールを扱うための設定を追加します。
Using Swift Mailer for Japanese Emails

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $challenge_result = $response->toArray();

  if ($challenge_result['success']) {
    \Swift::init(function () {
    \Swift_DependencyContainer::getInstance()
      ->register('mime.qpheaderencoder')
      ->asAliasOf('mime.base64headerencoder');

      \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
    });
  }
}

SMTP 認証情報を設定する

以下のコードは Gmail の SMTP サーバを使用した例ですが、お使いになるものに置き換えてください。

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $challenge_result = $response->toArray();

  if ($challenge_result['success']) {
    \Swift::init(function () {
    \Swift_DependencyContainer::getInstance()
      ->register('mime.qpheaderencoder')
      ->asAliasOf('mime.base64headerencoder');

      \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
    });

    $transport = (new \Swift_SmtpTransport('smtp.gmail.com', 587, 'tls'))
        ->setUsername('username@gmail.com')
        ->setPassword('****************');
  }
}

送信先・件名・本文を設定する

バリデーション処理などは一旦おいておいて、とりあえず送信テストをするための記述を行います。
setTo() メソッドは例外が起きる可能性があるので、 try-catch で例外処理をしておきます。

Sending Emails in Batch

If you add recipients automatically based on a data source that may contain invalid email addresses, you can prevent possible exceptions by validating the addresses using Egulias\EmailValidator\EmailValidator (a dependency that is installed with Swift Mailer) and only adding addresses that validate. Another way would be to wrap your setTo(), setCc() and setBcc() calls in a try-catch block and handle the Swift_RfcComplianceException in the catch block.

info@example.com の部分は送信したいメールアドレスに置き換えてください。

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $challenge_result = $response->toArray();

  if ($challenge_result['success']) {
    \Swift::init(function () {
    \Swift_DependencyContainer::getInstance()
      ->register('mime.qpheaderencoder')
      ->asAliasOf('mime.base64headerencoder');

      \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
    });

    $transport = (new \Swift_SmtpTransport('smtp.gmail.com', 587, 'tls'))
        ->setUsername('username@gmail.com')
        ->setPassword('****************');

    $mailer = new \Swift_Mailer($transport);

    $message = (new \Swift_Message('お問い合わせフォームからお問い合わせがありました'))
        ->setFrom([filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) => filter_input(INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS)])
        ->setBody(
          'お名前: ' . filter_input(INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS) . PHP_EOL .
          'メールアドレス: ' . filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) . PHP_EOL .
          'お問い合わせ内容: ' . filter_input(INPUT_POST, 'contents', FILTER_SANITIZE_FULL_SPECIAL_CHARS));

    try {
      $message->setTo(['info@example.com']);
    } catch (\Swift_RfcComplianceException $e) {
      echo '不正なメールアドレスです。';
    }
  }
}

送信処理

send.php
<?php
require_once __DIR__ . '/vendor/autoload.php';
use ReCaptcha\ReCaptcha as ReCaptcha;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
  $response = (new ReCaptcha('<シークレット キー>'))
                  ->setExpectedHostname($_SERVER['SERVER_NAME'])
                  ->setExpectedAction('inquiry')
                  ->setScoreThreshold(0.5)
                  ->verify($_POST['token'], $_SERVER['REMOTE_ADDR']);
  $challenge_result = $response->toArray();

  if ($challenge_result['success']) {
    \Swift::init(function () {
    \Swift_DependencyContainer::getInstance()
      ->register('mime.qpheaderencoder')
      ->asAliasOf('mime.base64headerencoder');

      \Swift_Preferences::getInstance()->setCharset('iso-2022-jp');
    });

    $transport = (new \Swift_SmtpTransport('smtp.gmail.com', 587, 'tls'))
        ->setUsername('username@gmail.com')
        ->setPassword('****************');

    $mailer = new \Swift_Mailer($transport);

    $message = (new \Swift_Message('お問い合わせフォームからお問い合わせがありました'))
        ->setFrom([filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) => filter_input(INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS)])
        ->setBody(
          'お名前: ' . filter_input(INPUT_POST, 'name', FILTER_SANITIZE_FULL_SPECIAL_CHARS) . PHP_EOL .
          'メールアドレス: ' . filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL) . PHP_EOL .
          'お問い合わせ内容: ' . filter_input(INPUT_POST, 'contents', FILTER_SANITIZE_FULL_SPECIAL_CHARS));

    try {
      $message->setTo(['info@example.com']);
    } catch (\Swift_RfcComplianceException $e) {
      echo '不正なメールアドレスです。';
    }

    try {
      $send_result = $mailer->send($message);
    } catch (\Swift_TransportException $e) {
      echo 'メール送信エラーです。';
    }

    if ($send_result) {
      echo 'メールを送信しました。';
    } else {
      echo 'メール送信に失敗しました。';
    }
  }
}

とりあえずここまででメールの送信テストが可能な状態になりました。
すべての入力欄を埋めて 送信する ボタンを押して試してみてください。

このままでは実用に耐えないので、

  • バリデーション処理
  • エラーが発生したときにお問い合わせフォームに戻し、エラーの内容を表示する
  • メール本文をテンプレート化して編集しやすくする

など、細かな改善を次回以降で行っていきます。

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

RedGL Release Master_V6.0 (WebGL Library)

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

【やってみた】kintoneアプリ「値を判定してサブテーブルへ値をコピー」

cybozu developer network の kintone カスタマイズ フォーラム に上がった質問を考えてみました。

アプリの仕様

質問内容を見て下記の様に想定してみました。

要求仕様

フィールドの値を判定して、それが0(ゼロ)以外の場合にテーブルに追加したい。

要件定義

  • 数値フィールドの値を取得する。
  • 値がゼロ、未入力の時はテーブルに追加しない。
  • 数字以外はkintoneの標準の機能でエラーで保存出来ない。
  • 値がゼロ、未入力でも保存は出来る。
  • 編集モードで数値が入力されたら、テーブルに追加する。

動作画面

フォーム画面

スクリーンショット 2019-07-11 20.35.11.png

新規入力して保存押下

スクリーンショット 2019-07-11 20.37.08.png

スクリーンショット 2019-07-11 20.36.51.png

保存後

スクリーンショット 2019-07-11 20.37.26.png

編集入力

スクリーンショット 2019-07-11 20.38.05.png

編集入力後の保存

スクリーンショット 2019-07-11 20.38.35.png

コードについて

関数のテストコードです。

test.sample.js
'use strict';
const assert = require('assert');
const commonModule = require('../src/common');

describe('オブジェクト配列をフィルターする', () => {
    let aryFields;
    let recrods;
    const undef = undefined
        aryFields = ['num01', 'num02', 'num03', 'num04']
        recrods = {
            num01: {
                value: "0"
            },
            num02: {
                value: "100"
            },
            num03: {
                value: ""
            },
            num04: {
                value: undef
            }
        }
    })
    it('フィルターした結果の配列が返る', () => {
        assert(commonModule.filterFields(aryFields, recrods).length === 1)
    })
    it('フィルターした結果の配列が返る2', () => {
        assert(commonModule.filterFields(aryFields, recrods)[0] === "num02")
    })

})

関数の一部です。

フィールドの値を判別しています。
kintoneのレコードを配列で渡して、判別結果を array.filter で処理しています。

common.js
/**
* フィールドコードをフィルターして返す
* @param {Array} fieldCodes フィールドコードの配列
* @param {object} records kintoneフィールドのハッシュ
* @return {Array} filters フィルター後のフィールドコードの配列
* */
exports.filterFields = (fieldCodes, records) => {
  const filters = fieldCodes.filter(fieldCode => {
    if (records[fieldCode].value) { // null or undefined 以外
      if (parseInt(records[fieldCode].value) !== 0) { // 数値変換した結果が0以外
        return true
      }
    } else {
      return false
    }
  })
  return filters
}

メインの処理です。
途中でフィールド情報を取得したり、キャンセル処理をする為に、Promiseで実装しています。
テストコードを書く為に、大した処理もしてませんが処理の大部分を関数としています。

main.js
import Common from './common'
import Swal from 'sweetalert2'

/**
 * 値をチェックする対象のフィールドコード配列
 */
const fieldsDiv = [
  {
    fieldCode: 'num01',
  },
  {
    fieldCode: 'num02',
  },
  {
    fieldCode: 'num03',
  },
  {
    fieldCode: 'num04',
  },
  {
    fieldCode: 'num05',
  },
  {
    fieldCode: 'num06',
  }
]

const HANDLE_EVENT = [
  'app.record.create.submit',
  'app.record.edit.submit',
  'mobile.app.record.create.submit',
  'mobile.app.record.edit.submit'
]
/** @type { array } HANDLE_EVENT */
kintone.events.on(HANDLE_EVENT, async (event) => {
  const record = event.record
  // console.log(record)

  // データ取得対象のフィールドコードの配列をセットする
  const aryFields = Common.destructFields(fieldsDiv, 'fieldCode')

  // 0と空白以外のフィールドコードを絞り込む
  const filterFields = Common.filterFields(aryFields, record)

  // フィールド情報取得
  let kintoneConfig = {}
  await kintone.api(kintone.api.url('/k/v1/app/form/fields', true), 'GET', {"app": kintone.app.getId()}).then( (resp) => {
    console.log(resp);
    kintoneConfig = resp.properties
  }), (err) => {
    console.log(err)
  }   

  // kintoneのテーブルレコードを生成
  const kintoneTableRows = filterFields.map(field => {
    return {
      "num": {"type": "NUMBER", "value": record[field].value},
      "category": {"type": "SINGLE_LINE_TEXT", "value": kintoneConfig[field].label},
    }
  })
  const kintoneTable = Common.createTableRows(kintoneTableRows, 'Table')

  return Swal.fire({
    title: 'info',
    text: "レコード保存の際に値をテーブルにセットします",
    type: 'info',
    showCancelButton: true,
    confirmButtonText: 'OK'
  }).then((result) => {
    if (result.value) { // OKボタン押下
      return Swal.fire({
        title: '保存',
        text: '値をテーブルにセットしました',
        type: 'success',
        confirmButtonText: 'OK'
      }).then(() => {
        record.Table = kintoneTable.Table
        return event  
      })
    } else { // 処理をキャンセルした
      console.log('do cancel')
      return Swal.fire('処理をキャンセルします').then(() => {
        console.log('cancel done')
        return false
      })
    }
  }).catch((error) => {
    event.error = 'レコード保存時にエラーが発生しました'
    return Swal.fire('エラーが発生しました' + error)
  })
});

環境設定

  • Babel7 + Webpack4 でコンパイル、バンドルしています。
  • Vue.js のテンプレートを利用してプロジェクトフォルダを生成しています。
  • テストは Mocha + power-assert を利用しました。

$ vue init webpack-simple プロジェクト名

package.json
{
  "name": "app285",
  "description": "A Vue.js project",
  "version": "1.0.0",
  "author": "Kazuhiro Yoshida <sy250f@gmail.com>",
  "license": "MIT",
  "private": true,
  "scripts": {
    "dev": "cross-env NODE_ENV=development webpack-dev-server --open --hot",
    "build": "cross-env NODE_ENV=production webpack --progress --hide-modules",
    "test": "mocha --require intelli-espower-loader -w"
  },
  "dependencies": {
    "vue": "^2.5.11"
  },
  "browserslist": [
    "> 1%",
    "last 2 versions",
    "not ie <= 8"
  ],
  "devDependencies": {
    "@kintone/dts-gen": "^1.0.3",
    "babel-core": "^6.26.0",
    "babel-loader": "^7.1.2",
    "babel-polyfill": "^6.26.0",
    "babel-preset-env": "^1.6.0",
    "babel-preset-stage-3": "^6.24.1",
    "cross-env": "^5.0.5",
    "css-loader": "^0.28.7",
    "file-loader": "^1.1.4",
    "intelli-espower-loader": "^1.0.1",
    "mocha": "^6.1.4",
    "node-sass": "^4.5.3",
    "power-assert": "^1.6.1",
    "sass-loader": "^6.0.6",
    "typescript": "^3.5.2",
    "vue-loader": "^13.0.5",
    "vue-template-compiler": "^2.4.4",
    "webpack": "^3.6.0",
    "webpack-dev-server": "^2.9.1"
  }
}
webpack.config.js
var path = require('path')
var webpack = require('webpack')

module.exports = {
  entry: ['babel-polyfill','./src/main.js'],
  output: {
    path: path.resolve(__dirname, './dist'),
    publicPath: '/dist/',
    filename: 'build.js'
  },
  module: {
    rules: [
      {
        test: /\.css$/,
        use: [
          'vue-style-loader',
          'css-loader'
        ],
      },
      {
        test: /\.scss$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'sass-loader'
        ],
      },
      {
        test: /\.sass$/,
        use: [
          'vue-style-loader',
          'css-loader',
          'sass-loader?indentedSyntax'
        ],
      },
      {
        test: /\.vue$/,
        loader: 'vue-loader',
        options: {
          loaders: {
            // Since sass-loader (weirdly) has SCSS as its default parse mode, we map
            // the "scss" and "sass" values for the lang attribute to the right configs here.
            // other preprocessors should work out of the box, no loader config like this necessary.
            'scss': [
              'vue-style-loader',
              'css-loader',
              'sass-loader'
            ],
            'sass': [
              'vue-style-loader',
              'css-loader',
              'sass-loader?indentedSyntax'
            ]
          }
          // other vue-loader options go here
        }
      },
      {
        test: /\.js$/,
        loader: 'babel-loader',
        exclude: /node_modules/
      },
      {
        test: /\.(png|jpg|gif|svg)$/,
        loader: 'file-loader',
        options: {
          name: '[name].[ext]?[hash]'
        }
      }
    ]
  },
  resolve: {
    alias: {
      'vue$': 'vue/dist/vue.esm.js'
    },
    extensions: ['*', '.js', '.vue', '.json']
  },
  devServer: {
    historyApiFallback: true,
    noInfo: true,
    overlay: true
  },
  performance: {
    hints: false
  },
  devtool: '#eval-source-map'
}

if (process.env.NODE_ENV === 'production') {
  module.exports.devtool = '#source-map'
  // http://vue-loader.vuejs.org/en/workflow/production.html
  module.exports.plugins = (module.exports.plugins || []).concat([
    new webpack.DefinePlugin({
      'process.env': {
        NODE_ENV: '"production"'
      }
    }),
    new webpack.optimize.UglifyJsPlugin({
      sourceMap: true,
      compress: {
        warnings: false
      }
    }),
    new webpack.LoaderOptionsPlugin({
      minimize: true
    })
  ])
}

まとめ

  • フォーム未入力時の値判別がなかなかに難しかったです。
  • JSのテストを初めて書いてみました。 power-assert はエラー内容が分かりやすくて助かった。テストは書くべきですね。
  • Vue.js の テンプレートが環境構築に便利でした。

参考サイト

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

JavaScriptの基礎~クリックしたとき⇒色を変える など

JavaScripの基礎

コピペして使ってください。

クリックしたとき⇒色を変える

sample.js
//要素の取得 → クリックされたとき
document.getElementById('target').addEventListener('click',function(){
  //要素の取得 → 背景色を変える
  document.getElementById('target').style.background='pink';
});

その他の処理

sample.js
//クリックしたとき⇒クラスを追加する
document.getElementById('target').classList.add('circle');
//クリックしたとき⇒クラスがついていたら外す、外れていたらつける
document.getElementById('target').classList.toggle('circle');
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pixi.jsでつくるスワイプスタイルのクイズアプリ[その3]

クイズで使う画像(金の斧)をスワイプさせて一定の領域に持っていったら除去し次の問題に遷移する処理を作っていきます。

はまったところ、スマホにはクリックはない。not CLICK but TAPである。

先に前回の処理の一部ではまったところ、書きます。
前回 スタートボタンの処理を'click'にていたのですが、
スマートフォンのブラウザで試したときに無応答となってしまうことがわかりました。
調べたみたところ、スマホでの場合'pointertap'で制御しないといけないらでしいです。
AndroidのChrome、iPhoneのSafari,Chromeで動作確認できました。PCでも'click'同様の処理をするため、以降すべて'pointertap'にしようと思います。

//〇
      button
        .on('pointertap', function () {
           //一旦部品をすべて除去
           destroySceneObjects();
           g_container = new PIXI.Container();
           gameScene();
         })

//×スマホブラウザでは動かない。
      button
        .on('click', function () {
           destroySceneObjects();
           g_container = new PIXI.Container();
           gameScene();
        })

本記事の実装

まずはおおまかな流れ

[問題画像][問題文表示] 2

[問題画像]をスワイプ

[一定領域でドラッグエンド]
右=〇 or 左=× で判定

[次の問題へ遷移]

今回画像を表示させ、スワイプで除去し次の画像を表示させていく。

まず問題のコントロール部分をつくっていく。多分あとで色々変える。
問題数や正当数、進捗状況を確認用。

      class QuestionControl {
        constructor(numberOfQuestions, numberOfRightQuestions) {
          this.numberOfQuestions = numberOfQuestions;
          this.numberOfRightQuestions = numberOfRightQuestions;
          this.progress = 1;//0
          var questironArray = new Array(10);
        }

        addProgress(value){
          this.progress += value;
        }
        getProgress() {
          return this.progress;
        }
      }
/**問題文表示用UIを表示 木の板に書く予定なのでboardという名称にしている。
  Container引数いらないかも・・いまは検証フェーズだからapp.stage一番下のレイヤーに置いてる**/
      function setQuestionBoard(container) {
            // Create a new texture
            console.log("setQuestionBoard this=" + this);
            console.log("setQuestionBoard container=" + container);
            let board_img_file_name = 'img/question_board.png';
            let board_texture = PIXI.Texture.from(board_img_file_name);
            let boardImage = new PIXI.Sprite(board_texture);
            boardImage.anchor.x = 0.5;
            boardImage.anchor.y = 0.5;
            boardImage.position.x = 250;
            boardImage.position.y = 700;
            app.stage.addChildAt(boardImage,BOARD_IMAGE_SPRITE_TAG);

    }
//問題画像を描画
    function setQuestionImage(container) {
      // Create a new texture
      img_file_name = 'img/quiz_img_'+questionControl.getProgress()+'.png'
      let quiz_texture = PIXI.Texture.from(img_file_name);
      let questionImage = new PIXI.Sprite(quiz_texture, QUESTION_IMAGE_SPRITE_TAG);
      questionImage.anchor.x = 0.5;
      questionImage.anchor.y = 0.5;
      questionImage.position.x =250;
      questionImage.position.y = 300;

      touchQuestionImage(questionImage);
      app.stage.addChildAt(questionImage, QUESTION_IMAGE_SPRITE_TAG+questionControl.getProgress()-2);
//正誤判定制御位置
    function touchQuestionImage(questionImage) {
      questionImage.interactive = true;
      questionImage.buttonMode = true;
      //questionImage.anchoser.set(0.5);
      questionImage
        .on('mousedown', onDragStart)
        .on('touchstart', onDragStart)

        // 判定
        if (this.position.x > 280) { alert("〇 右"); 
        PIXI.sound.play('seikai'); nextQuestion();}
        if (this.position.x < 180) { alert("× 左");
        PIXI.sound.play('fuseikai');nextQuestion();}
      }

次の問題へ遷移
画像の除去 本来であれば、同じスプライトを使いまわして画像だけ差し替えて
座標を戻す方が吉かもしれないが今回はあくまで検証なので・・・
前問題画像を除去して、問題番号+1のタグで画像を生成していく・・。

   function nextQuestion(){
      removeQuestionImage();
      setQuestionImage(g_container);
      setQuestionBoard(g_container);
      questionControl.addProgress(1);
      console.log(questionControl.getProgress());
    }
    function removeQuestionImage(){
       app.stage.removeChildAt(QUESTION_IMAGE_SPRITE_TAG+ questionControl.getProgress()-2);
    }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

すべての新米フロントエンドエンジニアに読んでほしい50の資料

はじめに

さいきんのWebはSPA技術を中心としたフロントエンドが賑わっていますね?

従来サーバーサイドを扱っていた人もフロントを触る機会が増えていたり、これからプログラミングを学んでいく人も、フロントエンド領域に興味を持っているのではと思います。
そこで、フロントエンドの経験が浅い方や初学者向けに、おすすめのドキュメントや勉強すべき領域をまとめました。

とりあえず動けば良い段階から一歩進んで、フロントエンドエンジニアとして、良いアプリケーションを作るために必要な知識を浅く広く紹介します。

※補足
新米と表記しましたが、実際には新卒や未経験でなく、新卒2~3年目の若手フロントエンドエンジニアやフロント分野に苦手意識のあるバックエンドエンジニアの方を対象としています。
数日で目を通せるような内容ではないため、マイルストーンやスキルセットの一つの参考にして頂けると幸いです。

フロントエンド入門

公式ドキュメントを知る

[1] https://jp.vuejs.org/v2/guide/
一例としてVueを貼りましたが、Vueに限らず信頼出来る情報に当たる癖付けがとても大切です。
特にVue.jsは一昔前のjQueryやRailsやPHPのように、技術に明るくない方が書いたページが多いと感じていて、ちゃんとVueを学ぶならまずは公式を見ましょう。

言語を知る

JSを知る

[2] https://jsprimer.net/basic/introduction/
今からJSを学ぶなら特別な事情がない限り、ES2015以降の文法で良いと思います。
js-primerはES2015以降のJSを知る上で素晴らしいドキュメントです。

昔のJSもちょっと知る

[3] https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures
データの隠蔽やカプセル化を実現するために使われたテクニックです。
今のJSを書く場合に意識する機会は少ないと思いますが、知っておいて損ないです。

[4] https://www.yunabe.jp/docs/javascript_class_in_google.html
ES5以前の古いJSで記述しなきゃいけない仕事に遭遇した際、あらためて読み返したい素晴らしいドキュメントです。ベストプラクティスのひとつだと思います。

昔のJSもしっかり学びたい方は、「JavaScript: The Good Parts」や「開眼!JavaScript」といった書籍の購入も検討してください。
上記の書籍を読んで、DouglasやJohnResigやSubstackを知っていると、先輩のフロントエンドエンジニアから気に入られる確率が向上します(当社比)

非同期処理を知る

[5] http://azu.github.io/promises-book/
非同期処理は初学者にとって大きな壁の一つだと思いますが、しっかりとPromiseオブジェクトを理解することがJSでは必須です。
ES2017で追加されたAsync Functionは、Promiseを返す関数なのでPromiseが分かればすぐ理解できます。

Promiseを理解したら、Asyncjsのようなライブラリで複雑な非同期処理を実装したり、
RxJSのようなライブラリを用いてPromise以外の非同期を扱う方法を学んでみると良きです。

HTMLLivingStandard & ECMAScript を知る

[6] https://html.spec.whatwg.org/multipage/
[7] https://www.ecma-international.org/publications/standards/Ecma-262.htm
聖書。HTMLやJSといった言語の仕様が記載してあります。

ちなみに私のチームでは、実装周りの裁判が発生した際に、六法全書代わりとして活用しています。
※私はおそらく全体の3%ほどしか読んでませんが、ちゃんと読んだぞという空気を出して会話します?

コーディング規約を知る

[8] https://qiita.com/mysticatea/items/f523dab04a25f617c87d
[9] https://qiita.com/soarflat/items/06377f3b96964964a65d
これからフロントエンドを学ぶ方すべてに、まずはLintとコードフォーマッターの導入をお勧めします。
Lintではeslintやstylelint、コードフォーマッターではprettierがデファクトです。
利用するルールは何でも良いと思いますが、とにかくルールを用意することが大切です。

UIを知る

モーショングラフィックやインタラクションを知る

[10] https://goodpatch.com/blog/ui-micro-interaction/
マイクロインタラクションはデザイナーの仕事という認識の方も多いと思いますが、
特殊な例外発生時にユーザーへ適切なフィードバックを返すべきといった気付きはエンジニアの方が強いと思うので、常にプログラム的な例外とUI的な例外(フィードバック)の両方を考える姿勢が大切だと感じます。

マテリアルデザインを知る

[11] https://material.io/design/
Google公式のデザインガイドです。英語のドキュメントですが、グラフィック中心なので分かりやすいです。

アトミックデザインを知る

[12] https://ygoto3.com/posts/atomic-design-on-actual-project/
ReactやVueを用いる際にコンポーネント分割の粒度としてアトミックデザインを活用するといったケースがあります。
アトミックデザインに固執する必要はないですが、コンポーネントをどう分割していくかをデザイナー交えて検討するのは大切だと考えていて、土台としてアトミックデザインは有用と思います。

アクセシビリティを知る

セマンティックを知る

[13] https://developer.mozilla.org/ja/docs/Learn/Accessibility/HTML
[14] https://developer.mozilla.org/ja/docs/Learn/Accessibility/CSS_and_JavaScript
セマンティックなコーディングを行うことでアクセシビリティだけでなく、SEOに対してもメリットがあると言われています。

ARIAを知る

[15] https://developer.mozilla.org/ja/docs/Learn/Accessibility/WAI-ARIA_basics
可能な限りネイティブなHTMLで解決すべきで、必要なときだけ使うことが推奨されています。
業務アプリなど利用ブラウザが制限できて、かつ複雑なUIでは活躍の機会があるかもしれません。

SEOを知る

お上の言うことを知る

[16] https://support.google.com/webmasters/answer/35769?hl=ja
まずはお上の言うことに従いましょう。
私は毎晩、「Googleは神」と三回復唱してから寝るようにしています。

構造化データを知る

[17] https://developers.google.com/search/docs/data-types/article?hl=ja
担当業務領域によっては、構造化データを設定することが必要かもしれません。

SEOと直接関係ありませんが、GAやdatalayerを正しく設定することもフロントでは大切です。

ブラウザを知る

ブラウザの仕組みを知る

[18] https://www.html5rocks.com/ja/tutorials/internals/howbrowserswork/
結構長いので、他国の文化を知るような気持ちで、ブラウザってこうなってんだって読むのをお勧めします。

デバイスおよびブラウザの特性を知る

[19] https://caniuse.com/
フロントエンドの辛いところとして、コードの実行環境(ブラウザ)が無数にあることが挙げられますが、JSやCSSがその環境で実行可能か確認できます。

JSに関しては、IE11などでもES2015以降の機能を使えるbabel-polyfillの導入も検討しましょう。

Threadを知る

[20] https://nhiroki.jp/2018/05/07/off-the-main-thread-api
JavaScriptにはメインスレッド(=UIスレッド)が一つしかないので、普通に書くと非同期処理であってもシングルスレッドの直列で実行されます。
重たい処理(UI的にカクついてしまうとか)をマルチスレッドで実行するために、Web Workerの利用を考えましょう。

JITを知る

[21] https://dev.mozilla.jp/2017/05/%E3%82%B8%E3%83%A3%E3%82%B9%E3%83%88%E3%83%BB%E3%82%A4%E3%83%B3%E3%83%BB%E3%82%BF%E3%82%A4%E3%83%A0-jit-%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%81%AE%E3%82%AF%E3%83%A9%E3%83%83%E3%82%B7/
(難しくてよく分かってませんが?)モダンブラウザは実行時最適化と呼ばれるコンパイルを行っているそうです。

常に同じ型で呼び出される型を仮定するそうなので、私のような市井のプログラマには、型を意識した開発を心掛けることでJITの恩恵を享受できる可能性があります。
お気づきの方も多いと思いますが、それはTypeScriptと相性が良いです。

セキュリティを知る

DOM Based XSSを知る

[22] https://gihyo.jp/dev/serial/01/javascript-security/0006
モダンなフロントのフレームワークを使っている場合、もはや意識しないかもしれませんが、JS初学者は一度目を通しましょう。

CORSを知る

[23] https://developer.mozilla.org/ja/docs/Web/HTTP/CORS
CORSを理解しそのクライアントとサーバーを実装すると、HTTPに対する理解も深まるのでとてもお勧めです。

CSRF脆弱性とjwtを知る

[24] https://techblog.yahoo.co.jp/advent-calendar-2017/jwt/
jwt(json web token)は、OAuth2.0という認可の仕組みのアクセストークンとして採用されるなど広く使われています。

CSRFという、なりすましのような脆弱性が存在すること、その対策の一つとしてredirect_uriの制限が可能なjwtが存在することを理解出来るとOKです。
GoogleやFacebookのような代表的なOpenID Providerは、stateにredirect_uriを含めuriのホワイトリストと突合するのが必須になっていると思います。

テストを知る

ユニットテストを知る

[25] https://jestjs.io/docs/ja/getting-started
Reactはもちろん、Vueを使う際もJestがお勧めです。
ただフロントエンドのテストはベストプラクティスが無いと言われていて、下記のようにどういったテストをすべきかをまず検討しましょう。
[26] http://akito0107.hatenablog.com/entry/2018/08/27/190333

もしかしたらあなたに必要なのはユニットテストではなく、TypeScriptかもしれません。

E2Eテストを知る

[27] https://techblog.lclco.com/entry/2018/06/28/080000
フロントエンドが自主的に行うE2EとしてはPuppeteerがデファクトではないかと思います。
ユニットテストでも書きましたが、E2Eの運用も楽ではないので何をテストするのかちゃんと計画しましょう。

代表的なUIテストを知る

[28] https://qiita.com/masaakikunsan/items/dad8d84807918f3a43cb
storybookを採用することで、再利用性やテスト容易性が向上すると思います。
実装やメンテは大変なので、小規模なチームでの採用はコストに見合わない可能性があります。

データストアを知る

cookieを知る

[29] https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies
仕様が複雑ですが、とても良く使うためちゃんと理解しましょう。ブラウザのユーザー固有情報においてサーバーとやり取りできる唯一のストレージです。

他にもブラウザだけで扱えるWeb Storageも存在します。サーバーとのやり取りが不要ならこちらを使いましょう。
[30] https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API#Web_Storage_concepts_and_usage
HTTPヘッダに乗ることもなくブラウザだけで完結するため扱いやすいですが、外部サービスのJSから取得可能な領域のため個人情報を置いたりするのは止めましょう?

NoSQLを知る

[31] https://firebase.google.com/docs/database/ios/structure-data?hl=ja
リンク先はFirebaseのRealtime Databaseですが、MysqlのようなRDBとは設計思想がまるで異なることが分かると思います。
※Cloud Firestoreの設計は、上記のベストプラクティスと異なります。

Cloud FirestoreやDynamoDBといった新世代の優れたデータストアは、これまでバックエンドエンジニアが培ってきた設計が通用しないため運用が難しいと議論されています。
[32] https://qiita.com/funasaki/items/6cdc8f7f7b709e5e601f

一方、多くのネイティブアプリエンジニアが運用してきたように、UIに近い立場のフロントエンドにとっては比較的自然に扱えるデータストアだと感じるので、触れてみるのがお勧めです。

モダンフロントエンドへ

Fluxと仮想DOMを知る

[33] https://qiita.com/mizchi/items/4d25bc26def1719d52e6
Qiita史に燦然と輝く魂の名文。
なぜVueやReactがこれほど重要な位置を占めたか、そのすべてが書いてあります。

私的な余談ですが、右も左も分からない新卒エンジニアだった当時この文章を読んで、全然内容は分からなかったんですが、この筆者が一体何に興奮しているのか興味を持ち、Reactを学びたくなったという背景があります。

言語を知る

TSを知る

[34] https://www.typescriptlang.org/docs/home.html
折に触れてTypeScriptの名前を出してきましたが、今からJSのプロジェクトを始める場合、どんなケースにおいてもTSの採用をオススメします。そこに迷いはありません?

React, Vueを知る

[35] https://ja.reactjs.org/docs/getting-started.html
とりあえずReactかVueのどちらかを学びましょう。

現時点ではTSとの相性がReactの方が高いです。Vue3.0がリリースされるとVueでもTSXがネイティブサポートされ解消される見込みですが。

個人的には、Vueの双方向と単方向データバインディングの合わせ技を便利と感じるか邪悪と感じるかで、VueとReactのどちらを採用するかが決まるかなと考えています。

CSS in JSを知る

[36] https://qiita.com/taneba/items/4547830b461d11a69a20
styled-componentsの採用が多いです。

CSS in JSを嫌っている人もいますが、コンポーネント単位でインターフェースやインタラクションを定義していきましょうという時代に適した手法と感じます。

アーキテクチャを知る

CDNを知る

[37] https://speakerdeck.com/sisidovski/nikkei-itpro-cdn
リリース当時、界隈で話題になった日経電子版のCDNを中心とした設計の話です。
とにかくCDNで出来ることが増えていて、なぜ使うのか、どういったことが可能なのか知ることが大切です。

JAMStackを知る

[38] https://qiita.com/ossan-engineer/items/4fe0e3e388f510bd5c68
CDNファーストを考えた際に、第一候補となる構成です。
この構成を目指す際に採用するGatsbyやVuePressのようなFWは、静的サイトジェネレータ(SSG)と呼ばれる事が多いです。

BFFを知る

[39] https://www.atmarkit.co.jp/ait/articles/1803/12/news012.html
特にこの記事で素晴らしいと思った点は、フロントエンドの担当領域を明確に定義している点です。

私的な余談ですが、なぜBFFという考え方が誕生しその領域をフロントエンドが担うべきなのか、おぼろげに理解できたとき私のなかで点だった技術や思想が線で繋がった感覚がありました。

BFFとSSRを知る

[40] https://speakerdeck.com/yosuke_furukawa/ssrfalsehua
SSRも一種のBFFと呼べますが、なぜSSRをすべきなのかが最もわかりやすいと思います。
さすが日本Node.jsユーザグループ会長、ウォーリーのアイコンも可愛いです。

アーキテクチャの補足ですが、既存の技術資産を活用するためRailsをAPIで残して、フロントエンドだけをモダンにするみたいなケースも見られます。
[41] https://speakerdeck.com/fukuiretu/notewonuxt-dot-jsdezai-gou-zhu-sitahua

レンダリングを知る

[42] https://developers.google.com/web/updates/2019/02/rendering-on-the-web
モダンなフロントエンドのアーキテクチャをレンダリングという観点からまとめたGoogle謹製ページ。
2019年に生きるすべてのフロントエンド必読です。

SSRやSSGを実現する方法を知る

[43] https://ja.nuxtjs.org/guide
NuxtやNextを良く聞くけどイマイチ分かってなかった方々、ここまでの話を読むと凄さが伝わるのではないかと思います。
サーバーサイドでReactやVueが実行可能になったことで、SSRが市井のエンジニアでも実現可能になりました。
NuxtやNextではSSGも可能です。

パフォーマンス改善を知る

Service Workerを知る

[44] https://developers.google.com/web/fundamentals/primers/service-workers/?hl=ja
Service Workerを用いたcacheを行うことで、2回目以降のFCPなどのタイムを大幅に削減可能です。

このAPIを用いると、従来Webから出来なかったプッシュ通知などの体験を提供できることから、Service Workerを活用したWebアプリはPWAとも呼ばれてます。

lighthouseを知る

[45] https://developers.google.com/web/tools/lighthouse/?hl=ja
lighthouseを用いることで、これまで紹介してきたアクセシビリティやパフォーマンスといった項目がサイト上でどの程度実現出来ているかの測定が可能です。

Performance Budgetを知る

[46] https://developers-jp.googleblog.com/2019/03/blog-post_15.html
ウェブサイトを高速に保つための手法として、Performance Budgetの導入が推奨されています。
上記で紹介したlighthouseなどと組み合わせてCIに導入すると良い感じだと思います。

Webpackを知る

[47] https://webpack.js.org/guides/asset-management/
本来パフォーマンスの項目で紹介するものではありませんが、Webpackをちゃんと使うことでBundleサイズの削減、ひいてはパフォーマンスの向上が期待できます。
出来ることはたくさんありますが、tree-shakingとcode-splittingをまず検討しましょう。

Webpackは汎用的な知識ではないし、vue-cliやNuxtのAPIでラッパーすれば触らなくても良いんですが、
現時点では未だフロントエンドが頑張るべき分野かなと思っています。

アセット最適化を知る

[48]https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/automating-image-optimization/?hl=ja
画像はCDNから配信するのがスタンダードだし、じゃぁCDN上で画像の最適化をするのがベストプラクティスという話です。
Googleが開発しているモダンな画像フォーマットである、webpも紹介されています。

ここまでの総括として、サーバーレスを知る

[49] https://qiita.com/jshimazu/items/e102cde5124609384d0c
[50] https://inside.dmm.com/entry/2018/11/06/nuxt2-pwa-gae-se
これまでに話してきたことを詰め込んだような内容で、かつインフラのメンテコストを減らす形でサーバーレスのNetlifyやGAEを用いた構成です。

SSGやSSRをサーバーレスで動かすのは現代のベストプラクティスの一つだと確信しています。
※弊社では専門のインフラチームがいるため実際にPaaSなどのサーバーレスを採用するケースは少ないですが、多くのチーム・個人にとって有用だと思います。

さいごに

GoogleやAWSのインフラエンジニアの力を借りることが可能な現代で、
フロントエンドエンジニアは自分たちだけで優れたサービスを提供できる存在だと思います。

知るべきことも、やるべきことも、山ほどありますが、
みんなでWebを少しでも良い世界にしていきましょう?

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

すべての新米フロントエンドに読んでほしい50の資料

はじめに

さいきんのWebはSPA技術を中心としたフロントエンドが賑わっていますね?

従来サーバーサイドを扱っていた人もフロントを触る機会が増えていたり、これからプログラミングを学んでいく人も、フロントエンド領域に興味を持っているのではと思います。
そこで、フロントエンドの経験が浅い方や初学者向けに、おすすめのドキュメントや勉強すべき領域をまとめました。

とりあえず動けば良い段階から一歩進んで、フロントエンドエンジニアとして、良いアプリケーションを作るために必要な知識を浅く広く紹介します。

フロントエンド入門

公式ドキュメントを知る

[1] https://jp.vuejs.org/v2/guide/
一例としてVueを貼りましたが、Vueに限らず信頼出来る情報に当たる癖付けがとても大切です。
特にVue.jsは一昔前のjQueryやRailsやPHPのように、技術に明るくない方が書いたページが多いと感じていて、ちゃんとVueを学ぶならまずは公式を見ましょう。

言語を知る

JSを知る

[2] https://jsprimer.net/basic/introduction/
今からJSを学ぶなら特別な事情がない限り、ES2015以降の文法で良いと思います。
js-primerはES2015以降のJSを知る上で素晴らしいドキュメントです。

昔のJSもちょっと知る

[3] https://developer.mozilla.org/ja/docs/Web/JavaScript/Closures
データの隠蔽やカプセル化を実現するために使われたテクニックです。
今のJSを書く場合に意識する機会は少ないと思いますが、知っておいて損ないです。

[4] https://www.yunabe.jp/docs/javascript_class_in_google.html
ES5以前の古いJSで記述しなきゃいけない仕事に遭遇した際、あらためて読み返したい素晴らしいドキュメントです。ベストプラクティスのひとつだと思います。

昔のJSもしっかり学びたい方は、「JavaScript: The Good Parts」や「開眼!JavaScript」といった書籍の購入も検討してください。
上記の書籍を読んで、DouglasやJohnResigやSubstackを知っていると、先輩のフロントエンドエンジニアから気に入られる確率が向上します(当社比)

非同期処理を知る

[5] http://azu.github.io/promises-book/
非同期処理は初学者にとって大きな壁の一つだと思いますが、しっかりとPromiseオブジェクトを理解することがJSでは必須です。
ES2017で追加されたAsync Functionは、Promiseを返す関数なのでPromiseが分かればすぐ理解できます。

Promiseを理解したら、Asyncjsのようなライブラリで複雑な非同期処理を実装したり、
RxJSのようなライブラリを用いてPromise以外の非同期を扱う方法を学んでみると良きです。

HTMLLivingStandard & ECMAScript を知る

[6] https://html.spec.whatwg.org/multipage/
[7] https://www.ecma-international.org/publications/standards/Ecma-262.htm
聖書。HTMLやJSといった言語の仕様が記載してあります。

ちなみに私のチームでは、実装周りの裁判が発生した際に、六法全書代わりとして活用しています。
※私はおそらく全体の3%ほどしか読んでませんが、ちゃんと読んだぞという空気を出して会話します?

コーディング規約を知る

[8] https://qiita.com/mysticatea/items/f523dab04a25f617c87d
[9] https://qiita.com/soarflat/items/06377f3b96964964a65d
これからフロントエンドを学ぶ方すべてに、まずはLintとコードフォーマッターの導入をお勧めします。
Lintではeslintやstylelint、コードフォーマッターではprettierがデファクトです。
利用するルールは何でも良いと思いますが、とにかくルールを用意することが大切です。

UIを知る

モーショングラフィックやインタラクションを知る

[10] https://goodpatch.com/blog/ui-micro-interaction/
マイクロインタラクションはデザイナーの仕事という認識の方も多いと思いますが、
特殊な例外発生時にユーザーへ適切なフィードバックを返すべきといった気付きはエンジニアの方が強いと思うので、常にプログラム的な例外とUI的な例外(フィードバック)の両方を考える姿勢が大切だと感じます。

マテリアルデザインを知る

[11] https://material.io/design/
Google公式のデザインガイドです。英語のドキュメントですが、グラフィック中心なので分かりやすいです。

アトミックデザインを知る

[12] https://ygoto3.com/posts/atomic-design-on-actual-project/
ReactやVueを用いる際にコンポーネント分割の粒度としてアトミックデザインを活用するといったケースがあります。
アトミックデザインに固執する必要はないですが、コンポーネントをどう分割していくかをデザイナー交えて検討するのは大切だと考えていて、土台としてアトミックデザインは有用と思います。

アクセシビリティを知る

セマンティックを知る

[13] https://developer.mozilla.org/ja/docs/Learn/Accessibility/HTML
[14] https://developer.mozilla.org/ja/docs/Learn/Accessibility/CSS_and_JavaScript
セマンティックなコーディングを行うことでアクセシビリティだけでなく、SEOに対してもメリットがあると言われています。

ARIAを知る

[15] https://developer.mozilla.org/ja/docs/Learn/Accessibility/WAI-ARIA_basics
可能な限りネイティブなHTMLで解決すべきで、必要なときだけ使うことが推奨されています。
業務アプリなど利用ブラウザが制限できて、かつ複雑なUIでは活躍の機会があるかもしれません。

SEOを知る

お上の言うことを知る

[16] https://support.google.com/webmasters/answer/35769?hl=ja
まずはお上の言うことに従いましょう。
私は毎晩、「Googleは神」と三回復唱してから寝るようにしています。

構造化データを知る

[17] https://developers.google.com/search/docs/data-types/article?hl=ja
担当業務領域によっては、構造化データを設定することが必要かもしれません。

SEOと直接関係ありませんが、GAやdatalayerを正しく設定することもフロントでは大切です。

ブラウザを知る

ブラウザの仕組みを知る

[18] https://www.html5rocks.com/ja/tutorials/internals/howbrowserswork/
結構長いので、他国の文化を知るような気持ちで、ブラウザってこうなってんだって読むのをお勧めします。

デバイスおよびブラウザの特性を知る

[19] https://caniuse.com/
フロントエンドの辛いところとして、コードの実行環境(ブラウザ)が無数にあることが挙げられますが、JSやCSSがその環境で実行可能か確認できます。

JSに関しては、IE11などでもES2015以降の機能を使えるbabel-polyfillの導入も検討しましょう。

Threadを知る

[20] https://nhiroki.jp/2018/05/07/off-the-main-thread-api
JavaScriptにはメインスレッド(=UIスレッド)が一つしかないので、普通に書くと非同期処理であってもシングルスレッドの直列で実行されます。
重たい処理(UI的にカクついてしまうとか)をマルチスレッドで実行するために、Web Workerの利用を考えましょう。

JITを知る

[21] https://dev.mozilla.jp/2017/05/%E3%82%B8%E3%83%A3%E3%82%B9%E3%83%88%E3%83%BB%E3%82%A4%E3%83%B3%E3%83%BB%E3%82%BF%E3%82%A4%E3%83%A0-jit-%E3%82%B3%E3%83%B3%E3%83%91%E3%82%A4%E3%83%A9%E3%81%AE%E3%82%AF%E3%83%A9%E3%83%83%E3%82%B7/
(難しくてよく分かってませんが?)モダンブラウザは実行時最適化と呼ばれるコンパイルを行っているそうです。

常に同じ型で呼び出される型を仮定するそうなので、私のような市井のプログラマには、型を意識した開発を心掛けることでJITの恩恵を享受できる可能性があります。
お気づきの方も多いと思いますが、それはTypeScriptと相性が良いです。

セキュリティを知る

DOM Based XSSを知る

[22] https://gihyo.jp/dev/serial/01/javascript-security/0006
モダンなフロントのフレームワークを使っている場合、もはや意識しないかもしれませんが、JS初学者は一度目を通しましょう。

CORSを知る

[23] https://developer.mozilla.org/ja/docs/Web/HTTP/CORS
CORSを理解しそのクライアントとサーバーを実装すると、HTTPに対する理解も深まるのでとてもお勧めです。

CSRF脆弱性とjwtを知る

[24] https://techblog.yahoo.co.jp/advent-calendar-2017/jwt/
jwt(json web token)は、OAuth2.0という認可の仕組みのアクセストークンとして採用されるなど広く使われています。

CSRFという、なりすましのような脆弱性が存在すること、その対策の一つとしてredirect_uriの制限が可能なjwtが存在することを理解出来るとOKです。
GoogleやFacebookのような代表的なOpenID Providerは、stateにredirect_uriを含めuriのホワイトリストと突合するのが必須になっていると思います。

テストを知る

ユニットテストを知る

[25] https://jestjs.io/docs/ja/getting-started
Reactはもちろん、Vueを使う際もJestがお勧めです。
ただフロントエンドのテストはベストプラクティスが無いと言われていて、下記のようにどういったテストをすべきかをまず検討しましょう。
[26] http://akito0107.hatenablog.com/entry/2018/08/27/190333

もしかしたらあなたに必要なのはユニットテストではなく、TypeScriptかもしれません。

E2Eテストを知る

[27] https://techblog.lclco.com/entry/2018/06/28/080000
フロントエンドが自主的に行うE2EとしてはPuppeteerがデファクトではないかと思います。
ユニットテストでも書きましたが、E2Eの運用も楽ではないので何をテストするのかちゃんと計画しましょう。

代表的なUIテストを知る

[28] https://qiita.com/masaakikunsan/items/dad8d84807918f3a43cb
storybookを採用することで、再利用性やテスト容易性が向上すると思います。
実装やメンテは大変なので、小規模なチームでの採用はコストに見合わない可能性があります。

データストアを知る

cookieを知る

[29] https://developer.mozilla.org/ja/docs/Web/HTTP/Cookies
仕様が複雑ですが、とても良く使うためちゃんと理解しましょう。ブラウザのユーザー固有情報においてサーバーとやり取りできる唯一のストレージです。

他にもブラウザだけで扱えるWeb Storageも存在します。サーバーとのやり取りが不要ならこちらを使いましょう。
[30] https://developer.mozilla.org/ja/docs/Web/API/Web_Storage_API#Web_Storage_concepts_and_usage
HTTPヘッダに乗ることもなくブラウザだけで完結するため扱いやすいですが、外部サービスのJSから取得可能な領域のため個人情報を置いたりするのは止めましょう?

NoSQLを知る

[31] https://firebase.google.com/docs/database/ios/structure-data?hl=ja
リンク先はFirebaseのRealtime Databaseですが、MysqlのようなRDBとは設計思想がまるで異なることが分かると思います。
※Cloud Firestoreの設計は、上記のベストプラクティスと異なります。

Cloud FirestoreやDynamoDBといった新世代の優れたデータストアは、これまでバックエンドエンジニアが培ってきた設計が通用しないため運用が難しいと議論されています。
[32] https://qiita.com/funasaki/items/6cdc8f7f7b709e5e601f

一方、多くのネイティブアプリエンジニアが運用してきたように、UIに近い立場のフロントエンドにとっては比較的自然に扱えるデータストアだと感じるので、触れてみるのがお勧めです。

モダンフロントエンドへ

Fluxと仮想DOMを知る

[33] https://qiita.com/mizchi/items/4d25bc26def1719d52e6
Qiita史に燦然と輝く魂の名文。
なぜVueやReactがこれほど重要な位置を占めたか、そのすべてが書いてあります。

私的な余談ですが、右も左も分からない新卒エンジニアだった当時この文章を読んで、全然内容は分からなかったんですが、この筆者が一体何に興奮しているのか興味を持ち、Reactを学びたくなったという背景があります。

言語を知る

TSを知る

[34] https://www.typescriptlang.org/docs/home.html
折に触れてTypeScriptの名前を出してきましたが、今からJSのプロジェクトを始める場合、どんなケースにおいてもTSの採用をオススメします。そこに迷いはありません?

React, Vueを知る

[35] https://ja.reactjs.org/docs/getting-started.html
とりあえずReactかVueのどちらかを学びましょう。

現時点ではTSとの相性がReactの方が高いです。Vue3.0がリリースされるとVueでもTSXがネイティブサポートされ解消される見込みですが。

個人的には、Vueの双方向と単方向データバインディングの合わせ技を便利と感じるか邪悪と感じるかで、VueとReactのどちらを採用するかが決まるかなと考えています。

CSS in JSを知る

[36] https://qiita.com/taneba/items/4547830b461d11a69a20
styled-componentsの採用が多いです。

CSS in JSを嫌っている人もいますが、コンポーネント単位でインターフェースやインタラクションを定義していきましょうという時代に適した手法と感じます。

アーキテクチャを知る

CDNを知る

[37] https://speakerdeck.com/sisidovski/nikkei-itpro-cdn
リリース当時、界隈で話題になった日経電子版のCDNを中心とした設計の話です。
とにかくCDNで出来ることが増えていて、なぜ使うのか、どういったことが可能なのか知ることが大切です。

JAMStackを知る

[38] https://qiita.com/ossan-engineer/items/4fe0e3e388f510bd5c68
CDNファーストを考えた際に、第一候補となる構成です。
この構成を目指す際に採用するGatsbyやVuePressのようなFWは、静的サイトジェネレータ(SSG)と呼ばれる事が多いです。

BFFを知る

[39] https://www.atmarkit.co.jp/ait/articles/1803/12/news012.html
特にこの記事で素晴らしいと思った点は、フロントエンドの担当領域を明確に定義している点です。

私的な余談ですが、なぜBFFという考え方が誕生しその領域をフロントエンドが担うべきなのか、おぼろげに理解できたとき私のなかで点だった技術や思想が線で繋がった感覚がありました。

BFFとSSRを知る

[40] https://speakerdeck.com/yosuke_furukawa/ssrfalsehua
SSRも一種のBFFと呼べますが、なぜSSRをすべきなのかが最もわかりやすいと思います。
さすが日本Node.jsユーザグループ会長、ウォーリーのアイコンも可愛いです。

アーキテクチャの補足ですが、既存の技術資産を活用するためRailsをAPIで残して、フロントエンドだけをモダンにするみたいなケースも見られます。
[41] https://speakerdeck.com/fukuiretu/notewonuxt-dot-jsdezai-gou-zhu-sitahua

レンダリングを知る

[42] https://developers.google.com/web/updates/2019/02/rendering-on-the-web
モダンなフロントエンドのアーキテクチャをレンダリングという観点からまとめたGoogle謹製ページ。
2019年に生きるすべてのフロントエンド必読です。

SSRやSSGを実現する方法を知る

[43] https://ja.nuxtjs.org/guide
NuxtやNextを良く聞くけどイマイチ分かってなかった方々、ここまでの話を読むと凄さが伝わるのではないかと思います。
サーバーサイドでReactやVueが実行可能になったことで、SSRが市井のエンジニアでも実現可能になりました。
NuxtやNextではSSGも可能です。

パフォーマンス改善を知る

Service Workerを知る

[44] https://developers.google.com/web/fundamentals/primers/service-workers/?hl=ja
Service Workerを用いたcacheを行うことで、2回目以降のFCPなどのタイムを大幅に削減可能です。

このAPIを用いると、従来Webから出来なかったプッシュ通知などの体験を提供できることから、Service Workerを活用したWebアプリはPWAとも呼ばれてます。

lighthouseを知る

[45] https://developers.google.com/web/tools/lighthouse/?hl=ja
lighthouseを用いることで、これまで紹介してきたアクセシビリティやパフォーマンスといった項目がサイト上でどの程度実現出来ているかの測定が可能です。

Performance Budgetを知る

[46] https://developers-jp.googleblog.com/2019/03/blog-post_15.html
ウェブサイトを高速に保つための手法として、Performance Budgetの導入が推奨されています。
上記で紹介したlighthouseなどと組み合わせてCIに導入すると良い感じだと思います。

Webpackを知る

[47] https://webpack.js.org/guides/asset-management/
本来パフォーマンスの項目で紹介するものではありませんが、Webpackをちゃんと使うことでBundleサイズの削減、ひいてはパフォーマンスの向上が期待できます。
出来ることはたくさんありますが、tree-shakingとcode-splittingをまず検討しましょう。

Webpackは汎用的な知識ではないし、vue-cliやNuxtのAPIでラッパーすれば触らなくても良いんですが、
現時点では未だフロントエンドが頑張るべき分野かなと思っています。

アセット最適化を知る

[48]https://developers.google.com/web/fundamentals/performance/optimizing-content-efficiency/automating-image-optimization/?hl=ja
画像はCDNから配信するのがスタンダードだし、じゃぁCDN上で画像の最適化をするのがベストプラクティスという話です。
Googleが開発しているモダンな画像フォーマットである、webpも紹介されています。

ここまでの総括として、サーバーレスを知る

[49] https://qiita.com/jshimazu/items/e102cde5124609384d0c
[50] https://inside.dmm.com/entry/2018/11/06/nuxt2-pwa-gae-se
これまでに話してきたことを詰め込んだような内容で、かつインフラのメンテコストを減らす形でサーバーレスのNetlifyやGAEを用いた構成です。
SSGやSSRをサーバーレスで動かすのは現代のベストプラクティスの一つだと確信しています。

さいごに

GoogleやAWSのインフラエンジニアの力を借りることが可能な現代で、
フロントエンドエンジニアは自分たちだけで優れたサービスを提供できる存在だと思います。

知るべきことも、やるべきことも、山ほどありますが、
みんなでWebを少しでも良い世界にしていきましょう?

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

Auth0のSPA用SDKでユーザー情報が取れない時の対処法

Auth0のSPA用SDKつかってみた

Auth0とReactのWebアプリケーションを連携させようとauth0-spa-jsを使用してみました。
公式のチュートリアル通りにやってみたけどうまくユーザー情報が取れない・・・

たぶんこのSDK自体がすごく最近できたものみたいなので(2019年7月現在)、もしかしたらチュートリアルも抜けがあったのかも

文脈

公式のReact:Loginというチュートリアルを元にしてます。

これでなおった

auth0-jsという多分昔からあるSDKのチュートリアルの方と比べてみたら
configの設定で
audienceという部分が抜けていました。
なので

auth0-config.json
{
    "audience": "https://<DomainName>/userinfo"
}

と入れ、index.jsのレンダー部分を

index.js
ReactDOM.render(
    <Auth0Provider
    domain={config.domain}
    client_id={config.clientId}
    audience={config.audience}
    redirect_uri={window.location.origin}
    onRedirectCallback={onRedirectCallback}
    >
    <App />
  </Auth0Provider>,
document.getElementById('root'));

このようにaudienceを追加したら治りました

まとめ

最初で手こずったけどAuth0なかなか良さそう

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

JavaScriptを触ってみた Part.2 オブジェクト指向

はじめに

ちゃんとがっつり開発のエンジニア歴2ヶ月目が学んだことをまとめる備忘録。
最近はまた毎日新しく学ぶことがたくさんで、楽しい気持ちとちょっと情報がたくさんすぎて追いつかないぞ!!!ってなっています。そんな自分の外付けHDDみたいな感じで書いていこうと思います!

↓ちなみにこれまでの記事はこちら
- 第15弾
- 第16弾
- 第17弾

今日はふたつも記事をあげましたよ(.・v・)ノ

オブジェクト(Object)の原義

オブジェクト指向とは

  • 開発における考え方。
  • 作成するアプリケーションなどをものとして捉えて考える開発手法。

Javaにおけるオブジェクト指向

  • 基本の考え方は上記と同じ。
  • クラスとその中に書かれているコードをもとにインスタンス化して作成 →つまり、設計図からものを作っていくというイメージ。
  • 存在するのは、クラスインスタンスのふたつ。

クラス

  • Javaにおける 『設計図』 としての役割をもつ。
  • クラスの中で実際の設計を行なっていくので、以下のようなものがクラスの中に存在する。
    • メソッド・・・設計図の中でも作るものの 『振る舞い』 を記載したもの
    • コンストラクタ・・・オブジェクト作成時に同時に作られる処理

インスタンス

  • 実物。実際に作られているものという認識。
  • クラスのインスタンス化 ・・・クラスを実物化して、そのクラスの中で使えるようにしたもの。インスタンス化した結果できたのがオブジェクト =クラスはオブジェクトの設計図!

JavaScriptにおけるオブジェクト指向

  • 基本の考え方は上記と同じ。
  • JavaScriptの場合は、クラスが存在しないのが大きな特徴のひとつ
  • クラスの代わりにプロトタイプオブジェクトが存在する
    • プロトタイプオブジェクト ・・・設計図としてのオブジェクト(オブジェクトを毎回書かなくても済むように作成)
  • JavaScriptにおけるオブジェクト ・・・実体、実物(Javaにおけるインスタンスに近いもの)。
  • 同じオブジェクトを何度も作るのが大変/機能を追加したい

→もととなるオブジェクトを利用してもっと簡単に対応できるように作られたのがプロトタイプオブジェクト
→つまり、もともとあるものから設計図を使ってより作りやすさを求めていくというイメージ。

まとめ

  • JavaでもJavaScriptでもおおもととなるオブジェクト指向の考え方については一緒
    • 開発する対象を 『もの』として見る ことで設計図と実体が存在する
  • ただ、オブジェクト指向の中でアプローチの仕方が反対
    • Javaは設計図をもとにものを組み立てていって完成させるというイメージ
    • JavaScriptは、作られた実体をもとに、さらに設計図や実体を作成していき、それらを使って最終的なものを完成させるというイメージ

あとがき

オブジェクト指向って大事だよって話はPythonを触っていたときから、今JavaやJavaScriptを学んでいる間もちょこちょこ注意はあったけど、どこまで自分って理解しているんだろう・・・というか、そもそもちゃんとわかっているのかなぁと思ってしっかり一度まとめてみてもいいかも、と思って今回はオブジェクト指向についてにしました!

オブジェクトのイメージは正直あまり理解しきれていなかったり、このコトバ、知ってはいるけど意味なんだっけ・・・ってわからないことがあったりしましたが、まとめるにあたって改めて調査したり、参考書を読んで落とし込んでなんとか今理解できているレベルまではかけたかなぁと思います。

アウトプットすることで時間がかかることもありますが、自分の頭の情報を整理して補完することができるので、改めてこうやって人を意識してまとめるって大事なんだなぁと再認識しました!!!

またまとめようと思います!!
ではでは

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

タップすると画像が切り替わるJavaScript

はじめに

未来電子テクノロジーでインターンをしている坂口です。
プログラミングを始めて2週間目。今週は初めてHTMLとCSSを自分で書いて、Github上でホームページを作成しました。
その際にタップすると画像がどんどん切り替わるJavaScriptを実装したのですが、そのコードが、JavaScriptの仕組みを一切分かっていない初心者にも使いやすいものだったので、シェアします。

コードがこちら

<img id="mypic" onclick="slideshow()" src="img1" width="" height="">
    <script>
      var pics_src = new Array ("img1", "img2", "img3");
      var num = 0;

      function slideshow(){
           if (num == 2) {
               num = 0;
           }
           else {
               num ++;
           }
           document.getElementById("mypic").src=pics_src[num];
       }
   </script>

このうち自分で入力しなければいけない部分は、img1~3と、width、height、そしてnum == ?(上のコードでは2) の部分です。

まず、img1~3にはその通りに、1番目に表示したい画像、2番目に表示したい画像の順番で、その画像のURLやファイル名を入力します。(※img1を入力するのは二ヵ所あります)
このコードでは3枚しか画像を表示しないことになっていますが、4枚目、5枚目の画像を表示したければ、その都度同様に""に入れて画像を追加していってください。

一行目のwidthとheightでは、表示させる画像の幅と高さを設定します。
1枚ごとに幅と高さを設定することはできないので、同じ縦横比の画像を使うことが好ましいですね。
(もっとJavaScriptに詳しければ、1枚ごとに幅とかを変えるようなコードを書くこともできるのでしょうか…初心者坂口にとっては未知の世界です)

そして、最後に大事な部分なのですが、if (num == ?)の?の部分を入力します。
上のコードでは、全部で3枚の画像が切り替わるようになっているので、3-1で2となっています。
つまり、表示させる画像の総数-1の値を?に入力してください。
全部で4枚なら?には3を、全部で5枚なら?には4を、といった具合です。

ちなみに、なぜ総数-1になるのかと言うと、このコードには一枚目の画像を0と設定するようになっていて、0,1,2と進んでいって2になったら0に戻る!という指示がされるようになっているらしいのです。
だから、この?には0から数えていって、どの数字になったら0に戻るか、を入力しなければならない、ということです。
(1から数えたら、?には画像の枚数をそのまま入力すればいいだけなので、よっぽど分かりやすいのに…と初心者的には考えてしまいますが)

一例

https://yuka-0718.github.io/portfolio/

成功したら、このホームページのPhotosの画像みたいに、タップしたら画像が切り替わるようになっているはずです。

まとめ

今回は、JavaScriptにまったく触れたことがない人を想定して、ものすごくかみ砕いて説明してみました。
(なぜなら、自分が初心者の身で、ネット上のみなさんのコードの解説を理解するのにとてつもなく苦労したので)
同じようなプログラミング初心者の方に少しでも参考になれば幸いです。

また、私自身プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。よろしくお願いいたします。

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

JavaScript タップして画像を切り替える

はじめに

未来電子テクノロジーでインターンをしている坂口です。
プログラミングを始めて2週間目。今週は初めてHTMLとCSSを自分で書いて、Github上でホームページを作成しました。
その際に、タップすると画像がどんどん切り替わってループするJavaScriptを実装したのですが、そのコードが、JavaScriptの仕組みを一切分かっていない初心者にも使いやすいものだったので、シェアします。

コードがこちら

<img id="mypic" onclick="slideshow()" src="img1" width="" height="">
    <script>
      var pics_src = new Array ("img1", "img2", "img3");
      var num = 0;

      function slideshow(){
           if (num == 2) {
               num = 0;
           }
           else {
               num ++;
           }
           document.getElementById("mypic").src=pics_src[num];
       }
    </script>

このうち自分で入力しなければいけない部分は、img1~3と、width、height、そしてnum == ?(上のコードでは2) の部分です。

まず、img1~3にはその通りに、1番目に表示したい画像、2番目に表示したい画像の順番で、その画像のURLやファイル名を入力します。(※img1を入力するのは二ヵ所あります)
このコードでは3枚しか画像を表示しないことになっていますが、4枚目、5枚目の画像を表示したければ、その都度同様に""に入れて画像を追加していってください。

一行目のwidthとheightでは、表示させる画像の幅と高さを設定します。
1枚ごとに幅と高さを設定することはできないので、同じ縦横比の画像を使うことが好ましいですね。
(もっとJavaScriptに詳しければ、1枚ごとに幅とかを変えるようなコードを書くこともできるのでしょうか…初心者坂口にとっては未知の世界です)

そして、最後に大事な部分なのですが、if (num == ?)の?の部分を入力します。
上のコードでは、全部で3枚の画像が切り替わるようになっているので、3-1で2となっています。
つまり、表示させる画像の総数-1の値を?に入力してください。
全部で4枚なら?には3を、全部で5枚なら?には4を、といった具合です。

ちなみに、なぜ総数-1になるのかと言うと、このコードには一枚目の画像を0と設定するようになっていて、0,1,2と進んでいって2になったら0に戻る!という指示がされるようになっているらしいのです。
だから、この?には0から数えていって、どの数字になったら0に戻るか、を入力しなければならない、ということです。
(1から数えたら、?には画像の枚数をそのまま入力すればいいだけなので、よっぽど分かりやすいのに…と初心者的には考えてしまいますが)

まとめ

今回は、JavaScriptにまったく触れたことがない人を想定して、ものすごくかみ砕いて説明してみました。
(なぜなら、自分が初心者の身で、ネット上のみなさんのコードの解説を理解するのにとてつもなく苦労したので)
同じようなプログラミング初心者の方に少しでも参考になれば幸いです。

また、私自身プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。よろしくお願いいたします。

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

tiptapのコードブロックの下に空行を挿入する方法

  • Vue: ^2.6.10
  • tiptap: ^1.23.1

tiptapを使っていて、困ったことがありました。
tiptapを使えば、ハイライト付きのコードブロックが簡単に実装できます。
しかし、挿入されたコードブロックからカーソルを外すには、command+Z(macだと)するか、コードブロック以外の要素をクリックするかしかないみたいでした。
これだと、エディト領域の一番下にコードブロックが作られた場合に不便です。
コードブロックが挿入されたら、その下に空行を挿入することで、上記の問題を解決したので、共有します。

editor.vue
<template>
  <div class="editor" style="width: 100%;">
    <editor-menu-bar :editor="editor" v-slot="{ commands, isActive }">
      <div class="menubar">

        <button
          class="menubar__button"
          :class="{ 'is-active-button': isActive.code_block() }"
          @click="startCodeBlock(commands, isActive)">
          <font-awesome-icon icon="code" />
        </button>

      </div>
    </editor-menu-bar>

    <editor-content class="editor__content" :editor="editor" />
  </div>
</template>

<script>
import { Editor, EditorContent, EditorMenuBar } from 'tiptap'
import { safeInsert, findBlockNodes } from 'prosemirror-utils'
import python from 'highlight.js/lib/languages/python'
import {
  CodeBlock,
  CodeBlockHighlight
} from 'tiptap-extensions'
export default {
  components: {
    EditorContent,
    EditorMenuBar,
  },
  data() {
    return {
      editor: new Editor({
        extensions: [
          new CodeBlock(),
          new CodeBlockHighlight({
            languages: { python }
          })
        ],
        content: ``,
      }),
    }
  },
  beforeDestroy() {
    this.editor.destroy()
  },
  methods: {
    startCodeBlock(commands, isActive) {
      commands.code_block()
      if (isActive.code_block()) {
        this.insertEmptyLineBelowCodeBlock()
      }
    },
    insertEmptyLineBelowCodeBlock() {
      const { schema, state, view } = this.editor
      const selection = state.tr.selection
      const anchorPos = selection.$anchor.pos
      const blockNodes = findBlockNodes(view.state.doc)
      for (const { node, pos } of blockNodes) {
        // コードブロックの場所より下に何らかのnodeがある時は空行を挿入する必要がないので、return
        if (anchorPos < pos) {
          return
        }
      }
      // <p>をコードブロックの下にinsertする(https://github.com/atlassian/prosemirror-utilsのsafeInsertを利用)
      const p = schema.nodes.paragraph.create()
      view.dispatch(safeInsert(p, anchorPos)(state.tr))
      // 選択カーソルをコードブロック内に戻す
      view.dispatch(view.state.tr.setSelection(selection))
    }
  }
}
</script>
<style>
.ProseMirror pre {
  white-space: pre-wrap;
  width: 100%;
}
.editor__content pre {
  padding: .7rem 1rem;
  border-radius: 5px;
  background: #000;
  color: #fff;
  font-size: .8rem;
  overflow-x: auto;
}
.editor__content * {
    caret-color: currentColor;
}
.is-active-button {
  background-color: antiquewhite
}
</style>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ティラノスクリプトのシナリオファイルの流れを視覚化

視覚化ツール?

ティラノスクリプトでプロジェクトをはじめました。
規模が大きくなると、ファイル分岐の流れのチェックが大変です。
それでこのツールを作りました。
このツールを使うとシナリオファイルの流れが一目でわかります。
これを使うと以下のようなシナリオファイルのjump, link, glink の移動先の流れを一目で確認できます。

image.png

ただし変数とかマクロには対応してません。
そしてラベルではなく、ファイル分岐だけに使えます。
ファイルを沢山作るタイプの人に向いてます。
いつかはラベル分岐にも対応できるようにしたいですね。

ちなみに、ティラノスクリプトの本体サンプルプロジェクトに適用してみたら、こんな図になりました。

image.png


開発

シナリオファイルの分析は、ティラノスクリプトエンジンのソースを利用しました。(ver 4.5)

https://github.com/ShikemokuMK/tyranoscript

核心はこれです。シナリオファイルを呼び出してのパーシングする部分です。

$.loadText(fullpath,function(text_str){
        var scenario = tyrano.plugin.kag.parser.parseScenario(text_str);
        console.log(scenario);
});

そしてその視覚化にはvis.js を使いました。

https://visjs.org/


ソース、使い方

以下のファイルをティラノスクリプトプロジェクトのindex.html と同じ位置に置いてブラウザで開くだけでオッケーです。

  • scenario_visualizer.html
<!DOCTYPE html>
<html>
<head>
  <title>Tyrano Scenario Visualizer</title>

<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="viewport" content=" user-scalable=no" />
<meta name="robots" content="noindex,nofollow" />
<script src="//cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis.min.js" type="text/javascript" ></script>
<link href="//cdnjs.cloudflare.com/ajax/libs/vis/4.21.0/vis-network.min.css" rel="stylesheet" type="text/css" />
<style type="text/css">
  html,body{
    margin: 0;
    height: 100%;
    overflow: hidden;
  }
  #mynetwork {
    width:100%;
    height:100%;
    border: 1px solid lightgray;
  }
  .spinner {
    width: 40px;
    height: 40px;
    background-color: #333;

    margin: 100px auto;
    -webkit-animation: sk-rotateplane 1.2s infinite ease-in-out;
    animation: sk-rotateplane 1.2s infinite ease-in-out;
  }

  @-webkit-keyframes sk-rotateplane {
    0% { -webkit-transform: perspective(120px) }
    50% { -webkit-transform: perspective(120px) rotateY(180deg) }
    100% { -webkit-transform: perspective(120px) rotateY(180deg)  rotateX(180deg) }
  }

  @keyframes sk-rotateplane {
    0% { 
      transform: perspective(120px) rotateX(0deg) rotateY(0deg);
      -webkit-transform: perspective(120px) rotateX(0deg) rotateY(0deg) 
    } 50% { 
      transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg);
      -webkit-transform: perspective(120px) rotateX(-180.1deg) rotateY(0deg) 
    } 100% { 
      transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
      -webkit-transform: perspective(120px) rotateX(-180deg) rotateY(-179.9deg);
    }
  }  
</style>

<script type="text/javascript" src="./tyrano/libs/jquery-2.0.3.min.js"></script>
<script type="text/javascript" src="./tyrano/libs/jquery-ui.min.js"></script>
<script type="text/javascript" src="./tyrano/libs/jquery.a3d.js"></script>
<script type="text/javascript" src="./tyrano/libs/jsrender.min.js"></script>


<script type="text/javascript" src="./tyrano/libs/alertify/alertify.min.js"></script>

<script src="./tyrano/libs/remodal/remodal.js"></script>

<script type="text/javascript" src="./tyrano/libs/html2canvas.js"></script>

<script type="text/javascript" src="./data/system/KeyConfig.js"></script>

<script type="text/javascript" src="./tyrano/lang.js" ></script>
<script type="text/javascript" src="./tyrano/libs.js" ></script>

<script type="text/javascript" src="./tyrano/tyrano.js" ></script>
<script type="text/javascript">
$(document).ready(function() {
    alert("ready");
});

// stop the ready handler
$.isReady = true;
</script>
<script type="text/javascript" src="./tyrano/tyrano.base.js" ></script>

<script type="text/javascript" src="./tyrano/plugins/kag/kag.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.event.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.key_mouse.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.layer.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.menu.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.parser.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.rider.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.tag_audio.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.tag_camera.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.tag_ext.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.tag_system.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.tag_system_mod.js" ></script>
<script type="text/javascript" src="./tyrano/plugins/kag/kag.tag.js" ></script>
<script type="text/javascript" src="./tyrano/libs/lz-string.min.js"></script>
<!-- <script type="text/javascript" src="./tools/graph.js"></script>
<script type="text/javascript" src="./tools/tyrano_analysis_for_web.js"></script> -->
<script type="text/javascript">
function ScenarioGraph(){
    this.node = {};
    this.edge = {};
    this.addNode = function(nodename){
        if(this.node[nodename]){
            return false;
        }
        this.node[nodename] = nodename;
        return true;
    }

    this.addEdge = function(nodename, child){
            if(!this.edge[nodename]){
                this.edge[nodename] = [];
            }
            if(this.edge[nodename].indexOf(child) == -1){
              this.edge[nodename].push(child);
            }
        }

    this.get_vis_edges = function(){
            var edges = [];
            for(var n in this.edge){
                var child_nodes = this.edge[n];
                for(var i in child_nodes){
                    var child = child_nodes[i];
                    edges.push({from: n, to: child, arrows:'to'});
                }
            }
            return edges;
        }

    this.get_vis_nodes = function(){
            var nodes = [];
            for(var n in this.node){
                nodes.push({id: n, label: n });
            }
            return nodes;
        }
}


if (typeof module !== 'undefined'){
    module.exports = ScenarioGraph;
}

var start_file_name = "first.ks";
var scenarioDir = "./data/scenario/";
var reqCnt = 0;
function createGraph(filename){
    var g = new ScenarioGraph();
    seekDAG(g, filename);
    return g;
}

function seekDAG(graph, filename){
    if(!filename){
        console.log(filename);  
        return;  
    }
    var isNew = graph.addNode(filename);
    if(!isNew){
        console.log("already exists " + filename);  
        return;  
    }

    var fullpath = scenarioDir + filename;
    console.log(fullpath);
    reqCnt++;
    $.loadText(fullpath,function(text_str){
        var scenario = this.tyrano.plugin.kag.parser.parseScenario(text_str);
        var child_list = [];

        for(var i = scenario.array_s.length-1; 0 <= i ; i--){
            var result_obj = scenario.array_s[i];
            //console.log(scenario);
            if(["jump","A","B","glink","link"].includes(result_obj.name)){
                if(result_obj.pm){
                    var child_filename =  result_obj.pm.storage;
                    if(child_filename){
                        child_list.push(child_filename);
                    }
                }
            }
        }

        for(var i=0; i<child_list.length; i++){
            var child_filename = child_list[i];
            graph.addEdge(filename, child_filename);
            seekDAG(graph, child_filename);
        }
        reqCnt--;
    });

}

function loadVis(document_id, g){
    if(0 < reqCnt){
        console.log("loading scenario files not complete ... ");
        return false;        
    }
    var nodes = new vis.DataSet(g.get_vis_nodes());
    var edges = new vis.DataSet(g.get_vis_edges());

    var container = document.getElementById(document_id);
    var data = {
    nodes: nodes,
    edges: edges
    };
    var options = {};
    var network = new vis.Network(container, data, options);
    $(".spinner").remove();
    return true;
}

function wait_and_load_vis(document_id, g, cnt){
    if(cnt > 10){
        console.log("loading text timeout " + cnt);
        return;
    }
    console.log("("+ cnt +"/10) waiting 0.5 second and retry create graph ");
    setTimeout(function(){
        var ret = loadVis(document_id, g);
        if(!ret){
            wait_and_load_vis(document_id, g, cnt+1);    
        }
    },500);
}

function create_vis(document_id){
    var g = createGraph(start_file_name);
    wait_and_load_vis(document_id, g, 0);
}


</script>

</head>

<body>
<!-- <div id="tyrano_base" class="tyrano_base" style="overflow:hidden" unselectable="on" ondragstart="return false" ></div> -->
<div id="mynetwork">
  <div class="spinner"></div>
  Now Loading...
</div>

<script type="text/javascript">
window.onload = function() {
  create_vis("mynetwork");
}
</script> 

</body>
</html>

ただし、ティラノスクリプト本体みたいに、file:// みたいなローカルファイルSchemaでは動きません。
http:// で開く必要があります。
そのためにはlocalhost Webサーバを起動する必要があるから、非プログラマーには難しいかも知れません。
一番簡単なアプローチは、nodejs の http-server を使う事です。
https://qiita.com/standard-software/items/1afe7b64c4c644fdd9e4

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

firebaseのデータベースを使うときにUncaught TypeError: firebase.database is not a functionと吐かれた時の解決法

未来電子テクノロジーでインターンをしています、Sotaです。
プログラミング初心者であるため、内容に誤りがあるかもしれません。
もし、誤りがあれば修正するのでどんどん指摘してください。

今回は、firebaseでホームページを作るときに、firebase.database();でエラーを吐かれた時の解決策のメモです。

Uncaught TypeError: firebase.database is not a function

databaseに問い合わせのデータを格納しようとjsを書いていたら、コンソールに見出しのエラーを吐かれました。
色々なサイトを参考に初期設定から立ち上げいていたのですが、このエラーに関する情報がなかなか無くて詰んでしまいました。
その時の解決法です。
firebaseで最初に

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/6.2.4/firebase-app.js"></script>

<!-- TODO: Add SDKs for Firebase products that you want to use
     https://firebase.google.com/docs/web/setup#config-web-app -->

<script>
  // Your web app's Firebase configuration
  var firebaseConfig = {
    apiKey: "***",
    authDomain: "***",
    databaseURL: "***",
    projectId: "***",
    storageBucket: "",
    messagingSenderId: "***",
    appId: "***"
  };
  // Initialize Firebase
  firebase.initializeApp(firebaseConfig);
  </script>

このコードをindex.htmlに書き込むと思います。
この部分に少しコードを加えると解決できました。

<!-- The core Firebase JS SDK is always required and must be listed first -->
<script src="https://www.gstatic.com/firebasejs/6.2.4/firebase-app.js"></script>
<script src="https://www.gstatic.com/firebasejs/6.2.4/firebase-auth.js"></script>  
<script src="https://www.gstatic.com/firebasejs/6.2.4/firebase-database.js"></script>

このように下の2行を追加すると解決しました。
auth.jsやdatabase.jsが読み込めていなかったのかもしれません。
参考にしたサイトは
https://stackoverflow.com/questions/38248723/firebase-database-is-not-a-function
こちらです。

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

【JS】ES6から出来るようになったClass構文書き方などについて

es6からのJavaScriptではclass構文が導入されているらしいです!
知らなかった...ので、学習したことをまとめてみます。

class Animal {
    constructor(type){      //コンストラクター
       this.type = type;
    }

    intro(){          //メソッド
        console.log(`${this.type}です。`);
    }
}

 let neko = new Animal('猫');
 neko.intro();    //猫です。

constructorでインスタンスの際の処理を、
そしてクラスの中にメソッドやプロパティが定義できます。
ただし、C#などと違ってアクセス修飾子は使えません。(全てがどこからでもアクセスできる)

注意!
あくまでクラスと同じ書き方ができるようになった、というもので、内部の処理的には関数。
ただし、今までのFunctionオブジェクトで定義したクラス = class命令で定義したクラスではない!

FunctionのクラスとClass構文のクラスの違い

①関数としての呼び出しは不可!
function命令の方では、それができないようにthisの性質によって条件分岐させたり...と面倒(ここの最後のところ)でしたが、これが不必要になりました!

②定義前のクラスは呼び出し不可!
functionでは呼び出し可能です。

【おまけ】プロトタイプチェーンについて学習しました。

これは私がまとめるよりも、リンク先をみてもらった方がわかりやすいです。
リンク教えてくれた@htsignさんありがとうございます。
https://qiita.com/howdy39/items/35729490b024ca295d6c

プロトタイプのよみやすい定義

ver Animal = function(){};
Animal.prototype.aa = function(){...},
Animal.prototype.bb = function(){...}
};

これでプロトタイプにメンバーを追加できますが、正直プロトタイププロタイプくどいです。
これをオブジェクトリテラルでスッキリさせることができます。

var Animal = function(){...};
Animal.prototype = {
   aa: function(){...},   //スッキリ!
   bb: function(){...}
};
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Railsで動的なセレクトボックスを作る最も簡単な方法。

主に使用する言語はRuby(on Rails), JSです。

動的なセレクトボックスとは?

https://qiita.com/jnchito/items/59a5f6bea3d7be84b839

伊藤さんの書いているこちらの記事を見てみてください。

ここで記述されているセレクトボックスのように親要素が選択された時に子要素のセレクトボックスの中身が選択された親要素に紐づいている子要素になるというものをここでは動的なセレクトボックスと呼んでいます。

上記の記事ではルーティングを設定したり、ajaxの記述をしたりと色々していますが、ある要素を使えばこのセレクトボックスは簡単に実装できます。

結論:template要素を使う

ここでもCategory, SubCategoryのモデルでアソシエーションを組んで実装していきましょう。

class Category < ApplicationRecord
  has_many :sub_categories
end
class SubCategory < ApplicationRecord
  belongs_to :category
end

このアソシエーションによってCategoryモデルとSubCategoryモデルで1対多の関係性を築けました。

次はビューファイルの編集です。

= f.collection_select :category, Category.all, :id, :name, include_blank: "カテゴリーを選択してください"

- Category.all.each do |category|
 %template{id: "sub-category-of-category#{category.id}"}
  = f.collection_select :sub_category, categroy.sub_categories, :id, :name, include_blank: "サブカテゴリーを選択してください"

今の状況はこんな感じです。

<select name="category" id="category">
 <option value="1">カテゴリー1</option>
 .
 .
 .
</select>
<template id="sub-category-of-category1">#document-fragment</template>
<template id="sub-category-of-category2">#document-fragment</template>
<template id="sub-category-of-category3">#document-fragment</template>
<template id="sub-category-of-category4">#document-fragment</template>
<template id="sub-category-of-category5">#document-fragment</template>
.
.
.

template要素とは何か??

template要素は、ページの読み込み時に描画されず、後で JavaScript を使用してインスタンスを生成できるクライアント側のコンテンツを保持するメカニズムです。
(引用元:https://developer.mozilla.org/ja/docs/Web/HTML/Element/template

簡単にいうと、JavaScriptで操作して本文へと挿入しない限りはデベロッパーツールのHTML上には表示されるがビューの見た目には何も影響しないということです。

なのでjsファイル内での記述も書いていきましょう。
今回はjQueryで書いていきます。

jsファイル内の記述

$(document).on('turbolinks:load', function() {
 $(document).on('change', '#category', function() {
  let categoryVal = $('#category').val();
  if (categoryVal !== "") {
   let selectedTemplate = $(`#sub-category-of-category#{categoryVal}`);
   $('#category').after(selectedTemplate.html());
  };
 });
});

ただ、このままだと初めは親要素のセレクトボックスしかなく、親要素を選択した際に子要素のセレクトボックスが現れるといった記述になることに加えて、一度選択して二度目の選択をする際に新しいセレクトボックスが生成されるという状況になってしまう。

少しビューファイルとjsファイルをいじってそれを直していきます。

= f.collection_select :category, Category.all, :id, :name, include_blank: "カテゴリーを選択してください"

= f.select :sub_category, [], include_blank: "サブカテゴリーを選択してください", class: "default-sub-category-select"

- Category.all.each do |category|
 %template{id: "sub-category-of-category#{category.id}"}
  = f.collection_select :sub_category, categroy.sub_categories, :id, :name, include_blank: "サブカテゴリーを選択してください"

これでページの読み込み時に親要素と子要素(option要素は何もない)が出来上がっている状況である。

ちなみにこの時のデフォルトで表示されている子要素のセレクトボックスのidが'sub_category'になっているということだけ覚えておいていただきたい。(jsファイルでの記述に使うから。)

そこからjsファイルの編集にうつる。

$(document).on('turbolinks:load', function() {
 //HTMLが読み込まれた時の処理
 let categoryVal = $('#category').val();
 //一度目に検索した内容がセレクトボックスに残っている時用のif文
 if (categoryVal !== "") {
  let selectedTemplate = $(`#sub-category-of-category${categoryVal}`);
  $('#sub_category').remove();
  $('#category').after(selectedTemplate.html());
 };

 //先ほどビューファイルに追加したもともとある子要素用のセレクトボックスのHTML
 let defaultSubCategorySelect = `<select name="sub_category" id="sub_category">
<option value>サブカテゴリーを選択してください</option>
</select>`;

 $(document).on('change', '#category', function() {
  let categoryVal = $('#category').val();
  //親要素のセレクトボックスが変更されてvalueに値が入った場合の処理
  if (categoryVal !== "") {
   let selectedTemplate = $('#sub-category-of-category${categoryVal}');
   //デフォルトで入っていた子要素のセレクトボックスを削除
   $('#sub_category').remove();
   $('#category').after(selectedTemplate.html());
  }else {
   //親要素のセレクトボックスが変更されてvalueに値が入っていない場合(include_blankの部分を選択している場合)
   $('#sub_category').remove();
   $('#category').after(defaultSubCategorySelect);
  };
 });
});

これで最初のビューに親要素・子要素のセレクトボックスが存在し、親要素が選択されていない場合はoption要素が何もないデフォルトのセレクトボックスが表示され、親要素が選択された際にその要素と紐づいた子要素が入ったセレクトボックスが現れるようになったと思う。ただIEがtemplate要素に対応してないみたいなの見たきがしないでもないからその辺りは注意してください。

リファクタリングはまた今度します。

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

イベントハンドラも含めてDOMを複製する(注!jQuery)

jQueryをバンバン使用している案件において、
clickイベント登録されたボタンを別の場所にも配置したい
でもCMS改修はしたくないから、フロントで何とかしてほしい
という要件でちょっとググったら出てきました。

今の今まで存在を知らなかっただけですが、自分用のメモとして…

clone(true)を使う

clone()はある要素のコピーを作成してDOMの他の場所に配置する関数です。
jQuery("#A").clone().insertBefore("#B")のように使います。

clone(true)とtrueをセットすることでイベントハンドラも含めてDOMをコピーできます。

jQuery("#clone-sitai-el").clone(true).insertBefore("#kono-el-no-ueni-insert");




長いページにおいて、ライブラリを使って簡単に実装したモーダル表示ボタンとかをいろんば場所に置いておきたい、等の場面で便利だなぁ

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

ブックマークレットで5chまとめを作る

5chまとめとは

スマートニュースにもカテゴリがあるほど馴染みの深い「まとめ」。
言ってみればただの転載なので、著作権とか大丈夫?と思いがちですが、5ちゃんねるの宣伝になるということで公式に認められているそうです。

この5chまとめを作成する上で便利なのがまとめツールです。その中でも、まとめくすが一番ポピュラーなツールです。多機能で、カスタマイズもできて、まとめくす上で投稿できちゃったり、、

しかし、ブックマークレットを使うと、もっと簡単にまとめを作成することができます。

ブックマークレット (Bookmarklet) とは、ユーザーがウェブブラウザのブックマークなどから起動し、なんらかの処理を行う簡易的なプログラムのことである。携帯電話のウェブブラウザで足りない機能を補ったり、ウェブアプリケーションの処理を起動する為に使われることが多い。

ブックマークレットならではの機能が
・5ちゃんねるのページ上でまとめ作業ができる
・レスをタップしたりスワイプしたりするだけ
・ブックマークを開くだけでいつでも作り始められる

私の作ったブックマークレットのプログラムはこちらからダウンロードできます→ 5chまとめレット

プログラムの流れ

起動すると同時に、全てのレスにタッチイベントを付加し、タップでレスの選択・スワイプで文字色や文字サイズの変更ができるようにする

選択したレスの数を表示するカウンターと、まとめを出力するコピーボタンを追加する

コピーボタンが押されたら、選択されたレスをまとめブログ用の形式に変換して、html形式でクリップボードに保存する

ブックマークレットのコード

javascript:var style=document.createElement('style');style.textContent='.active>.threadview_response_body{font-weight:600}.color_1>.threadview_response_body{color:#0000cd}.color_2>.threadview_response_body{color:#ff0000}.color_3>.threadview_response_body{color:#ffa500}.color_4>.threadview_response_body{color:#800080}.color_5>.threadview_response_body{color:#ff4f86}.stress_1>.threadview_response_body{font-size:1.25em!important}.stress_2>.threadview_response_body{border:1px solid #d2d2d2;background:#f0f0f0;padding:.7em;margin-left:.5em;margin-right:.5em}.highlighted{color:#f30100!important}.actionbtn{position:fixed;left:20px;bottom:32px;font-size:14px;text-align:center;padding-top:10px;padding-bottom:10px;width:100px;border-radius:6px;box-shadow:0 0 10px 0 gray;background:#324356;color:#fff}.rescounter{position:fixed;left:20px;bottom:80px;font-size:18px;text-align:center;padding-top:4px;padding-bottom:4px;width:70px;border-radius:6px;border:1px solid #324356;color:#324356}';document.body.appendChild(style);var actionbtn=document.createElement('div');actionbtn.classList.add('actionbtn');actionbtn.addEventListener("click",generateMat);actionbtn.innerHTML='コピー';document.body.appendChild(actionbtn);var rescounter=document.createElement('div');rescounter.classList.add('rescounter');rescounter.innerHTML='<span id="rescount">0</span><span style="font-size:12px">レス</span>';document.body.appendChild(rescounter);function execCopy(string){var tmp=document.createElement("div");var pre=document.createElement('pre');pre.style.webkitUserSelect='auto';pre.style.userSelect='auto';tmp.appendChild(pre).textContent=string;var s=tmp.style;s.position='fixed';s.right='200%';document.body.appendChild(tmp);document.getSelection().selectAllChildren(tmp);var result=document.execCommand("copy");document.body.removeChild(tmp);return result;};var res=document.getElementsByClassName('threadview_response');for(var i=res.length-1;i>=0;i--){res[i].addEventListener("click",function(){if(this.classList.contains('active')){this.classList.remove('active','color_0','color_1','color_2','color_3','color_4','color_5','stress_0','stress_1','stress_2');rescount-=1;document.getElementById('rescount').innerHTML=rescount;}else{this.classList.add('active','color_0','stress_0');rescount+=1;document.getElementById('rescount').innerHTML=rescount;};});res[i].addEventListener("touchstart",function(event){touchStartX=event.touches[0].pageX;},false);res[i].addEventListener("touchmove",function(event){touchMoveX=event.changedTouches[0].pageX;},false);res[i].addEventListener("touchend",function(event){if(touchStartX>touchMoveX){if(touchStartX>(touchMoveX+200)){var id=this.getElementsByClassName('res_id')[0].textContent;id=id.replace(/\([\s\S]*$/,'');if(id=='ID:'){alert('抽出できません');}else{var result=window.confirm(id+'を抽出しますか?');highlight(id);if(result){highlight(id);highlighted.push(id);};};}else if(touchStartX>(touchMoveX+90)){if(this.classList.contains('stress_0')){this.classList.add('stress_1');this.classList.remove('stress_0');}else if(this.classList.contains('stress_1')){this.classList.add('stress_2');this.classList.remove('stress_1');}else if(this.classList.contains('stress_2')){this.classList.add('stress_0');this.classList.remove('stress_2');};};}else if(touchStartX<touchMoveX){if((touchStartX+90)<touchMoveX){if(this.classList.contains('color_0')){this.classList.add('color_1');this.classList.remove('color_0');}else if(this.classList.contains('color_1')){this.classList.add('color_2');this.classList.remove('color_1');}else if(this.classList.contains('color_2')){this.classList.add('color_3');this.classList.remove('color_2');}else if(this.classList.contains('color_3')){this.classList.add('color_4');this.classList.remove('color_3');}else if(this.classList.contains('color_4')){this.classList.add('color_5');this.classList.remove('color_4');}else if(this.classList.contains('color_5')){this.classList.add('color_0');this.classList.remove('color_5');};};};},false);};function generateMat(){var result='';var active=document.getElementsByClassName('active');for(var i=0;i<active.length;i++){var response=document.createElement('div');response.classList.add('res-card');response.setAttribute('style','margin-bottom:50px');var raw_info=active[i].getElementsByClassName('threadview_response_info')[0].textContent;var raw_id=active[i].getElementsByClassName('res_id')[0].textContent;var index=raw_info.match(/[1-9][0-9]*/)[0];if(''){var name='';}else{var name=raw_info.replace(/\s*[0-9]+\s*/,'');name=name.replace(/\s*[0-9]+\/[0-9]+\/[0-9]+\s[0-9]+\:[0-9]+\:[0-9]+\s*$/,'');};var time=raw_info.match(/[0-9]+\/[0-9]+\/[0-9]+\s[0-9]+\:[0-9]+\:[0-9]+/)[0];var id=raw_id.replace(/\([\s\S]*$/,'');if(highlighted.indexOf(id)>=0){id='<span class="r_highlight" style="color:#ff0000;font-weight:600;background:#ddd;font-style:italic">'+id+'</span>';}else if(id=='ID:'){id='';};var res_head=document.createElement('div');res_head.classList.add('r_h');res_head.setAttribute('style','font-size:14px;margin-bottom:8px;line-height:1.4');if(true){var bold=';font-weight:bold';}else{var bold='';};if(true&&true){res_head.innerHTML=index+': <span class="r_name" style="color:#008000'+bold+'">'+name+'</span> <span class="r_info" style="color:gray">'+time+' '+id+'</span>';}else if(true){res_head.innerHTML=index+': <span class="r_name" style="color:#008000'+bold+'">'+name+'</span> <span class="r_info" style="color:gray">'+time+'</span>';}else if(true){res_head.innerHTML=index+': <span class="r_info" style="color:gray">'+time+' '+id+'</span>';}else{res_head.innerHTML=index+': <span class="r_info" style="color:gray">'+time+'</span>';};var raw_body=active[i].getElementsByClassName('threadview_response_body')[0].cloneNode(true);var res_body=document.createElement('div');res_body.classList.add('r_b');res_body.innerHTML=raw_body.innerHTML;res_body.style.fontSize='18px';res_body.style.fontWeight='bold';for(var n=0;n<6;n++){if(active[i].classList.contains('color_'+n)){if(n==0){break};res_body.classList.add('r_color-'+n);res_body.style.color=colors[n];break;};};for(var n=0;n<3;n++){if(active[i].classList.contains('stress_'+n)){if(n==0){break};res_body.classList.add('r_stress-'+n);for(var prop in stresses[n]){res_body.style[prop]=stresses[n][prop];};break;};};var anchor=res_body.getElementsByClassName('threadview_response_body_link-anchor');while(anchor.length>0){var span=document.createElement('span');span.classList.add('r_anchor');span.setAttribute('style','color:#0000cd');span.textContent=anchor[0].textContent;res_body.replaceChild(span,anchor[0]);};var images=res_body.getElementsByClassName('threadview_response_body_link-image');while(images.length>0){var src=images[0].getAttribute("href");var div=document.createElement('div');var img=document.createElement('img');res_body.removeChild(images[0].nextElementSibling);img.setAttribute('src',src);img.setAttribute('style','max-width:100%');div.classList.add('r_image');div.setAttribute('style','text-align:center');div.appendChild(img);res_body.replaceChild(div,images[0]);};var lazyload=res_body.getElementsByClassName('threadview_response_body_imagelink');while(lazyload.length>0){res_body.removeChild(lazyload[0]);};lazyload=res_body.getElementsByClassName('threadview_response_body_embedlink');while(lazyload.length>0){res_body.removeChild(lazyload[0]);};var links=res_body.getElementsByTagName('a');for(var n=0;n<links.length;n++){if(links[n].hasAttribute('href')){var href=links[n].getAttribute('href');var atag=document.createElement('a');atag.setAttribute('href',href);atag.textContent=href;res_body.replaceChild(atag,links[n]);}else{var atag=document.createElement('a');res_body.replaceChild(atag,links[n]);};};res_body.innerHTML=res_body.innerHTML.replace(/^[\n\s]*/,'');res_body.innerHTML=res_body.innerHTML.replace(/((<br>)*\n*\s*)*$/,'');response.appendChild(res_head);response.appendChild(res_body);if(false){response.removeAttribute('style');var ch=response.querySelectorAll('[style]');for(var n=0;n<ch.length;n++){ch[n].removeAttribute('style');};};result+=response.outerHTML+'\n\n';};if(false){result+='<font size="1">引用元: <a href="'+location.href+'">'+location.href+'</a></font>';};execCopy(result);alert('コピーしました');};function highlight(target){var ids=document.getElementsByClassName('res_id');for(var m=0;m<ids.length;m++){if(ids[m].textContent.replace(/\([\s\S]*$/,'')==target){ids[m].classList.add('highlighted');};};};var colors=['','#0000cd','#ff0000','#ffa500','#800080','#ff4f86'];var stresses=[{},{fontSize:'1.4em'},{border:'1px solid #d2d2d2',background:'#f0f0f0',padding:'.7em',marginLeft:'.5em',marginRight:'.5em'}];var touchStartX;var touchMoveX;var highlighted=[];var rescount=0

ソースコード (ver1.0)

matomelet.js
/*
カスタマイズ用にプレースホルダーによって変数を挿入していますが、それぞれの変数の意味は以下の通りです
  is_handle       ハンドルネームの表示(boolean)
  custom_handle   ハンドルネームの変更(srting)
  is_id           IDの表示(boolean)
  is_link         引用元のリンクを貼る
  own_css         独自CSSモード(boolean)
  handle_bold     ハンドルネームを太文字に(boolean)
  handle_color    ハンドルネームの色(srting)
  color_1         本文の色のレパートリー(srting)
  color_2         
  color_3
  color_4
  color_5
  head_size       ヘッダの文字サイズ(srting)
  body_size       本文の文字サイズ(srting)
  internal_margin ヘッダと本文の間隔(srting)
  external_margin レスとレスの間隔(srting)
*/



//このプログラム用のスタイル
var style = document.createElement('style');
style.textContent = '.active>.threadview_response_body{font-weight:600}.color_1>.threadview_response_body{color:${color_1}}.color_2>.threadview_response_body{color:${color_2}}.color_3>.threadview_response_body{color:${color_3}}.color_4>.threadview_response_body{color:${color_4}}.color_5>.threadview_response_body{color:${color_5}}.stress_1>.threadview_response_body{font-size:1.25em!important}.stress_2>.threadview_response_body{border:1px solid #d2d2d2;background:#f0f0f0;padding:.7em;margin-left:.5em;margin-right:.5em}.highlighted{color:#f30100!important}.actionbtn{position:fixed;left:20px;bottom:32px;font-size:14px;text-align:center;padding-top:10px;padding-bottom:10px;width:100px;border-radius:6px;box-shadow:0 0 10px 0 gray;background:#324356;color:#fff}.rescounter{position:fixed;left:20px;bottom:80px;font-size:18px;text-align:center;padding-top:4px;padding-bottom:4px;width:70px;border-radius:6px;border:1px solid #324356;color:#324356}';
document.body.appendChild(style);


//コピーボタン
var actionbtn = document.createElement('div');
actionbtn.classList.add('actionbtn');
actionbtn.addEventListener("click", generateMat);
actionbtn.innerHTML = 'コピー';
document.body.appendChild(actionbtn);

//レス数表示
var rescounter = document.createElement('div');
rescounter.classList.add('rescounter');
rescounter.innerHTML = '<span id="rescount">0</span><span style="font-size:12px">レス</span>';
document.body.appendChild(rescounter);


//文字列をクリップボードに保存する関数
function execCopy(string) {
  // 空div 生成
  var tmp = document.createElement("div");
  // 選択用のタグ生成
  var pre = document.createElement('pre');
  // 親要素のCSSで user-select: none だとコピーできないので書き換える
  pre.style.webkitUserSelect = 'auto';
  pre.style.userSelect = 'auto';
  tmp.appendChild(pre).textContent = string;
  // 要素を画面外へ
  var s = tmp.style;
  s.position = 'fixed';
  s.right = '200%';
  // body に追加
  document.body.appendChild(tmp);
  // 要素を選択
  document.getSelection().selectAllChildren(tmp);
  // クリップボードにコピー
  var result = document.execCommand("copy");
  // 要素削除
  document.body.removeChild(tmp);
  return result;
};


//通常のレスをタップした時の動作を加える
var res = document.getElementsByClassName('threadview_response');
for (var i = res.length - 1; i >= 0; i--) {

  //レスのliタグのクラスを操作
  res[i].addEventListener("click", function(){
    if(this.classList.contains('active')) {
      this.classList.remove('active', 'color_0', 'color_1', 'color_2', 'color_3', 'color_4', 'color_5', 'stress_0', 'stress_1', 'stress_2');
      rescount -= 1;
      document.getElementById('rescount').innerHTML = rescount;
    }else {
      this.classList.add('active', 'color_0', 'stress_0');
      rescount += 1;
      document.getElementById('rescount').innerHTML = rescount;
    };
  });

  //スワイプによる機能
  res[i].addEventListener("touchstart", function(event) {
    touchStartX = event.touches[0].pageX;
  }, false);
  res[i].addEventListener("touchmove", function(event) {
    touchMoveX = event.changedTouches[0].pageX;
  }, false);
  res[i].addEventListener("touchend", function(event) {

    //左へスワイプしたとき
    if (touchStartX > touchMoveX) {
      //IDを抽出する
      if (touchStartX > (touchMoveX + 200)) {
        var id = this.getElementsByClassName('res_id')[0].textContent;
        id = id.replace(/\([\s\S]*$/, '');
        if (id == 'ID:') {
          alert('抽出できません');
        }else {
          var result = window.confirm(id + 'を抽出しますか?');
          highlight(id);
          if (result) {
            highlight(id);
            highlighted.push(id);
          };
        };

      //強調の種類を選べるように
      }else if (touchStartX > (touchMoveX + 90)) {
        if (this.classList.contains('stress_0')) {
          this.classList.add('stress_1');
          this.classList.remove('stress_0');
        }else if (this.classList.contains('stress_1')) {
          this.classList.add('stress_2');
          this.classList.remove('stress_1');
        }else if (this.classList.contains('stress_2')) {
          this.classList.add('stress_0');
          this.classList.remove('stress_2');
        };
      };

    //右へスワイプしたとき
    }else if (touchStartX < touchMoveX) {
      //文字色を変更できるように
      if ((touchStartX + 90) < touchMoveX) {
        if (this.classList.contains('color_0')) {
          this.classList.add('color_1');
          this.classList.remove('color_0');
        }else if (this.classList.contains('color_1')) {
          this.classList.add('color_2');
          this.classList.remove('color_1');
        }else if (this.classList.contains('color_2')) {
          this.classList.add('color_3');
          this.classList.remove('color_2');
        }else if (this.classList.contains('color_3')) {
          this.classList.add('color_4');
          this.classList.remove('color_3');
        }else if (this.classList.contains('color_4')) {
          this.classList.add('color_5');
          this.classList.remove('color_4');
        }else if (this.classList.contains('color_5')) {
          this.classList.add('color_0');
          this.classList.remove('color_5');
        };
      };
    };
  }, false);

};


//まとめを出力する
function generateMat() {
  var result = '';
  var active = document.getElementsByClassName('active');
  for (var i = 0; i < active.length; i++) {

    var response = document.createElement('div');
    response.classList.add('res-card');
    response.setAttribute('style', 'margin-bottom:${external_margin}px');



    //レスのヘッダを作る

    //元データ
    var raw_info = active[i].getElementsByClassName('threadview_response_info')[0].textContent;
    var raw_id = active[i].getElementsByClassName('res_id')[0].textContent;

    //番号を取得
    var index = raw_info.match(/[1-9][0-9]*/)[0];

    //名前を取得
    if ('${custom_handle}') {
      var name = '${custom_handle}';
    }else {
      var name = raw_info.replace(/\s*[0-9]+\s*/, '');
      name = name.replace(/\s*[0-9]+\/[0-9]+\/[0-9]+\s[0-9]+\:[0-9]+\:[0-9]+\s*$/, '');
    };

    //時刻を取得
    var time = raw_info.match(/[0-9]+\/[0-9]+\/[0-9]+\s[0-9]+\:[0-9]+\:[0-9]+/)[0];

    //IDを取得
    var id = raw_id.replace(/\([\s\S]*$/, '');
    if (highlighted.indexOf(id) >= 0) {
      id = '<span class="r_highlight" style="color:#ff0000;font-weight:600;background:#ddd;font-style:italic">' + id + '</span>';
    }else if (id == 'ID:') {
      id = '';
    };

    //ヘッダ要素を作成
    var res_head = document.createElement('div');
    res_head.classList.add('r_h');
    res_head.setAttribute('style', 'font-size:${head_size}px;margin-bottom:${internal_margin}px;line-height:1.4');

    //ヘッダの中身を作成
    if (${handle_bold}) {
      var bold = ';font-weight:bold';
    }else {
      var bold = '';
    };
    if (${is_handle} && ${is_id}) {
      res_head.innerHTML = index +': <span class="r_name" style="color:${handle_color}' + bold + '">' + name + '</span> <span class="r_info" style="color:gray">' + time + ' ' + id + '</span>';
    }else if (${is_handle}) {
      res_head.innerHTML = index +': <span class="r_name" style="color:${handle_color}' + bold + '">' + name + '</span> <span class="r_info" style="color:gray">' + time + '</span>';
    }else if (${is_id}) {
      res_head.innerHTML = index +': <span class="r_info" style="color:gray">' + time + ' ' + id + '</span>';
    }else {
      res_head.innerHTML = index +': <span class="r_info" style="color:gray">' + time + '</span>';
    };




    //レスの本文を作る

    //元データ
    var raw_body = active[i].getElementsByClassName('threadview_response_body')[0].cloneNode(true);

    //本文の要素を作成
    var res_body = document.createElement('div');
    res_body.classList.add('r_b');
    res_body.innerHTML = raw_body.innerHTML;

    //スタイル
    res_body.style.fontSize = '${body_size}px';
    res_body.style.fontWeight = 'bold';
    for (var n = 0; n < 6; n++) {
      if (active[i].classList.contains('color_' + n)) {
        if (n == 0) {break};
        //文字色
        res_body.classList.add('r_color-' + n);
        res_body.style.color = colors[n];
        break;
      };
    };
    for (var n = 0; n < 3; n++) {
      if (active[i].classList.contains('stress_' + n)) {
        if (n == 0) {break};
        //強調
        res_body.classList.add('r_stress-' + n);
        for (var prop in stresses[n]) {
          res_body.style[prop] = stresses[n][prop];
        };
        break;
      };
    };

    //アンカーを整形
    var anchor = res_body.getElementsByClassName('threadview_response_body_link-anchor');
    while (anchor.length > 0) {
      var span = document.createElement('span');
      span.classList.add('r_anchor');
      span.setAttribute('style', 'color:#0000cd');
      span.textContent = anchor[0].textContent;
      res_body.replaceChild(span, anchor[0]);
    };

    //画像のリンクをimgタグに置き換え
    var images = res_body.getElementsByClassName('threadview_response_body_link-image');
    while (images.length > 0) {
      var src = images[0].getAttribute("href");
      var div = document.createElement('div');
      var img = document.createElement('img');
      res_body.removeChild(images[0].nextElementSibling);
      img.setAttribute('src', src);
      img.setAttribute('style', 'max-width:100%');
      div.classList.add('r_image');
      div.setAttribute('style', 'text-align:center');
      div.appendChild(img);
      res_body.replaceChild(div, images[0]);
    };

    //lazy-loadの画像を取り除く
    var lazyload = res_body.getElementsByClassName('threadview_response_body_imagelink');
    while (lazyload.length > 0) {
      res_body.removeChild(lazyload[0]);
    };
    lazyload = res_body.getElementsByClassName('threadview_response_body_embedlink');
    while (lazyload.length > 0) {
      res_body.removeChild(lazyload[0]);
    };

    //その他のリンクをシンプルに
    var links = res_body.getElementsByTagName('a');
    for (var n = 0; n < links.length; n++) {
      if (links[n].hasAttribute('href')) {
        var href = links[n].getAttribute('href');
        var atag = document.createElement('a');
        atag.setAttribute('href', href);
        atag.textContent = href;
        res_body.replaceChild(atag, links[n]);
      }else {
        var atag = document.createElement('a');
        res_body.replaceChild(atag, links[n]);
      };
    };

    //前後すっきり
    res_body.innerHTML = res_body.innerHTML.replace(/^[\n\s]*/, '');
    res_body.innerHTML = res_body.innerHTML.replace(/((<br>)*\n*\s*)*$/, '');

    //レスをまとめ上げ
    response.appendChild(res_head);
    response.appendChild(res_body);

    //独自CSSモード
    if (${own_css}) {
      response.removeAttribute('style');
      var ch = response.querySelectorAll('[style]');
      for (var n = 0; n < ch.length; n++) {
        ch[n].removeAttribute('style');
      };
    };

    result += response.outerHTML + '\n\n';
  };

  //引用元リンクを貼る場合
  if (${is_link}) {
    result += '<font size="1">引用元: <a href="' + location.href + '">' + location.href + '</a></font>';
  };

  execCopy(result);
  alert('コピーしました');
};


//IDを抽出して目立たせるための関数
function highlight(target) {
  var ids = document.getElementsByClassName('res_id');
  for (var m = 0; m < ids.length; m++) {
    if (ids[m].textContent.replace(/\([\s\S]*$/, '') == target) {
      ids[m].classList.add('highlighted');
    };
  };
};


//文字色のレパートリー
var colors = ['', '${color_1}', '${color_2}', '${color_3}', '${color_4}', '${color_5}'];

//強調のスタイル
var stresses = [{}, {fontSize:'1.4em'}, {border:'1px solid #d2d2d2', background:'#f0f0f0', padding:'.7em', marginLeft:'.5em', marginRight:'.5em'}];

//スワイプ用の変数
var touchStartX;
var touchMoveX;

//抽出したIDのリスト
var highlighted = [];

//選択したレスの数
var rescount = 0```
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptを触ってみた Part.1 基本的な処理コード系

はじめに

ちゃんとがっつり開発のエンジニア歴2ヶ月目が学んだことをまとめる備忘録。
訳あって、1回データサイエンスから仕事では少し離れて、開発の見聞を広めていくことにしました!
また心機一転、頑張ります!

↓ちなみに、前回までの記事はこちら。

懐かしい記事がたくさんだ!w

使用環境

今回は、Webアプリケーションの開発になれる目的も含め、以下の環境で触ってみた。

  • テキストエディター→Sublime Text 3
    • 実際にコードを書く場所として使用。
    • 文法が正しく記入されていれば、自動でカラーリングをしてくれるため、何を書いたかの確認がよりしやすくなる。
  • Webブラウザ→Google Chrome
    • Webアプリケーションの動作確認を行うために使用。
    • .htmlファイルをこのアプリケーションで開き、以下の項目を確認
      • ページビュー→実際に画面にどのように表示されているか
      • Console→コードが正しく書けているか(求めている処理を正しく行なっているか)、行なっていない場合はどこでエラーなどが発生しているかを特定するために、段階的に処理結果をはじめとする状況を確認

変数/変数定義の仕方

変数定義

  • JavaScriptは基本的に変数の宣言はその変数に格納されるデータの型に関わらず、この書き方で宣言する
var 変数名 ; 
  • 変数への数値の代入はJavaと同じで変数を左辺において右辺に代入する値をおき、『=』演算子でつなぐ。
変数名 = 変数に代入する値 ; 
  • 数値演算子についても、Javaと同じものを使用すればよい(詳細は演算子のところで記載)。

変数について

  • 文字列は、シングルクオーテーションで文字列を囲んで右辺におく(Javaではダブルクオーテーション
    • 別にダブルクオーテーションで囲んでも問題は発生しない
    • が、.htmlファイルでは、文字列値を代入する際にダブルクオーテーションを使用
      • .htmlファイル内に<すくりぷと> </すくりぷと> で囲んでJavaScriptを使用してコーディングを行う際に、識別がしづらくなるため、開発のチームによってシングルクオーテーションを推奨していたりする
      • コーディングルールについては、開発のチームによってそれぞれ統一して決められている(統一することで、エラーの発生箇所を特定したり、バグ発生のリスクを下げたり、そもそもとしてコードをチームの全員が読みやすいようにすることが目的)ため、そのルールに従う
  • 文字列の結合を行いたい場合は、Java同様、『+』を使用して結合することができる

    • 文字列と数値の結合も可能。ただし、求めている結果と違うものが返ってくる可能性もあるため注意。

      var a = 5 + 6 ; 
      console.log(a) ; 
      
      var b = '5' + '6' ; 
      console.log(b) ; 
      
      var c = '5' + 6 ; 
      console.log(c) ; 
      
      • a は数値同士を足すということになるため、 5 + 6 = 11 として処理を行うため、出力結果は 11 になる。
      • b は文字列同士を結合する処理を行うため、出力結果は 56 になる。
      • c は文字列『5』に数値の 6 を結合するという処理を行うため、出力結果は 56 になる。

スコープ

Java同様、JavaScriptにもスコープという概念は存在する。

スコープとは

  • スコープ・・・変数の有効範囲。どこから参照することができて、どこから参照することができないのか。

グローバル変数とローカル変数

Java同様、スコープによって変数は分類することができる。

  • グローバル変数・・・プログラム内全体から呼び出し、使用することができる変数(関数の外で宣言しているため)。
  • ローカル変数・・・関数の中で宣言、その関数の中でしか使うことができない変数。

変数使用時のルール(適宜更新)

  • グローバル変数は、なるべく使わないようにする
    • どこからでも参照できてしまう分、使い勝手が悪くなってしまう
    • グローバル変数を使用するのではなく、即時関数で代用できないかまず検討するとよい(即時関数については関数で詳細を記載)

関数

  • 関数・・・複数の処理をまとめて再利用できる形にするための概念(Javaでいうメソッドに近い)。
  • 基本的な書き方は以下の通り。
  function 作成したい関数名 (引数) {
      実行させたい命令 ; 
      return 戻り値 ;  ←コンソールなどで段階的にそこまでの処理が正しく行われているか確認したいときは必ず書くようにする
  }
  • 関数には名前を設定する必要がない→無名関数という

即時関数

  • 即時関数・・・関数名を設定することなく、即時的にそこでだけ使用するために書く関数。
  • 書き方は以下の通り。

    (function 作成したい関数名 (引数) { 
        実行させたい命令 ; 
        return 戻り値 ;  ←コンソールなどで段階的にそこまでの処理が正しく行われているか確認したいときは必ず書くようにする
        })();
    
    • 関数全体を ()で囲んで、スコープに閉じるようにする
  • 即時関数を使用するメリット

    • 関数名の重複を考えることなく、関数を書くことができる =エラー発生などの可能性を減らすことができる

制御文

制御文とは

  • 制御文・・・『〇〇(条件)ならば処理Aを行い、そうでなければ処理Bを行う』などの条件に合わせた処理設定を行う文(=Javaなどの条件分岐や繰り返し処理と同じ)。
  • 代表的な制御文の例は以下のふたつ。
    • if文
    • for文

制御文の書き方

基本的な書き方は、Javaとほぼ変わらない

if文

if (実行するための条件式) { 
    実行したい命令 ; 
    } else if (実行するための条件式) { 
    実行したい命令 ; 
    } else { 
    実行したい命令 ; 
} 
  • 『else if』は条件を複数回重ねて設定したい場合にのみ記載する。
  • 条件式がtrueのときに命令が実行され、falseのときに次の条件式がtrueか判定していき、最終的に条件を満たすところでそこで定義した命令が実行される。

for文

for (初期化(実行開始時の状態) ; 実行するための条件式 ; 完了したときの更新命令) { 
    実行したい命令 ; 
}
  • 実行するための条件式がtrueの間、繰り返しで実行される。
  • 『完了したときの更新命令』は、命令した実行が完了した時に、条件式で使用している変数を更新する命令を記載する。

演算子

制御文では特に、演算子を使用することが多い他め、各演算子についてここでまとめておく。

四則演算子

基本はJavaと同じ。

  • +』:たす(加)
  • -』:ひく(減)
  • 『*』:かける(乗)
  • /』:わる(除)
  • %』:割り算のあまり(余)

比較演算子

等式をのぞき、基本はJavaと同じ

  • >=』:左辺は右辺以上
  • >』:左辺は右辺よりも大きい
  • <=』:左辺は右辺以下
  • <』:左辺は右辺よりも小さい

等式は、JavaScriptにおいては基本は以下のふたつを使用する。

  • ===』:左辺と右辺は等しい(データ型が同じ場合に使用)
  • !==』:左辺と右辺は等しくない

Javaで使用する以下の演算子は、JavaScriptの場合は少し異なる使い方をするため、注意。

  • ==
    • Javaでは、『左辺と右辺は等しい』という意味で使用。
    • JavaScriptでも、基本的な意味は『左辺と右辺は等しい』。ただしJavaScriptでは、この記号を使用した場合は左辺と右辺それぞれのデータの型に関係なく同じ値か確認する。

JavaScriptにおける論理演算子

基本はJavaと同じ。

  • &&』:左辺かつ右辺
  • ||』:左辺または右辺

配列とオブジェクト

配列

JavaScriptでは、グループ化した値を格納するために配列を使用できる。

  • 配列の書き方

    配列名 = ['要素', '要素', '要素', ・・・]
    
  • 配列内の各要素の数え方は、順番に 0番目、1番目、2番目・・・ となる。

    配列の操作方法

    配列を操作したいときに使えそうな命令をまとめておく。

// 配列のn番目の要素を出力
console.log(配列名[n]) ; 

// 配列の要素をすべて出力
console.log(配列名) ; 

// 配列の要素を追加したい
配列名.push('追加する要素') ; 

// 配列の要素を削除したい
配列名.splice(削除したい要素の始まる要素番号, 消す個数) ; 

// 配列を構成する要素の個数を確認/出力したい
console.log(配列名.length) ; 

オブジェクト

※JavaとJavaScriptのオブジェクト指向の違いについては、改めてまとめる。

オブジェクト(Object)の原義

JavaScriptにおけるオブジェクト

  • オブジェクト・・・名前と値をセットでグループ化して値を扱うための概念。
  • 基本的な書き方

    var 変数名(オブジェクト名) = { 
        キー(名前) : バリュー(値), 
        キー(名前) : バリュー(値), 
        ・・・
    }
    
    • キーとバリューがそれぞれ対応している(対応しているもの同士を : でくっつけて書く)
  • オブジェクト内の指定のバリュー(値)の呼び出し方
    →以下のふたつのかきかたのいずれでもOK

    変数名['キー(名前)'] ; 
    変数名.キー(名前) ; 
    
  • オブジェクト内の指定のバリュー(値)を上書きする
    →基本は呼び出して代入するだけ。

    変数名['キー(名前)'] = 代入する値 ; 
    変数名.キー(名前) = 代入する値 ; 
    
  • オブジェクトの情報を出力する

    console.log(オブジェクト) ; 
    
  • メソッド・・・オブジェクトの中に書いた関数

JavaScriptを触ってみて

結論

Javaと比較すると、使いやすかった!

理由

  • Javaと比較して、環境構築がしやすかった(おそらく、学習時に指定された環境が大きく違ったから?)
    →Javaでは、ローカルサーバホストの設定やサーバーでの実行時の設定、DBとの連携のために別途Visual Studio 2017のパッケージのインストールなどの対応が多く、大変だった。

    • ちなみにJavaで使用した環境はこんな感じ
      • エディター→eclipse 2019-06
      • サーバーホスト→Tomcat 9.0
      • 使用DB→MySQL
  • 変数の型の宣言が必要ないため、変数定義が楽だった

→Javaではデータ型も指定する必要があった
→データ型を設定する手間が省ける分、バグも起きやすいかも・・・?(データ型がわからなくてもとりあえず定義してある程度のところまでコードを書き進めることができるから)

あとがき

開発って難しいな、と思いつつ、とっつきやすい言語もあるんだなって認識しました。
少しずつこの難しさと仲良くなれるようにマイペースにまた勉強していこうと思います^^

ではまた!

P.S.)今回はSimple is the BESTなあとがきをめざしてみた!w

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

jQueryを使った新規作成モーダル

index.html
<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>Progate</title>
  <link rel="stylesheet" type="text/css" href="stylesheet.css">
  <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.4.0/css/font-awesome.min.css">
  <script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
</head>
<body>


  <!-- モーダル部分 -->
  <div id="signup-modal" class="signup-modal-wrapper" >
    <div class="modal">
      <div>
        <i id="close-modal" class="fa fa-2x fa-times" ></i>
      </div>
      <div id="signup-form">
        <h2>Emailで新規登録</h2>
        <form action="#">
          <input class="form-control" type="text" placeholder="メールアドレス">
          <input class="form-control" type="password" placeholder="パスワード">
          <div id="submit-btn">新規登録</div>
        </form>
      </div>
    </div>
  </div>
  <!-- モーダルここまで -->


      <!-- 新規登録ボタン -->
      <div class="btn signup signup-show">新規登録はこちら</div>





      <!-- 新規登録ボタン -->
      <span class="btn message signup-show">さっそく開発する</span>


  <script src="script.js"></script>
</body>
</html>
style.css
/*モーダル*/
.signup-modal-wrapper {
  position: fixed; /*画面転換*/
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
  background-color: rgba(0, 0, 0, 0.6);
  z-index: 100;
  display:none;
}

.modal {
  position: absolute;/*画面を中央に寄せる*/
  top: 20%;
  left: 34%;
  background-color: #e6ecf0;
  padding: 20px 0 40px;
  border-radius: 10px;
  width: 450px;
  height: auto;
  text-align: center;

}
main.js
$(function(){
$('#close-modal').click(function(){
    $('#signup-modal').fadeOut();
  });

  $('.signup-show').click(function(){
    $('#signup-modal').fadeIn();
  });


  });

カスタマイズ用

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

APIの3つの作り方

APIの3つの作り方について解説する。

APIとは?

APIとは、JavaScriptやjQueryを用いたAjax通信を行う際、必要なデータのみをJSON形式で返すサーバの仕組みのことをいう(JSON API)。

APIの3つの作り方

1.既存のコントローラを共用してAPIとして使えるようにする方法

respond_to メソッドを使い、コントローラーでHTMLとJSONを返すための処理を条件分岐させる。

respond_to do |format|
  format.html 
  format.json 
end

2.新たに、API専用のコントローラーを作成する方法

コントローラ以下のディレクトリにAPIのファイルを作成する。

controllers/api/コントローラ名_controller.rb

もしも、APIのコントーローラと同じ名前のコントローラがすでに存在している場合は、名前空間(namespace,::)をつけることにより、それらを区別することができる。

class Api::コントローラ名Controller < ApplicationController

end

ちなみに、この場合、ルーティングの設定方法が通常と異なる。

namespace :api do
 resources :messages, only: :index, defaults: { format: 'json' }
end

3.APIを作成するためのgemを利用する方法

rails-apiなどのgemをインストールするとできるようである(gemを利用したAPI作成ををまだ行なったことがない。)

詳しくは、以下の記事を参照ください。
https://www.sejuku.net/blog/10906#RailsAPI-2

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

【ヘルパー関数】ストアとコンポーネント間の通信

参考文献
Vue.js入門

Vueインスタンスにstoreを渡してあるものとします。

ヘルパー関数

ヘルパー関数とは、コンポーネントからストアにアクセスする為の関数で下記の4つが用意されています。

・mapState
・mapMutations
・mapGetters
・mapActions

これを使い、コンポーネントの算出プロパティやメソッドに結び付けられます。

比較のためにthis.storeを使った参照の例を書きます。

this.storeを使って参照

store.js
import Vue from 'vue'
import Vuex from 'vuex'

Vue.use(Vuex)

export default new Vuex.Store({
  state: {
    count: 0
  },
  mutations: {
    increment: function(state) {
      state.count++
    }
  }
})
App.vue
<template>
  <div id="app">
    <p>{{ count }}</p>
    <button v-on:click="increment(1)">add1</button>
  </div>
</template>

<script>
export default {
  computed: {
    count: function() {
      //store内のcountのstateを渡す
      return this.$store.state.count
    }
  },
  methods: {
    increment: function(value) {
      //ストア内の'increment'mutationをcommit
      this.$store.commit('increment', value)
    }
  }
}
</script>

ヘルパー関数を使って参照

先ほどのApp.vueをmapStateとmapMutationsを使い記述した例です。

App.vue
<template>
  <div id="app">
    <p>{{ count }}</p>
    <button v-on:click="increment">add1</button>
  </div>
</template>

<script>
// 'mapState'と'mapMutations'を使えるようにする
import { mapState, mapMutations } from 'vuex'

export default {
  // '$sotre.state.count'をthis.countと結びつける
  computed: mapState([
    'count'
  ]),
  // 'this.$store.commit('increment', value)'を、this.increment(value)で呼び出せる
  methods: mapMutations([   
    'increment'
  ]),
}
</script>

また、 下記のようにヘルパー関数の引数にオブジェクトを渡すと、コンポーネント上でthis.プロパティ名で参照可能になります。

App.js
import { mapState } from 'vuex'

export default {
  // '$sotre.state.count'をthis.valueと結びつける
  computed: mapState({
    value: 'count'
  })
}

ヘルパー関数は簡単にコンポーネントからストアを使うことができますが、これでは通常の算出プロパティやメソッドを書く場所がなくなります。
そのような場合、ヘルパー関数の戻り値と通常のこれでは通常の算出プロパティ・メソッドの定義を、オブジェクトスプレッド演算子やobject.assign関数で結合し、両方一度に使うことができます。

下記はmapStateでstateのcountを結びながら通常の算出プロパティdoubleを定義しています。

App.vue
import { mapState } from 'vuex'

export default {
  // 通常の算出プロパティを定義
  computed: {
    double: function() {
      return this.conut * 2
    },
    // 通常の算出プロパティとmapStateの戻り値を結合
    ...mapState([
      'count'
    ])
  }
}

名前空間つきのモジュールは、ヘルパー関数の第一引数に名前空間の文字列を渡すことにより記述できます。
下記は、counter名前空間にあるcountをthis.countに結ぶ例です。

App.vue
import { mapState } from 'vuex'

export default {
  // '$store.state.counter.count'を'this.count'に結ぶ
  computed: mapState('counter',[
    'count'
  ])
}

以上です。
また学習が進み次第、更新、掲載していきます。
ここまでで補足や訂正などありましたらご教授いただけると嬉しいです。
最後まで読んでいただきありがとうございます。

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

Js

JavaScript スクリプト言語

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

JointJSのフローチャートから各要素の属性を抽出する

はじめに

JointJSを用いるとJavaScript側でフローチャート等の図を簡単に描くことができます。

JointJSの基本的な使用方法は以下が参考になります。
また、本記事で紹介する内容も以下記事のサンプルをほぼそのまま利用させていただいています。
参考記事:インタラクティブに図が書けるJointJsを使ってみた

これを使って、例えば裏で動いているデータ分析やデバイス連携の処理をフローチャートで簡単に設定できるようにする、
といったWebサービスを開発することができます。(例えばNode-REDのような)

そのためにはインタラクティブなフローチャートを描画するだけでなく、
フローチャートの各要素(ブロックやリンク)の属性を抽出し、
POSTしたりajaxしたりでデータをサーバに送信する、という処理が必要になってきます。

そこで本記事ではJointJSのAPIの中から、要素の属性抽出に関連するAPIを紹介していきます。

動作確認環境

  • JointJS: Development version 3.0.2
  • jQuery: 3.4.1
  • Lodash: 4.17.11
  • Backbone: 1.4.0

すべてこちらからダウンロードできます。

フローチャートのサンプル

以下の3つのブロック(element)と2本のリンク(link)をもつフローチャートを例に説明していきます。

flowchart.png

各ブロックにはdivNameに"mycell1"、"mycell2"、"mycell3"が設定されています。

以降、JavaScriptにて
graph = new joint.dia.Graph;
と定義されているものとしてgraphに対してAPIを使用していきます。

APIの紹介

多数あるAPIの中でもdia.Graph.prototype.getBBoxから始まるdia.Graph.prototype.get~というAPI群がありますが数が多いので一部だけ紹介します。

dia.Graph.prototype.getCells

graph内のすべてのelementとlinkを配列で取得します。

var cells = graph.getCells();
cells.forEach(function(cell){
    console.log(JSON.stringify(cell));
});

結果

{"type":"org.Arrow","source":{"selector":".card","id":"0148eff1-ea1b-40b2-afc9-2bc031e586ce"},"target":{"selector":".card","id":"2e8732f3-8b98-4793-a6ea-406dd60ddba2"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"276c7e3c-ca4c-4e4c-8e32-0c20b88b3f98","attrs":{}}

{"type":"org.Arrow","source":{"selector":".card","id":"2e8732f3-8b98-4793-a6ea-406dd60ddba2"},"target":{"selector":".card","id":"1d8a053e-98ac-49a9-bab5-758d66759881"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"89e965e8-e1c2-4200-ba27-ed3d73fa82aa","attrs":{}}

{"type":"html.Element","position":{"x":80,"y":80},"size":{"width":170,"height":80},"angle":0,"divName":"mycell1","isCompany":true,"id":"0148eff1-ea1b-40b2-afc9-2bc031e586ce","z":1,"attrs":{}}

{"type":"html.Element","position":{"x":490,"y":90},"size":{"width":170,"height":80},"angle":0,"divName":"mycell2","id":"2e8732f3-8b98-4793-a6ea-406dd60ddba2","z":2,"attrs":{}}

{"type":"html.Element","position":{"x":558,"y":344},"size":{"width":170,"height":80},"angle":0,"divName":"mycell3","id":"1d8a053e-98ac-49a9-bab5-758d66759881","z":3,"attrs":{}}
  • "type"が"org.Arrow"になっているものがブロックを接続する線です。"source"から"target"に接続されており、それぞれの"id"でどのブロックであるか特定できます。
  • "type"がhtml.Element"になっているものがブロックです。描画されている座標("position")やサイズ("size")が取得できます。設定した"diveName"も反映されていることがわかります。こちらの"id"と"org.Arrow"の"source"や"target"の"id"を比較して操作することもできそうです。

dia.Graph.prototype.getElements

getCellsではelementとlinkの全てを取得しましたが、getElementsでは全てのelementのみ取得します。

dia.Graph.prototype.getLinks

同様にgetLinksでは全てのlinkのみ取得します。

dia.Graph.prototype.getConnectedLinks

あるelementに接続されている全てのlinkを取得します。

var elements = graph.getElements();
console.log(JSON.stringify(elements[0]));
var links = graph.getConnectedLinks(elements[0]);
links.forEach(function(link){
    console.log(JSON.stringify(link));
});

結果

{"type":"html.Element","position":{"x":80,"y":80},"size":{"width":170,"height":80},"angle":0,"divName":"mycell1","isCompany":true,"id":"1d0d270e-642a-403e-808e-0d63d60dd589","z":1,"attrs":{}}

{"type":"org.Arrow","source":{"selector":".card","id":"1d0d270e-642a-403e-808e-0d63d60dd589"},"target":{"selector":".card","id":"f904240e-92fb-4af0-aef1-97f0422a047a"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"f3bd7d00-4697-4f29-acc5-331e857e69ba","attrs":{}}
  • getElementsで取得したelementから最初のelementを取得してgetConnectedLinksに入れています。
  • element[0]に接続されているlinkのみ取得されていることが"id"からわかります。
  • オプションで「外に出ていくlink」や「内に入ってくるlink」を指定することもできます。

dia.Graph.prototype.toJSON

getCellsとほぼ同じ情報が得られるようです。
toJSONが返却するのはJSON文字列ではない点にお気を付けください。

console.log(JSON.stringify(graph.toJSON())); 

結果

{"cells":[{"type":"org.Arrow","source":{"selector":".card","id":"26578cbf-aa01-40ab-8752-05c874a64fa9"},"target":{"selector":".card","id":"de7626c7-6cab-48e8-a5c2-8298e6a11483"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"cf9a1a1a-234f-43ec-83d6-019e8dc80010","attrs":{}},{"type":"org.Arrow","source":{"selector":".card","id":"de7626c7-6cab-48e8-a5c2-8298e6a11483"},"target":{"selector":".card","id":"b0a28724-1a33-4deb-8877-02c1378f7465"},"z":-1,"vertices":[{"x":100,"y":120},{"x":150,"y":60}],"id":"f5d76d9d-a256-483d-abc0-f1ba228e90a9","attrs":{}},{"type":"html.Element","position":{"x":80,"y":80},"size":{"width":170,"height":80},"angle":0,"divName":"mycell1","isCompany":true,"id":"26578cbf-aa01-40ab-8752-05c874a64fa9","z":1,"attrs":{}},{"type":"html.Element","position":{"x":190,"y":277},"size":{"width":170,"height":80},"angle":0,"divName":"mycell2","id":"de7626c7-6cab-48e8-a5c2-8298e6a11483","z":2,"attrs":{}},{"type":"html.Element","position":{"x":297,"y":483},"size":{"width":170,"height":80},"angle":0,"divName":"mycell3","id":"b0a28724-1a33-4deb-8877-02c1378f7465","z":3,"attrs":{}}]}

おわりに

JointJSはあまり日本語の情報がないため、本家のAPI一覧を見ていくのが基本になります。
一方でAPIの数が多く探しにくいこともあるため、この記事が参考になれば幸いです。

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

JSでifやswitchを使いたくなくて変数名も考えたくない時の方法

分岐が多くても見た目カオスにならない三項演算子の代替的な何か。
ふと思いついたのでメモっておきます、結構便利。

ズバリ

JavaScript
const str = "aaa";
const num = 0;
const arg = "0123";

// オブジェクト
{
    "aaa": v => `${v}hoge`,
    "bbb": v => `${v}huga`
}[str](arg);
// => "0123hoge"

// 配列
[
    v => `${v}00`,
    v => `${v}01`
][num](arg);
// => "012300"

SpecialThanks!

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

Qiitaのいかがでしたかを警告する

発端

いかがでしたか Qiita - Twitter検索

ほう、そんなものがあるのかと調べました。

しょうもない“いかがでしたかブログ”を警告するGoogle Chrome拡張【レビュー】 - 窓の杜
今年の三月にまたとり上げられたようですが、2017年にすでにあった拡張ですね。

ということでぱぱっとQiita用に書くだけ書きます。1

コード

// ==UserScript==
// @name         Qiita ikaga alert
// @namespace    http://tampermonkey.net/
// @version      0.1
// @description  本文のいかがでしたかを検知する
// @author       khsk
// @match        https://qiita.com/*/items/*
// @grant        none
// ==/UserScript==

(function() {
  'use strict';

  const item = document.querySelector('section.it-MdContent')
  const match = item.textContent.match(/(いかが|如何)でした(でしょう)?か/)
  if (!match) {
    return;
  }

  // 古い記事警告を参考にしている
  const alert = document.createElement('div')
  // 色被るので背景を手動で赤にしてもよいだろう
  alert.className = 'it-Alert mb-5 p-2'
  alert.textContent = 'この記事は「' + match[0] + '」が含まれています。'
  const icon = document.createElement('span')
  icon.className = 'fa fa-warning mr-1'
  alert.insertBefore(icon,  alert.firstChild)

  item.insertBefore(alert, item.firstChild)
})();

スクリーンショット

自分の記事には遺憾にも「いかがでしたか」がなかったようなので(「いかがかな」はありました)

失礼ながら他人様の記事をお借りします。

「いかがでしたか」タグについて - Qiita2
ikaga.JPG

タグは見てないのでご了承ください。


なお、先日リリースされたFirefox 68からUser Scripts APIがデフォルトで使えるといった話も見かけたので、
Tampermonkeyなどのアドオン不要のブラウザのみでユーザースクリプトを活用できるかもしれません。便利ですね。


  1. それ系は多分タイトルで避けていると思うので使わないかな 

  2. この記事で提唱されているタグ説明と私の記事へのいかがでしたかタグ付けの意味は違っていると思いますが、倣うより自分感覚優先で… 

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

【web開発】動的に用意した要素にイベントを追加

躓き

<a class="anker" href="#">anker0</a>

こちらはHTMLで処理前に用意した要素・

let string = array("anker1","anker2","anker3");
for (let i=0; i<3; i++){
  $('.class').append('<li><a class="anker" href="#">'+string[i]+'</a></li>');
}

こちらはJavaScriptで動的に用意した要素。

$('.class').on("click", function(e){ /* 処理 */});

上で用意した要素全てにclickイベントを追加したつもりが、JavaScriptで動的に用意した要素に対してはclickイベントは機能しなかった。

解決策

$(document).on("click", ".anker", function(e){ /* 処理 */ });

こうすることで、動的に用意した要素にもclickイベントを追加することができた。

onメソッドについて

基本形

.on( events [, selector ] [, data ], handler )
引数 データ型 説明
events String 半角スペース区切りでイベントタイプ("click"など)とoptional namespaces("keydown.myPlugin"など)を単数もしくは複数設定可能
selector String 選択された要素の子孫要素に対してイベントデリゲートできる
data Anything イベントハンドラに渡したいデータをハッシュで指定できる。
handler Function eventsが起きたタイミングで実行される機能

events

基本的な使い方は省略するが、連想配列を使用しイベント毎に機能を付与できるのは便利。

$('.foo').on({
  'mouseenter': function(){ /* 処理 */ },
  'mouseleave': function(){ /* 処理 */ }
});

selector

こちらは子要素で発生するイベントが親要素に次々に伝搬するイベントバブリングというJavaScriptの特徴を利用した設定方法であるイベントデリゲートを利用してイベントを付与する方法。動的に用意された要素に対しても付与できること、親要素に設定するためメモリを節約できることのメリットがある。

$("ul").on("click","li",function(){ /* 処理 */ });

data

event.dataに引数のdataを格納することができる。

for (let i = 0; i < 3; i++) {
  $('button').eq(i).on('click', { value: i }, function(e){
    alert(e.data.value + '番目のボタンをクリックしました');
  });
}

参考URL

jQuery APIドキュメント(公式)
【jQuery】onメソッドの使用方法

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

Selenium for Python Elementにフォーカスを当てる

はじめに

Seleniumで要素をクリックするとフォーカスが移るものだと思っていました。
挙動を監視していると、どうもそうではないように思える事が多々あります。
そこで確実にフォーカスを移す方法を考えてみました。

実装

JavaScriptとPythonを使って実現します。

JavaScript

実際にファーカスを移す処理はJavaScriptで行います。
ここでは、以下のような記述にしてみました。

javascript
arguments[0].focus()

Python

上記のJavaScriptをSeleniumから呼び出します。

python
def focusToElement(driver, by, value):
  JavaScriptFocusToElement = "arguments[0].focus()"
  element = driver.find_element(by, value)
  driver.execute_script(JavaScriptFocusToElement, element)

実際に使う場合は、以下のようになります。

python
focusToElement(driver, By.XPATH, "//input[@name='cat']")

最後に

フォーカスを移す処理を入れてみたら既存のプログラムの安定性が上がりました。
また、スクロールの挙動がおかしかったのが治りました。
明示的にフォーカスを当てるのも考えて方が良いかもしれません。

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

vue.jsのディレクティブをまとめてみた。part2

v-for

jsのfor文

index.html
<div id="app">
      <ul>
          <li v-for="pref in prefs"><!--変数名 in 配列名-->
           {{pref.name }}   
          </li>
      </ul>     
    </div>

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!--vueのCDNを読み込む-->
<script>
   let app=new Vue({
        el:"#app",
        data:{
            prefs:[
                {name:'北海道'},
                {name:'青森県'},
                {name:'岩手県'},
                {name:'宮城県'},
                {name:'秋田県'},
                {name:'山形県'},
                {name:'福島県'},
            ]
        }
   });
</script>

上記にボタンを押すと、シャッフルできるようにしたい。
lodashというライブラリを使う。

index.html
<div id="app">
    <ul>
        <li v-for="pref in prefs"><!--変数名 in 配列名-->
            {{pref.name }}   
        </li>
    </ul>   
    <button v-on:click="shuffle">シャッフル</button>  

    </div>

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<!--vueのCDNを読み込む-->
<script src="https://cdn.jsdelivr.net/npm/lodash@4.17.14/lodash.min.js"></script>
<script>
    let app=new Vue({
        el:"#app",
        data:{
            prefs:[
                {name:'北海道'},
                {name:'青森県'},
                {name:'岩手県'},
                {name:'宮城県'},
                {name:'秋田県'},
                {name:'山形県'},
                {name:'福島県'},
            ]
        },
        methods:{
            shuffle:function(){
               this.prefs=_.shuffle(this.prefs); //_.はlodashを使うためのルール
            }

        }
    });  //ボタンを押すとprefがシャッフルされるという処理になる。
</script>

v-on:click

addEventLintener('click',()=>{});に意味が似ている?クリックした後処理をする。

index.html
<div id="app"> 

 <p>{{now}}</p>
    <button v-on:click="time">現在時刻を表示する</button>
    </div>

<script src="https://cdn.jsdelivr.net/npm/vue"></script>
<script>
   let app=new Vue({
       el:"#app",
       data:{
           now:"00:00:00"
       },
       methods:{
           time:function(e){
               var date=new Date();

               this.now=date.getHours()+":"
               +date.getMinutes()+":"+
               date.getSeconds();  //現在時刻を表示
           }
       }
   });
</script>

part1
vue.jsのディレクティブをまとめてみました。part1

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