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

ポップアップの背景色をカスタマイズする

マーカーをクリックしたときや、任意の位置にポップアップを表示させることができます。
既定の背景色は「白」ですが、css でカスタマイズできます。

こんな見た目にカスタマイズできます

image.png

const opt = {
  container: 'map',
  style: {
    version: 8,
    sources: {
      OSM: {
        type: "raster",
        tiles: [
          "https://a.tile.openstreetmap.org/{z}/{x}/{y}.png",
        ],
        tileSize: 256,
        attribution:
        "OpenStreetMap",
      },
    },
    layers: [{
      id: "BASEMAP",
      type: "raster",
      source: "OSM",
      minzoom: 0,
      maxzoom: 18,
    }],
  },      
};

opt.center = [138.73072, 35.36286];
opt.zoom = 13;
const map = new mapboxgl.Map(opt);

map.addControl(new mapboxgl.NavigationControl());

const html = `<H3>富士山</H3>
<span>
富士山(ふじさん、英語: Mount Fuji)は、静岡県(富士宮市、裾野市、富士市、御殿場市、駿東郡小山町)と、山梨県(富士吉田市、南都留郡鳴沢村)に跨る活火山である。標高3776.12 m、日本最高峰(剣ヶ峰)の独立峰で、その優美な風貌は日本国外でも日本の象徴として広く知られている。<a href='https://ja.wikipedia.org/wiki/%E5%AF%8C%E5%A3%AB%E5%B1%B1' target="_blank">Wikipedia より</a>
<span>
`;

// create the popup
const popup = new mapboxgl.Popup({ 
  //anchor: 'bottom',
  className: 'my-class', 
  closeButton: false,
})
.setMaxWidth('400px')
.setHTML(html);

// create the marker
new mapboxgl.Marker()
  .setLngLat([138.73072, 35.36286])
  .setPopup(popup) // sets a popup on this marker
  .addTo(map)
  .togglePopup();
body { margin: 0; padding: 0; }

/* $popupBkColor: darkblue; */

.my-class {
  color: white;  
}

.my-class .mapboxgl-popup-content {
  background-color: darkblue;
  box-shadow: 6px 6px 6px rgba(1,1,1,.4);
}

.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {
  align-self: center;
  border-bottom: none;
  border-top-color: darkblue;
}

.mapboxgl-popup-anchor-top .mapboxgl-popup-tip {
  align-self: center;
  border-top: none;
  border-bottom-color: darkblue;
}

.mapboxgl-popup-anchor-left .mapboxgl-popup-tip {
  align-self: center;
  border-left: none;
  border-right-color: darkblue;
}

.mapboxgl-popup-anchor-right .mapboxgl-popup-tip {
  align-self: center;
  border-right: none;
  border-left-color: darkblue;
}

.mapboxgl-popup-anchor-top-left .mapboxgl-popup-tip {
  align-self: flex-start;
  border-top: none;
  border-left: none;
  border-bottom-color: darkblue;
}

.mapboxgl-popup-anchor-top-right .mapboxgl-popup-tip {
  align-self: flex-end;
  border-top: none;
  border-right: none;
  border-bottom-color: darkblue;
}

.mapboxgl-popup-anchor-bottom-right .mapboxgl-popup-tip {
  align-self: flex-end;
  border-bottom: none;
  border-right: none;
  border-top-color: darkblue;
}

.mapboxgl-popup-anchor-bottom-left .mapboxgl-popup-tip {
  align-self: flex-start;
  border-bottom: none;
  border-left: none;
  border-top-color: darkblue;
}

解説

Popup 生成時のオプションに className: 'my-class' などとして任意のクラス名を割り当てることができます。
が、これは 吹き出し全体(コンテンツ部と矢印<三角形のつなぎ>部)を示します。
このクラスの backgroud-color を指定しても期待した結果にはなりません。
既定の吹き出しっぽいデザインを完全に捨てて、オリジナルの吹き出しを配置する場合は使用することになると考えられます。

ポップアップのコンテンツ部は .mapboxgl-popup-content というクラスを上書きすることで背景色などを変更できます。
矢印<三角形のつなぎ>部のところはちょっと面倒で、ポップアップが表示される8方向に応じて bottom, left, top, right, top-right, top-left, bottom-right, bottom-left のそれぞれのクラスを上書きする必要があります。

Popupanchor を指定しない場合は、ポップアップの出現方向が可変になるため、8方向すべての CSS 定義が必要になります(次の図は bottom-right の例)。

image.png

anchorbottom と設定した場合は、ポップアップは上方向にしか表示されず、CSS上書きは bottom だけで大丈夫そうです。

ポップアップの幅は、Popup.setMaxWidth('400px') として拡張できます。高さはコンテンツ次第なようなので、制限する場合は自力で行う必要がありそうです。

ギモン

anchor の CSS セレクタは

.mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {

としていますが、

.my-class .mapboxgl-popup-anchor-bottom .mapboxgl-popup-tip {

こう書きたかったのですよね。でも効いてくれませんでした。
「一つのマーカーのポップアップのみ、背景色を変える」ということができないかな?と考えたのですが・・・。

参考

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

【JavaScript】JavaScriptにおけるクロージャーとは

※当方駆け出しエンジニアのため、間違っていることも多々あると思いますので、ご了承ください。また、間違いに気付いた方はご一報いただけると幸いです。

クロージャーとは

「外側のスコープにある変数への参照を保持できる」という関数が持つ性質のこと。

...なんのこっちゃ。

実際にクロージャーを用いたコード

function outside() {

    let count = 0;

    function inside() {
        count = count + 1;
        return count;
    }
    return increment;
}

このような関数があるとします。
この時、

const myCounter = outside();
console.log(myCounter());

このように、outsideを実行して、変数myCounterに格納し、中身をみてみると。

>>  function inside() {
        count = count + 1;
        return count;
    }

当然、関数が返ってきます。

では、下のようにmyCounterを呼び出した結果をみてみると

console.log(myCounter());

>> 1

この時、countは、 「スコープチェーン」 によりoutside配下のcountの参照します。
スコープチェーンとは、
変数を参照する際には、現在のスコープから外側のスコープへと順番に変数が定義されているかを確認することです。
つまり、現在のスコープ

    function inside() {  //ここ  }

に、countなんて定義されてないぞ!!しゃーない、外に見に行くか。はー、めんどくさ。

function outside() {
    let count = 0;
}

おお、すぐいた!!
ということで、一つ外側のスコープoutsideにcountが定義されてたので
めでたく count が 0であることが分かりました。
そして、insideで定義された処理を実行した結果、当然返り値は

>>1

となるのです。
では、二回実行するとどうなるか。

console.log(myCounter());
console.log(myCounter());

>> 1
>> 2

となります。。。。なんでや。

これこそが 「クロージャー」 の動きとなります。

なぜ、このような挙動をとるのか。

JavaScriptの「静的スコープ」と「メモリ管理の仕組み」によります。

「静的スコープ」について

JavaScriptのスコープには、どの識別子がどの変数を参照するかが静的に決定されるという性質があります。

つまり、どこでinside関数を呼び出そうが、inside内のcountは、outside直下のcountを参照するということですね。

「this」という特別なキーワードだけは、呼び出し元によって動的に参照先が変わります。

「メモリ管理の仕組み」について

変数のデータはパソコンの上のメモリに保存されます。これは、決まったタイミングで解放しないといけません。メモリは有限だからです。
JavaScriptでは 「ガベージコレクション」 という「どこからも参照されなくなったデータを不要なデータと判断して自動的にメモリ上から解放する仕組み」をとっています。

逆にいうと、参照され続けている限りメモリには保存され続けます。
「関数の中で作成したデータは、その関数の実行が終了したら解放される」訳ではありません。

例えば

function outX () {
  let x = 1;
  console.log(x);
}
outx();

この関数が実行された時、xが参照する1が保管されたメモリが確保されますが、関数終了後に、このメモリは解放されます。これは「関数が終了したから」ではなく「xが参照されなくなったから」です。

よって表題例の場合
内側の関数内から、外側の関数内の変数countを参照しているため、メモリが解放されず値を保持し続けるということですね。

よって一回目の

myCounter();

が実行された段階で、countには 1 が格納され、メモリ上に保持されるので

二回目に

myCounter();

が呼び出された時、返り値は 2 となります。

また、このように再度outside()を実行しなおしすと、

const myCounter = outside();
const myCounter2 = outside();

console.log(myCounter()); // => 1
console.log(myCounter2()); // => 1

と、別々の関数が作り直されて let count = 0が 各々で実行されるので、当然結果は上記のようになります。

こんなことしなくても、関数内でプロパティーを定義したらいいやん。

と思われるかもしれません。

下記のcountUp関数は、自身のプロパティcountの値に +1 をして、呼び出し元に返却する関数です。

function countUp() {
    // countプロパティを参照して変更する
    countUp.count = countUp.count + 1;
    return countUp.count;
}
// 関数オブジェクトにプロパティとして値を代入する
countUp.count = 0;
// 呼び出すごとにcountが更新される
console.log(countUp()); // => 1
console.log(countUp()); // => 2

これでも、プロパティとして値を保持し続けます。しかしクロージャーとの違いは、

関数の外からでも値を変更できてしまう。ということです。

countUp.count = 0;

そもそもこの式が、外から関数のプロパティーの値を操作してますよね。
関数をconstで 変数に格納して定義したとしても、関数もオブジェクトでありミュータブルであるため、プロパティの値の変更は可能です。

詳しくはこっちの記事をごらんください。

https://qiita.com/sho_U/items/3d64d23fe31ed09e9dcb

上記のクロージャーだと、関数の外から直接countの値を変更することはできません。

関数の外から count = 100; みたいなことをしても 関数スコープに阻まれるので操作できないのですね。

これこそがクロージャー(「閉ざされた。」)の由来であり、一番のメリットであります。この辺は勝手な想像ですが。

ということで 自分なりに学習したクロージャーについてでした。

。。。合ってますか?

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

【備忘録】htmlのselectで特定のものを選んだときに追加項目を出し、かつ必須項目に設定する

やりたいこと

formのselect(プルダウンメニュー)で特定のものを選んだら
1.追加項目を出す
2.追加項目に必須項目を設定する

Qiita01.gif

結論

jsを使って入力可否を入れ替える。

index.html
<form method="post" action="〇〇.php" name="〇〇_form">
  <!--もともと表示している項目-->
  <table>
    <tr>
      <th>生産地</th>
      <td><input name="生産地" type="text" id="name" required></td>
    </tr>
    <tr>
      <th>種類</th>
      <td>
        <select name="種類" id="fruitstype" onchange="entryChange();" required>
          <option hidden value="">選択してね</option>
          <option value="ぶどう">ぶどう</option>
          <option value="りんご">りんご</option>
        </select>
      </td>
    </tr>
  </table>
  <!--りんごが選ばれたら表示したい項目-->
  <table id="fruitstypeID">
    <tr>
      <th>品種</th>
      <td><input class="fruitstype-additional-items" size="30" type="text" name="品種" required /></td>
    </tr>
    <tr>
      <th>糖度</th>
      <td>
        <select class="fruitstype-additional-items" name="糖度" required>
          <option hidden value="">選択してね</option>
          <option value="あまい">あまい</option>
          <option value="あまくない">あまくない</option>
        </select>
      </td>
    </tr>
    <div>
      <input type="submit" value="確認" />
    </div>
</form>

script.js
function entryChange() {
  if (document.getElementById('fruitstype')) {
    id = document.getElementById('fruitstype').value;
    //りんごが選ばれたら
    if (id == 'りんご') {
    //追加項目を表示
      document.getElementById('fruitstypeID').style.display = "";
    //追加項目の入力を有効化
      $('.fruitstype-additional-items').removeAttr('disabled');
    //りんごが選ばれなかったら
    } else if (id != 'りんご') {
    //追加項目を非表示
      document.getElementById('fruitstypeID').style.display = "none";
    //追加項目の入力を無効化
      $('.fruitstype-additional-items').attr('disabled', 'disabled');
    }
  }
}
window.onload = entryChange;

考え方

必須項目であるrequiredをつけ外しするのではなく入力可否であるdisabledをつけ外しする。
ぶどうを選んだときでも追加項目が入力可能のままだと確認画面で入力されていないまま表示されてしまうが、disabledを設定することによって確認画面での表示も、必須項目未入力判定も出ない。

他に試した方法

1.PHPの条件分岐(ボタンを押すなどして受け渡しをしなければ発動しないので違う)
2.非同期ajax(その画面のままやるなら非同期型、もっと単純なはず)
3.requitedのつけ外し(思ったままシンプルに)
$('.fruitstype-additional-items').attr('requited');が出来るんじゃないかと思っていたが、出来なかった。

まとめ

「requitedのつけ外し」ができそうで出来なくてかなり時間を使いました。requitedのつけ外しはこんなにシンプルじゃなければ出来るようなので機会があればそっちもやってみたいな…と思います。機会があれば……

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

Nuxtでlayout適用時にページが表示されないときの解決策

Nuxtでlayout適用時にページが表示されない

起きていること

layoutを適用した場合、pagesの<template>タグで設定した内容が表示されない。

SamplePage.vue
<template>
  <div>
    <!-- いい感じのコンテンツ -->
  </div>
</template>
<style>
</style>


<script>
export default {
  layout:'MyLayout'
};
</script>

解決策

layoutでは、<nuxt/>タグが必要です。
この<nuxt/>タグがlayoutに存在している場合、パスと一致するページ(pagesフォルダ内のファイル)を呼び出します。

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

【TypeScript】ObjectのKeyに変数でアクセスする

1. はじめに

こんにちは!みなさま楽しいプログラミンライフをお過ごしでしょうか?
最近TypeScriptでのReact開発を勉強しています。

ObjectのKeyに変数でアクセスしようとしたときに、
型付けのところでハマったポイントがあったので、備忘録として残しておきたいと思います。

Version
node.js 14.12.0
yarn 1.22.7
TypeScript 3.8.3

2. やりたいこと

以下のような、
object2のcategoryのvalueと、object1のkeyが一致するvalueの配列に、object2をまるっとpushしたい。object3のようになるイメージ。

const object1 = {
  category1: [],
  category2: []
}

const object2 = {
  id: 1,
  title: "nazeudon",
  category: "category1",
}

const object3 = {
  category1: [
    {
      id: 1,
      title: "nazeudon",
      category: "category1",
    },
  ],
  category2: []
}

3. JavaScriptだとこう書ける

とてもシンプル。まさに。Simple is the Best.

const cat = object2.category;
object1[cat].push(object2);

4. ハマったポイント

同じことをTypeScriptでやろうとすると。。。

const cat = object2.category;
object1[cat].push(object2);

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type '{ category1: never[]; category2: never[]; }'.
No index signature with a parameter of type 'string' was found on type '{ category1: never[]; category2: never[]; }'.(7053)

要素は暗黙のうちに 'any' 型を持っています。なぜなら、型 'string' の式は型 '{ category1: never[]; category2: never[]; }' をインデックス化するために使用できないからです。
type '{ category1: never[]; category2: never[]; }' には、型 'string' のパラメータを持つインデックスシグネチャが見つかりませんでした。

そりゃ、TypeScriptだからね。型付けしろと言うわけですよね!
interfaceで型付けしてやって〜、でも。。。

interface OBJECT1 {
  category1: OBJECT2[]
  category2: OBJECT2[]
}

interface OBJECT2 {
  id: number
  title: string
  category: string
}

const object1: OBJECT1 = {
  category1: [],
  category2: []
}

const object2: OBJECT2 = {
  id: 1,
  title: "nazeudon",
  category: "category1",
}

const cat = object2.category;
object1[cat].push(object2);

Element implicitly has an 'any' type because expression of type 'string' can't be used to index type 'OBJECT1'.
No index signature with a parameter of type 'string' was found on type 'OBJECT1'.(7053)

要素は暗黙のうちに 'any' 型を持っていますが、それは 'string' 型の表現が型 'OBJECT1' のインデックスに使用できないからです。
型 'OBJECT1' の型 'string' のパラメータを持つインデックス・シグネチャは見つかりませんでした。

おっと、これでもダメなのね。OBJECT1のインデックスにString型を指定できないと言われています。

ってことで、この解決に小一時間費やしたので、解決策を残しておきます。

5. TypeScriptでの書き方(例)

const cat: keyof OBJECT1 = object2.category as keyof OBJECT1;
object1[cat].push(object2);

こんな感じで、cat変数とobject2.categoryを共に、OBJECT1のkeyですよ、と明示してあげれば無事動きました!!

6. 最後に思ったこと

  • 自分のわかっている知識の範囲だと、型付けはバグを防げて便利だなと言う印象。
  • 自分のわからない範囲の知識が必要で、型に起因するエラーが出ると、型付けめんどくさ!ってなる。
  • 結論、もっと勉強しましょう。今回も良い勉強になりました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

超基本的なプログラミング用語の英単語メモ

日本語 ☞ 英語

変数 → variable
定数 → constant
配列 → array
オブジェクト指向プログラミング → object‐oriented programming(OOP)
演算子 → operator
識別子 → identifier
代入 → assignment
属性 → attribute
キャッシュ → cache (cashまたはcasheではない)

今日の名言

Done is better than perfect.
(完璧であることよりも終わらせることの方が重要)

-マーク・ザッカーバーグ

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

jQueryに匹敵するフレームワークを作ってやりたいので、中身を作ります。

こんばんは、Sorakimeです。

昨日の投稿を読んでくださると内容が理解できると思うので、先にそちらのほうご一読を推奨します。

昨日の投稿では、骨組みだけを作りましたがあれでは結局なにも動作しません。ですから、簡単に作り方をここに書いておきます。

メソッドというかなんというかを作る方法

昨日の投稿でやりましたね。現在以下のようなソースコードになっていると思います。

shortwrite.js
var _$={
  version: '1.0'
}
window._$=_$;

これは、機能も言った通り連想配列です。ですから、ここからもっと下にキーと値というかここで言うメソッドとその動作する機能の実装をしていばよいわけです。まずは、version: '1.0'の後に,を追加して、改行してから書いていきましょう。
例えば、ここではselectという名前で、querySelectorのそのままの実装を作ってみましょう。ちなみに、この場合には無名関数、function () {}的な書き方かアロー関数、() => {}でもよいです。ここでは、引数は仮にqueryとしておきましょう。そうすれば、以下のようなソースコードが出来上がると思います。

shortwrite.js
var _$={
  version: '1.0',
  select: (query) => {
    return document.querySelector(query);
  }
}
window._$=_$

ちょっと違う、という人もいるかと思います。あと、空白に関しては一行目と4行目以外は無視してくれてよいです。

shortwrite.js
var _$={
  version: '1.0',
  select: function (query) {
    return document.querySelector(query);
  }
}
window._$=_$

これでも違う人はまぁコピペしよう。
基本的な機能はこれで完成です。まぁ、

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <script src="shortwrite.js"></script>
</head>
<body>
  <script>console.log(_$.select())</script>
</body>
</html>

なんて文書かれたらこの場合はどうしようもありませんけどね。対処法としてはif文とかでqueryに何か入ってるかを確認してから動作させるなんてことをすればよいと思います。まぁこんな感じかなぁ。

shortwrite.js
var _$={
  version: '1.0',
  select: (query) => {
    if (query!=null) {
      return document.querySelector(query);
    } else {
      return false;
    }
  }
}
window._$=_$

とまぁこんな感じで機能をどんどん追加していきます。ちなみに何個かメソッドを追加していく方法は知っていますが、メソッドなしの動作方法は知りません。考えておきますが、忘れてる可能性もあります。

ちなみに、Class文とか使うとIEでのサポートができなくなり、少し文も見にくい気がするのでこっちで書いてます。アロー関数使ってる時点でIE無理だけどね。
MDNへGO!
まぁ、最適というだけで使えるので、ファイルサイズ削減にもいいんじゃないかな。昨日さえ思いついたら、全部乗っけていって作ってもいいかも。

ということで、若干雑かもしれないですが終わります。

最後まで読んでくださり、ありがとうございました。

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

Unity WebGL C# <=> JS (jslib)

Unity (#1) Advent Calendar 第1日目を飾るにはものすごくふさわしくない超地味な内容となっています。申し訳ございません。

以前に
Unity(WebGL)でC#の関数からブラウザー側のJavaScript関数を呼び出すまたはその逆(JS⇒C#)に関する知見(プラグイン形式[.jslib])
という長ったらしいタイトルの記事を書きましたが、今回はこれの更新版+αという内容となっています。
前回の記事はもう古くなってしまったので、改めて調査をしました。
(古い記事は一応古いバージョンとして残しておきます)

調査したUnityのバージョン: 2019.4.1f1 (と2020.2.0b2.3094)
(文中ではそれぞれ 2019, 2020 と省略して表記します)

どうしてもES6+でコードが書けない

なんかemscriptenの最新バージョンだとES6+でコードが書けるようになったとかならないとか。ただしUnityで使用されているemscriptenではいまだ(Unity 2020)にES6+でコード書くと怒られます。

同じ関数名の関数はどちらかが上書きされる

複数の.jslibを用意してコンパイルすると、同じスコープ(ブロック)に展開されます。
なので、別々の.jslibファイルであっても同じ関数名の関数を定義した場合、どちらかが上書きされてしまいますので注意が必要です。ですので、名前が被らないような少し長めな関数名にすることがいいでしょう。
(どのような順番で上書きされるのかまでは未調査)
特に、アセットストアにあるWebGL用のアセットをインポートすると高確率で.jslibファイルがありますので、もしかするとこういったアセットの.jslibの関数を上書きしてしまう可能性があることに注意してください。

逆に、これがとても有効に働くときもあります。それがUnity自身が用意している.jslib(実際は.js)の上書きです。
例えば、WebCamTextureのWebGLビルド用ソースはWebCam.jsというファイルになっています。ただ、このWebCam.jsは複数カメラが接続された状態でのカメラの選択などが行えないなどの非情なまでのバグがあります。このWebCam.jsで定義されている関数と同じ関数名で正しく動作する関数を定義した.jslibファイルを用意してコンパイルすればきちんとカメラデバイスを選択できるようになります。
きちんとデバイスを選択できるようにしたサンプル

dynCallパターン

dynCallのパターンが2019では 165パターンに結構増えており、さらに2020においては566パターンとめちゃくちゃ増えてます。
ちなみに、C:\Program Files\Unity\Hub\Editor[version]\Editor\Data\PlaybackEngines\WebGLSupport\BuildTools\Emscripten にあるemscripten-version.txtの内容を見ると2019, 2020ともに"1.38.11"となっており一緒でした。
同じバージョンなのになぜパターン数が違うのかが疑問です。

dynCallデータ型に'j'が追加される(2019~)

データ型に'j'が追加されています。
ドキュメントから引用させていただくと

  • 'v': void type
  • 'i': 32-bit integer type
  • 'j': 64-bit integer type (currently does not exist in JavaScript)
  • 'f': 32-bit float type
  • 'd': 64-bit float type

となっており'j'はBigIntとして扱うのでしょう。
(なぜ'j'なのかをDiscordで聞いてみたら'i'の次だからそうです)
とすると、HEAP64やHEAPU64があるのかと予想しましたが2020でもありませんでした。
このIssueの最後の開発者コメントで、WASM_BIGINTフラグを有効にすることでHEAP64が追加されるということです。
調べてみるとWASM_BIGINTフラグは1.39.13で追加されたもので、試せる環境が手元にないため未検証です。

Runtimeオブジェクトの廃止(2019~)

古いバージョンでは、dynCall()などのメソッドはRuntimeオブジェクトにありましたが、このRuntimeが廃止されているようで見当たりませんでした。dynCall()も見当たりません。ですので、dynCall()の代わりに直接dynCall_viといったパターン分用意されたメソッドを使用します。

古いバージョン(2018以前?)でのdynCall_viiの実行
// ptrCSFuncは、C#側関数のポインター
Runtime.dynCall('vii', ptrCSFunc, [arg1, arg2]);
新しいバージョン(2019以降)でのdynCall_viiの実行
Module.dynCall_vii(ptrCSFunc, strPtr1, strPtr2);

数値配列を渡す(引数)、数値配列を戻す(戻り値)

配列を引数に渡すと.jslib側ではポインターとして受け取ります。ですのでポインターから配列に戻す処理が必要です。
戻り値として配列を戻す場合は、_malloc()したポインターで戻すということをしなければなりません。
固定長配列でしたら、それほど苦労せずに受け渡すことができますが、問題は可変長配列の場合です。
特に戻り値として戻す場合は、1つのデータにしなければなりません。
配列の最初の要素に要素数を追加するという方法も考えたのですが、バイト配列だと最大でも要素数が256までになってしまいますのでこの方法はあまり有効ではありません。頑張って導き出した答えが、最初の4バイトを要素数にし以降を配列のデータとすることでとりあえずできました。
unsafeを使えばある程度すっきりしたコードになりパフォーマンスも上がりますが、ここではあえて(皆さん嫌いな)Marshalを使った方法をとってみました。

可変長配列を受け取り、可変長配列を戻すサンプルコード

// Unity

[DllImport("__Internal")]
private static extern IntPtr byteArrayFunc(byte[] arg, int length);

private static byte[] ptrToByteArray(IntPtr ptr)
{
    int len = Marshal.ReadInt32(ptr);
    byte[] arr = new byte[len];
    Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
    return arr;
}

private static void test() {
    // byte[]を渡し、byte[]の戻り値を受け取る
    byte[] byteArrayArg = new byte[] { 1, 2, 3 };
    IntPtr ptrByteArray = byteArrayFunc(byteArrayArg, byteArrayArg.Length);
    byte[] byteArrayRet = ptrToByteArray(ptrByteArray);
    Debug.Log($"byteArrayFunc ret: [{string.Join(", ", byteArrayRet.Select(x => x.ToString()).ToArray())}]");
}
// .jslib

$utils: {
    arrayToReturnPtr: function (arr, type) {
        var buf = (new type(arr)).buffer;
        var ui8a = new Uint8Array(buf);
        var ptr = _malloc(ui8a.byteLength + 4);
        HEAP32.set([arr.length], ptr >> 2);
        HEAPU8.set(ui8a, ptr + 4);
        return ptr;
    },
},

byteArrayFunc: function (arg, len) {
    debugger;
    var byteArray = HEAPU8.subarray(arg, arg + len);
    console.log('byteArrayFunc arg: ' + utils.arrayToString(byteArray));

    var ret = [3, 2, 1];
    var ptr = utils.arrayToReturnPtr(ret, Uint8Array);
    return ptr;
}

_free()するタイミング

前述のサンプルコードを見ていただくと一つ問題に気付いた方もいると思います。
_malloc()したのですから_free()しなければいけません。
戻り値として_malloc()したポインターを戻す場合、いつ_free()するかという問題にあたります。
return ステートメント以降で行わないといけないですが、当然returnステートメント以降は実行されません。
簡単な方法としてはsetTimeout()を使って_free()を実行することで一応、回避可能です。

// .jslib

    //前述のサンプルコードのarrayToReturnPtr関数部分
    arrayToReturnPtr: function (arr, type) {
        var buf = (new type(arr)).buffer;
        var ui8a = new Uint8Array(buf);
        var ptr = _malloc(ui8a.byteLength + 4);
        HEAP32.set([arr.length], ptr >> 2);
        HEAPU8.set(ui8a, ptr + 4);
        // setTimeout()で_free()を行う
        setTimeout(function() { _free(ptr) }, 0);
        return ptr;
    },
//...

もっと確実な方法としては、面倒ではありますが.jslib側に_free()を行う関数を用意しておき、C#側から戻り値を受け取り用が済んだらその関数を実行することです。

// Unity

// _free()を行う関数追加
[DllImport("__Internal")]
private static extern void execFree(uint arg);

[DllImport("__Internal")]
private static extern IntPtr byteArrayFunc(byte[] arg, int length);

private static byte[] ptrToByteArray(IntPtr ptr)
{
    int len = Marshal.ReadInt32(ptr);
    byte[] arr = new byte[len];
    Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
    // 用が済んだら_free()を行う
    execFree((uint)ptr);
    return arr;
}

private static void test() {
    // バイト配列を渡し、バイト配列の戻り値を受け取る
    byte[] byteArrayArg = new byte[] { 1, 2, 3 };
    IntPtr ptrByteArray = byteArrayFunc(byteArrayArg, byteArrayArg.Length);
    byte[] byteArrayRet = ptrToByteArray(ptrByteArray);
    Debug.Log($"byteArrayFunc ret: [{string.Join(", ", byteArrayRet.Select(x => x.ToString()).ToArray())}]");
}

// .jslib

// _free()を行う関数を追加
execFree(ptr) {
    _free(ptr);
}

byteArrayFunc: function (arg, len) {
    debugger;
    var byteArray = HEAPU8.subarray(arg, arg + len);
    console.log('byteArrayFunc arg: ' + utils.arrayToString(byteArray));

    var ret = [3, 2, 1];
    var ptr = utils.arrayToReturnPtr(ret, Uint8Array);
    return ptr;
}

可変長の文字列配列を渡す、文字列配列を戻す

じゃあ、可変長数値配列の受け渡しができたなら文字列配列も受け渡しできたい。文字コードはUTF8で。
数値配列の受け渡しを応用すれば一応できました。

// Unity

[DllImport("__Internal")]
private static extern void execFree(uint arg);

[DllImport("__Internal")]
private static extern IntPtr stringArrayFunc(string[] arg, int length);

private static byte[] ptrToByteArray(IntPtr ptr)
{
    Debug.Log($"ptr: {(uint)ptr}");
    int len = Marshal.ReadInt32(ptr);
    Debug.Log($"byteArry len:{len}");
    byte[] arr = new byte[len];
    Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
    execFree((uint)ptr);
    return arr;
}

private static string[] ptrToStringArray(IntPtr ptr)
{
    int len = Marshal.ReadInt32(ptr);
    Debug.Log($"stringArry len:{len}");
    IntPtr[] ptrArr = new IntPtr[len];
    Debug.Log(ptrArr);
    Marshal.Copy(IntPtr.Add(ptr, 4), ptrArr, 0, len);
    List<string> ret = new List<string>();
    for (var i = 0; i < len; i++)
    {
        var byteArray = ptrToByteArray(ptrArr[i]);
        var str = Encoding.UTF8.GetString(byteArray);
        ret.Add(str);
    }
    execFree((uint)ptr);
    return ret.ToArray();
}

public static void test()
{
    string[] stringArrayArg = new string[] { "foo", "bar", "baz" };
    IntPtr ptrStringArray = stringArrayFunc(stringArrayArg, stringArrayArg.Length);
    string[] stringArrayRet = ptrToStringArray(ptrStringArray);
    Debug.Log($"stringArrayFunc ret: [{string.Join(", ", stringArrayRet)}]");
}

// .jslib

stringArrayFunc: function (arg, len) {
    var strArray = [];
    for (var i = 0; i < len; i++) {
        var ptr = HEAP32[(arg >> 2) + i];
        var str = Pointer_stringify(ptr);
        strArray.push(str);
    }
    console.log('strArrayFunc arg: ' + strArray);

    var ret = ['hoge', 'fuga', 'piyo', 'hogera', 'ほげほげ', '叱る'];
    var retPtr = utils.stringArrayToReturnPtr(ret);
    return retPtr;
}

見ていただくとわかる通り、可変長数値配列の受け渡しもそうですが、可変長文字列配列の受け渡しはさらにめんどいことに。はっきり言ってJSONで受け渡したほうが楽です。
文字列を_malloc()した場合は、Unity側で自動で_free()してくれるのですが、C#側でUTF8に変換したいためにbyte[]に変換しているため自動で_free()されません。
(Marshal.PtrToStringAnsi()で一応、ポインターから文字列に変換することは可能ですがUTF16に変換されてしまいます。.NET5ではMarshal.PtrToStringUTF8()というまんまな関数が用意されましたが、いかんせんUnityでの.NET5のサポートはまだまだ先になるようです)

固定長数値配列の参照渡し

UnityのWebXR Exporterというアセットのソースを覗いてたら、お!っと思うコードが記述されていました。

// Unity

[DllImport("__Internal")]
private static extern void refArrayFunc(float[] a, int l);

int[] refIntArray = new int[3];
refArrayFunc(refFloatArray, a.Length);
// .jslib

refIntArrayFunc: function (arg, len) {
    Module.refIntArray = new Int32Array(buffer, arg, len);
}

このように書くことで、C#側のrefFloatArrayと.jslib側のModule.refFloatArrayは参照渡しの関係となり、.jslib側でModule.refFloatArrayの値を変更すると、(returnステートメントなしに)C#のrefFloatArrayに値が反映されます。

テクスチャー

テクスチャーは、C#側で生成し、Texture.GetNativeTexturePtr()でポインターを取得し、ポインターを.jslibの関数に渡す。.jslib側でGL.textures[ptr]でテクスチャーを参照することが可能"らしいです"
"らしいです"というのは、C#で

var texture = new Texture2D(0, 0, TextureFormat.ARGB32, false);
var ptr = texture.GetNativeTexturePtr();

としても、ptrは0になり有効な値になってくれません。
"もし、有効なポインターを取得する方法をご存じの方がいらっしゃればぜひご教授をお願いします"

仮に有効なポインターの値が取得できた場合は

// .jslib

textureFunc(ptr) {
    GLctx.bindTexture(GLctx.TEXTURE_2D, GL.textures[ptr]);
    GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, true);
    GLctx.texImage2D(GLctx.TEXTURE_2D, 0, GLctx.RGBA, GLctx.RGBA,GLctx.UNSIGNED_BYTE, video);
    GLctx.pixelStorei(GLctx.UNPACK_FLIP_Y_WEBGL, false);
}

といったコードを書くことにより、そのテクスチャーにimgエレメントの画像や、videoエレメントの映像、WebRTCなどのMediaStreamの映像などもほぼ直接的に表示できるようになる"はずです"

最後に

Unity (#1) Advent Calendar 第1日目の内容は以上となります。
ちょっとネタに走った感はありますが、.jslibを書けるようになればUnityだけではできないこと、特にJS(Web)のいろんなAPIなどをUnityに取り入れることが可能となりますのでぜひかけるようになりましょう!

あ、あとまとめたテストコードも載せておきます

// Unity

using AOT;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Text;
using UnityEngine;

public class jslibtest : MonoBehaviour
{
    [DllImport("__Internal")]
    private static extern void execFree(uint arg);


    [DllImport("__Internal")]
    private static extern byte byteFunc(byte arg);

    [DllImport("__Internal")]
    private static extern short shortFunc(short arg);

    [DllImport("__Internal")]
    private static extern int intFunc(int arg);

    [DllImport("__Internal")]
    private static extern float floatFunc(float arg);

    [DllImport("__Internal")]
    private static extern double doubleFunc(double arg);

    [DllImport("__Internal")]
    private static extern IntPtr byteArrayFunc(byte[] arg, int length);

    [DllImport("__Internal")]
    private static extern IntPtr shortArrayFunc(short[] arg, int length);

    [DllImport("__Internal")]
    private static extern IntPtr intArrayFunc(int[] arg, int length);

    [DllImport("__Internal")]
    private static extern IntPtr floatArrayFunc(float[] arg, int length);


    [DllImport("__Internal")]
    private static extern IntPtr doubleArrayFunc(double[] arg, int length);

    [DllImport("__Internal")]
    private static extern IntPtr stringArrayFunc(string[] arg, int length);

    [DllImport("__Internal")]
    private static extern void refIntArrayFunc(int[] arr, int len);

    private int[] refIntArray = new int[3];

    private void Start()
    {
        test();

        refIntArrayFunc(refIntArray, refIntArray.Length);
        StartCoroutine(chekRefArray());
    }

    IEnumerator chekRefArray ()
    {
        while(true)
        {
            yield return new WaitForSeconds(0.3f);
            Debug.Log($"refIntArray: [{string.Join(", ", refIntArray.Select(x => $"{x}"))}]");
        }
    }

    private void Update()
    {
    }

    private static byte[] ptrToByteArray(IntPtr ptr)
    {
        Debug.Log($"ptr: {(uint)ptr}");
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"byteArry len:{len}");
        byte[] arr = new byte[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        execFree((uint)ptr);
        return arr;
    }

    private static short[] ptrToShortArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"shortArry len:{len}");
        short[] arr = new short[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        return arr;
    }

    private static int[] ptrToIntArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"intArry len:{len}");
        int[] arr = new int[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        return arr;
    }

    private static float[] ptrToFloatArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"floatArry len:{len}");
        float[] arr = new float[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        return arr;
    }

    private static double[] ptrToDoubleArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"doubleArry len:{len}");
        double[] arr = new double[len];
        Marshal.Copy(IntPtr.Add(ptr, 4), arr, 0, len);
        return arr;
    }

    private static string[] ptrToStringArray(IntPtr ptr)
    {
        int len = Marshal.ReadInt32(ptr);
        Debug.Log($"stringArry len:{len}");
        IntPtr[] ptrArr = new IntPtr[len];
        Debug.Log(ptrArr);
        Marshal.Copy(IntPtr.Add(ptr, 4), ptrArr, 0, len);
        List<string> ret = new List<string>();
        for (var i = 0; i < len; i++)
        {
            var byteArray = ptrToByteArray(ptrArr[i]);
            var str = Encoding.UTF8.GetString(byteArray);
            ret.Add(str);
        }
        execFree((uint)ptr);
        return ret.ToArray();
    }

    public static void test()
    {
        byte byteArg = 210;
        byte byteRet = byteFunc(byteArg);
        Debug.Log($"byteFunc ret: {byteRet}");

        short shortArg = 210;
        short shortRet = shortFunc(shortArg);
        Debug.Log($"shortFunc ret: {shortRet}");

        int intArg = 210;
        int intRet = intFunc(intArg);
        Debug.Log($"intFunc ret: {intRet}");

        float floatArg = 210.123f;
        float floatRet = floatFunc(floatArg);
        Debug.Log($"floatFunc ret: {floatRet}");

        double doubleArg = 210.321d;
        double doubleRet = doubleFunc(doubleArg);
        Debug.Log($"doubleFunc ret: {doubleRet}");


        byte[] byteArrayArg = new byte[] { 1, 2, 3 };
        IntPtr ptrByteArray = byteArrayFunc(byteArrayArg, byteArrayArg.Length);
        byte[] byteArrayRet = ptrToByteArray(ptrByteArray);
        Debug.Log($"byteArrayFunc ret: [{string.Join(", ", byteArrayRet.Select(x => $"{x}"))}]");

        short[] shortArrayArg = new short[] { 4, 5, 6 };
        IntPtr ptrShortArray = shortArrayFunc(shortArrayArg, shortArrayArg.Length);
        short[] shortArrayRet = ptrToShortArray(ptrShortArray);
        Debug.Log($"shortArrayFunc ret: [{string.Join(", ", shortArrayRet.Select(x => $"{x}"))}]");

        int[] intArrayArg = new int[] { 7, 8, 9 };
        IntPtr ptrIntArray = intArrayFunc(intArrayArg, intArrayArg.Length);
        int[] intArrayRet = ptrToIntArray(ptrIntArray);
        Debug.Log($"intArrayFunc ret: [{string.Join(", ", intArrayRet.Select(x => $"{x}"))}]");

        float[] floatArrayArg = new float[] { 1.1f, 2.2f, 3.3f };
        IntPtr ptrFloatArray = floatArrayFunc(floatArrayArg, floatArrayArg.Length);
        float[] floatArrayRet = ptrToFloatArray(ptrFloatArray);
        Debug.Log($"floatArrayFunc ret: [{string.Join(", ", floatArrayRet.Select(x => $"{x}"))}]");

        double[] doubleArrayArg = new double[] { 5.5d, 6.6d, 7.7d };
        IntPtr ptrDoubleArray = doubleArrayFunc(doubleArrayArg, doubleArrayArg.Length);
        double[] doubleArrayRet = ptrToDoubleArray(ptrDoubleArray);
        Debug.Log($"doubleArrayFunc ret: [{string.Join(", ", doubleArrayRet.Select(x => $"{x}"))}]");

        string[] stringArrayArg = new string[] { "foo", "bar", "baz" };
        IntPtr ptrStringArray = stringArrayFunc(stringArrayArg, stringArrayArg.Length);
        string[] stringArrayRet = ptrToStringArray(ptrStringArray);
        Debug.Log($"stringArrayFunc ret: [{string.Join(", ", stringArrayRet)}]");
    }
}
var lib = {
    $utils: {
        arrayToString: function (arr) {
            var ret = '[';
            for (var i = 0; i < arr.length; i++) {
                var spl = i === arr.length - 1 ? '' : ', ';
                ret += arr[i].toString() + spl;
            }
            return ret + ']';
        },
        arrayToReturnPtr: function (arr, type) {
            var buf = (new type(arr)).buffer;
            var ui8a = new Uint8Array(buf);
            var ptr = _malloc(ui8a.byteLength + 4);
            HEAP32.set([arr.length], ptr >> 2);
            HEAPU8.set(ui8a, ptr + 4);
            // setTimeout(function() { _free(ptr) }, 0);
            return ptr;
        },
        stringArrayToReturnPtr: function (strArr) {
            var ptrArray = [];
            var enc = new TextEncoder();
            for (var i = 0; i < strArr.length; i++) {
                var byteArray = enc.encode(strArr[i]);
                var ptr = utils.arrayToReturnPtr(byteArray, Uint8Array);
                ptrArray.push(ptr);
            }
            var ptr = utils.arrayToReturnPtr(ptrArray, Uint32Array);
            return ptr;
        }
    },

    execFree: function (ptr) {
        console.log('free ptr: ' + ptr);
        _free(ptr);
    },

    byteFunc: function (arg) {
        console.log('byteFunc arg: ' + arg);

        var ret = 128;
        return ret;
    },

    shortFunc: function (arg) {
        console.log('shortFunc arg: ' + arg);

        var ret = 128;
        return ret;
    },

    intFunc: function (arg) {
        console.log('intFunc arg: ' + arg);

        var ret = 128;
        return ret;
    },

    longFunc: function (arg) {
        console.log('longFunc arg: ' + arg);
        var ret = 128;
        return ret;
    },

    floatFunc: function (arg) {
        console.log('floatFunc arg: ' + arg);

        var ret = 128.123;
        return ret;
    },

    doubleFunc: function (arg) {
        console.log('doubleFunc arg: ' + arg);

        var ret = 128.123;
        return ret;
    },

    byteArrayFunc: function (arg, len) {
        var byteArray = HEAPU8.subarray(arg, arg + len);
        console.log('byteArrayFunc arg: ' + utils.arrayToString(byteArray));

        var ret = [3, 2, 1];
        var ptr = utils.arrayToReturnPtr(ret, Uint8Array);
        console.log('jslib ptr: ' + ptr);
        return ptr;
    },

    shortArrayFunc: function (arg, len) {
        var shortArray = HEAP16.subarray(arg, len);
        console.log('shortArrayFunc arg: ' + shortArray);

        var ret = [6, 5, 4];
        var ptr = utils.arrayToReturnPtr(ret, Int16Array);
        return ptr;
    },

    intArrayFunc: function (arg, len) {
        var intArray = HEAP32.subarray(arg, len);
        console.log('intArrayFunc arg: ' + intArray);

        var ret = [9, 8, 7];
        var ptr = utils.arrayToReturnPtr(ret, Int32Array);
        return ptr;
    },

    floatArrayFunc: function (arg, len) {
        var floatArray = HEAPF32.subarray(arg, len);
        console.log('floatFunc arg: ' + floatArray);

        var ret = [3.3, 2.2, 1.1];
        var ptr = utils.arrayToReturnPtr(ret, Float32Array);
        return ptr;
    },

    doubleArrayFunc: function (arg, len) {
        var doubleArray = HEAPF64.subarray(arg, len);
        console.log('doubleFunc arg: ' + doubleArray);

        var ret = [6.6, 5.5, 4.4, 3.3, 2.2];
        var ptr = utils.arrayToReturnPtr(ret, Float64Array);
        return ptr;
    },

    stringArrayFunc: function (arg, len) {
        var strArray = [];
        for (var i = 0; i < len; i++) {
            var ptr = HEAP32[(arg >> 2) + i];
            var str = Pointer_stringify(ptr);
            strArray.push(str);
        }
        console.log('strArrayFunc arg: ' + strArray);

        var ret = ['hoge', 'fuga', 'piyo', 'hogera', 'ほげほげ', '叱る'];
        var retPtr = utils.stringArrayToReturnPtr(ret);
        return retPtr;
    },

    refIntArrayFunc: function (arg, len) {
        console.log('ref len:' + len);
        Module.refIntArray = new Int32Array(buffer, arg, len);
        Module.sampleValue = 0;
        setInterval(function () {
            console.log('refIntArray update: ' + Module.refIntArray.length + ' ' + Module.sampleValue );
            for (var i = 0; i < Module.refIntArray.length; i++) {
                Module.refIntArray[i] = Module.sampleValue + i;
            }
            Module.sampleValue += Module.refIntArray.length;
        }, 1000);
    }
};
autoAddDeps(lib, '$utils');
mergeInto(LibraryManager.library, lib);
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Mapbox GL JS でアトリビューションをコントロールする -利用編-

調査編につづき、今回は実際にユーザーコードでAttributionをコントロールしてみます。なお、SDKとしての挙動を試す目的でAttributionの表示をコントロールしますが、実際には利用許諾にしたがった表示を行ってください。

Text attribution

Attributionを非表示

options.attributionControlfalseに設定するとtext attributionが非表示になります。

    var map = new mapboxgl.Map({
        container: 'map', // container id
        style: 'mapbox://styles/mapbox/streets-v11', // style URL
        center: [-74.5, 40], // starting position [lng, lat]
        zoom: 9, // starting zoom
        attributionControl: false
    });

off.png

カスタムAttributionを表示

options.attributionControlに文字列、もしくは文字列の配列を指定するとAttributionとして表示されます。

    var map = new mapboxgl.Map({
        container: 'map', // container id
        style: 'mapbox://styles/mapbox/streets-v11', // style URL
        center: [-74.5, 40], // starting position [lng, lat]
        zoom: 9, // starting zoom
        customAttribution: '<a href="https://example.com/custom1">custom1</a>'
    });

custom1.png

    var map = new mapboxgl.Map({
        container: 'map', // container id
        style: 'mapbox://styles/mapbox/streets-v11', // style URL
        center: [-74.5, 40], // starting position [lng, lat]
        zoom: 9, // starting zoom
        customAttribution: ['<a href="https://example.com/custom1">custom1</a>', 'custom2']
    });

custom2.png

デフォルトのAttributionを消した上でカスタムAttributionを追加(compact表示設定)

    var map = new mapboxgl.Map({
        container: 'map', // container id
        style: 'mapbox://styles/mapbox/streets-v11', // style URL
        center: [-74.5, 40], // starting position [lng, lat]
        zoom: 9, // starting zoom
        attributionControl: false
    });

    map.addControl(new mapboxgl.AttributionControl({
        compact: true,
        customAttribution: '<a href="https://example.com/custom1">custom1</a>'}));

初期表示
compact1.png

展開表示
compact2.png

StyleでAttributionを設定

urlでアクセス

    var map = new mapboxgl.Map({
        container: 'map', // container id
        center: [-74.5, 40], // starting position [lng, lat]
        style: {
            version: 8,
            sources: {
                street: {
                    type: 'vector',
                    url: 'mapbox://mapbox.mapbox-streets-v8'
                }
            },
            layers: [
                {
                    id: 'water',
                    type: 'fill',
                    source: 'street',
                    'source-layer': 'water',
                    minzoom: 0,
                    maxzoom: 22
                }
            ]
        },
        zoom: 9 // starting zoom
    });

style_url.png

urlでアクセス + Attributionを指定

    var map = new mapboxgl.Map({
        container: 'map', // container id
        center: [-74.5, 40], // starting position [lng, lat]
        style: {
            version: 8,
            sources: {
                street: {
                    type: 'vector',
                    url: 'mapbox://mapbox.mapbox-streets-v8',
                    attribution: 'in style',
                }
            },
            layers: [
                {
                    id: 'water',
                    type: 'fill',
                    source: 'street',
                    'source-layer': 'water',
                    minzoom: 0,
                    maxzoom: 22
                }
            ]
        },
        zoom: 9 // starting zoom
    });

style_url_attr.png

tilesでアクセス

    var map = new mapboxgl.Map({
        container: 'map', // container id
        //style: 'mapbox://styles/mapbox/streets-v11', // style URL
        center: [-74.5, 40], // starting position [lng, lat]
        style: {
            version: 8,
            sources: {
                street: {
                    type: 'vector',
                    tiles: ['http://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoieW9jaGkiLCJhIjoiY2tjZThvdWExMDV2dDJxcDgxZzBwbzlxYSJ9.M0yRA6SXDMRgXzXGuYnvsg'],
                    tileSize: 512
                }
            },
            layers: [
                {
                    id: 'water',
                    type: 'fill',
                    source: 'street',
                    'source-layer': 'water',
                    minzoom: 0,
                    maxzoom: 22
                }
            ]
        },
        zoom: 9 // starting zoom
    });

style_tiles.png

tilesでアクセス + Attributionを指定

    var map = new mapboxgl.Map({
        container: 'map', // container id
        //style: 'mapbox://styles/mapbox/streets-v11', // style URL
        center: [-74.5, 40], // starting position [lng, lat]
        style: {
            version: 8,
            sources: {
                street: {
                    type: 'vector',
                    tiles: ['http://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoieW9jaGkiLCJhIjoiY2tjZThvdWExMDV2dDJxcDgxZzBwbzlxYSJ9.M0yRA6SXDMRgXzXGuYnvsg'],
                    tileSize: 512,
                    attribution: 'in style'
                }
            },
            layers: [
                {
                    id: 'water',
                    type: 'fill',
                    source: 'street',
                    'source-layer': 'water',
                    minzoom: 0,
                    maxzoom: 22
                }
            ]
        },
        zoom: 9 // starting zoom
    });

style_tiles_attr.png

Workmark

urlでアクセスし、wordmarkを非表示

    var map = new mapboxgl.Map({
        container: 'map', // container id
        //style: 'mapbox://styles/mapbox/streets-v11', // style URL
        center: [-74.5, 40], // starting position [lng, lat]
        style: {
            version: 8,
            sources: {
                street: {
                    type: 'vector',
                    url: 'mapbox://mapbox.mapbox-streets-v8',
                    mapbox_logo: false
                }
            },
            layers: [
                {
                    id: 'water',
                    type: 'fill',
                    source: 'street',
                    'source-layer': 'water',
                    minzoom: 0,
                    maxzoom: 22
                }
            ]
        },
        zoom: 9 // starting zoom
    });

logo_url.png

tilesでアクセスし、wordmarkを表示

    var map = new mapboxgl.Map({
        container: 'map', // container id
        //style: 'mapbox://styles/mapbox/streets-v11', // style URL
        center: [-74.5, 40], // starting position [lng, lat]
        style: {
            version: 8,
            sources: {
                street: {
                    type: 'vector',
                    tiles: ['http://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoieW9jaGkiLCJhIjoiY2tjZThvdWExMDV2dDJxcDgxZzBwbzlxYSJ9.M0yRA6SXDMRgXzXGuYnvsg'],
                    tileSize: 512,
                    mapbox_logo: true
                }
            },
            layers: [
                {
                    id: 'water',
                    type: 'fill',
                    source: 'street',
                    'source-layer': 'water',
                    minzoom: 0,
                    maxzoom: 22
                }
            ]
        },
        zoom: 9 // starting zoom
    });

logo_tiles.png

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

「初心者向け」JavaScriptで簡単なくじ引きシステムを作る

どうも7noteです。簡単なくじ引きシステムをjavascriptで作ります。

2チームに分けたり、あたりとはずれの本数を決めてくじびきすることができます。

見本
sample.gif

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

仕様

・参加メンバーとくじの本数を選択
・スタートボタンを押す。
・あたりの人とはずれの人の2種類に分ける

※くじの本数が足りない時はアラートで警告を出す。

ソース

index.html
<div class="wrapper">
  <div class="area">
    <div id="member">
      <h2>参加メンバーを選ぼう!:</h2>
      <label><input type="checkbox" value="佐藤" checked/> 佐藤</label>
      <label><input type="checkbox" value="田中" checked/> 田中</label>
      <label><input type="checkbox" value="伊藤" checked/> 伊藤</label>
      <label><input type="checkbox" value="鈴木" checked/> 鈴木</label>
      <label><input type="checkbox" value="高橋" checked/> 高橋</label>
    </div>

    <div id="honsuu">
      <h2>くじの本数:</h2>
      あたり:<select name="hit">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4" selected>4</option>
      </select> <!--
-->      はずれ:<select name="miss">
      <option value="1">1</option>
      <option value="2">2</option>
      <option value="3">3</option>
      <option value="4" selected>4</option>
      </select><br>
    </div>
    <div class="button">
      <input type="button" value="抽選開始" onclick="start();">  <!-- 関数startを実行する。 -->
    </div>
    <div id="result">
      <h3>あたり:</h3>
      <h3>はずれ:</h3>
    </div>
  </div>

</div>
style.css
.wrapper {
  margin: 50px 10%;
  text-align: left;
}

.area {
  width: 80%;
  margin: 0 auto;
}

h2 {
  display: inline-block;
  font-weight: bold;
}

#member {
  margin: 5px auto 20px;
}

#member label {
  display: inline-block;
}

#honsuu {
  margin: 0 auto 20px;
}

.button {
  border-bottom: solid 1px #000;
  padding: 0 0 20px;
  margin: 0 auto 50px
}

#result h3 {
  font-size: 18px;
  font-weight: bold;
}
script.js
var toddayMember = new Array();  // 本日の参加メンバーを入れる配列を新規作成

// 関数start
function start() {
  var theamA = new Array();               // あたりの人たちを入れる配列を新規作成
  var theamB = new Array();               // はずれの…〃
  var hitNum = Number($('select[name="hit"]').val());   // あたりのくじの本数を変数[hitNum]に格納
  var missNum = Number($('select[name="miss"]').val()); // はずれの…〃
  var rand = 0;                           // ランダムの数字 初期値は0

  todayMember = $('input[type="checkbox"]:checked').map(function(){
    return $(this).val();                 // チェックのついたメンバーを配列に格納
  }).get();

  // 人数に対してくじが足りない時にアラートして処理を終了
  if(todayMember.length > hitNum+missNum){
    alert("くじの本数が足りないよ");
    exit;
  }

  // 1人ずつ、あたりもしくははずれを決める
  for(var i = 0; i < todayMember.length; i++ ){
    if(hitNum == 0 && missNum > 0){       // あたりくじがなくなっていて、はずれが余っていればrand=2
      rand = 2;
    }else if(missNum == 0 && hitNum > 0){ // はずれくじがなくなっていて、あたりが余っていればrand=1
      rand = 1;
    }else{                                // どちらでもなければランダムで1か2
      rand = Math.floor( Math.random() * 2 ) + 1;
    }

    if(rand == 1){
      theamA.push(todayMember[i]);        // 1だったらあたりチームの配列に追加
      hitNum += -1;                       // あたりくじを1本減らす
    }else if(rand == 2){
      theamB.push(todayMember[i]);        // 2だったらはずれチームの配列に追加
      missNum += -1;                      // はずれくじを1本減らす
    }
  }
  $("#result h3:first-child").text("あたり:"+theamA); // あたりチームを表示
  $("#result h3:last-child").text("はずれ:"+theamB);  // はずれチームを表示
}

アラートの出方
sample2.gif

解説

分かりやすく分解して書いているので最短の処理や記述ではございませんのであしからず。

  1. ボタンがクリックされ、「start関数」が起動
    • htmlからjavascriptを動かすときは、onclick="start();"のように書くことで、動かすことができます。
  2. あたりチーム、はずれチームそれぞれの配列を作成。また選択されたくじの本数を変数に格納。
    • 新しい配列をつくる時は、new Array()。またselectの要素を取得すると文字列として取得してしまうので、Number()でくくることで数字として変数に格納します。
  3. :checkedの疑似クラスを使って、チェックされた人をtodayMember(変数)に格納
  4. もし「人数 > くじの本数」だった場合、警告文を出して処理を強制終了
    • .lengthで配列にあるデータ数、今回でいうと参加人数の数値を取得できます。
  5. for文を使って1人ずつあたりはずれを抽選する。
    • 最初のif文では、もしすでにどちらかのくじがなくなっていた場合、もう片方のくじを引くように処理を入れています。どちらのくじもまだ残っている場合はランダムの数字を入れます。ランダムはMath.floor( Math.random() * 2 ) + 1とかくことで1もしくは2を取得できます。範囲を広げたいときは2の部分を任意の数時にします。
  6. 後のif文ではランダムで決めた1もしくは2でそれぞれチーム移動とくじを1本減らしています。
    • push()で指定の配列から別の配列に値を追加(複製)します。くじを引いたので-1してのこりのくじの本数を調整します。
  7. text()でhtmlのほうに結果をテキストで出力して完了。

まとめ

プログラミングを始めるきっかけってそれぞれあると思いますが、ゲームなどの面白いものを作りたいって思って始める人が多いように思います。私自身もゲームがすきで自分で作れたらいいなと思いプログラミングの勉強を始めました。

でも始めたての頃は右往左往して何から手を付けたらいいのか分からないものです。
この記事がこれからjavascriptを始めようと思っている人の練習課題的なものになればいいなと思います。
簡単なものから作り始めれば、経験値を重ねていつかは壮大なものも作れるようになるし、もの作りを続けていれば仲間も増えてより様々なもの作りに挑戦できると思います。

おそまつ!

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

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

【Vue.js】ドロップダウンの値を取得する方法

App.js
<template>
  <div>
    <select v-model="selected">
      <option>りんご</option>
      <option>バナナ</option>
      <option>ぶどう</option>
    </select>
    {{selected}}
  </div>
</template>

<script>
export default {
  data() {
    return {
      selected: 'りんご'
    }
  },
};
</script>

demo

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

Nuxt.jsにCKEditorを導入し、CKEditorに独自プラグインを導入する

はじめに

以前、Nuxt.jsで開発をしていてCKEditorを試行錯誤で導入しました。

そちらの経験を、Nuxt.jsにCKEditorを導入し、エディタ内の画像の移動、リサイズをドラッグで実現するとしてまとめました。

その後、少し経験値を積みまして、変わったというか、あぁ、恥ずかしいという箇所を修正します。

CKEDITR4の導入方法の変更

CKEditorにプラグインを追加するのが手間だと思っていましたが、以下のように、あっさりと実現ができました(汗)

<template>
  <ckEditor
    v-model="contents"
    :editor-url="editorUrl"
    :config="editorConfig"
  ></ckEditor>
</template>

<script>
import CKEditor from 'ckeditor4-vue'
export default {
  name: 'CKEditor4',
  components: { ckEditor: CKEditor.component },
  props: {
    value: {
      type: String,
      required: true
    },
    height: {
      type: Number,
      required: false,
      default: 500
    }
  },
  data() {
    return {
      editorUrl: 'https://cdn.ckeditor.com/4.15.1/full-all/ckeditor.js',
      editorConfig: {
        height: this.height,
        extraPlugins: ['image2'],
        removePlugins: ['image']
      }
    }
  },
  computed: {
    contents: {
      get() {
        return this.value
      },
      set(value) {
        this.$emit('input', value)
      }
    }
  }
}
</script>

<style scoped></style>

editorUrlにローカルに設置したCKEditorを使用していましたが、CDN経由からのダウンロードに変更しました。

この背景には、プラグインを設定ファイルで自由に変更できることがわかったからです。
editorConfigextraPluginsで追加のプラグインを指定でき、removePluginsで除外するプラグインを指定できました。

独自プラグインを導入する

前回の記事には書きませんでしたが、独自プラグインを導入することを視野に入れていましたので、CKEditor自体をダウンロードすることにも抵抗がありませんでした。

今回CDN経由にして、独自プラグインを呼び出す方法がわかったのでお伝えしようと思います。

CKEDITORでaddExternalメソッドを呼び出すと、独自のプラグインが読み込まれます。

ckeditor4-vueならどうするのかな?って思ったら、以下のようにするようです。

<template>
  <ckEditor
    v-model="contents"
    :editor-url="editorUrl"
    :config="editorConfig"
    @namespaceloaded="onNamespaceLoaded"
  ></ckEditor>
</template>

<script>
import CKEditor from 'ckeditor4-vue'
export default {
  name: 'CKEditor4',
  components: { ckEditor: CKEditor.component },
  props: {
    value: {
      type: String,
      required: true
    },
    height: {
      type: Number,
      required: false,
      default: 500
    }
  },
  data() {
    return {
      editorUrl: 'https://cdn.ckeditor.com/4.15.1/full-all/ckeditor.js',
      editorConfig: {
        height: this.height,
        extraPlugins: ['image2', 'timestamp'],
        removePlugins: ['image']
      }
    }
  },
  computed: {
    contents: {
      get() {
        return this.value
      },
      set(value) {
        this.$emit('input', value)
      }
    }
  },
  methods: {
    onNamespaceLoaded(CKEDITOR) {
      // Add external `placeholder` plugin which will be available for each
      // editor instance on the page.

      let pluginBaseDir = '/ckeditor/plugins/timestamp/'
      if (process.env.NUXT_ENV_DEPLOY_SUBDIR) {
        pluginBaseDir =
          process.env.NUXT_ENV_DEPLOY_SUBDIR + '/ckeditor/plugins/timestamp/'
      }
      CKEDITOR.plugins.addExternal('timestamp', pluginBaseDir, 'plugin.js')
    }
  }
}
</script>

<style scoped></style>

ckeditrコンポーネントに対して、namespaceloadedというイベントを受信するようにします。
こちらは、ckeditor4-vueがCKEDitorインスタンスをロードした場合に呼ばれるそうです。

それをVueのMethodsで定義した、onNamespaceLoadedメソッドで処理をして、外部のプラグインを読み込みます。

プラグインは、プラグインのチュートリアルで記載された、現在時刻の挿入プラグインとなります。

2020-11-30_20h28_32.gif

デモサイト

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

コンパイルの流れをざっくり掴んでいく

概要

JSを触っていく上でどのように解釈してブラウザに表示されているのだろう?と思いました。この機会に裏で何をやっているのか理解をしておこうとおもったのでまとめていきます。

ブラウザが認識できる言語

まず、ブラウザで認識されるのはHTML, CSS, JavaScript, WebAssemblyという言語です。

たとえば、Railsでコードを書いていったとしても最終的に開発者ツールなど見たらHTMLなどに変換されています。

高級言語

高級言語とはプログラミング言語の分類のひとつで、人間がわかりやすいように設計されているものです。反対に、機械がわかりやすい言語のことを低級言語と呼びます。たとえば、開発するときに効率的にはやく書きたいときに扱うのが高級言語でSCSSなどの人間が便利だと思うもののことです。

コンパイル

コンピュータが分かる言葉にするには、コンパイルをして表示させています。プログラミング言語は機械に認識してもらわないと動きません。端的に言うと、機械に認識してもらう作業、要するに機械が理解できるようプログラミング言語を翻訳する作業のことをコンパイルといいます。

コンパイルの流れ

コンパイルはコンピュータがわかるように翻訳する作業のことで、翻訳作業を行ってくれているのはコンパイラというプログラムです。

一度整理すると

1.プログラミング言語をコンピュータに理解してもらうには翻訳をしなければならない。
2.翻訳作業のことをコンパイルと呼びコンパイラというプログラムで翻訳される。

ただコンパイラはなんでも翻訳してくれるわけではありません。そこで出てくるのがプリコンパイルというものです。

プリコンパイルとは

コンパイラが言語の翻訳ができるように、事前に準備することです。コンパイラに翻訳をお願いする前に、もっとコンピュータのわかりやすい言葉にしてあげることです。

アセットパイプライン

アセットパイプラインとは、JavaScriptやCSSなどのアセットを小さくしたり圧縮したりしてまとめてくれる機能のことです。複数のアセットファイルを一つにまとめてブラウザに表示するために必要な機能です。処理の流れを簡単にいうと、複数のアセットファイルをプリコンパイルして連結して、最小化または圧縮して最終的に軽量化されたものをブラウザに渡せるようにpublicディレクトリに配置します。

Sprockets

Railsでは、プリコンパイルするために、SprocketsというGemを用いてアセットパイプラインをしていました。しかし、JavaScriptの使用が多くなり、Railsのバージョン6系からモジュールバンドラによるプリコンパイルが主流になりました。

モジュールバンドラ

モジュールバンドラとはJavaScriptの依存関係を考慮しながら管理してくれるものです。依存関係を考慮しながらというのが大切で、複数あるJavaScriptファイルを1つにまとめることでブラウザで読み取ることができるようにしています。また、1つのファイルにすることで通信の高速化を可能にしています。

JavaScriptではwebpackというモジュールバンドラを利用しています。

webpackとWebpacker

webpackを使えば依存関係の管理ができるのですが、Railsではもっと簡単に管理できるものが用意されています。それはWebpackerというものです。専用の設定ファイルやヘルパーメソッドを用意してくれるGemでRailsのバージョン6系からはデフォルトでWebpackerというGemが用意されているので、導入のことを意識せずとも使えています。Webpackerによってプリコンパイルに加え、JavaScriptのパッケージを管理することを可能にしています。

npmとyarn

Webpackerにパッケージをインストールして入れていくにはパッケージ管理ツールを使います。パッケージ管理ツールにはnpmというものがありますが、Railsではyarnというものを使います。yarnはpackage.jsonとyarn.lockで使用するパッケージを管理します。RailsのGemfileとGemfile.lockのように管理されるようになります。簡単にいうと、package.jsonで使用するパッケージを管理して、yarn.lockで使用するパーケージのバージョンを管理しているということです。

まとめ

  • コンパイルとはコンピュータが理解できることばに翻訳すること。
  • コンパイラというプログラムが翻訳している。
  • ブラウザは一つのファイルしか読み取ることができない。
  • プリコンパイルとは、コンパイラが理解できるように事前に準備をすること。
  • アセットパイプラインとはプリコンパイル連結圧縮配置する機能のこと。
  • publicディレクトリに配置することでブラウザが読み込めるようにしている。
  • SprocketsというGemでのプリコンパイルからWebpackerというGemが使われるようになり、プリコンパイルに加えJavaScriptのパッケージ管理を行ってくれている。
  • 最近はwebpackerというモジュールバンドラで管理しており、依存関係を解決しながらモジュールを一つにまとめることでブラウザに表示されるような仕組みになっている。
  • webpackerにパッケージを入れるにはnpmを使うがRailsではyarnというものが使われていてパッケージを管理している。

参考文献

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

リストからfindを使わずにキー指定でvalueを取得する方法&ts(7053)エラー対応簡易メモ

やりたいこと

interface Hoge {
  id: number | string;
  value: string;
}

const hogeList: Hoge[] = [
 { id: 'A', value: 'a' },
 { id: 'B', value: 'b' },
 { id: 'C', value: 'c' },
];
const hogeList2: Hoge[] = [
 { id: 1, value: 'a' },
 { id: 2, value: 'b' },
 { id: 3, value: 'c' },
];

const byB = hogeList.find(hoge => hoge.id === 'B');
console.log(byB); // 'b'

const by3 = hogeList2.find(hoge => hoge.id === 3);
console.log(by3); // 'c'

hogeListリストから、
findを使わずにid指定でvalueを取得したい

方法

リストからidをキーにしたオブジェクトへ変換

const obj = toObjectFromList(hogeList);
console.log(obj['B']); // 'b'
console.log(obj); // Object{A: 'a', B: 'b', C: 'c'}

const obj2 = toObjectFromList(hogeList2);
console.log(obj2[3]); // 'c'
console.log(obj2); // Object{1: 'a', 2: 'b', 3: 'c'}

ts(7053)エラーになる書き方

export function toObjectFromList(list: Hoge[]): {[k: number]: string} | {[k: string]: string} {
  return list.reduce((acc, { id, value }) => {
    acc[id] = value;
    return acc;
  }, {} as {[k: number]: string} | {[k: string]: string});
}
型 'string | number' の式を使用して型 '{ [k: number]: string; } | { [k: string]: string; }' にインデックスを付けることはできないため、要素は暗黙的に 'any' 型になります。
型 'string' のパラメーターを持つインデックス シグネチャが型 '{ [k: number]: string; } | { [k: string]: string; }' に見つかりませんでした。ts(7053)

tsconfig.jsoncompilerOptions"noImplicitAny": true,を指定した場合にエラーになります。

修正

export function toObjectFromList(list: Hoge[]): {[k in string | number]: string} {
  return list.reduce((acc, { id, value }) => {
    acc[id] = value
    return acc;
  }, {} as {[k in string | number]: string});
}   

Playground

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

Reactでのスクロール連動エフェクトなら「react-intersection-observer」が良き

この記事は、株式会社エイチームフィナジー の Advent Calendar 2020 6日目の記事になります。

6日目は、React環境でスクロール連動エフェクトを実装する際に便利な
react-intersection-observerについて、デザイナーの@masaki632dがご紹介します。

背景

現在、React(JSフレームワーク)とEmotion(CSSinJS)を導入した環境でタスクを進めており、
特定の高さまでスクロールしてきた時にテキストにマーカーが引かれるやつを実装する機会がありました。

当初は、JavaScriptのscrollイベントやwindow.pageYOffsetなどを使って一度実装したのですが、
この手法だとスクロールのたびに関数が呼び出されるため、パフォーマンスに影響を及ぼしてしまうようです。

scrollイベントの手法で実装後、ふとエンジニアから、
「最近はこれで実装するのがパフォーマンスも軽くて良きです」と紹介されたのが、
「Intersection Observer」 でした。(名前がカッコ良い)

なるほど!と思った数日後には、
エンジニア側で軽やかに「Intersection Observer」で実装完了されており、
表示を確認すると、特定の高さまでスクロールしてきた時にテキストにマーカーが引かれるやつがしっかり再現されていました。

今後同じような実装が必要になった時、自分の方でも軽やかに実装したいと思ったのと、
せっかくのReact環境ということで、
今回はreactと名前の付いたreact-intersection-observerで実装を試してみました。

Intersection Observer とは

「Intersection(要素間の交差)」を「Observe(監視)」するAPIのことです。
指定したDOM要素の交差点(DOM)を監視することができます。
「Intersection Observer」の詳細はこちら

ブラウザサポートも拡充しており、今後標準となる技術のようです。
「Intersection Observer」の対応ブラウザはこちら

react-intersection-observer とは

今回使用するライブラリ react-intersection-observer は、
上記の Intersection Observer API をReactで簡単に扱うためのライブラリになります。
「react-intersection-observer」の詳細はこちら

何はともあれ実装

  • $ yarn add でライブラリ react-intersection-observer をインストールします。 (package.jsonyarn.lockに react-intersection-observer が追加されます。)
$ yarn add react-intersection-observer
  • コンポーネントに使用していきます。
index.tsx
/** @jsx jsx */
import { FC } from "react";
import { jsx } from "@emotion/core";
import { styles } from "./Styled";

import { useInView } from 'react-intersection-observer';

export const index: FC = () => {
  // Hooksを使って簡単に書くことができます。
  // refで要素を指定して、inViewを使って要素がViewに入ったかどうかを判定します。
  // Optionについては、rootMarginを設定。要素が100pxより上に入った段階でinViewを取得できます。
  const [ref, inView] = useInView({
    rootMargin: '-100px 0px',
  });

  return (
    // スクロールして要素が表示されると同時に styles.show が付与される
    <p ref={ref} css={[styles.yellow, inView && styles.show]}>
      テキストテキスト
    </p>
  );
};

  • Styled.ts
import { css } from "@emotion/core";

export const styles = {
  // マーカー箇所のstyle
  yellow: css`
    width: 200px;
    margin: 0 auto;
    text-align: center;
    opacity: 0;
    transition: 2s;
    background: -webkit-linear-gradient(
      left,
      rgb(255, 250, 153) 50%,
      transparent 50%
    );
    background-repeat: no-repeat;
    background-size: 200% 0.8em;
    background-position: 100% 0.5em;
  `,

  // スクロールして要素が表示されると同時に付与されるstyle
  show: css`
    opacity: 1;
    background-position: 0% 0.5em;
    animation: SlideIn 3s ease-out both;
    transform: translateY(-10px);
  `,
};
  • ブラウザをスクロールして確認すると、
    テキスト箇所が表示される高さになった瞬間に、アニメーションが発火してくれました!
    (動き的には、ふわっと下から表示 + マーカーが左から引かれる) 画面収録_黄色マーカー.gif

利用に便利なシチュエーション

  • 特定の高さまでスクロールしてきた時にテキストにマーカーが引かれるやつ
  • 画像の遅延ロード
  • YouTubeの自動再生
  • コンテンツの追加読み込み
    などなど

まとめ

いかがだったでしょうか?
今回はReact環境で簡単にIntersection Observer APIを使用することができる、
react-intersection-observerをご紹介しました。
とても簡単で便利ですので、ぜひ一度使ってみてください。

参考

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

JavaScript 型確認方法 (急がしい方向け)

型確認 / JAVASCRIPT

型確認方法をどうやるのか?
たくさん情報がありすぎて時間を食われないように、すぐにハンズオンできる簡単な方法を提示します。

コード 内容
typeof([型を調べたい値]) ※別の記法もあります 型の確認を行う

※ 別記法:typeof [型を調べたい値]

動作確認をしていきましょう!

最初にコードの出力結果を見てみましょう!

typeof(1) =>  number
typeof("1") =>  string
typeof("文字") => string

上記の結果から、数値の場合には「number」、文字列の場合には「string」が出力されます。

様々な型が存在しますので、実際に手を動かして挙動を見てみましょう。
:warning: typeofでは配列・nullなどはobjectクラス型にひとくくりで出力されるようなので、その場合はObject.prototype.toString.call() を使って見てください。

現状で全てを学習するのは時間効率悪い為、使う時が来て勉強する必要があると感じた時に更新しようと思います。

深く勉強する必要がない人は、現状はtypeofかObject.prototype.toString.call()のどちらかを必要な時に使うということを覚える程度でいいかもしれないですね。

☆結論: 型確認したい時はtypeofかObject.prototype.toString.call()の使い方を調べて、実際にコンソールで型確認。 おわり。

もし気になる方は、良い勉強になりそうな雰囲気の記事を見つけたので、時間があったら見てみてください。

データ型について / 参考1 / 参考2

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

【簡単にできる】ブックマークレットのすゝめ【初心者向け】

こんにちは、@len_crow です。

DMM WEBCAMP Advent Calendar 2020 9日目の記事になります。

初めてアドベントカレンダーに参加してから3年目になりました。月日が経つのは早いですね。

さてこの記事では、ブラウザのブックマークバーから簡単に実行できるブックマークレットについて紹介をします。

ブックマークレットって?

ブックマークレットとは、ブラウザのブックマークから起動できるプログラムのことです。
普段みなさんが使われているブックマークはページなどを保存しておき、クリック1つですぐにページを開くことができるもの。
そこにプログラムを入れることができるというわけです。

どんなことに使われているのかというと、

などWeb上のちょっとした機能や効率化ができる機能を作ることができます。

また、スマホでも問題なく使えます。

自分が作ったブックマークレットもあります。

これは、自分がよくプレイしているWACCAという音楽ゲームの公式サイトのマイページ上で使用できるものになります。(詳しくはリンク先をご覧ください。)

ではどのように作成していくのか、順を追って説明していきましょう。

作成方法

今回は簡単に「現在時刻をアラートする」ブックマークレットを作成していきましょう。

使うもの

  • GoogleChromeなどのウェブブラウザ
  • GitHub

ブックマークにプログラムを登録する

(ブラウザはGoogleChromeを使用しています。Safari,FirefoxやMicrosoft Edgeでも動きます。)

ブックマークバーで右クリックをし、赤枠の「ページを追加(G)」をクリックします。
スクリーンショット_2020-11-30_15_52_16.png

するとページの追加画面が出てきます。

スクリーンショット 2020-11-30 15.54.15.png

名前は特に決まりはないので、「現在時刻アラート」としましょう。

次にURL欄に以下のコードを記入します。

bookmarklet
javascript:(function(){const date=new Date();alert(date);})();

上記のコードは何をしているのかというと、
URL内でjavascript:と記入するとそれ以下はjsのコードとして認識されます。

その後、function()の命令が実行されてアラートが表示されるといったものになります。

完成形はこちらになります。
スクリーンショット 2020-11-30 16.13.53.png

実行する

ブックマークバーにある「現在時刻アラート」をクリックしたら現在時刻をアラートしてくれます。
スクリーンショット 2020-11-30 16.21.14.png
スクリーンショット 2020-12-03 23.11.04.png
(Google Chromeの新しいタブ画面ではうまく実行されません。どこか新しいページに移動してから実行しましょう。)

整形したコードを入れた方が楽なんじゃないかという意見があると思います。
実際にはどちらとも動くので問題はないのですが、以下みたいに「LF」という改行コードが現れてしまい見た目的に綺麗ではないので1行にしています。

スクリーンショット 2020-11-30 16.13.20.png

これだけでも完成なのですが、このままだとコードを間違えた時に修正するのが大変面倒ですので、コードを別の場所で管理するようにしましょう。

JSのコードをデプロイする

今回はデプロイ先として「GitHub Pages」を用いてコードを別の場所に保管しましょう。

まずは上記のコードをGitHubにあげましょう。
(Git、GitHubについて知らない方はGitを触り始めてからGitHubにpushするまでの流れを誰よりも丁寧に説明するをご覧になるといいかもしれません。)

alert.js
const date = new Date();
alert(date);

こんな状態になっていたらOKです。
スクリーンショット 2020-11-30 17.14.42.png

そしたら「Settings」から「GitHub Pages」を探します。

スクリーンショット 2020-11-30 17.28.33.png

Source欄にある「None」を「main」(ブランチ名)に設定し「Save」をクリックします。
以下の状態になっていたらデプロイ完了です。

スクリーンショット 2020-11-30 17.29.01.png

あとは実際にURLに飛んでみて、JSのコードが表示されていれば正常にデプロイができています。

https://saezurucrow.github.io/CurrentTimeAlertBookmarklet/alert.js
(後ろにファイル名をつけるのを忘れずに!)

ブックマークレットからJSを呼び出す

最後にブックマークレットのURL欄からデプロイしたJSを呼び出すようコードを修正しましょう。

以下のコードをURL欄に上書きしてください。

bookmarklet
javascript:(function(url){s=document.createElement('script');s.src=url;document.body.appendChild(s);})('デプロイしたURL');

整形するとこんなかんじです。

javascript:(
function(url) {
  s = document.createElement('script');
  s.src = url;
  document.body.appendChild(s);
}
)('デプロイしたURL');

上記のコードでは、まずjavascript:でJSを使えるようにし、function(url)を起動させます。
s = document.createElement('script');でHTML内に<script>タグを生成させて、src内の属性値をurlに設定します。
次にdocument.body.appendChild(s);で生成した<script>タグを今開いているページの<body>内にに追加します。
そうすることにより、デプロイしたJSのコードがすぐに実行されます。

保存をして、同じ実行結果が得られるか確認をしてみましょう。

一度URLを設定してしまえばあとはGitHubを更新をすれば処理が変わるのでとても便利です。


以上がブックマークレットの作成方法になります。

みなさんのちょっとしたアイデアを、このブックマークレットで活かしてみませんか?

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

JavaScriptの関数(基本中の基本)

はじめに

脱初心者にむけてアウトプットをしていこうと思って記事を書いております。
私は、まだ現場に出たことのない完全にど素人です。
間違ったことがありましたら、ぜひコメントいただけると幸いです。

対象読者

JavaScriptの関数を学習している方が対象です!
関数の基本的な機能や使い方をざっくりと紹介しているので、ぜひ復習にお使いください。

JavaScriptにおける関数とは

一つの目的のために、文がまとまったものです。
他の言語を学んだことがある方なら理解が早いと思いますが、簡単にいうと、メソッドです。

function sayHello() {  //宣言始まり

  console.log("Hello world");
  console.log("こんにちは、世界!");

}  //宣言終わり

sayHello();  //関数が呼び出され、本文が実行される

このプログラムの「{」から「}」までが関数の宣言になります。
これにより関数seyHello()が定義されます。

最後のsayHello( )で関数の呼び出しを行います。
呼び出しが行われると、宣言内に書かれたコードが実行され、コンソール上に結果が出力されます。

関数の戻り値

戻り値とは値を呼び出し側に戻すことです。
下記のプログラムの場合、returnに書かれたHello world が値を返ます。

function getGreeting() {

  return "Hello world!";  //値(戻り値)を返す

}
const message = getGreeting(); //関数が呼び出され。戻り値が代入される
console.log(message);

この場合関数が呼び出された後に、定数messageに代入され、結果として出力される戻り値は、関数getGreeting()に記述された戻り値、
Hello world!になるのです。

呼び出しと参照

呼び出しと参照の違いについて、
違いは簡単です

function getGreeting() {

  return "Hello world!";

}

console.log(getGreeting()); //Hello world!         呼び出し
console.log(getGreeting);   //function getGreeting()  参照

()をつけるか付けないかで、呼び出しか参照を区別することができるのです。

関数の引数について、

上記で説明した、呼び出しや、参照などは関数から値を受け取る方法でした。
逆に関数に情報を渡すには、引数を使います。

function avg(a,b) {  //関数宣言においてaとbは「仮引数」と呼ばれる
  return (a + b)/2;
}
console.log(avg(5, 10));  //7.5

function avg(a,b) に書かれた(a, b)は仮引数と呼ばれます。
関数が呼び出された時に、呼び出し側で渡された値が代入され、本体が実行さえる。
そして出力される、戻り値は7.5になる。

無名関数について、

その名の通り、関数名をしてしない書き方です。

const f = function() {
//
};

このように書くことで実質的な効果としては関数宣言と変わらないが省略することができます。
呼び出し、参照するときも、変わらず

console.log(f());  //呼び出し
console.log(f);    //参照

アロー関数

アロー関数とは省略できる書き方です。
関数の定義と違う点が三つあります。

  1. functionが省略できる
  2. 引数が一つならば「()」を省略できる
  3. 関数本体が一つの式からなる場合、「{}」とreturn文を省略できる
const f1 = function() { return "Hello!";} // 関数
const f1 = () => "hello!";

このアロー関数はES2015から導入された便利な記法です

まとめ

今回は関数についてざっっくりと説明してみました。
ここの説明間違ってる!!とかありましたら、ぜひお声かけください?‍♀️
最後までご覧いただきありがとうございました。

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

マイクロソフト公式のWeb開発入門コースに触れてみた

この記事は「【マイスター・ギルド】本物の Advent Calendar 2020」8日目の記事です。

今年11月から入社いたしましたマイスター・ギルド(以下MG)開発部の吉川です。Qiita初投稿となります。

やろうと思ったきっかけ

はてなブックマークの方で話題になっていたことがきっかけです。
400スターくらいついててこれはすごいコースじゃないかと思ってみたら、
コース概要にタイピングゲームやブラウザ拡張機能を作るよ!とあったので
これはやるしかねぇとなったのがきっかけです。MGでは主にフロントエンドを担当させていただいており、JavaScriptを改めて復習したいと思っていたのもあります。

https://github.com/microsoft/Web-Dev-For-Beginners

で、どんなコース?

コースは24レッスンあって、これを12週間かけて取り組みましょうな構成です。
ちなみにオール英語です。

各レッスンには次の内容が入っています。

  • optional sketchnote(オプションのスケッチノート)
  • optional supplemental video(オプションのYoutubeビデオ)
  • pre-lesson warmup quiz(レッスン前のウォームアップクイズ)
  • written lesson(レッスン)
  • for project-based lessons, step-by-step guides on how to build the project-  (プロジェクトの構築方法に関するステップバイステップガイド ※プロジェクトベースのレッスンのみ)
  • knowledge checks(知識チェック)
  • a challenge(チャレンジ)
  • supplemental reading(補足資料)
  • assignment(割りあて)
  • post-lesson quiz(レッスン後のクイズ)

レッスン内容は下記表の通り。

プロジェクト名 教えること 学習目標
1 入門 プログラミングとプログラム売買のツールの紹介 ほとんどのプログラミング言語の背景にある基本的な基盤と、プロの開発者が仕事をするのに役立つソフトウェアについて学習します
2 入門 GitHubの基本(チームでの作業が含む) プロジェクトでGitHubを使用する方法、コードベースで他のユーザーとコミュニケーションする方法
3 入門 アクセシビリティ Webアクセシビリティの基本を学びます
4 JSの基本 JavaScriptデータ型 JavaScriptデータ型の基本
5 JSの基本 関数とメソッド アプリケーションのロジックフローを管理する関数と方法について学習します
6 JSの基本 JSで意思決定 意思決定を使用してコードに条件を作成する方法を学びます
7 JSの基本 配列とループ JavaScriptで配列とループを使用してデータを操作します
8 テラリウム HTMLの実践 レイアウトの構築に重点を置いて、HTMLを構築してオンラインテラリウムを作成します
9 テラリウム CSSの実践 ページをレスポンシブにするなど、CSSの基本に焦点を当てて、オンラインテラリウムのスタイルを設定するCSSを構築します
10 テラリウム JavaScriptクロージャ、DOM操作 クロージャとDOM操作に焦点を当てて、テラリウムをドラッグアンドドロップインターフェイスとして機能させるJavaScriptを構築します
11 タイピングゲーム タイピングゲームを構築する キーボードイベントを使用してJavaScriptアプリのロジックを駆動する方法を学びます
12 グリーンブラウザ拡張機能 ブラウザの操作 ブラウザーのしくみ、ブラウザーの履歴、およびブラウザー拡張機能の最初の要素を足場にする方法を学習します
13 グリーンブラウザ拡張機能 フォームの作成、APIの呼び出し、ローカルストレージへの変数の保存 ブラウザ拡張機能のJavaScript要素を構築して、ローカルストレージに保存されている変数を使用してAPIを呼び出します
14 グリーンブラウザ拡張機能 ブラウザのバックグラウンドプロセス、Webパフォーマンス ブラウザのバックグラウンドプロセスを使用して、拡張機能のアイコンを管理します。Webパフォーマンスといくつかの最適化について学習します
15 宇宙ゲーム JavaScriptを使用したより高度なゲーム開発 ゲームを構築するための準備として、クラスと構成の両方、およびPub / Subパターンを使用した継承について学習します
16 宇宙ゲーム Canvasへの描画 画面に要素を描画するために使用されるCanvasAPIについて学習します
17 宇宙ゲーム 画面上で要素を移動する デカルト座標とCanvasAPIを使用して、要素がどのように動きを得ることができるかを確認します
18 宇宙ゲーム 衝突検出 キーを押すことで要素を衝突させて反応させ、クールダウン機能を提供してゲームのパフォーマンスを確保します
19 宇宙ゲーム スコアを維持する ゲームのステータスとパフォーマンスに基づいて数学計算を実行します
20 宇宙ゲーム ゲームの終了と再会 アセットのクリーンアップや変数値のリセットなど、ゲームの終了と再開について学習します
21 銀行アプリ WebアプリのHTMLテンプレートとルート ルーティングとHTMLテンプレートを使用して、複数ページのWebサイトのアーキテクチャの足場を作成する方法を学習します
22 銀行アプリ WebアプリのHTMLテンプレートとルート ルーティングとHTMLテンプレートを使用して、複数ページのWebサイトのアーキテクチャの足場を作成する方法を学習します
23 銀行アプリ データを取得して使用する方法 アプリにデータが出入りする方法、データを取得、保存、破棄する方法を学習します
24 銀行アプリ 状態管理の概念 アアプリが状態を保持する方法と、プログラムでアプリを管理する方法を学習します

こんな感じで

やったプロジェクト

今回は時間が足りなくてアドカレの一環だし全レッスンやっていると分量が増えすぎてしまうので、グリーンブラウザ拡張機能までやってみました。

  • 入門
  • JSの基本
  • テラリウム
  • タイピングゲーム
  • グリーンブラウザ拡張機能

どんな手順で勉強したの?

筆者はこんな手順で勉強しました。

スケッチノート見る→レッスン前のウォームアップクイズやる→レッスン本文読む
(テラリウムプロジェクト以降のみ)実際にソースコードを動かす→ レッスン後のクイズやる

実際に取り組んだ所感

英語なのでGoogle翻訳とかDeepL翻訳に助けてもらいながら頑張りました。
あと詳しい学習内容はコースページを見ればわかるのでここでは割愛する。1レッスン長くないしね。

入門~JS入門プロジェクト

※以下レッスンのみ日本語化済み(2020/11/28時点)

レッスン1: プログラミングとプログラム売買のツールの紹介

この2プロジェクトは実際に手を動かしてコードを動かすというよりかは、テキストを読んで学習するスタイルとなる。筆者もテキストを読みながらふむふむと学習していた。

入門コースは「プログラミングとは」「プログラミングで使うツール」「GitとGithubの基本的な使い方」「アクセシビリティ」が主なトピック。

なお入門プロジェクトにあるアクセシビリティレッスンに関してはこの手のコースで触れられるのは初めて見たと思う。アクセシビリティについては個人でサイトを作った際にLighthouseというツールを触れた程度なのでこんなツールがあるのか!と感じながら新鮮な気持ちで読んでいました。

JS入門はJavaScriptの「データタイプ」「関数とメソッド」「意思決定」「配列とループ」の4レッスン。レベルはProgateのJavaScriptコースと同じくらい。ES6の初学者は読んでもいいけど、それ以外の人はページ最初のスケッチノートだけ見ておけばいいかなという印象。スケッチノートに学習内容が詰め込まれているし。クラス文に関するレッスンがない点は疑問に感じた。

テラリウム

HTML、CSS、JavaScriptのDOM操作について学習しながら、簡単なドラッグアンドドロップができるテラリウムアプリを作るコース。ここから実際にコードを書いて学習を進めることになる。

完成図はこんな感じです。
image.png

で、植物の絵をドラッグアンドドロップすると、次のように真ん中の瓶の中に植物を設置できるようになります。
image.png

やってみた所感ですが、レッスン中のソースコードを指示に従って写経し完成させる形で作っていくのは楽しい。
ですが、CSSやJavaScriptのプロパティはいくつか補足説明されている一方、特に説明されていない点も少なくなく入門コースにしては説明不足感は否めないように思えた。他サイトのドキュメントで調べてねってことだろうか。
(筆者はpositionプロパティとか位置関係は苦手なのでただ写経する形じゃ辛かった、この辺もっと勉強しないと…)

タイピングゲーム

JavaScriptキーボードイベントを学習しながら、タイピングゲームを作るコース。

完成するとこんな感じでゲームができる。
20201128223158.gif

これはコードが比較的簡単だったのとロジックの説明が順序立てて説明されていたので取り組みやすかったです。ゲームが完成したときはこんな簡単なロジックでエラーも検知できて、クリア時間まで出てくるタイピングゲームができるのかと少し感動していました。

グリーンブラウザ拡張機能

今回筆者が一番楽しみにしていたプロジェクトです。
ここでは「ブラウザについて」「フォームの作成、APIの呼び出し、ローカルストレージの使い方」「Webパフォーマンスの測定・改善」を学びならが、ブラウザ拡張機能を作ることになります。ここで作る拡張機能はC02 Signal APIという外部APIを使用して、地域の二酸化炭素排出量を確認できる拡張機能です。

※ここで作る拡張機能はEdge(おそらくChromium版のみ)、Chrome、Firefoxで動作するとのこと

ここのプロジェクトは拡張機能を作る前の準備がちょっと分かりづらいのでメモを残しておきます。

  1. Node.jsなどでnpmをインストール(多分yarnに置き換えてもいいはず)。
  2. APIを使用するためのAPI Key:ここからメールアドレスを入力するとメールが送られてくるのでそこに書かれているpersonal API token: xxxxxxxxxxxxxxxxxxを見て使ってください。
  3. 地域コード:炭素取得料を調べたい地域のコードをここから取得します。本当は関西の排出量を知りたかったのでJP-KNで調べたかったのですが、取り組んでいた日時だとデータが取得できず、九州を表すJP-KYとしました。
  4. スターターフォルダ:ここのフォルダを今回は使うのでgit cloneなどしてダウンロード。
  5. 4のスターターフォルダをコマンドツールで開き、npm installnpm run buildの順にコマンドを叩く。
  6. 公式はEdgeで紹介されているので、ここではEdgeでの手順を記載。Chromeも拡張機能ページを開いたあとの手順はほぼ同じ(Firefoxは何故か上手く行かなかった)。url欄にedge://extensions/を入力→左下の開発者モードをON→展開して読み込みをクリックし、4のスターターフォルダのdistフォルダを開く。するとMy Carbon Triggerという拡張機能が読み込まれるはずです。
  7. レッスンの指示どおりにコードを書き、終えたあとはnpm run buildするとコードの記載内容が反映されているはずです。

完成図はこんな感じ。調べたい地域の地域コードとAPIKeyを入力し、Submitボタンを押下すると、二酸化炭素使用量と電気の供給における化石燃料(石炭とか石油などのこと)の割合を確認することができる。
image.png


image.png

このプロジェクトを感想した所感としては、axiosを使用したAPI取得を通して、非同期処理とLocalStorage/SessionStorageの説明はわかりやすかった。preventDefaultメソッドはReactを使った開発で出てくるイメージがあって、React専用のものだと思っていたのですが、JavaScript標準のメソッドだったんですね。

最後のパフォーマンスに関するレッスンなのですが、やっていて感じたのが今回の小さめの拡張機能だとパフォーマンス改善は伝わりづらいじゃないかと思った(開発者ツールのパフォーマンスタブの使い方のところは良かったです)。この辺りはWebアプリレッスンでやった方が取り組みやすかったと思う。

今回の記事でやったプロジェクトはここまで。

最後に

本コースをある程度進めた感想としましては、beginnerコースでありながら、「アクセシビリティ」や「LocalStorage/SessionStorage」などこの手のコースでは触れられることは少ない項目もあり、新しく学ぶことも少なくなくやりがいはありました。
ただ、実際にアプリを作るプロジェクトになると、コード写経してくださいと言わんばかりにCSSとかJSプロパティの意味は公式ドキュメントとか外部サイト見てね感はあり、全くのWeb開発初心者がやるのはきついかもしれません。

筆者としては今回できなかった「宇宙アプリ」「銀行アプリ」も忘れないうちに触れていきたい次第です。

本コースは翻訳活動が歓迎されており、Githubのpull requestを開くとすでにいろんな言語の翻訳が進んでいます。ただ、日本語については殆どタッチされておらず残念に思います。ということで時間と気力があれば、本コースの日本語翻訳をいつか私が担当したいなぁと思っている次第です。(まずはJS Primer1周するのが優先ですが…)

あと本コース、docsifyという文書作成ツールをローカルにインストールすると、localhostで読めるみたいですよ。筆者はまだ試せてないので、また別の機会に…

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

非テキストボックスの要素をJavaScriptでテキストボックス化する

概要

spanタグで記述されている要素をJavaScriptでinputタグのテキストボックスに変更する。
口座番号と月末残高の2項目をもつ明細一覧のテーブルがある。月末残高のセルには修正ボタンがあり、修正ボタンを押すとテキストボックス化の処理が実行される。<span>タグで記述されている月末残高の値が、値が保持されたまま<input type="text">に変更される。
なお画面のコードはHTMLだけでなくThymelaefも含む。また、HTMLのid属性が使えない前提がある。
スクリーンショット 2020-11-30 18.21.10.png

用いたコード

example.html
<!DOCTYPE html>
<html xmlns:th ="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>明細一覧</title>
</head>
<body>
  <div align="center">
    <table border="1">
    <tr>
        <th>
        口座番号
        </th>
        <th>
        月末残高
        </th>
    </tr>
    <tr th:each="statement, statementInfo : ${allStatements}">
        <td>
        <span th:text="${statement.accountId}"></span>
        </td>
        <td>
        <form>
          <span th:text="${statement.eomBalance}" class="eomBalance"></span>
          <button type="button" onclick="btn_click()" class="modify" th:name="${statementInfo.index}">修正</button>
        </form>
        </td>
    </tr>
    <tr>
        <td colspan="3">
          <input type="submit" value="送信">
        </td>
    </tr>
    </table>
  </div>
</body>
<script>
function btn_click() {
    var num = event.target.form.querySelector("button[name]").name;
    var eomBalance = document.getElementsByClassName("eomBalance")[num].textContent;
    // inputタグに変更
    document.getElementsByClassName("eomBalance")[num].innerHTML = '<input type=text maxlength="4" th:field="*{eomBalance}" value=' + eomBalance + '>';
}
</script>
</html>

処理

修正ボタンを押下すると、btn_click()が呼ばれる。
押下された修正ボタンがどのフォームか識別するため、buttonタグのname属性から番号を取得する。
name属性はindexを使いth:each処理のインデックスを取得している。

<button type="button" onclick="btn_click()" class="modify" th:name="${statementInfo.index}">修正</button>
var num = event.target.form.querySelector("button[name]").name;

取得した番号を用いて、変更されるセルの月末残高の値をspanタグから取得する。

<span th:text="${statement.eomBalance}" class="eomBalance"></span>
var eomBalance = document.getElementsByClassName("eomBalance")[num].textContent;

月末残高の値を変数eomBalanceに保持したまま、inputタグの文字列を生成する。inputタグの文字列をinnerHTMLから代入する。
※本来はinnerHTMLではなくcreateElementメソッドを用いるべきである。なぜならクロスサイトスクリプティングの脆弱性が発生するからである。

// inputタグに変更
document.getElementsByClassName("eomBalance")[num].innerHTML = '<input type=text maxlength="4" th:field="*{eomBalance}" value=' + eomBalance + '>';
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

そろそろNode14へ移行せな

はじめに

  • 2020/4/22にNode.js14がリリースされました。
  • また、2020/10/27にNode.js14のActive LTSが開始され、2020/11/30にNode.js12のMaintenance LTSに移った為、そろそろ切り替え時かなということで、この記事を残します。

参考: Node公式リリース時期
スクリーンショット 2020-11-30 13.03.47.png

LTSってなんだ??

LTSとは、[Long Term Support]の略を指し、長期の保守運用が約束されているバージョンになります。

Current LTS

  • 最新版だが、安定性を約束しないことで機能追加を盛り込んだバージョン

Active LTS

  • リリースラインに適切で安定していると判断された新機能、バグ修正、および更新。

安定しているため、本番環境をアップグレードする最適な時期ともいえます

Maintenance LTS

  • 重大なバグ修正とセキュリティアップデート

Node14の新機能

診断レポートの安定

  • 診断レポートは、Node14の安定した機能としてリリースされています (Node12では、実験的な機能として追加されていました)

→ 診断レポート機能を使用すると、オンデマンドまたは特定のイベントが発生したときにレポートを生成できます。

  • このレポートには、クラッシュ、パフォーマンスの低下、メモリリーク、高いCPU使用率、予期しないエラーなど、本番環境での問題の診断に役立つ情報が含まれています。

実行方法については、次のように[--report-on-fatalerror]を指定します。
また、例外がcatchされなかったときにレポートを出力する[--report-uncaught-exception]などがあります。

node --report-on-fatalerror server.js

参照: 診断レポート機能の詳細

V8がV8 8.1にアップグレード

V8のバージョンが上がることで使用できるJavaScriptの構文や機能が増えます。

Optional Chaining

Optional chainingは、参照したオブジェクトや関数の値がundefinedやnullの可能性があっても、その値が持つプロパティに安全にアクセスすることができます。

example.js
// 使用前
let nameLength;
if (user && user.info && user.info.name)
  userName = user.info.name;

// Optional Chainingを使用
const userName = user?.info?.name;

参照: MDN_Optional Chaining

Nullish Coalescing

Nullish coalescingは、参照する値がundefined または null の時、デフォルト値を取得することができます。

example.js
const resultString = null ?? 'default';
console.log(resultString);
// => default

const resultNumber = 0 ?? 42;
console.log(resultNumber);
// => 0

参照: MDN_Nullish Coalescing

Intl.DisplayNames

Intl.DisplayNamesは、指定したロケールとオプションに基づいた表示名称の翻訳を取得することができます。

example.js
// 国/地域コードから国名/言語名を出力する例
const languageNamesInEnglish = new Intl.DisplayNames(['en'], { type: 'language' });
const languageNamesInFrance = new Intl.DisplayNames(['fr'], { type: 'language' });

console.log(languageNamesInEnglish.of('ja'));
// => "Japanese"
console.log(languageNamesInFrance.of('en-US'));
// => "anglais américain"

参照: MDN_Intl.DisplayNames

Intl.DateTimeFormatのcalendar optionとnumberingSystemオプションの有効化

Intl.DateTimeFormatのoptions引数を用いて、calendarとnumberingSystemが使えるようになりました。

example.js
const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));

console.log(new Intl.DateTimeFormat('en-US').format(date));
// => "12/20/2012"

console.log(new Intl.DateTimeFormat('en-GB').format(date));
// => "20/12/2012"

console.log(new Intl.DateTimeFormat('ja', { calendar: 'japanese',  numberingSystem: 'jpan', era: 'long' }).format(date));
// => "平成24年十二月二十日"

参照: MDN_Intl.DateTimeFormat

実験的にAsync Local Storage APIの追加

AsyncLocalStorageは、コールバックとプロミスチェーン内に非同期状態を作成するために使用されます。

→ これにより、Webリクエストの存続期間またはその他の非同期期間を通じてデータを保存できます。これは、他の言語のスレッドローカルストレージに似ています。

参照: ドキュメント_AsyncLocalStorage

Streams API全体の一貫性を向上

変更点としては、

  • [http.OutgoingMessage] → [stream.Writable]に一貫しました。

  • [net.Socket] → [stream.Duplex]に一貫しました。

変更としては以上ですが、アプリケーションに影響はないと思っています。

ES Moduleの警告を削除

ES Modulesとは

JavaScriptにおけるモジュール機能としては、下記のものがあります。

  • CommonJS
  • ECMAScript Modules(ES Modules)
  • etc..

CommonJS

CommonJSとは、言語仕様のModules解決するために主にNodeに実装されています。

example.js
const { test } = require("./test");

ES Modules

ES Modulesとは、再利用のためにJavaScriptコードをパッケージ化するための公式の標準形式です。

example.js
import { test } from "./test.js"

じゃあどうなったの??

今までは、ES Modulesを使用する場合、以下の警告が表示されていました。

ExperimentalWarning: The ESM module loader is experimental.

これが、Node.js v14 からは上記の警告は表示されなくなります。
注意点としては、あくまでまだ実験的なものであることです

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

そろそろNode.js 14へ移行せな (新機能まとめ)

はじめに

  • 2020/4/22にNode.js14がリリースされました。
  • また、2020/10/27にNode.js14のActive LTSが開始され、2020/11/30にNode.js12のMaintenance LTSに移った為、そろそろ切り替え時かなということで、この記事を残します。

参考: Node公式リリース時期
スクリーンショット 2020-11-30 13.03.47.png

LTSってなんだ??

LTSとは、[Long Term Support]の略を指し、長期の保守運用が約束されているバージョンになります。

Current LTS

  • 最新版だが、安定性を約束しないことで機能追加を盛り込んだバージョン

Active LTS

  • リリースラインに適切で安定していると判断された新機能、バグ修正、および更新。

安定しているため、本番環境をアップグレードする最適な時期ともいえます

Maintenance LTS

  • 重大なバグ修正とセキュリティアップデート

Node14の新機能

診断レポートの安定

  • 診断レポートは、Node14の安定した機能としてリリースされています (Node12では、実験的な機能として追加されていました)

→ 診断レポート機能を使用すると、オンデマンドまたは特定のイベントが発生したときにレポートを生成できます。

  • このレポートには、クラッシュ、パフォーマンスの低下、メモリリーク、高いCPU使用率、予期しないエラーなど、本番環境での問題の診断に役立つ情報が含まれています。

実行方法については、次のように[--report-on-fatalerror]を指定します。
また、例外がcatchされなかったときにレポートを出力する[--report-uncaught-exception]などがあります。

node --report-on-fatalerror server.js

参照: 診断レポート機能の詳細

V8がV8 8.1にアップグレード

V8のバージョンが上がることで使用できるJavaScriptの構文や機能が増えます。

Optional Chaining

Optional chainingは、参照したオブジェクトや関数の値がundefinedやnullの可能性があっても、その値が持つプロパティに安全にアクセスすることができます。

example.js
// 使用前
let nameLength;
if (user && user.info && user.info.name)
  userName = user.info.name;

// Optional Chainingを使用
const userName = user?.info?.name;

参照: MDN_Optional Chaining

Nullish Coalescing

Nullish coalescingは、参照する値がundefined または null の時、デフォルト値を取得することができます。

example.js
const resultString = null ?? 'default';
console.log(resultString);
// => default

const resultNumber = 0 ?? 42;
console.log(resultNumber);
// => 0

参照: MDN_Nullish Coalescing

Intl.DisplayNames

Intl.DisplayNamesは、指定したロケールとオプションに基づいた表示名称の翻訳を取得することができます。

example.js
// 国/地域コードから国名/言語名を出力する例
const languageNamesInEnglish = new Intl.DisplayNames(['en'], { type: 'language' });
const languageNamesInFrance = new Intl.DisplayNames(['fr'], { type: 'language' });

console.log(languageNamesInEnglish.of('ja'));
// => "Japanese"
console.log(languageNamesInFrance.of('en-US'));
// => "anglais américain"

参照: MDN_Intl.DisplayNames

Intl.DateTimeFormatのcalendar optionとnumberingSystemオプションの有効化

Intl.DateTimeFormatのoptions引数を用いて、calendarとnumberingSystemが使えるようになりました。

example.js
const date = new Date(Date.UTC(2012, 11, 20, 3, 0, 0));

console.log(new Intl.DateTimeFormat('en-US').format(date));
// => "12/20/2012"

console.log(new Intl.DateTimeFormat('en-GB').format(date));
// => "20/12/2012"

console.log(new Intl.DateTimeFormat('ja', { calendar: 'japanese',  numberingSystem: 'jpan', era: 'long' }).format(date));
// => "平成24年十二月二十日"

参照: MDN_Intl.DateTimeFormat

実験的にAsync Local Storage APIの追加

AsyncLocalStorageは、コールバックとプロミスチェーン内に非同期状態を作成するために使用されます。

→ これにより、Webリクエストの存続期間またはその他の非同期期間を通じてデータを保存できます。これは、他の言語のスレッドローカルストレージに似ています。

参照: ドキュメント_AsyncLocalStorage

Streams API全体の一貫性を向上

変更点としては、

  • [http.OutgoingMessage] → [stream.Writable]に一貫しました。

  • [net.Socket] → [stream.Duplex]に一貫しました。

変更としては以上ですが、アプリケーションに影響はないと思っています。

ES Moduleの警告を削除

ES Modulesとは

JavaScriptにおけるモジュール機能としては、下記のものがあります。

  • CommonJS
  • ECMAScript Modules(ES Modules)
  • etc..

CommonJS

CommonJSとは、言語仕様のModules解決するために主にNodeに実装されています。

example.js
const { test } = require("./test");

ES Modules

ES Modulesとは、再利用のためにJavaScriptコードをパッケージ化するための公式の標準形式です。

example.js
import { test } from "./test.js"
じゃあどうなったの??

今までは、ES Modulesを使用する場合、以下の警告が表示されていました。

ExperimentalWarning: The ESM module loader is experimental.

これが、Node.js v14 からは上記の警告は表示されなくなります。
注意点としては、あくまでまだ実験的なものであることです

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

ES6記述 備忘録

ES6記述一覧

const, let

varで定義していたがES6からはconst,letで定義する。
constは変わらない変数
letは今後変わる可能性がある変数

テンプレートリテラル

’今日は’ + ‘currentDay’ + ‘です’ とかいていたものが
 `今日は${currentDay}です`  とかけるようになった。

アロー関数でfunctionを省略できるようになった

numbers.map(function(number) {
return number + 1;
});

って書いたものが

numbers.map((number)  => {
return number + 1;
});

とかけて引数が一つの場合は{}とreturnと()も省略できる

numbers.map(number  => number + 1);
とかける

オブジェクトリテラル

const fields = ['firstName', 'lastName', 'phoneNumber'];

const props = { fields: fields };

と書いていたが、keyとvalueが同じ値なら :valueを省略できる

const fields = ['firstName', 'lastName', 'phoneNumber'];

const props = { fields };

関数(function)も省略

const color = 'red';

const Car = {
  color: color,
  drive: function() {
    return 'ブーーン!';
  },
  getColor: function() {
    return this.color;
  }
};

と書いていた物が

const color = 'red';

const Car = {
  color,
  drive() {
    return 'ブーーン!';
  },
  getColor() {
    return this.color;
  }
};

とかける。

デフォルト引数

function addOffset(style) {
  if (!style) {
    style = {}; 
  }

  style.offset = '10px';

  return style;
}

と書いていた物が

function addOffset(style = {}) {

  style.offset = '10px';

  return style;
}

と引数に直接デフォルト値を設定できる。

Rest,Spread演算

function unshift(array, a, b, c, d, e) {
  return [a, b, c, d, e].concat(array);
}

と書いていた物が

function unshift(array, ...rest) {
  return [...rest, ...array];
}

と書けるようになった。

分割代入

var expense = {
tyoe: '交際費'
amount: '4500 JPY'
};

var type = expense.type;
var amount = expense.amount;

と書いていた物が

const { type, amount } = expense;

と一行でかけるようになった。

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

【kintone】ボタンをクリックしてフィールドの値を書き換えてみよう!

前回はアプリにボタンをつける方法について書きました。

【kintone】アプリの「スペース」フィールドにボタンを設置する

今回は、前回作ったアプリのボタンでフィールドの値を書き換えます。

「名前(文字列(1行))」フィールドに「太郎」と書くボタンにしたいと思います。
イメージ図↓

今回もkintone & JavaScriptカスタマイズを始めたばかりの方向けに書きましたのでご安心を^0^

アプリの準備

文字列(1行)を設置して、フィールドコードを「名前」としておきましょう。

JavaScript

まずは前回と同様、コピペしたら動くプログラムです。
コピペとは「コピーしてペーストする(貼り付ける)」のことです。

説明は後述します。

//新規追加画面でボタンを使いたいときは'app.record.create.show'
//編集画面でボタンを使いたいときは'app.record.edit.show'
kintone.events.on(['app.record.create.show', 'app.record.edit.show'], event => {
    //ボタンを置きたいスペースフィールドを取ってくる
    const sp = kintone.app.record.getSpaceElement('spSuteki');

    //ボタンを作る
    const btn = document.createElement('button');
    //ボタンに表示したいテキスト
    btn.textContent = 'ボタン';

    //スペースフィールドにボタンを追加する
    sp.appendChild(btn);

    //ボタンをクリックしたときの動作
    btn.onclick = () => {
        const obj = kintone.app.record.get();
        obj.record.名前.value = "太郎";
        kintone.app.record.set(obj);
    }

    return event;
});    

JavaScript解説

ボタンの設置は前回の記事のこちらを見てね
【kintone】アプリの「スペース」フィールドにボタンを設置する-JavaScript解説

今回は、ボタンをクリックしたら「名前」フィールドの値を「太郎」に書き換えたいのでした。

編集中の画面でボタンをクリックしてフィールドの値を変えたいときは、
「ボタンをクリックしたときの動作を書くところ」に、以下の順でプログラムを書きます。

  1. レコードの値を取得する
  2. レコードの値を変更する
  3. レコードに値をセットする

順番に解説したいと思います。

ボタンをクリックしたときの動作を書くところとは

下記の//ボタンをクリックしたときの動作を書くところ に書きます。
先程のプログラムコードの中にもあるので見つけてくださいネ。

btn.onclick = () => {
    //ボタンをクリックしたときの動作を書くところ
}

1. レコードの値を取得する
レコードの値を取得するときは
const obj = kintone.app.record.get();
とします。

objという入れ物に、kintone.app.record.get()を入れるという意味です。

kintone.app.record.get()は、今見ているアプリのレコードの値をゲットする(get、取得する)プログラムです。

つまり・・・
objという入れ物に、今見ているアプリのレコードの値を入れるという意味になります。

参考:kintone.app.record.get()

2. レコードの値を変更する

次は、フィールドの値を変更します。
「名前」フィールドの値を「太郎」にしたいのでした。

obj.record.名前.value ="太郎";

obj.record で、objという入れ物の中のrecord(レコード)を表しています。1.でgetしたレコードを呼び出しています。

obj.record.名前.value で、objの中のレコード(record)の「名前フィールド」の値(value) の意味を表します。
これが="太郎"とされているので、「取得したレコードの名前フィールドの値を「太郎」に変更する」という意味になります。

しかしこれで終わりではありません。

3.レコードに値をセットする

最後は、kintone.app.record.set(obj);として、変更した値を確定させる必要があります。
set の後の () には get で取得した obj を書きます。

2.のように値を書き換えただけで満足してはいけません。
この1文がないと、フィールドの値が書き換わらないのです。

参考:kintone.app.record.set(obj)

「ボタンクリック」によるデータの書き換えの流れを一言ずつ書くと

get

変更

set

となります。getとsetを覚えておきましょう!

ボタンの動き完成

というわけで
ボタンをクリックしたときに「名前」フィールドの値を「太郎」に書き換えるプログラムは以下のようになります。

//ボタンをクリックしたときの動作
btn.onclick = () => {
    const obj = kintone.app.record.get();
    obj.record.名前.value = "太郎";
    kintone.app.record.set(obj);
}

もし複数のフィールドがあったら?

例えば、「名前」フィールド以外に、「年齢」、「性別」などのフィールドがあった場合

フィールドの値を変更する前に
const obj = kintone.app.record.get();

フィールドの値を複数変更した後に
kintone.app.record.set(obj);

の順番で書くとOKです。

//ボタンをクリックしたときの動作
btn.onclick = () => {
    const obj = kintone.app.record.get(); //値変更の前にgetを1回
    obj.record.名前.value = "太郎";  //フィールドの値変更1
    obj.record.年齢.value = 10;    //フィールドの値変更2
    obj.record.性別.value = "秘密"; //フィールドの値変更3
    kintone.app.record.set(obj);  //値変更のあとにsetを1回
}

これでボタンで値の変更ができるようになりました?

まとめ

一見簡単そうに見えるボタン付け~フィールドの変更ですが、

  • ボタンを設置する
  • ボタンをクリックしたときの動作を書く
    1. レコードを取得(get)する
    2. レコードの値を変更する
    3. レコードをセット(set)する

という流れを抑えておくとボタン設置名人になれるかも!
ぜひボタン付け練習してみてくださいね!

また、要注意なのですが、
cybozu developer network内にはこう書かれています。

  • kintone.events.on のイベントハンドラ内で kintone.app.record.get および kintone.mobile.app.record.get を実行することはできません。 上記のイベントハンドラ内ではレコードデータの取得は引数のeventオブジェクトを、レコードデータの更新はeventオブジェクトのreturnを使用してください。
  • kintone.events.on のインベントハンドラ内で kintone.app.record.set および kintone.mobile.app.record.set を実行することはできません。 上記のイベントハンドラ内ではレコードデータの取得は引数のeventオブジェクトを、レコードデータの更新はeventオブジェクトのreturnを使用してください。

ざっくり説明すると、
表示、保存前、保存後等、のイベント中はgetとsetが使えないという意味です。
しかしここはひとまず、ボタンを使って値を変更したいときはgetとsetを使うんだなと思っていただいて、
他のイベントだとどうなるのか?ボタンの動作を書くところじゃないところに書いたらどうなるのか?など試していただけたらいいなぁと思います。

以上です!

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

【JS】表 (table)のHTMLを2次元配列に変換するプログラム

tableタグを2次元で表現するプログラムを作成する。
rowspanやcolspanも加味して考える。

目次

・セルを1で表現するプログラム
・列番号で表現するプログラム


1. セルを1で表現するプログラム

実際のブラウザ上の表示に合わせて、セルがある場所を1で埋めて表を表現する。

完成イメージ

例えば3行3列の表なら以下のように出力する。

image.png

arr = [
  [1, 1, 1]
  [1, 1, 1]
  [1, 1, 1]
]



▼結合がある場合1

結合がある場合セルの数(tdタグ)は減るが、colspanやrowspanに合わせて1を埋める。

image.png

arr = [
  [1, 1, 1]
  [1, 1, 1]
  [1, 1, 1]
]



▼結合がある場合の例2

image.png

arr = [
  [1, 1, 1]
  [1, 1, 1, 1]
  [1, 1, 1, 1, 1]
]

コード

処理には、HTMLTableElementを利用する。

HTMLTableElementはtableタグをオブジェクトに変換しjsで処理できるAPI。

HTMLTableElementの使い方


function scanTable(table)
{
    var arr = [];
    for(var y=0; y<table.rows.length; y++)
    {
        var row = table.rows[y];

        for(var x=0;x<row.cells.length;x++)
        {
            var cell = row.cells[x], xx = x, tx, ty;

            for(; arr[y] && arr[y][xx]; ++xx);                        

            for(tx = xx; tx < xx + cell.colSpan; ++tx)            
            {
                for(ty = y; ty < y + cell.rowSpan; ++ty)
                {
                    if( !arr[ty] ) arr[ty] = [];                    
                    arr[ty][tx] = 1;
                }
            }
        }
    }

    return arr
};

コードの中身

▼変数

arr 1が入る2次元配列。最終的なアウトプット。

ty 1を入力する行番号

tx 1を入力する列番号

y 行番号

x 列番号

xx 1を入力するセルの列開始番号。



▼主な処理

for(var y=0; y<table.rows.length; y++)
    {
        var row = table.rows[y];

        for(var x=0;x<row.cells.length;x++)

1行毎、1セル毎に処理を繰り返す。
x, yは結合を加味せず、1づつ増えていく。



for(; arr[y] && arr[y][xx]; ++xx);
1を挿入しようとしているセルに、結合などにより既に1が挿入されていないか判断。

既に1が挿入してある場合は、隣のセルに移動(xxを1増やす)

for(tx = xx; tx < xx + cell.colSpan; ++tx)
列方向の結合状況を加味する処理。cospanの分だけ、列方向に1を入力するセルを増やす。

for(ty = y; ty < y + cell.rowSpan; ++ty)
行方向の結合状況を加味する処理。rowspanの分だけ、行方向に1を入力するセルを増やす。

if( !arr[ty] ) arr[ty] = [];
行方向の結合(rowspan > 1)があった場合に、まだその行が定義されていない場合は、空のオブジェクトを生成する。

これがないと、rowspan > 1 の時にエラーが発生する。

エラー
Cannot set property '0' of undefined

//undefinedのプロパティ0には値をセットできない


2. 列番号で表現するプログラム

結合によりセルの列番号がズレるが、ズレが発生した場合に各セルの見た目の列番号を取得するプログラム。

先ほどは見た目を1で表現したが、今回はセルを基準にして表示する。

完成イメージ

image.png

arr = [ 
 [0, 1, 2]
 [0, 1, 2]
 [0, 1, 2]
]



▼結合がある場合1
image.png

arr = [ 
 [0, 1, 2]
 [1, 2]
 [0, 1, 2]
]

2行目のセルが2つになっている状態。実際のセルの行列番号は (1, 0) だが、見た目の列番号は1から始まる。



▼結合がある場合2
image.png

arr = [ 
 [0, 1, 2]
 [1, 2, 3]
 [0, 3, 4]
]

コード

function scanTable2(table)
{
    var arr = [];
    const colIndices = [];
    for(var y=0; y<table.rows.length; y++)
    {
        var row = table.rows[y];

        for(var x=0;x<row.cells.length;x++)
        {
            var cell = row.cells[x], xx = x, tx, ty;

            for(; arr[y] && arr[y][xx]; ++xx);                        // skip already occupied cells in current row

            for(tx = xx; tx < xx + cell.colSpan; ++tx)            // arrark arratrix elearrents occupied by current cell with true
            {
                for(ty = y; ty < y + cell.rowSpan; ++ty)
                {
                    if( !arr[ty] ) arr[ty] = [];                    // fill arrissing rows
                    arr[ty][tx] = 1;

                    if ( !colIndices[ty] ) colIndices[ty] = [];
                    colIndices[ty][x] = xx;
                }
            }
        }
    }
    return colIndices;
};

9割は前述の表を1で表現するプログラムと同じ。追加した処理は以下3行のみ。

追加コード
const colIndices = [];

if ( !colIndices[ty] ) colIndices[ty] = [];
colIndices[ty][x] = xx;

列番号を格納する変数colIndicesを作成し、セル番号に合わせて、変数xxを格納している。

x = row.cells.lengthであり、何番目のセルかを表している。

xxは配列arrで1を格納するセルの開始番号を表してる。つまり結合によりセルがズレていれば、ズレも加味した列数を指す値となっている。

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

wikipediaのワードを使った連想ゲームアプリを作ってみた【Node.js+express】

このアプリはいたってシンプルで、wikipediaからランダムに取得したワードに関連した言葉を3つ入力するアプリです。
(めちゃくちゃ簡単なアプリです。ご承知おきください。)

実装環境

ubuntu:18.04.5 LTS
node.js :v10.14.2
express : 4.16.0

アプリの使い方

image.png
こちらがトップ画面です。「岐阜中警察署」と出ています。思いついたものなんでもいいので、3つ入力します。

image.png
中学校なのか、警察署なのか、どちらなのでしょうか…?
あとは岐阜から連想しました。

image.png

送信を押すと、また新しいワードが出てきます。「光原逸裕」って誰でしょうか。何回もやっていると「この人誰だよ」っていう人物名がめちゃくちゃ出てきます。笑
本当に知らないものが多いので、高度な連想力が試されます。

技術紹介

今回は、random-word-wikipediaというAPIを使いました。Wikipediaのあらゆるページからランダムに単語を引っ張ってきてくれるものです。

使い方

まずrandom-word-wikipediaをインストールします。

$ npm install random-word-wikipedia

そして、requireでモジュールを読み込み、thenによって取得できます。下のコードは公式ドキュメントのusageです。

const randomWordWikipedia = require('random-word-wikipedia');

randomWordWikipedia().then(console.log);
//=> [ 'Saxifraga spathularis' ]

randomWordWikipedia('ja', 2).then(console.log);
//=> [ 'ジョン・イサーク・ブリケ', '月は闇夜に隠るが如く' ]

ソースコード

実際にこのAPIを使用したコードの抜粋です。

index.js
const randomWordWikipedia = require('random-word-wikipedia');

function wrap_randomWordWikipedia(callback){
  randomWordWikipedia('ja').then((value) => {
    callback(value);
  })
}

router.get('/', function(req, res, next){
  function callback(param){
    var data = {
        theme: param,
    }
    res.render('index', data);
  }

  wrap_randomWordWikipedia(callback);

});

router.post('/add', (req, res, next) => {
  function callback(param){
    console.log('callback: ', param);
    var ans1 = req.body['ans1'];
    var ans2 = req.body['ans2'];
    var ans3 = req.body['ans3'];
    var data = {
      theme: param,
      ans1: ans1, 
      ans2: ans2, 
      ans3: ans3
    }
    res.render('index', data);
  }
  wrap_randomWordWikipedia(callback);
})
index.pug
extends layout

block content
  h1= "Output three objects"
  p 「#{theme} 」に関連したものを入力してください
  form(method="post" action="/add")
    input(type="text" name="ans1")
    input(type="text" name="ans2")
    input(type="text" name="ans3")
    input(type="submit" name="送信")

  p #{ans1} #{ans2} #{ans3}

苦戦したポイント

random-word-wikipediaが非同期で動くモジュールという点に苦戦しました。
後から動くモジュールをどうレンダリングするか色々試行錯誤しましたが、結局コールバック関数を使うことにより同期処理を非同期処理の後に実行させました。
もっといい書き方があればご教授いただけると幸いです。

解決方法を書いていたら長くなったので、別記事にします。(アップ次第、こちらにリンク貼ります。)

これを作った意図

人と会話するときに、何にも話題とか返答が思いつかないことってありませんか?
私はよくあるので、頭の体操になるんじゃないかという軽い思いつきで作りました。

最後に

まだデータベース接続もしておらずとても簡単なつくりですが、とりあえずアウトプットしました。
ちょっとだけ作るつもりでしたが、作り出すと色々発想が生まれてきて、タイム計測やオリジナル問題など色々機能追加しようかと考え中です。やっぱ自作アプリは楽しいですね。

最後までご覧いただきありがとうございました!
もし参考になったという方がいれば、LGTMお願いします!

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

tableタグをJavascriptで操作する。HTMLTableElementについて

HTMLtableElementというインターフェースを使うことで、tableタグをJavaScriptで操作できることが判明。

HTMLTableElementの概要とは何か?と使い方のまとめ。


HTMLTableElementとは?

HTML5から実装された機能で、tableタグをオブジェクトに変換し行列の状態を取得したり、行列を追加したりなどの操作ができる

HTMLElementであるタグ(要素)をAPIで操作する形となる。

image.png

これを使うと、例えばtableタグ内のtrタグは、rowsプロパティで表される。

列を追加したい場合はinsertRow()メソッドを使うことで、trタグを追加できる。

・HTMLTableElement関連のAPI

HTMLTableElementで用意されているのは、tableタグ直下のthタグ、trタグなどの操作のみ。

trタグ内のtdタグの取得や追加にはHTMLTableRowElementを使う。


なにが便利なのか?

tableタグをそのまま処理できるということに尽きる。

例えば、以下のような、3行3列のtableは、trタグやtdタグで表される。

image.png

tableタグ
<table id="xx">
  <tr>
    <td rowspan="1" colspan="1"><br></td>
    <td rowspan="1" colspan="1"><br></td>
    <td rowspan="1" colspan="1"><br></td>
  </tr>
  <tr>
    <td rowspan="1" colspan="1"><br></td>
    <td rowspan="1" colspan="1"><br></td>
    <td rowspan="1" colspan="1"><br></td>
  </tr>
  <tr>
    <td rowspan="1" colspan="1"><br></td>
    <td rowspan="1" colspan="1"><br></td>
    <td rowspan="1" colspan="1"><br></td>
  </tr>
</table>

これを、tableタグのまま、行や列の数を取得したり、追加することができる。


実例

1. 行と列数の取得

実例として、列数と行番号を取得してみる。

取得したtableタグを変数table(任意)とし、その行プロパティの長さを取得する場合は、

table.rows.length

同様の考え方で、取得した行のセル数を取得するには、HTMLTableRowElementのcellsを使う。1行目のセル数を取得する式は以下となる。

table.rows[0].cells.length

行列数の取得
function RowCellLength(table){
    console.log("row-length:", table.rows.length)
    console.log("cell-length:", table.rows[0].cells.length)
}

▼実際の実行例
以下のようなテーブルのid名をxxとして、行列数を取得する関数RowCellLengthに渡すと、行数と列数を返してくれる。

image.png

image.png


2. 行の追加

応用編としてテーブルの3行目に3列追加する場合は以下となる。

function createTable(table){

    //3行目に行を追加
    newRow = table.insertRow(2)

    //セルを3つ追加
    for (let i=0; i <3; i++ ){
      newCell = newRow.insertCell(0) //先頭にセルを追加
      newCell.colSpan = "1"  //colspan="1"をセット
      newCell.rowSpan = "1" //rowspan="1"をセット
      newCell.innerHTML = "new cell"  //セルの中に値を入力
    }
}

table.insertRow(i)
i番目に行を追加する。0スタート。

row.insertCell(i)
指定した行のi番目にセル(tdタグ)を追加する。0スタート。

cell.colSpan = "1"
指定したセルの属性にcolspan="1"をセットする。

row.colSpan = "1"
指定したセルの属性にrowspan="1"をセットする。

cell.innerHTML = "文字列"
指定したセル(tdタグ)内に文字列を挿入する。タグも挿入可能。

上記処理で、以下のようなセルが生成される。

<tr><td colspan="1" rowspan="1">new cell</td>

これをfor文で3回繰り返すとセルが3つになる。

image.png

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

ArmにあるというJavaScript専用命令とは何か、あるいは浮動小数点数を整数に変換する方法について

Armv8.3の FJCVTZS 命令を解説します。

浮動小数点数から整数へのキャスト

予備知識として、いくつかのプログラミング言語における浮動小数点数から整数へのキャストの仕様を解説します。

C言語

基本的に0方向への丸め(切り捨て)で計算されます。

(int32_t)(-2.8); // -> -2
(int32_t)3.9999; // -> 3

元の値の絶対値が大きすぎる場合や、無限大、NaNの場合は、

  • 6.3.1.4: 表現できない場合はundefined behavior。
  • Annex F.4: 表現できない場合はinvalid例外が発生して、値はunspecified。

とされています。

これ以外の浮動小数点数→整数型の変換方法には (l)lrint(l)lround 関数などがあります。

Java

基本的に0方向への丸め(切り捨て)で計算されますが、コーナーケースについても言語仕様で定めています。

  • NaN:0を返す
  • 結果が表現できないもしくは無限大の場合:符号に応じて最大値または最小値が返る。

参照:

JavaScript

JavaScriptではビット演算やいくつかのMath関数で「浮動小数点数から32ビット整数への変換」が行われます。ECMAScriptの規格ではこの操作にはToInt32/ToUint32という名前がついています。

手順としては次の通りです。

  1. NaN, 無限大の場合:0を返す。
  2. そうでない場合:0方向への切り捨てを行って整数にし、それを$2^{32}$で割ったあまりを計算する。
  3. ToUint32の場合はそれがそのまま返して、ToInt32の場合はさらに符号の調整が入る。

C言語やJavaと違い、範囲外の値に対してはmodを取ることが特徴的です。擬似コードで書けばこんな感じになるでしょう:

function ToInt32(x: number): int32
{
    if (!Number.isFinite(x) || x === 0) {
        return 0;
    }
    const t = Math.trunc(x); // 0方向に丸める
    const r = ((t % 2**32) + 2**32) % 2**32; // 2**32で割った余り(非負)
    if (r >= 2**31) {
        return r - 2**32;
    } else {
        return r;
    }
}

命令セットでの事情

大抵の浮動小数点数演算ではデフォルトで最近接丸めが使われますが、C言語の浮動小数点数から整数型へのキャストでは常に(0方向への)切り捨てが行われます。

C言語でキャストする度に丸めモードを変更していると大変なので、アーキテクチャーによっては「その時の丸めモードにかかわらず切り捨てを行う」命令を持っています。

x86系を例に説明すると、x87 FPUでは FIST/FISTP 命令がその時点での丸めモードを使用するのに対して、 FISTTP 命令は常に0方向への丸め(切り捨て)を使用します。SSE2では CVTS[SD]2SI が現在の丸めモードを使用するのに対して、 CVTTS[SD]2SI は切り捨てを行います。

Power ISAもチラ見した感じでは浮動小数点数から整数の変換の際に「切り捨て」専用の命令があるようです。

というわけで、そういうアーキテクチャーではC言語の浮動小数点数から整数型へのキャストは1命令で実行できます。

(ちなみにAArch64は「切り捨て」のみを特別扱いせず、IEEEに規定された5つの丸め方法のそれぞれに対応する変換命令を持っています。)

JavaScript専用命令

C言語は昔から重要な言語でしたが、最近重要性を増しているのがJavaScriptです。JavaScriptではビット演算を行う度にToInt32/ToUint32が実行されます。(入力が最初から32ビット整数であることが分かっていれば変換の必要はないかと思いますが)

Armの中の人(もしくはArm準拠のチップを開発している誰か)は「JavaScriptのToInt32/ToUint32専用の命令を用意すると良さそう」と考えたのか、Armv8.3に FJCVTZS という命令を導入しました。命令名の J はJavaScriptのJです。まさしく「JavaScript専用命令」です。

この命令の細かい動作は今更説明するまでもないでしょう。ToInt32と同じです。

なお、この命令はArmのC拡張 (ACLE) で定義された組み込み関数 __jcvt として、C言語からも利用できます。

C言語のキャストと __jcvt の実行結果を比較してみましょう:

arm-jcvt.c
// gcc-10 -march=armv8.3-a arm-jcvt.c という風にコンパイル
#include <stdio.h>
#include <math.h>
#include <inttypes.h>
#include <arm_acle.h>

// Prototype:
//   int32_t __jcvt(double);

#if defined(__GNUC__)
__attribute__((noinline))
#endif
int32_t cast_double_to_i32(double x)
{
    return (int32_t)x;
}

int main(void)
{
    printf("(int32_t)(-2.8) = %" PRId32 "\n", cast_double_to_i32(-2.8));
    printf("(int32_t)1.99 = %" PRId32 "\n", cast_double_to_i32(1.99));
    printf("(int32_t)(-Infinity) = %" PRId32 "\n", cast_double_to_i32(-INFINITY));
    printf("(int32_t)Infinity = %" PRId32 "\n", cast_double_to_i32(INFINITY));
    printf("(int32_t)NaN = %" PRId32 "\n", cast_double_to_i32(NAN));
    printf("(int32_t)(0x1p50 + 123.0) = %" PRId32 "\n", cast_double_to_i32(0x1p50+123.0));
#if defined(__ARM_FEATURE_JCVT)
    printf("__jcvt(-2.8) = %" PRId32 "\n", __jcvt(-2.8)); // -> -2
    printf("__jcvt(1.99) = %" PRId32 "\n", __jcvt(1.99)); // -> 1
    printf("__jcvt(-Infinity) = %" PRId32 "\n", __jcvt(-INFINITY)); // -> 0
    printf("__jcvt(Infinity) = %" PRId32 "\n", __jcvt(INFINITY)); // -> 0
    printf("__jcvt(NaN) = %" PRId32 "\n", __jcvt(NAN)); // -> 0
    printf("__jcvt(0x1p50 + 123.0) = %" PRId32 "\n", __jcvt(0x1p50+123.0)); // -> 123
#else
    puts("__jcvt is not available");
#endif
}

実行例:

(int32_t)(-2.8) = -2
(int32_t)1.99 = 1
(int32_t)(-Infinity) = -2147483648
(int32_t)Infinity = 2147483647
(int32_t)NaN = 0
(int32_t)(0x1p50 + 123.0) = 2147483647
__jcvt(-2.8) = -2
__jcvt(1.99) = 1
__jcvt(-Infinity) = 0
__jcvt(Infinity) = 0
__jcvt(NaN) = 0
__jcvt(0x1p50 + 123.0) = 123

GCC/Clangで試した感じでは、AArch64ではC言語のキャストには FCVTZS 命令が使用されました。こちらは範囲外の値には32ビット整数の最大値・最小値を返します(Javaっぽいですね)。一方で、 __jcvt 関数(FJCVTZS 命令)の方はJavaScriptのセマンティクスに沿っています。

筆者のラズパイ4(Cortex-A72搭載)は FJCVTZS 命令に対応していなかったので、上記のコードはQEMUで動作確認しました。QEMUは偉大です。


今話題のApple M1は少なくともArmv8.3以降に対応しているようなので、この「JavaScript専用命令」の恩恵を受けることができる、というわけです。実際にどのぐらい高速化につながるのかは筆者は知りません。

追記:JavaScriptエンジンによる利用状況

せっかくJavaScript専用命令があっても、実際の処理系で使われないと意味がありません。というわけで、各種JavaScriptエンジンが FJCVTZS 命令を使用するのか(ソースを見て)確認してみました。いずれも執筆時点(2020年11月30日)の状況です。

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

[Javascript] Javascript Pattern要約 - DOMとブラウザパターン

DOMアクセス

DOMアクセス

・DOMアクセスは最小化する。
・ループ内でのDOMアクセスは避ける。

// アンチパターン
for (var i = 0; i < 100; i += 1) {
  document.getElementById("result").innerHTML += i + ", ";
}

// 変数を利用した改善
var i, content = "";
for (var i = 0; i < 100; i += 1) {
  content += i + ", ";
}
document.getElementById("result").innerHTML += content;

・DOM参照を変数に割り当てて使用する。

// アンチパターン
var padding = document.getElementById("result").style.padding,
    margin = document.getElementById("result").style.margin;

// 改善案
var style = document.getElementById("result").style.
    padding = style.padding;
    margin = style.margin;

document.querySelector("ul .selected");
// クエリーセレクターは他のDOMメソッド選択方式より速い。

・頻繁にアクセスするエレメントにid属性を追加するのも性能向上に役立つ。 なぜなら、document.getElementById("id")がノードを探す最も早い方法。

DOM操作

DOM操作

・DOMアップデートは最小化した方が良い。
・アップデート時に画面をrepaintしてエレメントをreflowするのに費用がたくさんかかるため。
・サブツリーを追加する時は、サブツリーの構成要素をすべて生成した後、最後に一度だけ追加してくれる。

// アンチパターン
var p, t;
p = document.createElement("p");
t = document.createTextNode("first paragraph");
p.appendChild(t);
document.body.appendChild(p);

p = document.createElement("p");
t = document.createTextNode("second paragraph");
p.appendChild(t);
document.body.appendChild(p);

// 改善案
var p, t, frag;
frag = document.createDocumentFragment();

p = document.createElement("p");
t = document.createTextNode("first paragraph");
p.appendChild(t);
frag.appendChild(p);

p = document.createElement("p");
t = document.createTextNode("second paragraph");
p.appendChild(t);
frag.appendChild(p);

document.body.appendChild(frag);

・存在するツリーを変更する場合には、サブツリーのルートを複製して変更した後、元のノードと変える。

var oldnode = document.getElementById("result"),
    clone = oldnode.cloneNode(true);

oldnode.parentNode.replaceChild(clone, oldnode);

イベント

イベントリスナーは以下のように実装できる。

var b = document.getElementById("clickme");
if (document.addEventListener) {
  b.addEventListener("click", myHandler, false);
} else if (document.attachEvent) {
  b.attachEvent("onclick". myHandler);
} else {
  b.onclick = myHandler;
}

function myHandler(e) {
    var src, parts;

    e = e || window.event;
    src = e.target || e.srcElement;

    if (src.nodeName.toLowerCase() !== "button") {
      return ;
    }

    parts = src.innerHTML.split(": ");
    parts[1] = parseInt(parts[1], 10) + 1;
    src.innerHTML = parts[0] + ": " + parts[1];

    if (typeof e.stopPropagation === "function") {
      e.stopPropagation();
    }
    if (typeof e.cancelBubble !== "undefined") {
      e.cancelBubble = true;
    }

    if (typeof e.preventDefault() === "funtion") {
      e.preventDefault();
    }
    if (typeof e.preventDefault() !== "undefined") {
      e.returnValue = false;
    }

}

setTimeOut()WebWorkerを利用してスレッドのような機能を実現することができる。

XMLHttpRequest

XMLHttpRequest

・JavaScript から HTTP リクエストを生成する特別なオブジェクト
・生成過程は以下の通りである。

var xhr = new XMLHttpRequest();

xhr.onreadystatechange = handleResponse;

xhr.open("GET", "page.html", true); 
xhr.send();

JSONP

JSONP

・JSON with padding
・ブラウザの同一ドメインポリシーの制約を受けない。
・JSONPリクエストURL形態

http://example.org/getdata.php?callback=myHandler

・上でgetdata.phpがウェブページかスクリプトになることができる。
・getdata.php ファイルが受信されると、myHandler({"hello":"world"}) のようなコールバック関数が実行される。

ウェブページロード戦略

ウェブページロード戦略

・scriptタグに入るエレメントを見てみよう。
・type="textjavascript":HTML5では必須属性ではなく、マークアップ有効性検査のための場合でなければ使用しない。
・async (defer):非同期スクリプトのロードで、スクリプトを受け取る間、他のダウンロードを妨げない。

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

[Javascript] Javascript Pattern要約 - オブジェクト生成パターン

ネームスペース·パターン

ネームスペース·パターン

・容易に実装できるパターンであり、全域変数の個数を減らし、変数名が不要に長くなることを防止する。
・全域ネームスペースオブジェクトはよく大文字で宣言する。

// アンチパターン
function Parent() {
}
function Child() {
}
var some_var = 1;
var module1 = {};
var module2 = {};

// 改善案
var MYAPP = {};
MYAPP.Parent = function() {

};
MYAPP.Child = function() {

};
MYAPP.some_var = 1;

MYAPP.modules = {};
MYAPP.modules.module1.data = {a:1, b:2};
MYAPP.modules.module2 = {};

・このパターンの短所は次のようである。
1. すべての変数と関数に接頭語をつけるため、全体コード量が長くなりダウンロードファイルのサイズが増加。
2. 全域インスタンスが1つなので、部分が修正されると他の全域インスタンスも全て修正される。
3. 名前が長くなるので、プロパティ判別のための検索作業が長くかかる。 (サンドボックスパターンで解決)

汎用ネームスペース関数
・プログラムサイズが大きくなって複雑になると、ネームスペース使用において次のような点検が必要である。

var MYAPP = {};
// 1. 基本
if (typeof MYAPP === "undefined") {
  var MYAPP = {};
}
// 2. *おすすめ*
var MYAPP = MYAPP || {};

・上記コードをオブジェクト生成ごとに追加するよりは下記のように関数で別途生成する方が効果的である。

var MYAPP = MYAPP || {};

MYAPP.namespace = function(ns_string) {
  var parts  = ns_string.split('.'),
      parent = MYAPP,
      i;

  if (parts[0] === "MYAPP") {
    parts = parts.slice(1);
  }

  for (i = 0; i < parts.length; i += 1) {
    if (typeof parent[parts[i]] === "undefined") {
      parent[parts[i]] = {};
    }

    parent = parent[parts[i]];
  }

  return parent;
}

・上コードは下記の様に、使用できる。

MYAPP.namespace("MYAPP.modules.module2");
// 上コードは下記と同じ
var MYAPP = {
  module : {
    module2 : {}
  }
};

var module2 = MYAPP.namespace("MYAPP.modules.module2");
module2 === MYAPP.modules.module2; // true

依存関係宣言

・関数やモジュール内の最上段に依存関係があるモジュールを宣言するのがよい。

var myFunction = function () {
  var event = YAHOO.util.Event,
      dom   = YAHOO.util.Dom;
};

・上記のパターンの長所は次の通りである。

var myFunction = function () {
  var event = YAHOO.util.Event,
      dom   = YAHOO.util.Dom;
};
  1. 依存関係が明示的なため、ページ内に必ず盛り込むべきスクリプトファイルが分かりやすい。
  2. 変数の値探索は、yahoo.util.dom のような重なりプロパティよりもはるかに速い。
  3. 高級Compressor は全域変数名変更は危険であるため縮約せず、上記イベントのような地域変数はa に縮約する。

コード圧縮例

// 1.
function test1() {
  alert(MYAPP.modules.m1);
  alert(MYAPP.modules.m1);
}
// 上コード圧縮
// alert(MYAPP.modules.m1);alert(MYAPP.modules.m2);

// 2.
function test2() {
  var modules = MYAPP.modules;
  alert(modules.m1);
  alert(modules.m2);
}
// 上コード圧縮
// var a=MYAPP.modules;alert(a.m1);alert(a.m2);

非公開のプロパティとメソッド

クローザーを用いた非公開メンバは以下のように実装する。

function Gadget() {
    var name = "iPhone";
    this.getName = function() {
      return name;
    };
}
var toy = new Gadget();
console.log(toy.name); // undefined
console.log(toy.getName()); // "iPhone"

var myobj;
(function () {
  var name = "Android";

  myobj = {
    getName: function() {
      return name;
    }
  };
}());

myobj.getName(); // "Android"

var myobj = (function () {
  var name = "Android";
  return {
    getName: function() {
      return name;
    }
  };
}());

myobj.getName(); // "Android"

・非公開メンバの実装方法:関数内で地域変数と宣言したプロパティを関数で囲む。
・生成者を利用して非公開メンバーを作る場合には、生成者で新しいオブジェクトを作るごとに非公開メンバーが再生成される短所がある。
・上記のような短所はプロトタイプで補完が可能である。

function Gadget() {
  var name = "Android";
  this.getName = function () {
    return name;
  };
}

Gadget.prototype = (function () {
  var browser = "Mobile";
  return {
    getBrowser : function() {
      return browser;
    }
  };
}());

var toy = new Gadget();
console.log(toy.getName()); 
console.log(toy.getBrowser()); 

モジュールパターン

モジュールパターン

・モジュールパターンを利用すれば個別のコードを緩く結合することが可能だ。
・大量のコードを構造化し整理するのに役立つ。

モジュールパターン適用のための手順

MYAPP.namespace("MYAPP.utilities.array");

MYAPP.utilities.array = (function () {
  return {
    inArray: function (needle, haystack) {
      // ...
    },
    isArray: function (e) {
      // ...
    }
  };
}());

MYAPP.utilities.array = (function () {

  var uobj  = MYAPP.utilities.object,
      ulang = MYAPP.utilities.lang,

      array_string = "[object Array]",
      ops = Object.prototype.toString;


  return {
    inArray: function (needle, haystack) {
      // ...
    },
    isArray: function (a) {
      return ops.call(a) === array_string;
    }
  };
}());

MYAPP.utilities.array = (function () {

  var uobj  = MYAPP.utilities.object,
      ulang = MYAPP.utilities.lang,

      array_string = "[object Array]",
      ops = Object.prototype.toString,

      inArray = function (needle, haystack) {
        // ...
      },
      isArray = function (a) {
        return ops.call(a) === array_string;
      };

  return {
    isArray : isArray
  };
}());

サンドボックス·パターン

サンドボックスパターンはモジュール間の相互に影響を及ぼさずに動作できる環境を提供する。

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