20201012のJavaScriptに関する記事は27件です。

GraphQLの基礎の基礎

はじめに

GraphQLについての知見が溜まってきたので、これから何回かに分けて記事としてまとめていきたいと思います。

GraphQLは、APIのクエリ言語であり、既存のデータへのクエリを実行するためのサーバーサイドランタイムのことを指します。
RESTAPIと異なり、エンドポイントが1つだけであり、処理ごとにエンドポイントを増やす必要がないので、管理がしやすいことが大きな特徴です。

本記事では、GraphQL公式チュートリアルを参考に、実際にスキーマ言語を記述した上でGraphQL IDEにてクエリを書き、データの取得を実践します。
記事内で使用する言語はJavaScriptです。

GraphQLの歴史

GraphQLは2012年頃Facebook社が開発をスタートしたこときっかけに、2015年にはオープンソース化、2018年にはGraphQL Foundationが設立されました。
2020年秋現在、Facebookはもちろん、GitHubやPinterest、Cousereなどによる採用実績があります。

GraphQLの構成

GraphQLは、「クエリ言語」と「スキーマ言語」の2つによって構成されています。

クエリ言語とは?

GraphQLサーバーに対してリクエストをするための言語(合計3種類)

クエリの種類 意味 効果
query データ取得系 GET
mutation データ更新系 POST/PUT/DELETE...etc
subscription イベントの通知 Websocket

スキーマ言語とは?

・GraphQL APIの仕様を記述するための言語(本記事で主にエディターに書くのはこちら)
・リクエストされたクエリは、スキーマ言語で記述したスキーマに従ってGraphQL処理系により実行されて、レスポンスを生成する

GraphQLのアーキテクチャ例

GraphQLを使ったアーキテクチャの例はこちらのページが参考になりました。
クライアントとサーバーの間にGraphQLサーバーを挟み、データの整形と受け渡しをするのが通例のようです。

下準備

プロジェクトを作った後に、npm経由でGraphQL.jsをインストールします。

mkdir GraphQL
cd GraphQL
touch server.js
npm init
npm install express express-graphql graphql --save

GraphQL APIサーバーを実行

GraphQL、Node.jsのフレームワークであるExpressを作業ディレクトリへインストールした状態でスタートします。

server.js
var express = require('express');
var { graphqlHTTP } = require('express-graphql');
var { buildSchema } = require('graphql');

// GraphQLスキーマ言語を記述してスキーマを構築する
// スキーマはあくまで定義のみで実際のデータ操作は行わない
var schema = buildSchema(`
  type Query {
    hello: String
  }
`);

// ルートは、APIエンドポイントごとにリゾルバー関数を提供します
// リゾルバとは特定のフィールドのデータを返す関数(メソッド)であり、
// 実際のデータ操作を行う部分
var root = {
  hello: () => {
    return 'Hello world!';
  },
};

// Expressでサーバーを立てます
// graphiql: true としたので、graphiql を利用できる
var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at http://localhost:4000/graphql');

ターミナルからサーバーを立ち上げます。

node server.js
Running a GraphQL API server at http://localhost:4000/graphql

http://localhost:4000/graphql 
にて、GraphQLのIDEが利用出来るようになっています。
こちらで、クエリを書きます。
スクリーンショット 2020-10-12 20.40.41.png

このように、GraphQLでは、
①スキーマ定義にてクライアントが操作できるクエリや様々な型を定義し、
②リゾルバにてデータを返す操作を行い、
③クエリを書くことによって、
RESTAPIでいうところのCRUD処理が可能になります。
本例ではhelloと定義したスキーマの情報を取得しました。

Basic Types

GraphQLスキーマ言語は
・String型
・Int型
・Float型
・Boolean型
・ID型

以上の謂わゆる「スカラー型」を型付けすることが出来ます。
リストタイプを使用する際は、タイプを角かっこで囲み、例えば [Int]のように定義します(詳細は下記の例を参照)

server.js
var express = require('express');
var { graphqlHTTP } = require("express-graphql");
var { buildSchema } = require("graphql");

// GraphQLスキーマ言語を記述してスキーマを構築する
var schema = buildSchema(`
  type Query {
    quoteOfTheDay: String
    random: Float!
    rollThreeDice: [Int]
  }
`);

//ルートは、APIエンドポイントごとにリゾルバー関数を提供する
var root = {
  quoteOfTheDay: () => {
    return Math.random() < 0.5 ? "Take it easy" : "Salvation lies within";
  },
  random: () => {
    return Math.random();
  },
  rollThreeDice: () => {
    return [1, 2, 3].map((_) => 1 + Math.floor(Math.random() * 6));
  },
};

var app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
  })
);
app.listen(4000);
console.log("Running a GraphQL API server at localhost:4000/graphql");
node server.js
Running a GraphQL API server at http://localhost:4000/graphql

http://localhost:4000/graphql 

スクリーンショット 2020-10-12 21.43.17.png

以上、様々なTypeを返すAPIの呼び出しを確認出来たかと思います。

引数の受け渡し

GraphQLではREST APIと同様に、GraphQLAPIのエンドポイントに引数を渡すのが一般的です。スキーマ言語で引数を定義することにより、タイプチェックが自動的に行われます。

exameple.js
type Query {
  rollThreeDice: [Int]
}

以下の例では実際に引数を渡すスキーマを構築します。

server.js
var express = require('express');
var { graphqlHTTP } = require("express-graphql");
var { buildSchema } = require("graphql");

// GraphQLスキーマ言語を記述してスキーマを構築する
var schema = buildSchema(`
  type Query {
    rollDice(numDice: Int!, numSides: Int): [Int]
  }
`);

//ルートは、APIエンドポイントごとにリゾルバー関数を提供する
var root = {
  rollDice: ({numDice, numSides}) => {
    var output = [];
    for (var i = 0; i < numDice; i++) {
      output.push(1 + Math.floor(Math.random() * (numSides || 6)));
    }
    return output;
  }
};

var app = express();
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema,
    rootValue: root,
    graphiql: true,
  })
);
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');

node server.js
Running a GraphQL API server at http://localhost:4000/graphql

http://localhost:4000/graphql 

スクリーンショット 2020-10-12 22.09.41.png

クエリで引数を渡すことにより、スキーマとリゾルバで定義した通りの結果が返ってくることを確認しました。

オブジェクトタイプ

各オブジェクトには、特定の型を返すフィールドと、引数を取るメソッドを含めることができます。

example.js
//type RandomDieの定義を「getDie」で利用
const schema = buildSchema(`
  type RandomDie {
    numSides: Int!
    rollOnce: Int!
    roll(numRolls: Int!): [Int]
  }

  type Query {
    getDie(numSides: Int): RandomDie
  }
`);

リゾルバも含めたコード。

server.js
var express = require('express');
var { graphqlHTTP } = require("express-graphql");
var { buildSchema } = require("graphql");

// GraphQLスキーマ言語を記述してスキーマを構築する
var schema = buildSchema(`
  type RandomDie {
    numSides: Int!
    rollOnce: Int!
    roll(numRolls: Int!): [Int]
  }

  type Query {
    getDie(numSides: Int): RandomDie
  }
`);

// このクラスは、RandomDieGraphQLタイプを実装します
class RandomDie {
  constructor(numSides) {
    this.numSides = numSides;
  }

  rollOnce() {
    return 1 + Math.floor(Math.random() * this.numSides);
  }

  roll({numRolls}) {
    var output = [];
    for (var i = 0; i < numRolls; i++) {
      output.push(this.rollOnce());
    }
    return output;
  }
}

//ルートは、APIエンドポイントごとにリゾルバー関数を提供する
var root = {
  getDie: ({numSides}) => {
    return new RandomDie(numSides || 6);
  }
}

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000);
console.log('Running a GraphQL API server at localhost:4000/graphql');
node server.js
Running a GraphQL API server at http://localhost:4000/graphql

http://localhost:4000/graphql 

スクリーンショット 2020-10-12 23.01.21.png

オブジェクト型を使うと、JSONのような整った構造で返ってくることが分かります。

MutationとInput Types

データの作成や更新、削除はqueryではなくmutatisonを使用します。
mutatisonのスキーマの引数にはtypeキーワードの代わりにinputキーワードを使用すると読みやすいコードになります。

example.js
input MessageInput {
  content: String
  author: String
}

type Message {
  id: ID!
  content: String
  author: String
}

type Query {
  getMessage(id: ID!): Message
}

// データの追加と更新をスキーマ定義
// 引数にはinputキーワードで定義したMessageInputを使用
type Mutation {
  createMessage(input: MessageInput): Message
  updateMessage(id: ID!, input: MessageInput): Message
}

リゾルバも含めたコード。

server.js
var express = require('express');
var { graphqlHTTP } = require("express-graphql");
var { buildSchema } = require("graphql");

// GraphQLスキーマ言語を記述してスキーマを構築する
var schema = buildSchema(`
input MessageInput {
    content: String
    author: String
  }

  type Message {
    id: ID!
    content: String
    author: String
  }

  type Query {
    getMessage(id: ID!): Message
  }

  // データの追加と更新をスキーマ定義
  // 引数にはinputキーワードで定義したMessageInputを使用
  type Mutation {
    createMessage(input: MessageInput): Message
    updateMessage(id: ID!, input: MessageInput): Message
  }
`);

// Messageに複雑なフィールドがある場合は、それらをこのオブジェクトに配置します。
class Message {
  constructor(id, {content, author}) {
    this.id = id;
    this.content = content;
    this.author = author;
  }
}

// データの入れ物
var fakeDatabase = {};

var root = {
  getMessage: ({id}) => {
    if (!fakeDatabase[id]) {
      throw new Error('no message exists with id ' + id);
    }
    return new Message(id, fakeDatabase[id]);
  },
  createMessage: ({input}) => {
    // ランダムなIdを生成
    var id = require('crypto').randomBytes(10).toString('hex');

    fakeDatabase[id] = input;
    return new Message(id, input);
  },
  updateMessage: ({id, input}) => {
    if (!fakeDatabase[id]) {
      throw new Error('no message exists with id ' + id);
    }
    // 古いデータの書き換え
    fakeDatabase[id] = input;
    return new Message(id, input);
  },
};

var app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: root,
  graphiql: true,
}));
app.listen(4000, () => {
  console.log('Running a GraphQL API server at localhost:4000/graphql');
});
node server.js
Running a GraphQL API server at http://localhost:4000/graphql

http://localhost:4000/graphql 

スクリーンショット 2020-10-12 23.18.56.png
新しくメッセージを作成

スクリーンショット 2020-10-12 23.20.20.png
メッセージの内容を更新

スクリーンショット 2020-10-12 23.22.27.png
内容の確認(ここはqueryを利用)

Mutationを利用して、メッセージデータの作成と更新を確認することが出来ました!!

おわりに

以上、GraphQLの基本部分について網羅的に掲載する事が出来たと思います。
はじめはクエリ言語とスキーマ言語の違いや書き方、それぞれの役割が整理出来ず混乱しておりましたが、公式を読み返しつつ手を動かすことで理解が深まりました。
次回はサブスクリプションの使い方を記事にしたいと思います!

それでは、また?

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

JavaScriptのイベントオブジェクトmemo

JavaScriptのイベントオブジェクトって?

イベントリスナーやイベントハンドラーは、引数としてイベントオブジェクトを受け取ります。
DOMで発生したイベントは、イベントオブジェクトを使用して、プロパティー(情報)を取得したり、メソッドを使用して値を操作することができます。

具体的に何ができるの?

  • typeプロパティ...イベントの種類は何か分かる(clike, mouseoverなど)
  • targetプロパティ...イベント発生元の要素を取得できる
  • clientX, clientY...イベントが発生した、ブラウザ上での座標を取得できる
  • keyプロパティ...押下されたキーボードのキーの値を取得できる

などなど

イベントオブジェクトを取得するには?

イベントリスナーに引数を指定するだけです。
慣例的にeevがよく使われます。

書き方例

document.getElementById('item').addEventListener('keydown', function(e) {
  console.log('キーコードは' + e.keyCode + 'です'), false);
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで毎秒カウントダウンを自動更新する方法

まず、htmlの中にテキストとspan idでtextContentでJSで取得したデータを置き換える要素を作っておきます。

<p id="timer">知ってました?大阪万博まであと<span id="day"></span>日<span id="hour"></span>時間<span id="min"></span>分<span id="sec"></span>秒</p>

次に、関数countdownを作っていきます。引数はdue(意味:期限)です。

function countdown(due){
    const now=new Date();
    const rest =due.getTime()-now.getTime();
    const sec =Math.floor(rest/1000)%60;
    const min =Math.floor(rest/1000/60)%60;
    const hours=Math.floor(rest/1000/60/60)%24;
    const days=Math.floor(rest/1000/60/60/24);
    const count=[days,hours,min,sec];
    return count;

    }

まず、Dateオブジェクトのインスタンス化をする。
rest(意味:残り)にdue(つまり、期限)オブジェクトにgetTime()メソッドで1970年1月1日0:00からの時間をミリ秒で取得。
そこから、今の時間までのミリ秒を引いて代入する。
const count =[day,hours,min,sec];
は配列で、この後インデックス番号を使って呼び出すときに使う。
そして、return count;でcount配列を呼び出す。
次は、目標の時間を設定する。

    const goal =new Date(2025,4,3)

これは、定数goalにDateオブジェクトを使って日時を設定している。
<日時を設定した状態でDateオブジェクトを初期化(つまりインスタンス化)する>
new Date(年、月、日、時、分、秒、ミリ秒);
注意点:月は0が1月、1が2月、2が3月というふうにコンピュータとこちらが表示させたい月に1ヶ月のズレがある。
この場合、new Date(2025,4,3)となっているがこの場合コンピュータ側の4なので、こちら側の5月になる。

    function recalc(){
        const counter=countdown(goal);
        document.getElementById('day').textContent=counter[0];
        document.getElementById('hour').textContent=counter[1];
        document.getElementById('min').textContent=counter[2];
        document.getElementById('sec').textContent=counter[3];

        refresh();
    }

この関数内では、定数counterに上で定義したcountdown(due)関数の引数dueに一つ上で定義したgoal=new Date(2025,4,3)を代入して、関数recalc()関数 return count;でcount=[days,hours,min,sec]を返す。
そして、一番上で書いたコードにcounter配列をインデックス番号で指定して書き換える。
その後にrefresh()関数を呼び出す。しかし、refresh()関数はまだ作っていないのでこのあと説明する。
次はrefresh()関数を定義する。

    function refresh(){
        setTimeout(recalc,1000);

    }

setTimeout()メソッドは指定した関数名を()を付けずに指定して、カンマの後ろに指定した数値ミリ秒後にもう一度指定した関数を呼び出すというもの。
つまり
setTimeout(関数名,ミリ秒);
つまり、recalc()関数の最後でrefresh()関数を呼び出すということは、1秒毎にrefresh()関数からまたrecalc()関数を呼び出して、そのrecalc関数内でまたrefresh()関数を呼び出しているということ。

    recalc();

そして最後にrecalc()関数を呼び出している。
参考:JavaScript超入門書p154〜

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

Javascript 変数定義とスコープについて

Javascriptの変数定義

3つの変数定義とスコープについて備忘録としてまとめました。

それぞれの変数について

再代入 再宣言 スコープ
var 可能 可能 関数スコープ
let 可能 不可 ブロック(関数スコープも形成する)
const 不可 不可 ブロック(関数スコープも形成する)

letとconstはES6から使用できるようになった変数宣言です。
基本的にはvarは厳密性に欠け、バグの温床となるため非推奨となっております。
※varで定義した場合、変数の上書きができてしまうので複数人による開発では意図しない再代入/再定義により挙動が思惑と異なることがあります。

スコープとは

実行中のコードから式や値を参照できる範囲のことです。
スコープの種類について書いていきます。

グローバルスコープ

グローバルスコープとは、どこからでもアクセスすることができる範囲です。
トップレベルで変数や関数を宣言した場合にグローバルスコープになります。

スクリプトスコープ

トップレベルでletとconstを用いて変数定義した場合にスクリプトスコープを形成します。
下記のように変数を定義します。

let aScript = "スクリプトスコープ";
const aScript2 = "スクリプトスコープ2";  
var aGlobal = "グローバルスコープ";
debugger

スクリーンショット 2020-10-12 17.43.08.png
コンソールで変数へアクセスしてみると・・・
スクリーンショット 2020-10-12 17.54.37.png

関数スコープからアクセスしてみると・・・・

let aScript = "スクリプトスコープ";
const aScript2 = "スクリプトスコープ2";  
var aGlobal = "グローバルスコープ";

function checkScope(){
  console.log(aScript);
  // スクリプトスコープ
  console.log(aScript2);
  // スクリプトスコープ2
  console.log(aGlobal)
  // グローバルスコープ
}
checkScope();

上記コードを確認すると関数スコープからも各変数が参照できていることから、グローバルスコープとスクリプトスコープでは扱いに差はないことがわかります。
※グローバルスコープの方がスクリプトスコープよりも外側にあります。

関数スコープ

関数スコープとは、関数宣言によって形成されたスコープです。
関数内部で定義された変数や関数は外部から参照することはできません。
関数スコープ内の変数はブラウザで確認するとローカルスコープとして扱われます。
※Rubyのローカル変数と同じ考え方と思ってもらえれば良いかと思います。

function checkScope(){
  let fncScope = "関数スコープ";
}
console.log(fnScope)
// Uncaught ReferenceError: fnScope is not defined

ブロックスコープ

if文やfor文によって形成されるスコープで、{}内部にスコープを生成します。
またこちらのスコープはletとconstが有効です。

{
var globalScppe = "グローバルスコープ";
let blckScope = "ブロックスコープ";
const blockScope2 = "ブロックスコープ";
}

console.log(globalScope)
// グローバルスコープ(参照可能)

console.log(blckScope)
// Uncaught ReferenceError: blckScope is not defined

console.log(blockScope2)
// Uncaught ReferenceError: blckScope2 is not defined

let/constはブロックスコープが有効であるため、{ブロックスコープ}の外からアクセスすることはできません。
そのため、let/constを使用することで変数をより厳密に扱うことができます。

スコープチェーンについて

スコープチェーンとは、スコープ複数階層にも連なっている状態です。
そのような状況では、変数はどのような参照を行うのか
実際に見てみましょう。

window.chainVal = "グローバルスコープの変数";

function chainFn(){
  //スコープ形成
 let chanVal = "chainFnのスコープの変数";
  function chainFn2(){
    //スコープ形成
    let chainVal = "chainFn2のスコープの変数";
    function chainFn3(){
      //スコープ形成
      let chainVal = "chainFn3のスコープの変数"
      console.log(chainVal);
    }
    chainFn3();
    //chainFn3関数実行
  }
  chainFn2();
  //chainFn2関数実行
}
chainFn();
//chainFn関数実行

上記ファイルを実行するとブラウザでは
スクリーンショット 2020-10-12 23.25.17.png

chainFn3関数の中にあるcahinValがコンソールに表示されています。

では、下記のように変更すると・・・・

window.chainVal = "グローバルスコープの変数";
function chainFn(){
  //スコープ形成
 let chanVal = "chainFnのスコープの変数";
  function chainFn2(){
    //スコープ形成
    let chainVal = "chainFn2のスコープの変数";
    function chainFn3(){
      //スコープ形成
      // chainValを消します。
      console.log(chainVal);
    }
    chainFn3();
    //chainFn3関数実行
  }
  chainFn2();
  //chainFn2関数実行
}
chainFn();
//chainFn関数実行

スクリーンショット 2020-10-12 23.27.55.png

chainFn2関数のスコープの内部にあるchainValを参照しに行きます。

ここでわかることは、スコープチェーンによって変数を参照しに行く時は内側から外側のスコープへと変数を探しに行きます。
最終的には、グローバルスコープまで参照しにいきます。

window.chainVal = "グローバルスコープの変数";

function chainFn(){
  //スコープ形成
 
  function chainFn2(){
    //スコープ形成

    function chainFn3(){
      //スコープ形成

    }
    chainFn3();
    //chainFn3関数実行
  }
  chainFn2();
  //chainFn2関数実行
}
chainFn();
//chainFn関数実行

上記コードを実行すると・・・・・
スクリーンショット 2020-10-12 23.31.07.png
スコープ内に変数が何場合は、1つ外のスコープに探し、さらに外へ探して最後にはグローバルスコープへ変数を探しに行きます。このように近くのスコープから順に辿り探していく仕組みとなってます。

※イメージしやすい画像を作成して添付する。

まとめ

let/constを実行するとスコープが狭くなるため、コードがより厳密にかつ安全に書くことができます。そのため、今後はvarではなくlet/constを使用していきましょう。

あ、普段JsやReactを書いている時に"const"を"cosnt"とよく打ち間違えてエラーが出ていることが結構あります。打ち間違いには気をつけましょう。

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

BFE.dev解答記録 #3. Array.prototype.flat()を実装する

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

bfe3.png

BFE.dev#3. Array.prototype.flat()を実装する をみてみよう

目標

ネストされている括弧を除去するためのflat()を実装すること。以下のように

const arr = [1, [2], [3, [4]]];
flat(arr)
// [1, 2, 3, [4]]
flat(arr, 1)
// [1, 2, 3, [4]]
flat(arr, 2)
// [1, 2, 3, 4]

recursion

Recursionでやるのは簡単、loopしながら集めれば良い。

  1. 配列であれは、flat()を実行してから要素を集める
  2. 配列でなければ、そのまま要素を集める

これを持って、以下のアプローチは自然に出てくる。

function flat(arr, depth = 1) {
  const result = []
  for (let item of arr) {
    if (Array.isArray(item) && depth > 0) {
      result.push(...flat(item, depth - 1))
    } else {
      result.push(item)
    }
  }
  return result
}

reduceで書き直す

reduceを使う絶好のチャンスだ、やってみよう

function flat(arr, depth = 1) {
  return arr.reduce((result, item) => {
    if (Array.isArray(item) && depth > 0) {
      result.push(...flat(item, depth - 1))
    } else {
      result.push(item)
    }
    return result
  }, [])
}

Iterative Approach

面接を受ける時は、recursionをiterationに書き直すのは常識。

まずステップバイステップで括弧を除去するプロセスを理解しよう。左はターゲットの配列で、右は結果。

[1, [2], [3, [4]]]  => []

1、配列ではない、結果にpushする。

[[2], [3, [4]]] => [1]

[2]、配列だ、まず括弧を除去し、またターゲットの配列に戻す

[2, [3, [4]]] => [1]

2、結果にpushする。

[[3, [4]]] => [1,2]

上記のステップを繰り返したら、以下の結果になる。

[3, [4]] => [1,2]
[[4]] => [1,2,3]
[4] => [1,2,3]
[] => [1,2,3,4]

問題は depthの処理、全ての括弧を除去するわけではないので、各要素は自分が何層の括弧に入っているのかをトラックしないといけない。

上記のプロセスをやり直そう、今回はdepthつきで。

depthは1とする:

[[1, 1], [[2], 1], [[3, [4]], 1]] => []

1、depth 1、なので結果にpushする。

[[[2], 1], [[3, [4]], 1]] => [1]

[2]、depth 1、括弧を除去し、2を入れ戻す、depthは0になる。

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

2 、depth 0、結果にpushする。

[[[3, [4]], 1]] => [1, 2]

[3,[4]] 、 depth 1、各要素を入れ戻し、depthを0にする。

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

3、depth 0、結果にpushする。

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

[4]、 depth 0。depthは0なので、もう括弧があっても除去することができない。結果に[4]をpushする。

[] => [1,2,3,[4]]

以上のプロセスを踏まえて、下記のiterative approachを書くのは難しくない。

function flat(arr, depth = 1) {
  const result = []
  const stack = [...arr.map(item => ([item, depth]))]

  while (stack.length > 0) {
    const [head, depth] = stack.shift()
    if (Array.isArray(head) && depth > 0) {
      stack.unshift(...head.map(item => ([item, depth - 1])))
    } else {
      result.push(head)
    }
  }
  return result
}

上記の実装ではパフォーマンス問題があるーshift/unshiftは避けるべき、実行するたびに全ての要素のindexが変わるから。

改善するために、pop/pushを使えば良い。そのために、最後にreverse()が必要になる。

function flat(arr, depth = 1) {
  const result = []
  const stack = [...arr.map(item => ([item, depth]))]

  while (stack.length > 0) {
    const [top, depth] = stack.pop()
    if (Array.isArray(top) && depth > 0) {
      stack.push(...top.map(item => ([item, depth - 1])))
    } else {
      result.push(top)
    }
  }

  return result.reverse()
}

通った!

Alt Text

上記のコードはこちらで見れます。https://bigfrontend.dev/ja/problem/implement-Array-prototype.flat/discuss

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

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

動画を挿入する方法

トップページなどに動画を挿入する方法

GIF

https://gyazo.com/3015a8b1f689153dcfe7fcb308d483bb

6582025a04cb61be20a4cabc8a556419.png

下記コード

index.html
<div class="bg-video-wrap">
  <p>Brilliant Blue</p>
  <video src="images/foreign.mp4" autoplay loop muted>
  </video>
</div>
css
.bg-video-wrap {
  position: relative;
}
p {
  font-family: serif;          
  color: #fff;
  font-size: 400%;
  position: absolute;
  left: 30%;
  top: 100px;
  z-index: 1;
}

以上です!

状況に応じてCSSは各自変更して使って下さい!

foreign.mp4の部分は各々、ダウンロードや作った動画の名前を決めると思うのでそれを当てはめて下さい!おそらく末尾はmp4でもmovでもどちらでも挿入できます!

現場からは以上です!

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

【vue.js】画像の遅延読み込みを実現する為にvue-lazyloadで導入してみた

実務でフロントのパフォーマンス改善を仰せつかり、
試行錯誤する中で、学習の一環で個人アプリにvue-lazyloadと言うライブラリを導入してみた。

他にもいくつかvueでLazyLoad出来るライブラリがあるので、後日そっちも試してみたい。

Lazy Load

Lazy Loadとは、画像やiframeなどコンテンツがブラウザの表示領域内に存在するものだけをロードし、領域外のコンテンツはロードしないことで、画面表示を高速化する仕組みのことです。

環境

vue.js 2.6.12
vue-lazyload 1.3.3

実装

まずはライブラリをインストールします。

$ npm install vue-lazyload -D

CDNでもOKです。

<script src=”https://unpkg.com/vue-lazyload/vue-lazyload.js"></script>

エントリーポイントでインポートして必要に応じてオプションを設定しておきます。

app.js
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  preLoad: // 事前ロードする高さの割合指定
  error: //エラー時に表示する画像指定する
  loading: // ロード中に表示する画像指定する
  attempt: // ロード失敗した時のリトライの上限指定
  listenEvents: // 遅延読み込みを起動するためのイベント
  adapter: // 要素の属性を動的に切り替えたい時に使用
  filter: // 画像を動的に切り替えたい時に使用
  throttleWait: // 遅延の待機時間
})

Lazy Loadさせたいimg要素にv-lazyディレクティブ的なもので画像パスを措定するだけと簡単です。

card.vue
<li v-for="item in items" :key="item">
  <img class="card-main-img" v-lazy="item.imageUrl">
</li>

実装イメージ。
分かりづらいですが、ロード中の画像にNoImageを設定しています。

jWsEyGD1vOdE0wKNuZKH1602506098-1602506114.gif

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

【vue.js】画像の遅延読み込みを実現する為にvue-lazyloadを導入してみた

実務でフロントのパフォーマンス改善を仰せつかり、
試行錯誤する中で、学習の一環で個人アプリにvue-lazyloadと言うライブラリを導入してみた。

他にもいくつかvueでLazyLoad出来るライブラリがあるので、後日そっちも試してみたい。

Lazy Load

Lazy Loadとは、画像やiframeなどコンテンツがブラウザの表示領域内に存在するものだけをロードし、領域外のコンテンツはロードしないことで、画面表示を高速化する仕組みのことです。

環境

vue.js 2.6.12
vue-lazyload 1.3.3

実装

まずはライブラリをインストールします。

$ npm install vue-lazyload -D

CDNでもOKです。

<script src=”https://unpkg.com/vue-lazyload/vue-lazyload.js"></script>

エントリーポイントでインポートして必要に応じてオプションを設定しておきます。

app.js
import VueLazyload from 'vue-lazyload'

Vue.use(VueLazyload, {
  preLoad: // 事前ロードする高さの割合指定
  error: //エラー時に表示する画像指定する
  loading: // ロード中に表示する画像指定する
  attempt: // ロード失敗した時のリトライの上限指定
  listenEvents: // 遅延読み込みを起動するためのイベント
  adapter: // 要素の属性を動的に切り替えたい時に使用
  filter: // 画像を動的に切り替えたい時に使用
  throttleWait: // 遅延の待機時間
})

Lazy Loadさせたいimg要素にv-lazyディレクティブ的なもので画像パスを措定するだけと簡単です。

card.vue
<li v-for="item in items" :key="item">
  <img class="card-main-img" v-lazy="item.imageUrl">
</li>

実装イメージ。
分かりづらいですが、ロード中の画像にNoImageを設定しています。

jWsEyGD1vOdE0wKNuZKH1602506098-1602506114.gif

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

【Web】Oculus Questなどのプロジェクトをパソコンでデバッグする際に便利な方法【PlayCanvas】

基本的には、Oculus Questのデバイスをデバッグする際にはVR端末などの実機でデバッグをすることが多いと思いますが、Oculus Questの実機で確認ができない場合にはこの、拡張機能として使えるエミュレーターを使うと便利です。

WebXR emulator extensionを使ってPlayCanvasのチュートリアル、WebXR UI Interactionパソコンのブラウザから操作をしてみます。
https://developer.playcanvas.com/en/tutorials/webxr-ray-input/

1. 拡張機能をインストール

拡張機能
WebXR emulator extension
https://github.com/MozillaReality/WebXR-emulator-extension

FirefoxとGoogle Chromeで使用できますのでこちらから拡張機能をインストールします。

Firefox

https://addons.mozilla.org/firefox/addon/webxr-api-emulator

Google Chrome

https://chrome.google.com/webstore/detail/webxr-api-emulator/mjddjgeghkdijejnciaefnkjmkafnnje

2. XRのプロジェクトへアクセスし開発者ツールを開く

  1. 対象のWebXRのプロジェクトのURLにアクセスをしブラウザのF12をクリックし開発者ツールを開きます。

https://developer.playcanvas.com/en/tutorials/webxr-ray-input/

  1. 拡張機能をインストールするとWebXRの項目が増えているので、WebXRを選択
  2. 使用したいVR機器を選択

3. WebXRのプロジェクトに入る

今回のプロジェクトでは、このVRボタンをクリックすることでVRのプロジェクトに入ることができます。

このような形でVRのプロジェクトを操作することができます。

使用したリンク
拡張機能
- WebXR emulator extension
https://github.com/MozillaReality/WebXR-emulator-extension

デモプロジェクト
- WebXR UI Interaction
https://developer.playcanvas.com/en/tutorials/webxr-ray-input/


PlayCanvas開発で参考になりそうな記事の一覧です。


その他関連

PlayCanvasのユーザー会のSlackを作りました!

少しでも興味がありましたら、ユーザー同士で解決・PlayCanvasを推進するためのSlackを作りましたので、もしよろしければご参加ください!

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

Symbol from NEMやmijin Catapultで最新のアカウント情報を取得する

ページにアクセスしたタイミング、入金したタイミングで最新のアカウント情報を取得するためのTIPSです。

今回はAliceアカウントを作成し、そこに記録されたモザイク一覧情報を、ページ表示時など能動的に取りに行く方法とWebSocketを利用して入金時に受動的に取得する方法を同じロジックを使用して処理する流れで説明します。

事前準備

(script = document.createElement('script')).src = 'https://xembook.github.io/nem2-browserify/symbol-sdk-pack-0.21.0.js';
document.getElementsByTagName('head')[0].appendChild(script);

chromeブラウザを開いてF12キーを押し、コンソール画面で上記スクリプトをコピー&ペーストしてください。

ライブラリインポート、変数初期化

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

nem = require("/node_modules/symbol-sdk");
op   = require("/node_modules/rxjs/operators");
rxjs = require("/node_modules/rxjs");
accountHttp = new nem.AccountHttp(NODE);
nsHttp = new nem.NamespaceHttp(NODE);
wsEndpoint = NODE.replace('http', 'ws') + "/ws";
listener = new nem.Listener(wsEndpoint,nsHttp,WebSocket);

今回使用するライブラリのインポートとノードへの接続など、必要情報を定義します。

アカウント準備

alice = nem.Account.generateNewAccount(nem.NetworkType.TEST_NET);

"http://faucet-0.10.0.x-01.symboldev.network/?recipient=" + alice.address.plain() +"&amount=2"

Aliceアカウントを作成してそこに2XYM入金しておきます。

アカウント情報取得時のアクションを記述

accountSubscribe = function(observer){

    observer.subscribe(_=>{
        //ここにアクションを記述
        console.log(_);
    },err => console.log(err));
}

アカウント情報を取得した後、どのようなアクションを起こしたいかを定義しておきます。
ページに表示された項目の更新などになるかと思います。今回はコンソールに出力させるだけです。

アカウント情報から所有モザイク一覧情報だけを抜き出すオペレーション

opMergeMosaics = function(){
    return rxjs.pipe(
        op.mergeMap(_=>_.mosaics),
        op.map(_ =>  _.id),
        op.toArray(),
    )
}

アカウント情報から所有Mosaicを取り出すためには、取得情報を少し操作する必要があります。あらかじめどのように操作すればMosaic情報を取り出せるかを定義しておきます。

アカウント情報取得

getMosaics = function(){
    assetHttp = accountHttp.getAccountInfo(alice.address)
    .pipe(
        opMergeMosaics()
    );
    accountSubscribe(assetHttp);
};
getMosaics();

能動的にアカウント情報を取得します。

アカウント情報取得リスナー

listener.open().then(() => {
    assetListener = listener.confirmed(alice.address)
    .pipe(

        op.mergeMap(x=>accountHttp.getAccountInfo(alice.address)),
        op.first(),
        opMergeMosaics(),
        op.repeat()
    );
    accountSubscribe(assetListener);
});

WebSocketを利用して、入金時にアカウント情報を取得します。
confirmedリスナーでsubscribeされる内容には所有モザイク情報は含まれていません。ですので再度getAccountInfoで情報を取得する必要があります。listenerを使用した場合はHOTストリームなので、firstで取得できた1件だけを処理に回し、あとはrepeatで再び待ち受け状態にします。

入金テスト

"http://faucet-0.10.0.x-01.symboldev.network/?recipient=" + alice.address.plain() +"&amount=2"

入金してみましょう。

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

Azure Communication Services ことはじめ (2) : 音声通話ができるまで

Azure Communication Services は、リアルタイム コミュニケーション基盤となるサービスで、テキスト | 音声 | ビデオ によるコミュニケーションのハブとなり、接続やコントロールを行うアプリやサービスを SDK などを用いて容易に開発できます。

今回は、Azure Communication Services を使ったアプリ開発の第一歩として、音声通話ができるまでを手順を追って確認し、Node.js の Web アプリを作成してみます。

開発環境

0. 事前準備

Azure Communication Services ことはじめ (1) : チャットができるまで0.事前準備 と同様に、Azure Portal で Azure Communication Services のサービスを作成し、接続文字列とエンドポイントを取得しておきます。

1. 音声通話ができるまで

今回は Node.js、Web アプリを念頭に、音声通話ができるまでの手順を確認します。

1-0. ライブラリの追加

以下のライブラリを冒頭に追加します。

ライブラリ
azure/communication-common ユーザーの作成、アクセストークン取得
azure/communication-administration  (同上) 
azure/communication-calling 音声通話のコントロール
client.js
import { AzureCommunicationUserCredential } from '@azure/communication-common';
import { CommunicationIdentityClient } from "@azure/communication-administration";
import { CallClient } from "@azure/communication-calling";

1-1. ユーザーを作成し、アクセストークンを取得する

接続文字列を用いて CommunicationIdentityClient を新規作成し、ユーザーの作成とアクセストークンの取得を行います。接続文字列には Azure Communication Services にアクセスするキーが含まれています。アクセストークンの種類は Calling を指定します。(他に Chat, SMS があります)

YOUR_CONNECTION_STRING は 事前準備で取得した 接続文字列に置き換えてください。

client.js
const identityClient = new CommunicationIdentityClient("YOUR_CONNECTION_STRING");

let userId;
let userToken;
identityClient.createUser().then(userResponse => {
    userId = userResponse.communicationUserId;
    identityClient.issueToken(userResponse, ["voip"]).then(tokenResponse => {
        userToken = tokenResponse.token;
}

1-2. 通話クライアントを作成

取得したトークンを用いて、音声通話をコントロールする CallClient を作成します。

client.js
let callClient;
const tokenCredential = new AzureCommunicationUserCredential(userToken);
callClient.createCallAgent(tokenCredential).then(agent => { callAgent = agent; }

1-3. 通話する

CallClient から、通話したい相手を CommunicationUserId で指定して Call します。

client.js
let call
call = callAgent.call([{ communicationUserId: "8:echo123" }], {});

1-4. 通話を終了する

Call を終了して音声通話を終了します。

client.js
call.hangUp({ forEveryone: true });

2. Web アプリの開発

以上の手順を踏まえて、Visual Studio Code で Node.js Web アプリを作成します。

2-1. 新規 Node.js アプリの作成

Node.js アプリを作成するフォルダーを作成し、Visual Studio Code で開き、npm init コマンドで package.json を作成します。

npm init -y

2-2. Azure Communication Services のライブラリのインストール

npm install コマンドで Azure Communication Services の必要なライブラリ (パッケージ) をインストールします

npm install @azure/communication-common --save
npm install @azure/communication-administration --save
npm install @azure/communication-calling --save

2-3. Webpack のインストール

今回は Webpack を利用して、JavaScript のモジュールバンドル & ローカルでの実行確認 を行います。
npm install コマンドで、webpack、webpack-cli、webpack-dev-server をインストールします。

npm install webpack webpack-cli webpack-dev-server --save-dev

2-4. コーディング

画面 (index.html)

ユーザーからの操作および音声入出力、各種情報を表示するため、index.html という名前でファイルを作成し、以下のような UI を作成します。

ACS_2_開発_03.png

ポイントとなるコードは以下になります。

index.html
<html>
  <body>
    <div>  <!-- UserId 出力-->
      <label>Your UserId: </label>
      <input id="caller-id-output" type="text" style="width: 600px;" disabled="true"/>
    </div>
    <div>  <!-- UserToken 出力-->
      <label>Your Token: </label>
      <textarea id="caller-token-output" type="text" style="width: 600px; height: 130px;" disabled="true"></textarea>
    </div>
    <div>  <!-- 呼び出し先 UserId 入力-->
      <input id="callee-id-input" type="text" style="width: 250px;"/>
    </div>
    <div> <!-- 通話開始、終了ボタン -->
      <button id="call-button" type="button" disabled="true">Start Call</button>
      <button id="hang-up-button" type="button" disabled="true">Hang Up</button>
    </div>
    <div>  <!-- 状態メッセージ出力 -->
      <label id="message-output" style="width: 1000px; height: 50px;"></label>
    </div>
    <script src="./index.js"></script>
  </body>
</html>

加筆を行って整えたコードは以下になります。
ACSCallWeb202010/index.html

通話機能 (client.js)

client.js という名前でファイルを作成し、VoIP 通話をコントロールする機能を記述します。

後ほど client.js を index.js にビルドして index.html で読み込みます。

利用ライブラリー

今回は、これらのライブラリーを利用します。

client.js
import { AzureCommunicationUserCredential } from '@azure/communication-common';
import { CommunicationIdentityClient } from "@azure/communication-administration";
import { CallClient } from "@azure/communication-calling";

接続文字列

client.js に connectionString を記載しておきます。

YOUR_CONNECTION_STRING は 事前準備で取得した 接続文字列に置き換えてください。

セキュリティの観点から別の設定ファイルなどに記載して読み出すのが一般的ですが、今回は動作を確認するのみのアプリなので本体に記載しています。

client.js
let connectionString = "YOUR_CONNECTION_STRING";

UI 画面の入出力

呼び出しを行う相手先の UserId の入力を取得できるようにします。
また、通話の開始、終了のボダンのクリックを取得できるようにします。
出力は、UserId、UserToken、動作に対するメッセージを表示するようにしておきます。

client.js
// 入力
const calleeInput = document.getElementById("callee-id-input"); //通話先 UserId
const callButton = document.getElementById("call-button");      //通話開始ボタン
const hangUpButton = document.getElementById("hang-up-button"); //通話終了ボタン
// 出力
const callerIdOutput = document.getElementById("caller-id-output");       //UserId
const callerTokenOutput = document.getElementById("caller-token-output"); //UserToken
const messageOutput = document.getElementById("message-output");          //状態メッセージ

CommunicationUser の新規作成、Token の取得、CallAgent の生成

接続文字列から User を新規作成して Token を取得します。Token を利用して、通話のコントロールを行う CallAgent を生成します。CAllAgent が無事生成出来たら callButton (通話開始ボタン) をクリック可能(disabled = false)にします。

client.js
const identityClient = new CommunicationIdentityClient(connectionString);
const callClient = new CallClient();
let callAgent;
let call;

identityClient.createUser().then(userResponse => {
    callerIdOutput.value = userResponse.communicationUserId;    // UserID 画面出力
    identityClient.issueToken(userResponse, ["voip"]).then(tokenResponse => {
        const userToken = tokenResponse.token;
        callerTokenOutput.value = userToken;            // UserToken 画面出力
        messageOutput.innerText += "Got user token.";   // 状態メッセージ画面出力
        const tokenCredential = new AzureCommunicationUserCredential(userToken);
        callClient.createCallAgent(tokenCredential).then(agent => {
          callAgent = agent;
          callButton.disabled = false;                   // 通話開始ボタンをクリック可能に
          messageOutput.innerText += "\nReady to call."; // 状態メッセージ画面出力
        });
    });
});

通話の開始

callButton (通話開始ボタン) がクリックされたら、callAgent から相手の Communication Id (userToCall TextBox から取得) の呼び出しを行います。

client.js
callButton.addEventListener("click", () => {
    // start a call
    const userToCall = calleeInput.value;
    call = callAgent.call( [{ communicationUserId: userToCall }], {} );

    // change button states & adding messages
    hangUpButton.disabled = false;  // 通話停止ボタンをクリック可能に
    callButton.disabled = true;     // 通話開始ボタンをクリック不可に
    messageOutput.innerText += "\nCall started.";  // 状態メッセージ画面出力
});

通話の終了

hangupButton (通話終了ボタン) がクリックされたら、callAgent の通話を切断します。

client.js
hangUpButton.addEventListener("click", () => {
    // end the current call
    call.hangUp({ forEveryone: true });

    // change button states & adding messages
    hangUpButton.disabled = true;  // 通話停止ボタンをクリック不可に
    callButton.disabled = false;   // 通話停止ボタンをクリック可能に
    messageOutput.innerText += "\nNow hanged up.";  // 状態メッセージ画面出力
  });

最終的なコードはこちらになります。
ACSCallWeb202010/client.js

3. 音声通話を試してみる

今回は Webpack を利用しているので、client.js を index.js にビルドして起動します。

npx webpack-dev-server --entry ./client.js --output index.js --debug --devtool inline-source-map

起動したら、ブラウザーから http://localhost:8080 にアクセスします。
User Id と Token が取得できると [Start Call] のボタンがアクティブになります。
音声通話テスト用のユーザーである 8:echo123 を入力して [Start Call] をクリックします。
"Hello, welcome to Azure Communication Services audio testing system..." と音声が聞こえれば OK です。音声を録音して再生することで、こちらの音声が取得できていることも確認できます。

ACSCallWeb202010.gif

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

SSRとは何かメリデメとフローを学んだまとめ(SPAと比較)

SSR(サーバーサイドレンダリング)とは

元々ブラウザ上でしか動かなかったJavaScriptをサーバー内部で実行して、HTMLを生成することを指します。
もう少し言うと、リクエストがあった時にJavaScriptをダウンロードしてクライアントでレンダリングするのではなく、初回のリクエストはサーバー側でレンダリングして返すと言うこと。

SSRはなぜ必要とされているのか

SSRが必要とされる一番の理由はSEO対策だと思っています。
ReactやVueなどのSPAサイトを構築するとGoogleクローラーがJavaScriptを解釈しない可能性があり評価してくれないことが考えられます。
その対策としてJavaScriptをサーバー側で実行してHTMLを生成して返してくれるSSRが必要とされている。(この辺りはメリットで話します)
しかしGoogleクローラーも進化を遂げて現在ではSPAサイトでも認識され評価してくれています。
ただこの懸念はGoogleを信じるしかないので、リプレイス等でSPAに変える際絶対にSEOに影響がないという保証はどこにもないのでSSRが必要なのではないでしょうか。

SSRのメリットは何か

初回レンダリングが早い

SSRではサーバー側のNode.jsでJavaScriptを実行してた結果HTMLを生成してクライアントに返してくれるため早い。
Webアプリにバンドルされる機能の大半は初期表示に必要がないため、初期表示をしてから読み込めばよい
SPAなどはリクエストしてレスポンスを受けてからJSをブラウザで実行するため待ち時間が発生する。

SEOに強い

SSRはサーバーでJavaScriptを実行しHTMLを生成してクラアント側に返すため正しく評価されると言えます。
一方SPAの課題として2つのSEO問題がSSRはそれらを解決できます。

・非同期コンテンツを待ってくれない
 Googleは同期的JavaScriptアプリケーションのインデックスを作成するためAjax等でコンテンツを取得する場合にクローラーが待ってくれない可能性があると言われている。そのためSEOが重要なページは非同期コンテンツを取得する場合SSRが必要。

・レンダリングエンジンが古い場合HTMLを認識してくれない
 クローラーがJavaScriptを解釈しない場合何もない状態を受け取ります。
 レンダリングエンジンが古いと正しく動かず正しいHTMLを認識してくれないということはありえますが、Googleから最新のChromeに常に対応していくことが発表されているのであまり問題にはならないと思う。

SSRのデメリットは何か

サーバー側の負荷が高い

サーバー側でレンダリングするということはサーバーのCPU負荷が増えるということ。
トラフィックが多いことが見込まれる場合はキャッシュ対応をしっかりと行う必要がある

Node.jsを実行できる環境が必要になる

SPAとは異なりJavaScriptを実行するためNode.jsが実行できる環境を準備しないといけない。

SPAとSSRの流れ

SPA

spa.jpg

SPAが生まれた理由

SPAがそもそも生まれた理由はページ遷移のたびにリクエストをすると動的処理が行われるからサーバーの負荷が高くなって遅いんじゃないか?だったらページ遷移ごとのリクエストやめちゃおうぜって言うことです。

SPAの流れ

ページ遷移やコンテンツの切り替えはサーバーで行なっていたものをブラウザ側の仕事にすることでサイトの高速化を図りました。
そのためSPAはWebサーバーへのリクエストは1度きりです。
そしてページ固有のコンテンツだけを変更させます。
例えばフッターとヘッダーはどのページも共通だった場合、それ以外のコンテンツだけを変更させます。
Reactのような仮装DOMの場合、変更のあった差分だけをDOMに反映させるためページの変更ごとに全てのDOMを作り直す必要がありません。

ただしSPAは最初の表示が遅いです。
一番最初の表示は必要のないrouterやstoreなどのjsも含まれて送られるためJavaScriptのダウンロードと実行に時間がかかります。
またOGPの設定もできません。
先ほどお話ししたようにページのコンテンツをDOM操作で変更しているだけなのでページごとのOGP設定ができないです。
JavaScriptでタグを変更してもいいですが動的な変更が読み取れない場合もあるためそうゆう場合はSSRを使う方がいいでしょう。

SSR

ssr.jpg

SSRが生まれた理由

SSRが生まれた理由は冒頭でお話しした理由です。
もう1つの理由があるとするならばSPAの初期表示が遅い部分を補うことです。

SSRの流れ

SSRの流れはほとんどSPAと変わらず差分だけを非同期リクエストします。
大きな違いは、レスポンスでレンダリング済みのHTMLを渡します。レンダリングまでブラウザが行なっていましたがSSRではレンダリングまではサーバーで行いpaintingはブラウザで行うと言う点です。
レンダリング済みのHTMLとはJavaScriptで変更したDOM操作なども反映されたファイルです。
そのためSPAでできないOGP設定もサーバー側でHTMLを作成する静的ページなのでできます。

デメリットは紹介しているので割愛します。

最後に

今回学んだことをまとめ見ました。
SSRとSPAってなんだ?となっていましたが明確に必要な理由やメリット・デメリットが理解できました。
色々と勘違いしている部分もあり情報不足だったなと実感しています・・・
今後はSSGについても学んでいきたいなと思います。

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

Nuxt.ts - axiosのheadersに常にtokenを付与する方法

JWTのトークンをリクエストの度に付与するのが面倒で、この実装で実現できたのでメモ

plugins/axios.ts
import { Context } from '@nuxt/types';
import Cookies from 'js-cookie';

export default function ({ $axios }: Context) {
  $axios.onRequest((config) => {
    const token = Cookies.get('authToken');
    if (token) {
      config.headers.common['Authorization'] = 'Bearer ' + token;
    }
    return config;
  });
}
nuxt.config.js
export default {
  // ...
  plugins: ['@/plugins/axios'],
  // ...
}
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

YellowfinでJavascriptだけでダッシュボードのレポートをすべてExcelでダウンロードする

excelDLWithoutJsp.gif

概要

Yellowfinのバージョン9ではダッシュボードのExportはPDFしかできないこともあり、なんとかできないかというリクエストから色々調べて実現できたところのメモです。
前提としてYellowfinはwebserviceのREST化を進めていますが、レポート周りの機能はまだxmlベースのSOAPのやり取りでレガシー過ぎて苦労しました。。
調べたことのそれぞれは下記になります。
今更ながらJavascriptでSOAPを通してxmlをparseする
Javascriptでbase64エンコード化されたExcelファイルをダウンロードする
JavascriptのFetchをネストして2回リクエストを投げる

前回の全Excelダウンロードとの違いはレポート名をファイル名につけれる点、JSPファイルを使用せずにJavascriptだけで完結できる点です。
YellowfinのAPIとwebserviceを使ってダッシュボードのレポートを全てExcelでダウンロードする(jspファイル使用)

前準備

今回は2つの前準備があります。

ボタンwidgetの設置

前回と同じようにダッシュボードを作成後に、コードwidgetからボタンのwidgetをドラッグ・アンド・ドロップして名前をつけます。例)export
これは次のJSタブに記述する部分で使います。ボタンのサイズやテキストは適宜変更してください(ここではExport Report)

image.png

web.xmlの編集からの再起動

※また、今回はリポジトリに直接SQLクエリを投げるwebserviceを使うのでこのページのSQLクエリー関数METADATAQUERYのクエリーwebサービスの有効化を参考にweb.xmlを編集して再起動してください。

JSタブのコード

少し長くなりますが、これはXMLを全て記述しているからに他ならないですね。。
まず、前準備で用意したボタンのクリックeventlistenerでdashboardAPIを呼び出し、このダッシュボードで使われている全てのレポートの情報を配列で取得します。それをforeachで回しつつreportUUIDを取得して管理サービスのリポジトリに直接SQLを投げるwebserviceを使います。※前準備のweb.xml変更を参照
これでレポートの内部reportidを取得できるので、xmlをparseしてレポートサービスへfetchのネストでリクエストを送ります。return doc.getElementsByTagName("dataValue")[1].textContent;の部分がreportidを取得してるところですね。
そこからはほぼこの2ページと同じです。
今更ながらJavascriptでSOAPを通してxmlをparseする
Javascriptでbase64エンコード化されたExcelファイルをダウンロードする

ExcelExportAll.js
// ここに変数を宣言したり、コードを記述します

/**
 * キャンバスとそのすべての子要素をページにレンダリングしたりアタッチするときに呼び出されます。
 */
this.onRender = function () {
    // ここにコードを記述します。これは、イベントリスナーを設定するのに理想的な場所です
    let button = this.apis.canvas.select('export');
    button.addEventListener('click', () => {
        var dash = this.apis.dashboard;
        var allrep = dash.getAllReports();
        allrep.forEach( item => {
            var uuid = item.reportUUID;
            fetch('http://localhost:8922/services/AdministrationService', {
                method: 'POST',
                body: '<?xml version="1.0" encoding="utf-8"?>' +
                '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://webservices.web.mi.hof.com/">' +
                '<soapenv:Header/>' +
                '<soapenv:Body>' +
                '<web:remoteAdministrationCall>' +
                '<arg0>' +
                '<loginId>admin@yellowfin.com.au</loginId>' +
                '<password>test</password>' +
                '<orgId>1</orgId>' +
                '<function>METADATASQLQUERY</function>' +
                "<query>SELECT reportid FROM reportheader where reportstatuscode = 'OPEN' and publishuuid = '" + uuid + "'</query>" +
                '</arg0>' +
                '</web:remoteAdministrationCall>' +
                '</soapenv:Body>' +
                '</soapenv:Envelope>',
                headers: {
                    'Content-Type': 'text/xml',
                    'SOAPAction': ''
                },
            }).then(function(response) {
                return response.text();
            }).then(function(responseText) {
                var parser = new DOMParser();
                var doc = parser.parseFromString(responseText, "text/xml");
                return doc.getElementsByTagName("dataValue")[1].textContent;
            }).then(function(reportId) {
                return fetch('http://localhost:8922/services/ReportService', {
                    method: 'POST',
                    body: '<?xml version="1.0" encoding="utf-8"?>' +
                    '<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:web="http://webservices.web.mi.hof.com/">' +
                    '<soapenv:Header/>' +
                    '<soapenv:Body>' +
                    '<web:remoteReportCall>' +
                    '<arg0>' +
                    '<loginId>admin@yellowfin.com.au</loginId>' +
                    '<password>test</password>' +
                    '<orgId>1</orgId>' +
                    '<reportRequest>XLSX</reportRequest>' +
                    '<reportId>' + reportId + '</reportId>' +
                    '</arg0>' +
                    '</web:remoteReportCall>' +
                    '</soapenv:Body>' +
                    '</soapenv:Envelope>',
                    headers: {
                        'Content-Type': 'text/xml',
                        'SOAPAction': ''
                    },
                });
            }).then(function(response) {
                return response.text();
            }).then(function(responseText) {
                var parser = new DOMParser();
                var doc = parser.parseFromString(responseText, "text/xml");
                var reportName = doc.getElementsByTagName("reportName")[0].textContent;
                var binary = doc.getElementsByTagName("binaryData")[0].textContent;
                binary = atob(binary);
                var decoded_array = new Uint8Array(Array.prototype.map.call(binary, c => c.charCodeAt()));
                var decoded = new Blob([decoded_array], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
                var url = window.URL.createObjectURL(decoded);
                var a = document.createElement("a");
                a.href = url;
                a.download = reportName + ".xlsx";
                a.click();
                window.URL.revokeObjectURL(url);
            }).catch(function(e) {
                // エラーが発生した場合の処理
                console.log(e);
            }); 
        });
    });
};

/**
 * キャンバスとそのすべての子要素がページから削除されるときに呼び出されます。
 */
this.onRemove = function () {
    // ここにコードを記述します。これは、イベントリスナーを削除するのに理想的な場所です
};

まとめ

これでJSタブに記載するだけで(長い)個別ですがダッシュボード中の全てのレポートのExcelダウンロードが可能になりました。。
これにダッシュボードのフィルター適用後の値が適用できたらいいのですがJSPファイルを使わないと難しそうで、さらにできるかどうかは確認中のようです。。。

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

既存のプロジェクトにprettierを使用する

最初に

自動コード整形に、prettier を使い始めてみたのですが、既存のコードに適応する方法が分からなかったので、調べてみました。

手順

  1. Install
  2. .prettierignore の作成
  3. prettierの実行

Install

最初にprettierをインストールします。

npm install -g  prettier

.prettierignore の作成

自動コード整形しないファイルを記載します。

.prettierignore
node_modules/

prettierの実行

最後にprettierを実行します。

prettier --write "**/*.js"

まとめ

プロジェクトの途中からでも、prettierを入れることが簡単なので、フォーマット揃えたいなと思った時は気軽に試してみてください。

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

JavascriptのFetchをネストして2回リクエストを投げる

やりたいこと

大元は、配列から値を1つずつ回してXmlHttpRequestでPOSTを2回やろうと思ってたのですが、なかなかネスト後の値が検知できずにいたところで難しそうだったのでFetchを使ったところうまくネストして2回リクエストを送った値が取得できたのでメモします。化石の知識しかないので過不足は多分結構あります。

コード例

これは例なのでPOSTする先やbody、headerは適宜変更してください。
1回目のリクエスト後にresponseをparseして、使いたい値を取り出してreturnで返すことで次のthenで使うことができます。
これをさらにretuenでfetchを投げることで次のthenで処理できるようにしているという流れになります。(or はJsonで返ってくることを想定したコードです)

fetchNest.js
fetch('http://localhost:8922/services/AdministrationService', {
    method: 'POST',
    body: 'fname=aaaaa&uuid=31963958-31b2-487e-b185-c3559e6bb611&format=XLSX',
    headers: {
        'Content-Type': 'application/x-www-form-urlencoded',
        'Accept': 'text/xml'//or'application/json'
    },
}).then(function(response) {
    return response.text();//or response.json();
}).then(function(responseText) {
    var parser = new DOMParser();//or responseText.dataValue.reportId;
    var doc = parser.parseFromString(responseText, "text/xml");
    return doc.getElementsByTagName("dataValue")[1].textContent;
}).then(function(reportId) {
    return fetch('http://localhost:8922/services/ReportService', {
        method: 'POST',
        body: 'fname=aaaaa&uuid=31963958-31b2-487e-b185-c3559e6bb611&format=XLSX&reportId='+reportId,
        headers: {
            'Content-Type': 'application/x-www-form-urlencoded',
            'Accept': 'text/xml'
        },
    });
}).then(function(response) {
    return response.text();
}).then(function(responseText) {
    console.log(responseText);
}).catch(function(e) {
    // エラーが発生した場合の処理
    console.log(e);
});

大変参考にさせて頂きました

まだXMLHttpRequestを使ってるの? fetchのすすめ
Fetch の使用

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

◯日前のデータだけを取得する方法【moment.js, firebase, firestore, typescript, javascript】

TODO

「〜日前」のデータだけが欲しい時のQuery

CODE

index.ts
const now = moment();
const days = 10
const time = now.subtract(days, "d");
const snapshot = await collectionReferennce
   .where("date", ">", time.startOf("day").toDate())
   .where("date", "<", time.endOf("day").toDate())
   .get();

DONE

console.log(time.startOf("day")); // Moment<2020-10-12T00:00:00+09:00>
console.log(time.endOf("day"));   // Moment<2020-10-12T23:59:59+09:00>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

◯日前のデータだけをQueryする方法【moment.js, firebase, firestore, typescript, javascript】

TODO

「〜日前」のデータだけQueryしたい

CODE

index.ts
const now = moment();
const days = 10
const time = now.subtract(days, "d");

const start = time.startOf("day").toDate()
const end = time.endOf("day").toDate()

console.log(time.startOf("day")); // Moment<2020-10-12T00:00:00+09:00>
console.log(time.endOf("day"));   // Moment<2020-10-12T23:59:59+09:00>

const snapshot = await collectionReferennce
   .where("date", ">", start)
   .where("date", "<", end)
   .get();

REFERENCE

https://momentjs.com/docs/#/manipulating/start-of/
https://momentjs.com/docs/#/manipulating/end-of/

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

LINE Botに羊を数えてもらったら目が覚めた

はじめに

 最近寝る時間が不規則で、すぐに寝つけないことが多いため、初めて作成するLINEBotに、羊を数えてもらうことで可能な限り早く眠りにつきたいと思いました。なお、本当に眠くなるLINEBotにするため、羊の画像をランダムで返す仕組みも考えたが、単純で、退屈で、眠気を誘うようなものをあえて目指しました。

LINE Botの詳細

 LINEmessagingAPIのCountAPI(単純な数値カウンターを作成できるAPI ※参考:https://countapi.xyz/ )を利用して、羊の数をカウントし、「眠れない」とつぶやくと、羊を数えてくれる。

コード

server2.js
'use strict'; // おまじない

// ########################################
//               初期設定など
// ########################################

// パッケージを使用します
const express = require('express');
const line = require('@line/bot-sdk');
const axios = require('axios');

// ローカル(自分のPC)でサーバーを公開するときのポート番号です
const PORT = process.env.PORT || 3000;

// Messaging APIで利用するクレデンシャル(秘匿情報)です。
const config = {
    channelSecret: 'aaaaaaaaaaaaaaaaaaaaaa',
    channelAccessToken: 'aaaaaaaaaaaaaaaaaaaaaa'
};

const sampleFunction = async (event) => {
    // ユーザーメッセージが「眠れない」かどうか
    if (event.message.text !== '眠れない') {
        return client.replyMessage(event.replyToken, {
            type: 'text',
            text: '「眠れない」と話しかけてね'
        });
    } else {        
        let pushText = '';
        try {
            const res = await axios.get('https://api.countapi.xyz/hit/sheep/num');
            const SheepNum = res.data.value;
            pushText = `羊が${SheepNum}匹`;
        } catch (error) {
            pushText = '検索中にエラーが発生しました。ごめんね。';
            // APIからエラーが返ってきたらターミナルに表示する
            console.error(error);
        }

        // 「プッシュ」で後からユーザーに通知します
        return client.pushMessage(event.source.userId, {
            type: 'text',
            text: pushText,
        });
    }
};


// ########################################
//  LINEサーバーからのWebhookデータを処理する部分
// ########################################

// LINE SDKを初期化します
const client = new line.Client(config);

// LINEサーバーからWebhookがあると「サーバー部分」から以下の "handleEvent" という関数が呼び出されます
async function handleEvent(event) {
    // 受信したWebhookが「テキストメッセージ以外」であればnullを返すことで無視します
    if (event.type !== 'message' || event.message.type !== 'text') {
        return Promise.resolve(null);
    }
    // サンプル関数を実行します
    return sampleFunction(event);
}



// ########################################
//          Expressによるサーバー部分
// ########################################

// expressを初期化します
const app = express();

// HTTP POSTによって '/webhook' のパスにアクセスがあったら、POSTされた内容に応じて様々な処理をします
app.post('/webhook', line.middleware(config), (req, res) => {
    // Webhookの中身を確認用にターミナルに表示します
    console.log(req.body.events);

    // 検証ボタンをクリックしたときに飛んできたWebhookを受信したときのみ以下のif文内を実行
    if (req.body.events[0].replyToken === '00000000000000000000000000000000' && req.body.events[1].replyToken === 'ffffffffffffffffffffffffffffffff') {
        res.send('Hello LINE BOT! (HTTP POST)'); // LINEサーバーに返答します
        console.log('検証イベントを受信しました!'); // ターミナルに表示します
        return; // これより下は実行されません
    }

    // あらかじめ宣言しておいた "handleEvent" 関数にWebhookの中身を渡して処理してもらい、
    // 関数から戻ってきたデータをそのままLINEサーバーに「レスポンス」として返します
    Promise.all(req.body.events.map(handleEvent)).then((result) => res.json(result));
});

// 最初に決めたポート番号でサーバーをPC内だけに公開します
// (環境によってはローカルネットワーク内にも公開されます)
app.listen(PORT);
console.log(`ポート${PORT}番でExpressサーバーを実行中です…`);

実行結果

IMG_9238.PNG

使ってみた結果

 全然眠れなかった。むしろ目が覚めた。
 ※242匹まで数えた。
 ※結局ディズニーオルゴールメドレーで寝ることができた。

考察

 なぜ羊を数えると眠くなるか、を調べてみたところ、理由は「人間は単調な音を聞き続けると眠たくなるため」であり、「聞く」という行為が、眠りを誘う上で重要であったことに気づいた。(参考:なぜ「羊」かというとsleep(スリープ)とsheep(シープ)が綴りや発音が似ていたため言葉遊びが理由で、寝る時に羊が数えられるようになったそうです。)
 つまり、羊の数をテキストデータとして明示するだけでは根本的に眠くならないということがわかった。※むしろ、スマホの画面が明るすぎて逆効果とさえ思う。

改善できるとしたら

 上記考察にも記載したように「単調な音を聞き続けられる」ように、眠いとつぶやいたら、羊の数を音声変換して発話してくれるBotの方が効果が高いと考えられる。
(参考となりそうなQiita記事:https://qiita.com/unokuncom/items/ffe95f6f7ffbaedcf562

最後に

最近早く寝られたときにしていることは、結局下記の3点である。
1.暖かいお風呂に20分以上つかる
2.適度な飲酒
3.オルゴールメドレーを聴く
非線形的なアプローチでLINEBotとの組み合わせからアイデアを考えてみたら、例えば、
・寝る時間を呟いたらお風呂に入るべきベストなタイミングを教えてくれるBot
・飲み仲間になってくれて、最後にそろそろ寝たら?って言ってくれるBot
・おすすめのオルゴールメドレーを検索してくれるBot
などが思いついた。時間があれば挑戦してみたい。

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

Javascriptでbase64エンコード化されたExcelファイルをダウンロードする

やりたいこと

これもタイトル通りなのですが、仕事の要件的にSOAPでサーバーサイドプログラムを使用せずにJavascriptだけでbase64binaryをブラウザでダウンロードさせるというものがあり、苦戦したので自分メモとして書いときます。

ソースからの解説

atobでASCIIからBinaryへ変換し、それを1文字ずつUnicodeの数値に変換し、その数字を配列化します。
この配列をBlobコンストラクタに入れてtypeをExcelの形式で指定します。
このBlobをcreateObjectURLでブラウザのメモリに展開してaタグを作成し、強制的にクリックしたことにしてダウンロードさせます。

sample.js
var filename = 'something';
var binary = 'バイナリ文字列ーーーUEsDBBQACAgIABp2QlEAAAAAAAAAAAAAAAALAAAAX3JlbHMvLnJlbHOtksFqwzAMhl/F6N447WCM
UbeXMuhtjO4BNFtJTGLL2NqWvf3MLltLChvsKCRDQANAGgDAAAYBAEAAAA=ーーー的な';
binary = atob(binary);
var decoded_array = new Uint8Array(Array.prototype.map.call(binary, c => c.charCodeAt()));
var decoded = new Blob([decoded_array], {type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"});
var url = window.URL.createObjectURL(decoded);
var a = document.createElement("a");
a.href = url;
a.download = filename + ".xlsx";
a.click();
window.URL.revokeObjectURL(url);

大変参考にさせていただきました

Blob, ArrayBuffer, Uint8Array, DataURI の変換
JavaScriptでBase64エンコード・デコード(UTF-8も)

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

~JavaScript~残り何日?をやりたくて

はじめに

Todoリストを作成中に残り何日?という期限をつけたくてそれの表示のために
new Date()を使って、今日の日付から残り何日かを表示させたかった。

コード

getLimit(limit) {// 2020/10/05
      const d = new Date()
      const nowDate = new Date(d.getFullYear(), d.getMonth(), d.getDate())//今の日付
      const year = Number(limit.slice(0, 4))
      const month = Number(limit.slice(5, 7)) - 1
      const date = Number(limit.slice(8, 10))
      const limitDate = new Date(year, month, date)//期限日
      const day = 1000 * 60 * 60 * 24//一日のミリ秒の値
      return (limitDate - nowDate) / day
    },

引数のlimitには2020/10/05というよな形で期限の日が入ります。
const month-1しているのは引数として取った時に既に+1された値が入ってくるため
limitDate - nowDateで日数分のミリ秒がでるのでそれをdayで割ると結果がでる。

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

querySelector()使用時の注意点(name属性を指定する時のひと工夫など)

この記事を書いた背景

JavaScriptの『querySelector()メソッド』を使って、任意のHTMLを取得する処理を記述していたのですが、最初、うまく取得することができませんでした。
うまく取得できなかった理由は、取得するHTMLを指定する際に記述したname属性の指定コードがうまく書けていなかったためです。
今回はname属性を指定するひと工夫も含め、querySelector()使用時の注意点を書いていきたいと思います。

そもそもquerySelector()メソッドとは

JavaScriptのメソッドで、任意のHTMLを取得するメソッドです。
指定したセレクタに一致する最初のHTML要素(Element)を取得するメソッドです。
『document.querySelector( CSSセレクタ )』のように使います。
実際の使い方は下記の例をご覧ください。

自分がどのようにつまづいたか

オリジナルアプリを作成中、ラジオボタンの実装を行ってましたが、
選択したボタンの値を取得する処理がうまく機能しない場合がありました。

下記の例で見ていきたいと思います。

index.html
<!--ラジオボタンの記述。iPhoneかAndroidかを選ぶラジオボタン-->
<group class="inline-radio">
  <div>
    <input type="radio" value="1" name="simulation[phone]" id="simulation_phone_1">
    <label for="simulation_phone">iPhone</label>
  </div>
  <div>
    <input type="radio" value="2" name="simulation[phone]" id="simulation_phone_2">
    <label for="simulation_phone">Android</label>
  </div>
</group>
index.js
//ラジオボタンの要素(NodeList)を取得する
const phone_list = document.getElementsByName('simulation[phone]');
//取得したラジオボタンの要素(NodeList)をラジオボタン1つずつ取り出し、変数eに格納する。
phone_list.forEach(function(e) {
  //クリック(ボタン押下)された要素eの場合イベント発火
  e.addEventListener("click", function() {
    //クリックされたinputタグの値(value値)を取り出し、変数phone_planに格納する
    const phone_plan = document.querySelector("input:checked").value;
    //変数phone_planの出力
    console.log(phone_plan);
  });
});

※NodeListは、ざっくり言えばHTMLの情報全てが詰まったオブジェクトです。

簡単なHTMLとJSがあります。色々端折ってますがご了承ください。
見た目的には簡単なラジオボタン(●iPhone ●Android)があるだけです。
ラジオボタンでiphoneを選択すると、検証ツールのconsoleにて 1 が出力されます。

console
1

普通に値は取得できました。
しかし、HTMLにラジオボタンで実装された質問等がいくつも有り(スマホを選ぶラジオボタン・PCを選ぶラジオボタン...など)、それぞれのラジオボタンの押下された値を取得する処理を書くときに問題が起きます。
記述を見直さないといけないのは、JSの『querySelector()メソッド』です。

querySelector()メソッドが取得する要素の位置は...

先程も書きましたが、『querySelector()メソッド』で取得するのは、指定したセレクタに一致する最初のHTML要素(Element)です。
そのため、現状の『querySelector("input:checked")』のような形でクリックされた要素を取得しようとすると、複数種類のラジオボタンがあるなかで、HTMLの一番上にあるラジオボタンの選択された値が取得されてしまい、HTMLの一番上にあるラジオボタン以外うまく値が取得できません。

querySelector()メソッドにおけるname属性の指定

そこで色々調べたところ、name属性を指定すれば良さそうです。
下記のように修正してみました。

javascript
//修正前
const phone_plan = document.querySelector("input:checked").value;
//修正後
const phone_plan = document.querySelector("input:checked[name=simulation[phone]]").value;

このように修正すれば、『name属性がsimulation[phone]のラジオボタンでクリックされた要素』のように指定できるはず。

しかし、結果は、、
スクリーンショット 2020-10-11 19.50.43.png

不正なセレクタということで、値が取得できないようです。(例とname属性違いますがご容赦ください。。。)

javascript
//この書き方は正しい
querySelector("input:checked[name=name属性名]]")

という書き方は正しいのですが、クラスが指定されたname属性(今回で言ったら、simulationクラスのname属性「phone」)の場合は、上記の書き方では通用しないようです。(角括弧まみれになるからかな??)

ということで、今度はクラス名外してみました。

javascript
//修正前
const phone_plan = document.querySelector("input:checked").value;
//1回目修正後
const phone_plan = document.querySelector("input:checked[name=simulation[phone]]").value;
//2回目修正後
const phone_plan = document.querySelector("input:checked[name=phone]").value;

結果は、、
スクリーンショット 2020-10-11 19.44.15.png

name属性がphoneのvalue値はなんにもないよ〜というエラーです。
正しいname属性は『simulation[phone]』ですからね。
やはり、クラス名を取るだけほど甘くない世界だ。

そして、ついにLGTMな書き方発見。
ということで、このようにname属性を指定すればいいというのを見つけました。

javascript
//修正前
const phone_plan = document.querySelector("input:checked").value;
//1回目修正後
const phone_plan = document.querySelector("input:checked[name=simulation[phone]]").value;
//2回目修正後
const phone_plan = document.querySelector("input:checked[name=phone]").value;
//3回目修正後(LGTM)
const phone_plan = document.querySelector("input:checked[name*=phone]").value;

name属性の指定のところで『*』を入れました。これは『部分一致検索』を意味します。
今回で言ったら、name属性に『phone』が含まれてるname属性という指定方法です。
ということで、無事、値が取得できました。

console
1

あとは、name属性の命名に気をつけながらname=*を使いこなしていけば、自分の思い通りのアプリが実装できると思います!

セレクタの様々な指定方法

ちなみにセレクタの指定方法は、name*=以外にもたくさん種類があります。
https://hakuhin.jp/js/selector.html

こちらのサイトにセレクタの書き方が詳しく載っているので、ぜひ見てみてください。
色んな属性の指定方法があって、実装の幅が広がりますね。大変参考になりました。

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

Three.jsを記述するための準備


今回、投稿するに当たりまして、参考にさせて頂いた記事になります。

https://ics.media/entry/14771/
https://qiita.com/watabo_shi/items/bf9bcd4569b6d480c608

ありがとうございました!!




以下、Three.jsを記述するための準備になります。

1.Three.js公式ページよりThree.js-masterのダウンロードを行います。
公式ページ左側、メニューcode/downloadより取得が可能です。
https://threejs.org/

2.ファイルの移動

ダウンロードしたファイルの中から
three.js-master/build/three.jsファイルをapp/javascriptへコピーします。
この際に、app/javascript/index.jsを作成し、

また

app/javascript/packs/application.js
reqire("../index")

と記述致します。

3. layouts/application.html.erbへの記述
次に

layouts/application.html.erb
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/110/three.min.js"></script>

と記述致します。

4.canvas要素にidの指定

three.jsによる記述を表示させたい場所に以下のように記述致します。

html.erb
  <canvas id="myCanvas"></canvas>

5.index.jsへの記述

最後に以下のように記述致します。

javascript/index.js
  function init() {
  const width = 960;//表示する画面幅の指定
  const height = 540;//表示する画面の高さの指定
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector("#myCanvas")//idの取得
  });

  renderer.setSize(width, height);

  //以下にThree.jsの記述を行います。

6.Three.jsの記述を行うための準備完了です!!

最後まで、ご閲覧頂きありがとうございました!
誤っている箇所、認識が不足している部分についてご指摘いただければ幸いに存じます。

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

複数のObservableをまとめてSubscribeする方法

以下のように、zipを利用すると複数のObservableを同時に一つの配列としてsubscribeすることができます。

zip(of(1), of(2), of(3)).subscribe((arr: [1,2,3]) => {
   console.log(arr)
})
// => [1,2,3]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

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

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

配列型

配列が持つデータに型を付けられる。

const 定数名: 配列の型名[]

const a: number[] = [1,2,3,4];
const b: boolean[];

a.push(5);
b = [true, false, true];

a.push("maguro");
//aの配列には数値しか入らないので型エラー

関数型

関数の引数や返り値に型を付けられる。

const 関数名:(引数名:引数の型名) => 返り値の型名
function 関数名(引数名:引数の型名): 返り値の型名 {行う処理};

const checkTuna:(tuna: string)  => boolean;

返り値がない時はvoidという型を指定できる。

void :中身がない、空っぽの、役に立たない、効果がない

function showTuna(): void {
  console.log('まぐろ');
};

nullとundefined

null型とundefined型は他の型に代入できる。
TypeScript Deep Dive 日本語版-nullとundefined

const fishName = string;
const fishSize = number;
fishName = undefined;
fishSize = null;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Next.jsでURLパラメータを取得する

getServerSidePropsを使う

http://localhost:3000/hoge?slug=tokyo
このslug部分を取得したい。

export async function getStaticProps({query}) {
  console.log(query.slug)
  return {}
}
// undefined

export async function getServerSideProps({query}) {
  console.log(query.slug)
  return {}
}
// tokyo

詳細はこちらに

https://nextjs.org/docs/basic-features/data-fetching

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

ドットインストールのJavascriptの第1章: はじめてのJavaScript

ドットインストール:はじめてのJavaScript

02 ひな形となるファイルを作ろう

image.png

03 クリックイベントを設定しよう

image.png

04 コードを詳しく見ていこう

image.png

06 CSSのクラスを操作してみよう

image.png

07 定数を使ってみよう

image.png

08 divを増やしてみよう

image.png

09 JavaScriptでdivを生成してみよう

image.png

10 for文でdivを生成してみよう

image.png

11 簡単なゲームを作ってみよう

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