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

【JavaScript】 海外Teck系YouTuberを真似てみた!partⅡ ToDoList App | No Frameworks

1,はじめに

この記事は、海外Teck系YouTuberの動画を参考に、同じプロジェクトを作成してみたものになります!

簡単にですが、動画を通して学べた技術や知識をまとめました!

partⅠでは、JavaScriptBookListを作成しています:book:
完成品は、下記に載せています:bangbang:

2,学んだこと

ゴール:triangular_flag_on_post:が見えているので、完成させたい意欲で一気に進めてしまいますね:v:
前回のBookListとの共通するソースコードが登場したときには、こういった場面ではこう書けばいいんだと記憶に定着するし、作品を見直せばすぐ応用がきくのでいろいろとメリットがたくさんあります:blush:

ぜひ、ハンズオンでトライしてみてください:star:

今回は、新たに
① DOMContentLoaded がドキュメントを起動したときに読み込まれる
② e.preventDefault(); を記述する理由
③ JavaScriptでhtmlを生成する方法
④ e.target();
⑤ localStorageの保存と削除方法
⑥ JSON.stringify();

を学ぶことができました!
localStorageに保存する場合は、JSON形式で保存することが大きな収穫でした:blush:

完成品では、localStorageの様子も見れるので下記にその動画を載せておきます:star:

3,参考動画

IMAGE ALT TEXT HERE

4,完成:tada:

localStorageの様子も見れるようにしました:star:

2021-02-26_23h31_01.gif

4,次回は、

Reactを使った、プロジェクトにトライしてみます:bangbang:

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

【JavaScript】 海外Teck系YouTuberを真似てみた!(partⅡ) ToDoList App | No Frameworks

1,はじめに

この記事は、海外Teck系YouTuberの動画を参考に、同じプロジェクトを作成してみたものになります!

簡単にですが、動画を通して学べた技術や知識をまとめました!

partⅠでは、JavaScriptBookListを作成しています:book:
今回の完成品は、下記に載せています:bangbang:

2,学んだこと

ゴール:triangular_flag_on_post:が見えているので、完成させたい意欲で一気に進めてしまいますね:v:
前回のBookListとの共通するソースコードが登場したときには、こういった場面ではこう書けばいいんだと記憶に定着するし、作品を見直せばすぐ応用がきくのでいろいろとメリットがたくさんあります:blush:

ぜひ、ハンズオンでトライしてみてください:star:

今回は、新たに

① DOMContentLoaded がドキュメントを起動したときに読み込まれる
② e.preventDefault(); を記述する理由
③ JavaScriptでhtmlを生成する方法
④ e.target();
⑤ localStorageの保存と削除方法
⑥ JSON.stringify();

を学ぶことができました!
localStorageに保存する場合は、JSON形式で保存することが大きな収穫でした:blush:

完成品では、localStorageの様子も見れるので下記にその動画を載せておきます:star:

3,参考動画

IMAGE ALT TEXT HERE

4,完成:tada:

localStorageの様子も見れるようにしました:star:

2021-02-26_23h31_01.gif

4,次回は、

Reactを使った、プロジェクトにトライしてみます:bangbang:

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

window.nameに関して

vscodeでファイルを開き、thisの挙動を調べるためにあえてwindow.nameを変更した。

問題なのはコメントアウトしても、window.nameの値は残り続けたこと。

これの解決方法は単純で、一旦vscodeを再起動させる。
これに限る。

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

create-next-app --example with-reduxを使った際の各ファイルの相関図

初投稿。

ど素人非エンジニアがNext.js、Reduxを学ぶ中で、
create-next-app --sample with-reduxが非常に便利なので、これを用いて勉強中です。
プロジェクト内に生成される各ファイルの関係性について自分の理解を深めるために、手元メモを備忘録として作ったので、自分のために参考メモとして残しておきたいと思います。

create-next-appしてみる。

Next.jsとreduxを使う場合

npx create-next-app --example with-redux [project_name]

フォルダ構成はこんな感じ

スクリーンショット 2021-02-26 23.28.55.png

各ファイルの関係性

拙い自分の理解です・・・
細かい部分など省いていますし、間違いなどが多々あるかもしれないです。
スクリーンショット 2021-02-26 23.32.11.png

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

【初心者でもわかる】ラジオボタンの選択で項目を出し分ける方法

どうも7noteです。選択したラジオボタンによって内容を出し分ける方法について。

男性にだけ効きたい質問だったり、学生にだけに効きたい質問。
または特定の条件にそって要素を出し分け質問項目を変える方法について書きます。

今回サンプルで作ったのは普段検索エンジン何を使っているのかによって質問を出し分けるフォームです。
※実際に送信ボタンの実装はしていませんので、あくまで出し分け部分のソースのみに特化した記事です。

ソース

※jQueryを使用しています。jQueryってなんだ?という方はこちら

index.html
<p>質問:普段使う検索エンジンを教えてください。</p>
<div>
  <label><input type="radio" name="engine" value="google">Google</label><br>
  <label><input type="radio" name="engine" value="yahoo">Yahoo!</label><br>
</div>
<p class="google_only">
  Googleをお使いの方に質問です。<br>
  Googleの良さを教えてください。<br>
  <input type="textarea">
</p>
style.css
.google_only {
  display: none;
}
script.js
$(function(){
  /* nameがengineのラジオボタンが変更された場合の処理 */
  $( 'input[name="engine"]:radio' ).change( function() {

    /* nameがengineのラジオボタンで選択されている値を取得 */
    var selectdata = $("input[name='engine']:checked").val();

    /* その値が`google`だったら追加質問項目を表示。違えば非表示にする */
    if(selectdata == "google"){
      $(".google_only").show();
    } else {
      $(".google_only").hide();
    }
  });
});

結果

sample.gif

解説

基本的な動作の流れはソースのコメントに書いていますが、以下のような順番です。

・ラジオボタンが変更される
 ↓
・値を取得
 ↓
もし値がgoogleだったらgoogle_onlyのクラスがついた要素を表示
 ↓
違った場合、google_onlyのクラスがついた要素を非表示

出し分けだけであればそこまで複雑なことはせず、シンプルに考えれば簡単に作ることができます。

これに加えて、実際にgetやpost等でデータを渡すかどうかなどの話になってくると、見えていないtextareaの情報を送信させないようにするための制御なども必要になってくるので、少し難しくなるかもしれません。

まとめ

出し分けはjavascriptで簡単に実装することができました。cssだけで行なう方法もなくはないのですが、今回のようなケースであればjavascriptやjQueryを使って実装するのが手がるで簡単かなと思います。
もしかしたらもっと簡単にできる方法もあるかも。もし知っている方がいればぜひコメントで教えてください!

おそまつ!

~ Qiitaで毎日投稿中!! ~
【初心者向け】WEB制作のちょいテク詰め合わせ

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

【GAS】スプレッドシートからInsert文を作成する手順

この記事について

This is for 自分 as メモ

概要

A1セルにテーブル名を入れる。

2行目はカラム名を羅列。3行目以降はレコードとなる情報をセットする。

また、result シートを予め作成しておく。

スクリーンショット 2021-02-26 22.47.30.png

そのうえで、Google Apps Scriptを開いて下記を実行。

function makeInsertQuery() {

 let spreadSheetByActive = SpreadsheetApp.getActive();
 let sheetByActive = spreadSheetByActive.getActiveSheet();
 let range = sheetByActive.getRange(1,1,sheetByActive.getLastRow(), sheetByActive.getLastColumn());
 let headerRowIndex = 1;
 let firstValuesRowIndex = headerRowIndex + 1;
 let cells = range.getValues();
 let numRows = range.getNumRows();
 let numColumns = range.getNumColumns();
 let tableName = cells[0][0];
 let columnNames = [];

 for (var i = 0; i < numColumns; i++) {
  columnNames.push(cells[headerRowIndex][i]);
 }

 let prefix = "INSERT INTO " + tableName + " (" + columnNames.join(",") + ") VALUES (";

 let deleteTarget = spreadSheetByActive.getSheetByName('result');
 spreadSheetByActive.deleteSheet(deleteTarget)
 let newSheet = range.getSheet().getParent().insertSheet();
 newSheet.setName('result')

 let targetCell = newSheet.getActiveCell();

 for (var i = firstValuesRowIndex; i < numRows; i++) {
    var values = [];
    for (var j = 0; j < numColumns; j++) {
      values.push('"' + cells[i][j] + '"');
    }
    targetCell.setValue(prefix + values.join(",") + "); ");
    targetCell = targetCell.offset(1, 0);
  }
}

結果。

スクリーンショット 2021-02-26 22.47.50.png

参考文献

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

毎日投稿(JavaScriptの復習)TODOアプリの実装

毎日投稿

アウトプットしない身にならないで、毎日投稿を始めました。
毎日の勉強内容を書いていきます。

本日はJavaScriptの復習しました。
2週間前ぐらいにある程度勉強したのですが、
身になっていないところがあったので復習をしました。

復習した内容は以下です。

・テンプレート文字列
・アロー関数 =>
・分割代入 {}
・デフォルト値 =
・スプレッド構文 ...
・mapやfilterを使った配列の処理
・三項演算子 ? :
・論理演算子の本当の意味を知ろう && ||

この復習で、”mapやfilterを使った配列の処理”の部分が身になった気がします。
例えば、配列の処理時に私は毎回、for文を使っていました。
for文が悪いということではないと思いますが、
これからはできる限りにmapやfilterといったメソッドを使っていきます。

アウトプットとして、最低限のHTML&CSSとJSでTODOアプリを作りました。
ソースコード展開します。。。
⬇️
https://github.com/takeshi1201/ToDo-javascript.git

明日はSPA,Reactの基本構文を中心に勉強していきます。
今日もお疲れ様でした。

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

毎日投稿(JavaScript)1日目

毎日投稿

アウトプットしない身にならないで、毎日投稿を始めました。
毎日の勉強内容を書いていきます。

本日はJavaScriptの復習しました。
2週間前ぐらいにある程度勉強したのですが、
身になっていないところがあったので復習をしました。

復習した内容は以下です。

・テンプレート文字列
・アロー関数 =>
・分割代入 {}
・デフォルト値 =
・スプレッド構文 ...
・mapやfilterを使った配列の処理
・三項演算子 ? :
・論理演算子の本当の意味を知ろう && ||

この復習で、”mapやfilterを使った配列の処理”の部分が身になった気がします。
例えば、配列の処理時に私は毎回、for文を使っていました。
for文が悪いということではないと思いますが、
これからはできる限りにmapやfilterといったメソッドを使っていきます。

アウトプットとして、最低限のHTML&CSSとJSでTODOアプリを作りました。
ソースコード展開します。。。
⬇️
https://github.com/takeshi1201/ToDo-javascript.git

明日はSPA,Reactの基本構文を中心に勉強していきます。
今日もお疲れ様でした。

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

Luxy.jsの使い方(慣性スクロール)

公式サイト

Luxy.js

使い方

ファイルをダウンロードして読み込む

<script src="js/luxy.min.js"></script>

慣性スクロールを適用させたいコンテンツを囲む要素にIDを付与

<div id="luxy">
  <!-- コンテンツ-->
</div>

下記を記述する

luxy.init();

オプション

慣性スクロールを適用させる要素、パララックス効果を与える要素、スピードを指定可能
下記は初期値

luxy.init({
  wrapper: '#luxy',
  targets : '.luxy-el',
  wrapperSpeed:  0.08
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

初投稿

2.26事件と岡本太郎の誕生月日の今日から、何となくキリの良さを感じて投稿を始めてみようと思いました。
プロフィールにも書いていますが、2020年秋後半から、 ITエンジニアとしての学習・勉強を始めています。上のタグは学習した事のある内容の一部です。
早寝早起きからやり直している最中です…
という事で今日はもう寝ます。

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

Swagger Codegenで出力したAPIクライアント(typescript-fetch)を Nuxt に組み込む

Nuxt に Swagger Codegen で出力した APIクライアントのコードを組み込む際のメモ。

Swagger から typescript-fetch のコードを出力する

まずは、API Client のコードを取得する。

Swagger Spec の書き方とか、Codegen の使い方は省略。
特に強い理由がないのであれば、SwaggerHub を使うのが賢いと思う。

Export から、Client SDK > typescript-fetch を選択して zip をダウンロード。

image.png

API Client ファイルを配置する

設置場所はどこでもいいが、ここでは /api 以下に、ダウンロードしてきた API Client のファイルを配置する。
image.png

Nuxt 内のコードから、API Client を読み込む

APIクラスは、Swaggerの tag に対応して作成されている。
次のSpecでは tagsuser となっているため、出力されたクライアントには、UserApi というAPIクラスが定義されている。

なお、この Spec は OpenAPI3 での記述なので、Swagger2 だと若干異なるかもしれない。

openapi.yaml
/user/{userId}:
  get:
    tags:
      - user
    summary: ユーザー情報参照
    operationId: getUserById
    parameters:
      - name: userId
        in: path
        required: true
        schema:
          type: string
    responses:
      200:
        description: successful operation
        content:
          application/json:
            schema:
              $ref: '#/components/schemas/User'

vue側では、まず使用するAPIクラスと、設定用のConfigurationをインポートする。
各メソッドは、Spec の operationId と対応している

index.vue
import { UserApi, Configuration } from '@/api'
index.vue
mounted () {
  // Specの記述と同じであれば、BasePathを指定する必要はない
  const conf = new Configuration({
    basePath: 'http://localhost:3000'
  })

  // APIインスタンスを作成
  const userApi = new UserApi(conf)

  // 実行
  // 各メソッドは、Spec の operationId と対応している
  userApi.getUserById('userid').then((value:User) => {
    // Success
    console.log('success', value)

    // 〜取得成功の処理〜

  }).catch((error:unknown) => {
    // Error
    if (error instanceof Response) {
      // エラーレスポンス
      console.error('Error Status', (error as Response).status)
    } else {
      // 接続エラー
      console.error('Error', error)
    }
  })
}

ESLintのエラーが出る場合

まあ、大量に出る。
これはもう、/api を丸ごと ignore しておく。

.eslintignore
static/js/*
static/
api/   <--追加

TypeScriptのエラーが出る場合

api_test.spec.ts のエラー

api_test.spec.ts の中で、
Cannot find name 'describe'
Cannot find name 'expect'
などのエラーが出る場合は、@types/jest をインストールする。

npm install -D @types/jest

そして、インストール後に、tsconfig で読み込んでおく。

tsconfig.json
  "compilerOptions": {
    "types": [
      "@types/node",
      "@nuxt/types",
      "@types/jest"  // <--追記
    ]
  }

api.ts のエラー

Property 'configuration' has no initializer and is not definitely assigned in the constructor.

どうしても、protected configuration: Configurationname: "RequiredError" の2箇所で TypeScript エラーが出てしまう。

どうやら、本家のリポジトリでは解決されているらしいが(修正コミット)、SwaggerHubには反映されていないようだ。

該当する2箇所を次のように書き換えれば解決する。

export class BaseAPI {
    protected configuration: Configuration | undefined;  // <--ここ
export class RequiredError extends Error {
    name: "RequiredError" = "RequiredError";  // <--ここ

ただ、面倒くさければ api.ts の先頭で、@ts-nocheck してもいいと思う。

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

if文なしでじゃんけん - JS版

if文なしでじゃんけん - Qiita を読んでJSで組んでみました。

解答

const gu = "gu";
const choki = "choki";
const pa = "pa";

const createResult = (winHand, loseHand, drawHand) => ({[winHand]: "勝ち", [loseHand]: "負け", [drawHand]: "あいこ"})
const hands = Object.freeze({
    [gu] : {view: "", judgeTable: createResult(choki, pa, gu)},
    [choki] :{view: "", judgeTable: createResult(pa, gu, choki)},
    [pa] : {view: "", judgeTable: createResult(gu, choki, pa)},
});

const getView = (hand) => hands[hand]?.view;
const battle = (my, your) => judge(hands[my]?.judgeTable, your);
const judge = (judgeTable, your) => judgeTable[your];

Object.keys(hands).forEach(myHand => {
    Object.keys(hands).forEach(yourHand => {
        console.log(`[${getView(myHand)} vs ${getView(yourHand)}] ${battle(myHand, yourHand)}`);
    });
});

動作結果
image.png

作ってみて

単純だからこそ考え甲斐があって楽しいですね。
本来であれば自分の手をSymbolで作るべきなのかなと思っていたのですが、出力部分が面倒なことになるので今回は定数という形で実装しました。

三項演算子、Null合体演算子も使うか迷ったのですが使いませんでした。
そのためjudgeで定義している関数で配列かどうかのチェックがありません。でも今回の要件に外からの入力は無いのでヨシとしました。
TypeScriptで実装していればこういった心配が少なくなるのが良いですよね。

今回は文字を出力するだけの要件なので、直接「勝ち」「負け」「あいこ」を書きましたが、関数を持たせるようにすれば勝利ポイントなども扱えるようになりそうです。

もっと短く書けるぞ!って方は是非挑戦してみてください。

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

【jquery,js】自作マークダウンエディタ、正規表現 [replace,html] [js08_20210226]

処理の概要

入力されたマークダウン形式のテキストをhtmlに変換。これをhtmlタグコードと、htmlで表示する

処理のフロー:

 (1)マークダウン形式のテキストを取得
 (2)関数にて置換
 (3)htmlタグ形式でテキストエリアに出力
 (4)htmlにて表示

画面イメージ

画像1

画像2

ソースコード

index.html
<head>
<link rel="stylesheet" href="./css/default.css">
</head>
<body>
<textarea id="userText">
#タイトル1
本文本文本文本文*本文*本文

##タイトル2
本文本文本文本文*本文*本文
</textarea><br>
<input type="button" id="execButton" value="実行"><br>
<textarea id="output"></textarea><br>
<div id="output2"></div>
</body>
main.js
// DOMの準備後に実行する内容
$(function() {
    $("#execButton").click(function(){
        var markDownText = $("#userText").val();
        htmlText = markDown(markDownText);

        $("#output").val(htmlText);
        $("#output2").html(htmlText);

        function markDown(argText){
            argText = argText.replace(/\*(.+?)\*/g, "<b>$1</b>");
            argText = argText.replace(/^## *(.+?)$/gm, "<h2>$1</h2>");
            argText = argText.replace(/^# *(.+?)$/gm, "<h1>$1</h1>");
            argText = argText.replace(/\n/gm, "<br>\n");

            return argText;
        }
    });
});
default.css
textarea {
    height: 8em;
    width: 40em;
}

.input-style {
    color:blue;
    width: 400px;
}

b {color: red;}

ポイント

html:
(1)なし
js:
(1)正規表現について
で挟まれた部分を、/\*(.+?)\*/gで表現します。replaceなので、ステークホルダーを使い、<b>$1</b>で置換します。
\は*(アスタリスク)をエスケープしています。下の参考資料の通り、*は0回以上繰り返しの特殊記号なので。この記事を書いている時も、エスケープしています(笑)
(.+?)は、任意の一文字を示しています。

②行頭は^。行末は$です

参考資料

JavaScript(仕事の現場でサッと使える!デザイン教科書) p150
正規表現はこちら↓
JavaScript(仕事の現場でサッと使える!デザイン教科書) p91

正規表現-参考資料1

正規表現-参考資料2

正規表現-参考資料3

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

IEだとHTMLCollection.item()の挙動が異なる

問題のコード

sample.html
<!DOCTYPE html>
<html lang="ja">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>sample</title>
</head>
<body>
    <p id="one">1</p>
    <p id="two">2</p>
    <p id="three">3</p>

    <div id="hoge"></div>
</body>
<script>
    let pElements = document.getElementsByTagName("p");
    let value = pElements.item("two").innerText;

    document.getElementById("hoge").innerText = value;
</script>
</html>

pタグの内、「id=two」の要素のテキストを取得し、「id=hoge」の要素に追加するコード。

MDNを参照するとHTMLCollection.item()の引数には、indexを指定しなければならず、上記コードは期待通りの動作をしないはず。
MDN Web Docs : HTMLCollection

Chromeでの表示

1
2
3
1

Chromeでは想定通り「id=two」の要素が取得できていない。

IEでの表示

1
2
3
2

IEでは「id=two」の要素が取得できている。

まとめ

IEでは引数に文字列を渡した場合、HTMLCollection.namedItem()と同じ挙動をする模様。
IEと他ブラウザで同じ挙動をさせたい場合は、.namedItem()か.getElementBy○○()を使用するのが良さそう。

妙にハマったので備忘録として残しておく。

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

Active Storageで追加した画像をjavascriptを使ってプレビューする方法

はじめに

active Storageを使って保存する画像を保存前に表示させて確認したいと思って調べたもののまとめです。

前提

すでに画像保存機能は出来上がっている

javascriptファイルの追加

app/javascript/packsの中にプレビュー用のファイルを追加します。今回、名前はpreview.jsとします。

preview.jsの読み込み

前述で追加したpreview.jsをpacks内application.jsで読み込みます。
app/javascript/packs/application.js

require('./preview')

読み込まれたときに動作する関数を定義

まずは動作ようの記述を。
app/javascript/packs/preview.js

document.addEventListener('DOMContentLoaded', function(){
});

画像を表示するビューファイルに画像表示スペースをつくる

app/views/messages/_form.html.erb

<div id="image-list"></div>

HTMLの要素を、JavaScript側で取得

続いて、今記述した画像を表示するためのHTMLの要素を、JavaScript側で取得します。
app/javascript/packs/preview.js

document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');
});

input要素で値の変化が起こったときに呼び出される関数を定義

document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');
  document.getElementById('message_image').addEventListener('change', function(e){
    console.log(222);
  });
});

念の為ファイルを選択する」をクリックして適当な画像を選択し、以下のように222と表示できているかを確認しておきましょう。

inputの中にある画像を取得

発火したイベントeの中の、targetの中の、filesという配列に格納されています。

次に取得した画像の情報は、変数fileとして宣言します。
app/javascript/packs/preview.js

document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
  });
});

URLオブジェクトのcreateObjectURLメソッドを呼び出し、変数fileを引数として渡す

次はcreateObjectURLメソッドを引数として渡します。
app/javascript/packs/preview.js

document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
    const blob = window.URL.createObjectURL(file);
  });
});

実際に画像を表示できるようにする。

preview.jsを以下のように編集して画像を表示します。

document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
    const blob = window.URL.createObjectURL(file);

    // 画像を表示するためのdiv要素を生成
    const imageElement = document.createElement('div');

    // 表示する画像を生成
    const blobImage = document.createElement('img');
  });
});

画像URLをimg要素のsrc属性に設定

setAttribute()というdocumentオブジェクトを用いて、画像情報をsrc属性に指定します。
app/javascript/packs/preview.js

document.addEventListener('DOMContentLoaded', function(){
const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
    const blob = window.URL.createObjectURL(file);

    // 画像を表示するためのdiv要素を生成
    const imageElement = document.createElement('div');

    // 表示する画像を生成
    const blobImage = document.createElement('img');
    blobImage.setAttribute('src', blob);
  });
});

appendChild()

指定した親要素の中に要素を追加するappendChildメソッドを使って、HTML要素を追加します。
app/javascript/packs/preview.js

document.addEventListener('DOMContentLoaded', function(){
  const ImageList = document.getElementById('image-list');

  document.getElementById('message_image').addEventListener('change', function(e){
    const file = e.target.files[0];
    const blob = window.URL.createObjectURL(file);

    // 画像を表示するためのdiv要素を生成
    const imageElement = document.createElement('div');

    // 表示する画像を生成
    const blobImage = document.createElement('img');
    blobImage.setAttribute('src', blob);

  // 生成したHTMLの要素をブラウザに表示させる
    imageElement.appendChild(blobImage);
    ImageList.appendChild(imageElement);
  });
});

条件分岐でトップページのエラーを解決

このままではpreview.jsで指定している要素が、新規投稿ページや編集ページにだけ存在し、TOP画面などでは表示されないため、エラーが発生します。
そのため、下記のようにして条件分岐してエラーを起こさないようにします。
app/javascript/packs/preview.js

if (document.URL.match( /new/ ) || document.URL.match( /edit/ )) {
  document.addEventListener('DOMContentLoaded', function(){
    const ImageList = document.getElementById('image-list');

    document.getElementById('message_image').addEventListener('change', function(e){
      const file = e.target.files[0];
      const blob = window.URL.createObjectURL(file);

      // 画像を表示するためのdiv要素を生成
      const imageElement = document.createElement('div');

      // 表示する画像を生成
      const blobImage = document.createElement('img');
      blobImage.setAttribute('src', blob);

      // 生成したHTMLの要素をブラウザに表示させる
      imageElement.appendChild(blobImage);
      ImageList.appendChild(imageElement);
    });
  });
}

表示画像を編集可能にする。

最後に既存のプレビューを削除した後に新しい画像のプレビューを表示することで画像を編集可能にして起きます。

if (document.URL.match( /new/ ) || document.URL.match( /edit/ )) {
  document.addEventListener('DOMContentLoaded', function(){
    const ImageList = document.getElementById('image-list');

    document.getElementById('message_image').addEventListener('change', function(e){
      // 画像が表示されている場合のみ、すでに存在している画像を削除する
      const imageContent = document.querySelector('img');
      if (imageContent){
        imageContent.remove();
      }
      const file = e.target.files[0];
      const blob = window.URL.createObjectURL(file);

      const imageElement = document.createElement('div');

      const blobImage = document.createElement('img');
      blobImage.setAttribute('src', blob);

      imageElement.appendChild(blobImage);
      ImageList.appendChild(imageElement);
    });
  });
}

完成

以上で完成です?

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

javascriptでsetAttributeメソッドが発動しているか判定する

目的

 以下のようなマス目を、出た目に応じて赤く点灯させる。

1 2 3 4 5

 1列全て点灯した時点でビンゴとする。

 そのため、特定のマス目が点灯していることを判別したい。

(1) マス目の要素を取得する

app.js
const boxNumbers = document.getElementsByName('box')

 各マス目のname属性から複数の要素を配列として取得する。

(2) これを赤く点灯させる

app.js
boxNumbers[sheetNumbersIndex].setAttribute("style", "background-color:#ff0000;")

 style属性に、赤色の背景色という値を追加

(3) 当該マス目でイベント発火が生じているか判別

  点灯前と点灯後の違いをコンソールで調査する

console.log(boxNumbers[0].style)
点灯前
CSSStyleDeclaration {alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}
点灯後
CSSStyleDeclaration {0: "background-color", alignContent: "", alignItems: "", alignSelf: "", alignmentBaseline: "", all: "", …}

 どうやら、boxNumbers[0].styleの中身を見ると、点灯後は、キー0、値"background-color"という要素が追加されるらしい。
 

結論 

 というわけで、boxNumbers[0].style[0]の存在の有無で判別できました。

app.js
if(boxNumbers[0].style[0])
//点灯前はfalse,点灯後はtrueが返る

 

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

GitHub コマンドでcloneからpushまでの手順

超初心者向け

~gitコマンドでcloneからcommitまでの手順~

cliコマンドを使ってリポジトリをcloneしたい場所に移動する

cd "ローカルリポジトリーを置きたい場所"

移動先にリモートリポジトリーを設置する

git clone "HTTPSのURL"

自分のHTTPSのURLはWEB版のgithubの自分のリポジトリーにいき
緑色のcodeを押すと出てきます。

そして自分のbranchを確認します。

git branch

すると、*mainがでてくると思います。
そこで新しくbranchを作成します。

git branch 新しいbranch名

これでbranchは作成できます。その後branchの切り替えを行います

一人で制作する場合は直接mainを使っていいかなとなるかもしれませんが
そう思う人はTECH ACADEMYさんのこの記事を見てもらうのがわかりやすいと思います。

git checkout 作成したbranch名

この2つを一緒に行えるコマンドもあるので、追記しておきます。

git checkout -b 新しいbranch名

Gitのローカルリポジトリにファイルを追加する(commit対象にする)

git add *

*でファイルをすべてcommit対象にします。

ローカルリポジトリーからリモートリポジトリーにコミットする(-mの先はコミットするときのコメント内容)

git commit -m "コメント内容"

mainブランチにpullリクエストを送る。

git push origin 作成したbranch名

ここまでがcloneからpushまでの流れです。

そもそもプルリクエストとは、

コードの変更をレビュワーに通知し、マージを依頼する機能です。 コードのレビューを受けることで、1人で作ると気がつかないコードの指摘やバグや記述ミスの発見ができ、コードの品質を高めます。 レビュワーにとっても、他人が書いたコードを読むことで新しい書き方を発見できるというメリットがあります。

ここまで完了したら、githubを見に行きcreate pull リクエストを作成します。

ここからはnpm iした際のリモートリポジトリーに上げる際にnode_modulesのあげない設定です
node_modulesに関してはpackage.json,package-lock.jsonがあればnpm iでダウンロードできるためリモートリポジトリーに上げる必要もないですし、ファイル数が多いためおすすめしません

フォルダ直下に

touch .gitignore

作成します。
そして.gitignoreの中に記述する内容は

node_modules/
ファイルのパターン一致でコンパイルなどで生成したファイルを指定する場合
*.com
*.class
*.dll
*.exe
*.o
*.so

これでnode_modulesだけリモートリポジトリーに上がらないようになります

あくまで僕のメモとして作成致しましたが誰かの役にたてると嬉しいです。

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

とりあえずconstしとけの罠-再宣言・再代入可能な場合

JavaScriptの基礎知識記事です。
「とりあえずconstで定義しておけば、再宣言・再代入できないから安心なんでしょ」と、今日の昼までは思っておりました。
しかし、その浅い認識でエラーが出てしまったので、備忘録として書いておきます。
同じように思っているJavaScript初心者の方に読んでいただければと思います。

スコープ問題

console.logクイズ-何が表示されるでしょう?

①グローバル変数を関数内で読込

以下のコードを実行するとどうなるでしょう?

index.js
const drink = "coffee";

const bitter = () => {
    console.log(drink);
};

bitter();

答えは、

coffee

です。

②グローバル変数と同名の変数を関数内で宣言

では、次の場合には?

index.js
const drink = "coffee";

const sweet = () => {
    const drink = 'cocoa';
    console.log(drink);
};

sweet();

答えは、

cocoa

です。

さっきはconstで代入していた変数を関数内でそのまま呼び出せたのに、関数内でもう一回同じ変数名で宣言できる!

③グローバル変数で定義した変数を、関数内で使用してから宣言

では最後に、次の場合はどうなるでしょう?

index.js
const drink = "coffee";

const salty = () => {
    console.log(drink);
    const drink = 'kobucha';
    console.log(drink);
};

salty();

答えは・・・

Uncaught ReferenceError: Cannot access 'drink' before initialization

エラーが返ってきます。
JavaScriptの仕様として、グローバル変数は関数内やブロック内でも読み込めますが、グローバル変数と同じ名前の変数を、関数やブロック内で宣言することもできます。
このあたり見るとわかるかと↓
https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Statements/block

そのため、①、②のような挙動になりました。

それでは、なぜ③でエラーが返ってきたのか。
最初のconsole.logでcoffeeと表示され、次のconsole.logでkobuchaと表示されても良いのでは?

JavaScriptではブロック内にconstがあると、書いた順番に関係なくそのブロック内の変数はすべてその定義された変数で上書きされてしまいます。ブロック内で定義した時点で、同じ変数名のグローバル変数はブロック内では読み込まれなくなります。

ただし、あくまでも上書きされるのは変数の存在だけです。代入処理は上から順番におこなわれていきます。だから「その変数は定義されていないよ」というエラーではなく、「その変数は初期化されていないよ」というエラーが返ってくるのです。

結論

スコープが変わると、constで定義していても同名で定義できてしまいます。グローバル変数と同じ名前の変数を関数内で定義してもエラーは出ずに上書きされてしまいます。気をつけようと思いました。

リスト・オブジェクト

ついでに、constで定義したリストとオブジェクトの扱いも勘違いしがちかなと思ったので触れておきます。

index.js
const drink = ['coffee','cocoa'];
drink.push('kobucha');
console.log(drink);

const food = {
  lunch: 'peperoncino',
  dinner: 'karaagekun'
};
food['breakfast'] = 'protain';
console.log(food);

["coffee", "cocoa", "kobucha"]
{lunch: "peperoncino", dinner: "karaagekun", breakfast: "protain"}

リストやオブジェクトの中身をいじる処理は自由にできます。もちろん、再宣言しようとしたり、以下のようにリスト自体、オブジェクト自体を再代入しようとするとエラーが出ます。

index.js
const drink = ['coffee','cocoa'];
drink = ['coffee','cocoa','kobucha'];

以上です。
ちなみにたいていのパスタは昆布茶を入れるとおいしくなります。
それでは。

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

Vue.jsのcomputedの書き方で学ぶJavascriptにおけるオブジェクトのメソッド定義

はじめに

Vue.js(Option api)のcomputedの部分のコードを読む際にあれ、いつも自分が書いている書き方とちょっと違うと戸惑った経験はないでしょうか?

最近、いつも自分がよく書く書き方と異なる書き方がされていて、なんでこの書き方で大丈夫なんだろうと感じたことがあったので整理してみました。

オブジェクトのメソッド定義の3通りの表現方法

Vue.jsのcomputed, method ではオブジェクトのメソッド定義(=関数プロパティ)が利用されます。
その場合のメソッド定義(=関数プロパティ)の表現方法は主に下記の3パターンあります。
(Vue.jsというより、Javascript(ECMAScript)の仕様ですが)

  1. アロー関数式を利用
  2. function式を利用
  3. メソッド定義のための省略構文を利用

オブジェクトのプロパティとして定義するため、1,2の場合はコロン[:]が必須ですが、3の場合はコロンの部分も省略できる構文であるためコロンは不要です。

3つの定義方法の違い

2,3の場合はfunction式として評価されますが、1はアロー関数として評価されます。

アロー関数は「文脈に依存せず、宣言された時点で、thisを確定(=束縛)する」特徴があるので、1の関数式内でthisを利用すると、Vueインスタンスをthisとして解釈できずにエラーとなります。
(参考:Vue.jsに書いてある「アロー関数は、this が期待する Vue インスタンスではなく・・・」とは?)

コードでの例


export default {
  name: 'SomeList'
  data () {
    return {
      contents: []
    }
  },
  computed: {
    // 1: アロー関数式を利用
    countOfInitialShowedContents: () => 10,
    // 2: function式を利用
    countOfContents1: function() {
      return this.contents != null ? this.contents.size : 0 
    },
    // 3: メソッド定義のための省略構文を利用
    countOfContents2 () {
      return this.contents != null ? this.contents.size : 0 
    },
}

メソッド定義のための省略構文(a shorter syntax for method definitions on objects initializers)とは

アロー関数式とfunction式については多くの方にとって馴染み深い表現だと思いますので、
ここでは「メソッド定義のための省略構文」 についてのみ記載させていただきます。

ECMAScript 2015から導入されたfunctionを利用したメソッド定義の省略形です。
このような表現で記述した場合は、↓のようにfunction式によるメソッド定義と同じように解釈されます。

省略構文(↓のfunction式で定義した場合と同様に解釈されます)

const obj = {
  foo() {
    // ...
  },
  bar() {
    // ...
  }
}

function式を利用した構文

const obj = {
  foo: function() {
    // ...
  },
  bar: function() {
    // ...
  }
}

まとめ

私も3の表現でVueのdataやcomputedをよく記載していたのですが、どのような仕様で実現しているのかをきちんと理解しておらず利用していました。

今回、調べてみたことで今後は自分の中で確信を持ってコーディングをすることができそうです。

もし、同じような疑問を持った方の参考になれば幸いです。
それでは、素敵なVueライフを。

参考リンク

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

[Javascript] うるう年かどうかを確認するJS

はじめに

うるう年であるかを確認するJS方法は様々です。
ですが、今回Dateを利用して簡単に解けたというのがポイントです。

Dateを利用して簡単

function isLeapYear(year) {
  return new Date(year, 1, 29).getDate() === 29;
}

isLeapYear(2024)
true
isLeapYear(2100)
false
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】JavaScriptの非同期処理について

プログラミング勉強日記

2021年2月26日

同期処理とは

 同期処理は、コードを順番に処理していって、1つの処理が終わるまで次の処理は行われない。同期処理では、実行している処理は1つだけとなるので直感的な動作になる。
 しかし、1つの処理が終わるまで次の処理ができないことによっての問題点も存在する。具体的には、同期的にブロックする処理があると大きな問題が生じる。JavaScriptでは基本的にブラウザのメインスレッドで実行されるので、メインスレッドが他の処理でいっぱいになってしまうと表示が更新されなくなってしまいフリーズしたような状況になる。これは、メインスレッドが表示の更新といった処理を行っているためである。

非同期処理とは

 非同期処理でもコードを順番に処理していくが、1つの非同期処理が終わるを待たずに次の処理を行うことができる。なので、非同期処理では同時に実行している処理が複数ある。
 JavaScriptにおける非同期処理には、コールバック、Promise、async/awaitの3種類ある。JavaScriptにおいての多くの非同期処理はメインスレッドで実行される。

 JavaScriptでは一部の例外を除いて、非同期処理が並行処理として扱われる。並行処置は、処理をある一定の単位ごとに分けて処理を切り替えながら実行することである。なので、非同期処理であってもその処理の実行中に重い処理があると非同期処理の切り替えが遅れる可能性もある。
 そのため、非同期処理の中でもメインスレッドとは別のスレッドで実行できるAPIが存在する。これによって、排他的に複数の処理を同時に実行することのできる並列処理をすることができる。
 そうはいっても、基本的な非同期処理はメインスレッドで実行されているという性質を知ることは大切である。

参考文献

JavaScriptの非同期処理を理解する その1 〜コールバック編〜
非同期処理:コールバック/Promise/Async

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

[JavaScript][iframe]別のフレームの入力フォームにアクセスしたい。

iframeでフレームを分けているWebサイトのようなものを作っているのですが。
フレームをまたいだDOM操作が難しく感じたので、備忘録として残しておきます。

スクリーンショット 2021-02-26 134057.png

index.htmlで左のフレーム、右のフレームそれぞれファイルを呼び出し、フレーム化しています。
今回のイメージは左のフレームのテキストをクリックして、右のフレームのフォームに入力するイメージです。

left.htmlの<p>タグをクリックするとindex.jsのinputText()ファンクションが発火するという流れです。

別フレームの要素にアクセスするための構文

parent.フレーム名.document.要素名称.プロパティ

//↓今回の場合
parent.right_frame.document.getElementById('target');

のようになります。

index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
<body>
    <iframe src="left.html" name="left_frame"></iframe>
    <iframe src="right.html" name="right_frame"></iframe>
</body>
</html>
left.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="index.js"></script>
</head>
<body>
    <p onclick="inputText()" id="text">この文章を右フレームのフォームへ</p>
</body>
</html>
right.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
    <script src="index.js"></script>
</head>
<body>
    <p onclick="inputText()" id="text">この文章を右フレームのフォームへ</p>
</body>
</html>
test.js
function inputText() {
    var frame = parent.right_frame;
    var input_form = frame.document.getElementById('target');
    var element = document.getElementById('text').textContent;
    input_form.value = element;
}

基本的にはこれで大丈夫と思うのですが、Google ChromeではDOMExceptionという例外が発生する可能性もあります。
ぼくは発生してしまいました、、

クロスオリジンフレーム(?よくわからん)絡みのエラーなので、Eclipseなりでサーバー立ち上げて、試してみると無事に動きました。

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

【 備忘録 】Promiseを一定数ごとに実行する

はじめに

promise使用の処理を指定件数毎に実行する方法を記録しておきます
環境的にasync,awaitを使用した書き方はできないため、一昔前(?)の書き方になっています。

まだまだ分かっていないことも多いので、気になることがあれば教えてください。

sample
function execute() {
  return new Promise(function (resolve, reject) {
    var currentIndex = 0;
    // 1回あたりの処理件数
    var concurrency = 10;
    // 処理済み件数
    var processCount = 0;
    // 処理件数
    var countMax = 100;

    dividePromise(currentIndex)
      .then(function () {
        // ・・・処理成功後にしたいこと記述
        return resolve();
      }).catch(function (e) {
        return reject(e);
      });

    /**
     * 指定した回数で分割実行
     * @param {integer} cIndex 処理開始の件数
     */
    function dividePromise(cIndex) {
      return new Promise(function (resolve, reject) {
        var chunks = [];
        // 実行したい件数分、配列へ設定
        for (var i = cIndex; i < cIndex + concurrency; i++) {
          if (i >= countMax) { break; }
          chunks.push(slowProcess());
        }
        // 設定した分をPromise.allですべて実行
        Promise.all(chunks)
          .then(function (results) {
            // 処理済み件数を更新
            processCount += results.length;
            // 全件処理したため、終了
            if (countMax === processCount) { return resolve(); }
            cIndex += concurrency;
            // 再呼び出し
            return resolve(dividePromise(cIndex));
          }).catch(function (e) {
            return reject(e);
          });
      });
    }
  });
}

/**
 * とっても重い処理
 */
function slowProcess() {
  return new Promise(function (resolve) {
    setTimeout(function () {
      console.log('test');
      return resolve();
    }, 20000);
  });
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

WEBページ上で、テーブルを列ごとにソートできるようにする

はじめに

image.png

WEBページを作る際、こういったテーブルを表示したいという時があるかと思います。
この時、テーブルのタイトル行をクリックしたら、その列に従って中身がソートされればいいのになと思うことがあるかと思います。
「名前」を一回クリックしたとき
 
「名前」をもう一回クリック

「おいしさ」を一回クリック

こういう動きを実装するにあたって、テーブルの中身が別ファイルから取ってくるもの(今回はWEBAPIでGETしてきたJSONデータ)だった場合に、簡単に実装出来てかつ思う通りに動くものがなかなか見つからなかったため、ここで紹介させていただきます。
sortable.jsです。

環境

OS:Windows10
sortable.js:Version 1.5.7

実装

sortable.jsの使い方を簡単に説明します。

JavaScript側

上記URLからsortable.jsをダウンロードし、sortable.jsとして保存します。
今回は、「js」フォルダを作成して、その中に置きました。


URL先が見れなかったときのためのコピペ
/*
Table sorting script  by Joost de Valk, check it out at http://www.joostdevalk.nl/code/sortable-table/.
Based on a script from http://www.kryogenix.org/code/browser/sorttable/.
Distributed under the MIT license: http://www.kryogenix.org/code/browser/licence.html .

Copyright (c) 1997-2007 Stuart Langridge, Joost de Valk.

Version 1.5.7

2007/9/26 japanese ver by KazumaNishihata
http://blog.webcreativepark.net/2007/09/26-024416.html
*/

/* You can change these values */
var image_path = "./";
var image_up = "arrow-up.gif";
var image_down = "arrow-down.gif";
var image_none = "arrow-none.gif";
var europeandate = true;
var alternate_row_colors = true;

/* Don't change anything below this unless you know what you're doing */
addEvent(window, "load", sortables_init);

var SORT_COLUMN_INDEX;
var thead = false;

function sortables_init() {
    // Find all tables with class sortable and make them sortable
    if (!document.getElementsByTagName) return;
    tbls = document.getElementsByTagName("table");
    for (ti=0;ti<tbls.length;ti++) {
        thisTbl = tbls[ti];
        if (((' '+thisTbl.className+' ').indexOf("sortable") != -1) && (thisTbl.id)) {
            ts_makeSortable(thisTbl);
        }
    }
}

function ts_makeSortable(t) {
    if (t.rows && t.rows.length > 0) {
        if (t.tHead && t.tHead.rows.length > 0) {
            var firstRow = t.tHead.rows[t.tHead.rows.length-1];
            thead = true;
        } else {
            var firstRow = t.rows[0];
        }
    }
    if (!firstRow) return;

    // We have a first row: assume it's the header, and make its contents clickable links
    for (var i=0;i<firstRow.cells.length;i++) {
        var cell = firstRow.cells[i];
        var txt = ts_getInnerText(cell);
        if (cell.className != "unsortable" && cell.className.indexOf("unsortable") == -1) {
            cell.innerHTML = '<a href="#" class="sortheader" onclick="ts_resortTable(this, '+i+');return false;">'+txt+'<span class="sortarrow">&nbsp;&nbsp;<img src="'+ image_path + image_none + '" alt="&darr;"/></span></a>';
        }
    }
    if (alternate_row_colors) {
        alternate(t);
    }
}

function ts_getInnerText(el) {
    if (typeof el == "string") return el;
    if (typeof el == "undefined") { return el };
    if (el.innerText) return el.innerText;  //Not needed but it is faster
    var str = "";

    var cs = el.childNodes;
    var l = cs.length;
    for (var i = 0; i < l; i++) {
        switch (cs[i].nodeType) {
            case 1: //ELEMENT_NODE
                str += ts_getInnerText(cs[i]);
                break;
            case 3: //TEXT_NODE
                str += cs[i].nodeValue;
                break;
        }
    }
    return str;
}

function ts_resortTable(lnk, clid) {
    var span;
    for (var ci=0;ci<lnk.childNodes.length;ci++) {
        if (lnk.childNodes[ci].tagName && lnk.childNodes[ci].tagName.toLowerCase() == 'span') span = lnk.childNodes[ci];
    }
    var spantext = ts_getInnerText(span);
    var td = lnk.parentNode;
    var column = clid || td.cellIndex;
    var t = getParent(td,'TABLE');
    // Work out a type for the column
    if (t.rows.length <= 1) return;
    var itm = "";
    var i = 0;
    while (itm == "" && i < t.tBodies[0].rows.length) {
        var itm = ts_getInnerText(t.tBodies[0].rows[i].cells[column]);
        itm = trim(itm);
        if (t.tBodies[0].rows[i].cells[column].tagName == "TH" || t.tBodies[0].rows[i].className == "unsortable" || t.tBodies[0].rows[i].className.indexOf("unsortable") != -1 || itm.length == 0) {
            itm = "";
        }
        i++;
    }
    if (itm == "") return; 
    sortfn = ts_sort_caseinsensitive;
    if (itm.match(/^\d\d[\/\.-][a-zA-z][a-zA-Z][a-zA-Z][\/\.-]\d\d\d\d$/)) sortfn = ts_sort_date;
    if (itm.match(/^\d\d[\/\.-]\d\d[\/\.-]\d\d\d{2}?$/)) sortfn = ts_sort_date;
    if (itm.match(/^-?\\?(\d+[,\.]?)+(\u5186)?%?$/)) sortfn = ts_sort_numeric;
    SORT_COLUMN_INDEX = column;
    var firstRow = new Array();
    var newRows = new Array();
    for (k=0;k<t.tBodies.length;k++) {
        for (i=0;i<t.tBodies[k].rows[0].length;i++) { 
            firstRow[i] = t.tBodies[k].rows[0][i]; 
        }
    }
    for (k=0;k<t.tBodies.length;k++) {
        if (!thead) {
            // Skip the first row
            for (j=1;j<t.tBodies[k].rows.length;j++) { 
                newRows[j-1] = t.tBodies[k].rows[j];
            }
        } else {
            // Do NOT skip the first row
            for (j=0;j<t.tBodies[k].rows.length;j++) { 
                newRows[j] = t.tBodies[k].rows[j];
            }
        }
    }
    newRows.sort(sortfn);
    if (span.getAttribute("sortdir") == 'down') {
            ARROW = '&nbsp;&nbsp;<img src="'+ image_path + image_down + '" alt="&darr;"/>';
            newRows.reverse();
            span.setAttribute('sortdir','up');
    } else {
            ARROW = '&nbsp;&nbsp;<img src="'+ image_path + image_up + '" alt="&uarr;"/>';
            span.setAttribute('sortdir','down');
    } 
    // We appendChild rows that already exist to the tbody, so it moves them rather than creating new ones
    // don't do sortbottom rows
    for (i=0; i<newRows.length; i++) { 
        if (!newRows[i].className || (newRows[i].className && (newRows[i].className.indexOf('sortbottom') == -1))) {
            t.tBodies[0].appendChild(newRows[i]);
        }
    }
    // do sortbottom rows only
    for (i=0; i<newRows.length; i++) {
        if (newRows[i].className && (newRows[i].className.indexOf('sortbottom') != -1)) 
            t.tBodies[0].appendChild(newRows[i]);
    }
    // Delete any other arrows there may be showing
    var allspans = document.getElementsByTagName("span");
    for (var ci=0;ci<allspans.length;ci++) {
        if (allspans[ci].className == 'sortarrow') {
            if (getParent(allspans[ci],"table") == getParent(lnk,"table")) { // in the same table as us?
                allspans[ci].innerHTML = '&nbsp;&nbsp;<img src="'+ image_path + image_none + '" alt="&darr;"/>';
            }
        }
    }       
    span.innerHTML = ARROW;
    alternate(t);
}

function getParent(el, pTagName) {
    if (el == null) {
        return null;
    } else if (el.nodeType == 1 && el.tagName.toLowerCase() == pTagName.toLowerCase()) {
        return el;
    } else {
        return getParent(el.parentNode, pTagName);
    }
}

function sort_date(date) {  
    // y2k notes: two digit years less than 50 are treated as 20XX, greater than 50 are treated as 19XX
    dt = "00000000";
    if (date.length == 11) {
        mtstr = date.substr(3,3);
        mtstr = mtstr.toLowerCase();
        switch(mtstr) {
            case "jan": var mt = "01"; break;
            case "feb": var mt = "02"; break;
            case "mar": var mt = "03"; break;
            case "apr": var mt = "04"; break;
            case "may": var mt = "05"; break;
            case "jun": var mt = "06"; break;
            case "jul": var mt = "07"; break;
            case "aug": var mt = "08"; break;
            case "sep": var mt = "09"; break;
            case "oct": var mt = "10"; break;
            case "nov": var mt = "11"; break;
            case "dec": var mt = "12"; break;
            // default: var mt = "00";
        }
        dt = date.substr(7,4)+mt+date.substr(0,2);
        return dt;
    } else if (date.length == 10) {
        if (europeandate == false) {
            dt = date.substr(6,4)+date.substr(0,2)+date.substr(3,2);
            return dt;
        } else {
            dt = date.substr(6,4)+date.substr(3,2)+date.substr(0,2);
            return dt;
        }
    } else if (date.length == 8) {
        yr = date.substr(6,2);
        if (parseInt(yr) < 50) { 
            yr = '20'+yr; 
        } else { 
            yr = '19'+yr; 
        }
        if (europeandate == true) {
            dt = yr+date.substr(3,2)+date.substr(0,2);
            return dt;
        } else {
            dt = yr+date.substr(0,2)+date.substr(3,2);
            return dt;
        }
    }
    return dt;
}

function ts_sort_date(a,b) {
    dt1 = sort_date(ts_getInnerText(a.cells[SORT_COLUMN_INDEX]));
    dt2 = sort_date(ts_getInnerText(b.cells[SORT_COLUMN_INDEX]));

    if (dt1==dt2) {
        return 0;
    }
    if (dt1<dt2) { 
        return -1;
    }
    return 1;
}
function ts_sort_numeric(a,b) {
    var aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    aa = clean_num(aa);
    var bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
    bb = clean_num(bb);
    return compare_numeric(aa,bb);
}
function compare_numeric(a,b) {
    var a = parseFloat(a);
    a = (isNaN(a) ? 0 : a);
    var b = parseFloat(b);
    b = (isNaN(b) ? 0 : b);
    return a - b;
}
function ts_sort_caseinsensitive(a,b) {
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]).toLowerCase();
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]).toLowerCase();
    if (aa==bb) {
        return 0;
    }
    if (aa<bb) {
        return -1;
    }
    return 1;
}
function ts_sort_default(a,b) {
    aa = ts_getInnerText(a.cells[SORT_COLUMN_INDEX]);
    bb = ts_getInnerText(b.cells[SORT_COLUMN_INDEX]);
    if (aa==bb) {
        return 0;
    }
    if (aa<bb) {
        return -1;
    }
    return 1;
}
function addEvent(elm, evType, fn, useCapture)
// addEvent and removeEvent
// cross-browser event handling for IE5+,   NS6 and Mozilla
// By Scott Andrew
{
    if (elm.addEventListener){
        elm.addEventListener(evType, fn, useCapture);
        return true;
    } else if (elm.attachEvent){
        var r = elm.attachEvent("on"+evType, fn);
        return r;
    } else {
        alert("Handler could not be removed");
    }
}
function clean_num(str) {
    str = str.replace(new RegExp(/[^-?0-9.]/g),"");
    return str;
}
function trim(s) {
    return s.replace(/^\s+|\s+$/g, "");
}
function alternate(table) {
    // Take object table and get all it's tbodies.
    var tableBodies = table.getElementsByTagName("tbody");
    // Loop through these tbodies
    for (var i = 0; i < tableBodies.length; i++) {
        // Take the tbody, and get all it's rows
        var tableRows = tableBodies[i].getElementsByTagName("tr");
        // Loop through these rows
        // Start at 1 because we want to leave the heading row untouched
        for (var j = 0; j < tableRows.length; j++) {
            // Check if j is even, and apply classes for both possible results
            if ( (j % 2) == 0  ) {
                if ( !(tableRows[j].className.indexOf('odd') == -1) ) {
                    tableRows[j].className = tableRows[j].className.replace('odd', 'even');
                } else {
                    if ( tableRows[j].className.indexOf('even') == -1 ) {
                        tableRows[j].className += " even";
                    }
                }
            } else {
                if ( !(tableRows[j].className.indexOf('even') == -1) ) {
                    tableRows[j].className = tableRows[j].className.replace('even', 'odd');
                } else {
                    if ( tableRows[j].className.indexOf('odd') == -1 ) {
                        tableRows[j].className += " odd";
                    }
                }
            } 
        }
    }
}


また、URL先(と上のコピペ)の実装では▲マークの表現にgif画像を使っているのですが、個人的に画像を用意するのが面倒だったため、image_up系はこのような文字列に変更しました。
cell.innerHTMLに代入するHTML文の中の、<span>タグ内の<img>内をそのままimage_noneに書き換える形です。
他の箇所もそれぞれ書き換えました。

var image_up = '&nbsp;&nbsp;▲▽';
var image_down = '&nbsp;&nbsp;△▼';
var image_none = '&nbsp;&nbsp;▲▼';

HTML側

ここでは普通にheadタグ内でsortable.jsを設定し、bodyタグ内でテーブルを作成します。
ソートができるようにするには<table>のclassにsortableを追加するだけです。
ここで、「購入」ボタンはソートできてもしょうがないので、ソート無しの設定にします。classにunsortableを追加することで、この列ではソートしないように設定することができます。

<head>
    <!-- 色々省略 -->
    <script src="./js/sortable.js" type="text/javascript"></script>
</head>
...
<body>
<div style="height:250pt; width:100%; overflow-y:scroll; margin-top: 10px;">
    <table id="eattable" border="solid gray 1px" class="sortable">
        <thead>
            <tr>
                <th class="th_1">名前</th>
                <th class="th_2">分類</th>
                <th class="th_3">収穫日時</th>
                <th class="th_4">おいしさ</th>
                <th class="th_5 unsortable">購入</th>
            </tr>
        </thead>
        <tbody id="eattablebody"></tbody>
        <!-- テーブルの中身はJSONデータから取得して別で設定 -->
    </table>
</div>
</body>

また、このままだとリストを更新した際に、ソート状態は解除されるのに「▲▽」「△▼」の表示が残ってしまいます。
なのでリスト更新処理を書いた後は以下のようにしてテーブル名内の▲マークを元の状態に戻すと良いかと思います。

var allspans = document.getElementsByTagName("span");
for (var ci=0;ci<allspans.length;ci++) {
    if (allspans[ci].className == 'sortarrow') {
        allspans[ci].innerHTML = '&nbsp;&nbsp;▲▼';
    }
}

CSS

ここは人それぞれの好みがあるかと思うため読み飛ばしOKです。
個人的にテーブル名にリンクの下線が引いてあるのはあまり見ないのでリンク線は無し、▲マークが黒だと列名の邪魔なので色を灰色にしました。

a.sortheader{
    text-decoration: none;
    color: black;
}

.sortarrow{
    color: gray;
}

注意点

テーブル内の列名(文字列)に対してリンク(<a>タグ)が設定されているため、テーブル名の文字列部分をクリックしないとソートできないようになっています。
なので、上の画像のように
「名前 ▲▼      」
のような不格好な状態になっている列名の右半分の白い部分をクリックしてもソートされないため、場合によっては文句を言われるかもしれません。

最後に

URL先を見ると、固定行を用意する、行ごとに色を変える、という実装も可能なようです。
今回実現したい内容には不要だったので試しませんでしたが、どちらもソート可能設定と同じく簡単な設定で実現可能なようなので、大変ありがたいです。
あと「名前 ▲▼    」みたいなのじゃなくて「名前    ▲▼」みたいにしたかったんですけども、「::after」とかを使うと3パターンの切り替えが大変そうだったので日和りました。

固定行と色

・行ごとに色を変える
 奇数行のclassに「odd」、偶数行のclassに「even」が追加されるため、CSSで指定すればOKです。
 (上に貼った画像の表はElements見てもなぜかodd, even等の設定が無かった…やはりファイル取得してくるパターンだとダメなのか)
・固定行を作る
 固定にしたい行(tr)のclassに「sortbottom」を追加すればOKです。
 ただし、「bottom」とついているだけあって最下行に固定されることに注意してください。

参考リンク

https://blog.webcreativepark.net/2007/09/26-024416.html (日本語対応版)
https://blog.webcreativepark.net/2007/09/24-172811.html (↑の元の紹介記事)

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

axiosの基本的な使い方

axiosとは

HTTP通信(データの更新・取得)を簡単に行うことができる、JavaScriptのライブラリ。
APIを提供するクラウドサービスに対して、データを送受信することができる。

インストール

$ npm install axios

CDNの使用

<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>

主なメソッド

Githubでは以下のメソッドが使用できると記載されています。

スクリーンショット 2021-02-25 19.05.11.png
今回は基本的なメソッドである、get,post,delete,put、またconfigオブジェクトについて軽く紹介してきます。

axios.get(url)

HTTP通信(API通信)でサーバーからデータを取得するには、axios.get()関数を使用することで実現可能です。

// GETリクエスト(通信)
const url = axios.get("http://localhost:3000/")

    // thenで成功した場合の処理
    .then(() => {
        console.log("ステータスコード:", status);
    })
    // catchでエラー時の挙動を定義
    .catch(err => {
        console.log("err:", err);
    });

結果
ステータスコード200 //http通信でデータ取得の成功

axios.post(url)

データをサーバーへ情報を送るときは、axios.post()メソッドで可能

//POSTリクエスト(通信)
const data = { firstName : "Taro", lastName : "Yamada" }
const url = axios.post("http://localhost:3000/user/123", data)

        .then(() => {
            console.log(url)
        })

        .catch(err => {
        console.log("err:", err);
        });

結果
Yamada Taro //サーバーへYamada Taroを送信

axios.delete(url)

データを削除する場合は、axios.delete()メソッドを使います。ただし、データIDで識別するため、IDを指定しないといけません。

const url = axios.delete("http://localhost:3000/user/123")

        .then(() => {
            console.log('削除ID:',url)
        })

        .catch(err => {
        console.log("err:", err);
        });

結果
削除ID:123 

axios.put(url)

データを更新する時は、axios.pu()メソッドを使いますが、こちらもIDを指定しないといけません。

const data = { firstName : "Taro", lastName : "Qiita" }
const url = axios.put("http://localhost:3000/user/123",data)

        .then(() => {
            console.log('データ更新:',url)
        })

        .catch(err => {
        console.log("err:", err);
        });

結果
データ更新Qitta Taro

configオブジェクト

configオブジェクトの形でも、上記のメソッドを使用することができます。

axios({
     method: "get",
     url: "http://localhost:3000/user/123",
     data: {firstName : "Taro", lastName : "Yamada" }
    })

    // thenで成功した場合の処理
    .then(() => {
        console.log("ステータスコード:", status);
        console.log(res.data)
    })
    // catchでエラー時の挙動を定義
    .catch(err => {
        console.log("err:", err);
    });

結果
ステータスコード200
Yamada Taro

参考にさせていただいたサイト

https://reffect.co.jp/vue/vue-axios-learn#i-2

感想

axiosを使用することで、API通信が簡単に行えることがわかりました。また、非同期処理のasync,awaitを使用することで可読性の高い非同期通信を行えるので、実践してみます。

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

【javascript】javascriptの記述をhtmlファイルから呼び出す

超初心者向けなお話です。

index.html
<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="ie=edge">
    <title>Document</title>
</head>

<body>
    <h1>JavaScriptのお勉強</h1>
    <script src="main.js">

    </script>
</body>

</html>

<script src="main.js"></script>この記述を書くことによって、同じディレクトリ内のmain.jsファイルの記述を呼び出すことができます。

main.js
alert('がんばれ')

お勉強がんばりましょう。

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

webpack.mix.jsのmix.jsで親ディレクトリ(parent)にjsファイルを出力する方法

webpack.mix.jsで下記のディレクトリ配置にしてnpm dev buildしてみる

ソース
/www/src/app.js
出力
/www/public/js/app.js

mix.setPublicPath.js('app.js', '../public/js')
✔ Compiled Successfully in 17215ms
/public/js/app.js

と表示されるも/www/public/js/はからっぽ...

検索していると下記のgithub issueを発見

Unable to mix output .js file to the parent directory
https://github.com/JeffreyWay/laravel-mix/issues/1220

mix.setPublicPath('../').js('app.js', 'public/js')

として設定すると無事希望通りのディレクトリに出力された

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

Next.js + RailsでポートフォリオサイトをISR対応&メンテナンスフリー化した

2年ほど前にNuxt.jsを使ってポートフォリオサイトを作成しました。
今回、このサイトをNext.js + Railsでリニューアルしたので、経緯を記事にまとめます。

リニューアル後のページ

https://portfolio.y-uuu.net/
image.png

デザインは前回のものを踏襲していて、ほとんど変わっていません。

リポジトリ

リニューアルの目的

Next.jsを使って何か作りたい

nextjs.png

昨年からReactやNext.jsを触ってノウハウを蓄積するようにしています。私自身普段はRailsを使った開発をしているので、Next.jsを採用するとしたらRailsと組み合わせて使う可能性が高いです。

昨今のフロントエンド界隈の盛り上がりを横目に、フロントエンドにNext.js/バックエンドにRailsを用いて、何か作りたいと思っていました。

デプロイなしで内容を更新したい

image.png

前回、勢いでポートフォリオサイトを作成したものの、単なる静的ページとして公開していたので記載内容を変更するためにはVueコンポーネントを直接編集する必要がありました。

今回は管理ページを別途作成し、ログインすることで記載内容を容易に追加・変更できるようにしています。

メンテナンスフリーにしたい

image.png

image.png

リニューアル後のサイトではQiitaやZennに公開した記事、SpeakerDeckに公開したスライドをを自動的に収集し、メンテナンスしなくても内容が自動更新されるようになっています。

システム構成

Next.jsのデプロイ先としてVercelを、Railsのデプロイ先としてherokuを使っています。
また、画像の格納先としてAWSのS3を利用しました。

Untitled_Portfolio_Site_-_Cacoo.png

ライブラリ・フレームワーク

フロントエンド

  • React
  • Next.js
  • React Hook Form
  • react-dropzone
  • react-spinners
  • react-tippy
  • axios
  • SWR
  • Tailwind CSS

バックエンド

  • Ruby on Rails
  • devise
  • faraday
  • rails_same_site_cookie
  • AWS SDK for Ruby V3

実装上のポイント

ISR(Incremental Static Regeneration)

Next.jsを使ってISRを実現しています。herokuがレスポンスを返す時間に関わらず、来訪者がすぐにページを閲覧できるようにする狙いです。

以下はISRの挙動の解説です。

Next.js(ISR有効)は、アクセスがあった際に生成済みの静的ページをレスポンスします。このとき、herokuへのアクセスは発生しません。

Notification_Center.png

前回のページ生成から指定した時間を経過した後にアクセスが発生すると、静的ページを再生成します。このときNext.jsはサーバーサイドでページを再生成するのを待たず、いったん前回の静的ページをレスポンスします。

Untitled_Portfolio_Site_-_Cacoo.png

再生成が完了すると、以降その静的ページをレスポンスします。

前提として、herokuのFreeプランだと30分間アクセスがない場合にdynoがSleepするので、次にアクセスがあった場合にdynoが起動するまで数十秒ほど待たされてしまうという問題があります。本来の使い方ではないかもしれませんが、バックエンドの処理に時間がかかる場合でも生成済みのページを即時にレスポンスできるという点で、ISRは有用だと感じました。

記事・スライドの自動収集

yuuu-portfolio-v2-api_·_Scheduler___Heroku.png

Heroku Schedulerを使うことで、日次でrakeタスクを実行して、記事・スライドを自動収集しています。

QiitaはAPIを、ZennはFeedを使って、自分自身の記事を収集しDBに保存しています。

namespace :qiita do
  desc "Fetch articles from qiita"
  task fetch: :environment do
    res = Faraday.get('https://qiita.com/api/v2/users/Y_uuu/items?per_page=100')
    return if res.status != 200

    items = JSON.parse(res.body)
    items.each do |item|
      next if Article.find_by(link: item['url']).present?

      item_res = Faraday.get(item['url'])
      next if res.status != 200

      Article.create(
        title: item['title'],
        body: item['body'].truncate(100) + '...',
        published_at: Time.zone.parse(item['created_at']),
        link: item['url'],
      )
    end
  end
end

SpeakerDeckは収集方法を悩んだのですが、よくよく調べると https://speakerdeck.com/yuuu.atom のように、自身のアカウント名の末尾に .atom を付与することでFeedを取得できることがわかったので、これを使って収集するようにしました。

認証

image.png

最初は「SPAの認証はJWT」という思い込みがあったのですが、いろいろ調べていくうちに「cookieを使った認証でも問題ない」との結論に至りました。認証のバックエンドもRailsで、deviseというGemを使ったよくある実装です。

ただし、今回はCrossOriginな構成のためつまづきポイントが多くありました。具体的な実装方法は別記事にまとめたので、興味のある方は参照ください。

Rails 6.1対応版: APIモードのRailsに対してCrossOriginなSPAからSession認証する方法

ファイルアップロード

image.png

当初は、バックエンドがRailsということで、Active Storageを使ってファイルアップロードを実現する予定でした。実装をしていく上で「わざわざActive Storageを使う必要があるのか?」という疑問が生じ、最終的にはS3の署名付きURLを使ってアップロード・閲覧する方式に変更したという経緯があります。

こちらも別記事にまとめたので、興味のある方は参照ください。

RailsをバックエンドとしたSPAでのファイルアップロード機能の作り方に悩んだ話

感想

ISRが良い

image.png

SSRとSSGのいいとこ取りができていて良いです。SSGのようにデプロイのビルドが長くなることもなく、かつSSRを使った場合に比べてページの表示が高速なので満足です。

Vercelが良い

image.png

今回初めてVercelを使ってみたのですが、GitHubのリポジトリを指定するだけで簡単にCI/CDを構築できました。前回Netlifyを使った時も同様の感動があったのですが、とかくNext.jsを使う場合はほとんど設定が不要で、噂通りVercelとの組み合わせがベストだと実感しました。

バックエンドのRails・herokuも良い

image.png

死んだと言われて久しいRailsですが、自分にとってはやはり最速で実装ができるフレームワークです。バックエンドは必要最低限実装しつつ、フロントエンドの実装に注力するスタイルで開発が進められました。

herokuを使うことでデプロイも非常に簡単でした。

まとめ

個人的には十分満足できるポートフォリオサイトが完成しました。

Next.js + Railsで何か作ろうとしている人の参考になれば幸いです。質問・感想などありましたら、ぜひコメントをお願いします。

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

【imi-enrichment-date】年号を含む日付表示の正規化

imi-enrichment-dateとは

経産省のジービスインフォというサイトで公開されているオープンソースライブラリの1つで、
令和3年2月26日のような日付表示を2021-02-26といった形式に正規化することができます。
MITライセンスで公開されているので商用利用も可能です。

インストールの仕方

このツールはnpmのようなパッケージ頒布サイトでは公開されていないようで、
経産省のサイトあるいはプロジェクトのGitHubページからダウンロードして用います。

# on your own project
npm install https://info.gbiz.go.jp/tools/imi_tools/resource/imi-enrichment-date/imi-enrichment-date-1.0.0.tgz

使い方

ここでは自分のプロジェクトに組み込んで使う方法を紹介します。

ライブラリから適当な名前で読み込んだ関数は、引数に2021年2月26日令和三年二月二六日のような日付を表す文字列か、{"@type": "日付型", "表記": "2021年2月26日"}のようなオブジェクトを引数にとります。

const normarizeDate = require("imi-enrichment-date");
console.log(normarizeDate("令和3年2月26日"));
// or
const date = {
    "@type": "日付型",
    "表記": "令和3年2月26日"
};
console.log(normarizeDate(date));
出力
{
  '@context': 'https://imi.go.jp/ns/core/context.jsonld',
  '@type': '日付型',
  '表記': '令和3年2月26日',
  '標準型日付': '2021-02-26'
}

出力はyyyy-mm-dd形式で、上記のようなオブジェクトとして返ってきます。

どのように変換を行っているのか気になったのでソースコードを見てみると、「漢数字・全角の算用数字を半角の算用数字に変換」→「年号が含まれる場合は正規表現で処理」といった流れで変換がなされているようでした。

どのようなパターンに対応しているのか

日付表示にはさまざまなフォーマットがありますが、このライブラリが対応しているフォーマットは以下のとおりです。

形式 備考
年・月・日がなんらかの文字で区切られているもの 2020/01/01 区切り文字は数字以外ならなんでもよい
先頭に元号を含むもの 平成10年10月10日 明・昭など一文字や、㍽・㋿のような合字、あるいはH・hのようなアルファベットでもよい
漢数字を含むもの 一九九九年十二月三〇日 三十ではなく三〇のような書き方をしてもOK
元年という表現を含むもの 令和元年3月24日

2/26/2021(2021年2月26日)のようなアメリカ式の日付表示には対応していませんが、日本で使われている日付表示の方式にはほぼ対応しているライブラリといえます。

ひとつだけ気をつけるべきこととしては、21年2月26日のように年の上二桁を省略した書き方をすると、意図せず1921-2-26となってしまう点があります。これはエラーにはならないので注意が必要です。

また(これはあまり一般的ではありませんが)、西暦2021年2月26日のような表現はエラーになります。役所に行ってなにか申請書を書こうとすると、最近では配慮が進んでいて年号ではなく「西暦」に丸がつけられるようになっていることがありますよね。西暦と年号表示を並行して使用しているシステムでは注意が必要です。

リンク

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

RustでWebAssemblyを使ってみた時の記録

この記事について

プログラミング独学3年目の文系大学生の開発メモ。

なぜWebAssemblyなのか

特に理由はありません(笑)
最近話題になっていると聞いて触ってみました。

なぜRustなのか

これも特に理由なしです。
システムプログラミング言語なのに低レベルだけではなく、色々なアプリケーションを開発するためのライブラリが揃っていると聞いて興味を持ちました。

開発環境 (2021/02/26現在)

Windows10 Education バージョン 20H2 (Windows10 Home バージョン 20H2上のVMware Workstation Player 16仮想マシン)
Visual Studio Code
gcc 8.1.0
node 11.13.0
npm 6.9.0
rustc 1.50.0
cargo 1.50.0

開発開始

MDNに掲載されているRust用のWebAssembly入門に沿ってやります。
WebAssembly自体をもっと知りたいという方はこちらから確認してください。

開発メモ

  • Rustをインストール際にC++のBuild Toolsのインストールを促された。 これを無視してやったらcargo install wasm-packするときにエラーが出た。 普段はVisual Studio Communityを使うことがほぼ無かったので、インストーラーを入れ、C++のデスクトップ開発用のツールを一通り入れたらエラーは治った。 (今回とは関係ないが、gccを入れておかないとエラーが出る場合もあるらしい。) -Rustのコード内の#[wasm_bindgen]という記述はアトリビュートと呼ばれ、直下の文のみを修飾してくれるらしい。 あくまで直下のみなので、何度も記述されてるからと言って2個目以降を省いてはダメ。
  • Rustにおいて「クレート」とは他言語でいうライブラリやパッケージのこと。
  • Rustにはクラスは存在しないけどstruct(構造体)とimpl(インプリメンテーション)という特殊な構文で構造体にメソッドをつけることが可能。 だから、実質オブジェクト指向に開発できる。
  • npmでpublicに公開するとき、package.jsonのnameに@xxxを含む記述があった場合、組織を予め用意しておく必要がある。

参考

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