20210220のJavaScriptに関する記事は15件です。

共通テスト「情報」試作問題のプログラミング言語を作ってみた

大学入試共通テスト「情報」試作問題で使われている独自の日本語プログラミング疑似言語を実際に実行できる環境を作りました。

ICTL - 大学入試共通テスト「情報」試作問題言語

はじめに

2022年度より高等学校でプログラミングを扱う「情報Ⅰ」の授業が必須となります。そして今後大学入試共通テスト(旧センター試験)において、「情報」を出題教科の1つにすることが検討されています。

ところで「情報Ⅰ」ではプログラミングを扱うこと自体は決まっているのですが、どのプログラミング言語を使うかは現場に任されています。 ですので仮に共通テスト「情報」の試験問題で Python が使われたとしたら、Python を使っていた高校の生徒が有利になってしまいます。

そのため昨年末に公開された「情報」試作問題では、独自の擬似言語を使っています。試作問題の解説中でも以下のように書かれています。

※高等学校の授業で多様なプログラミング言語が利用される可能性があることから,問題中で使用するプログラミング言語は,公平性を鑑みて,大学入試センター独自の日本語表記の疑似言語としている。

その「独自の日本語表記の擬似言語」とは、以下のようなものです。(試作問題から引用)

(01)Angoubun = ["p","y","e","b",…(省略)…"k","b","d","r","."]
(02)配列 Hindo のすべての要素に 0 を代入する
(03)i を 0 から 要素数(Angoubun)-1 まで 1 ずつ増やしながら:
(04)| bangou = 差分( Angoubun[i] )
(05)| もし bangou != -1 ならば:
(06)⎿ ⎿ Hindo[bangou] = Hindo[bangou] + 1
(07)表示する(Hindo)

確かにメジャーなプログラミング言語を学んだ人であれば初見でもなんとなく文法を理解できそうで、学んだ言語による有利不利はあまり無さそうです。1

さて、擬似言語と言われつつも、プログラムを見れば実行してみたくなるのが人情というもの。というわけで実行環境を作ってみました。

方針

プログラミング言語を1から作成するのは大変です。Ruby の作者である Matz 氏によれば、Ruby は「Hello World」を出力できるようになるのに半年かかったのだとか。2 さすがにそんな労力をかける気にはなりません。

幸い、この擬似言語は基本的な部分は Ruby や JavaScript とそれほど違いはありません。そこで、この擬似言語をまず JavaScript に変換する一種のトランスコンパイラを作成し、その変換された JavaScript を eval で実行するという方向で進めていきます。

とりあえず、試作問題内の2つのプログラムが実行できることと FizzBuzz が書けることを目標にして、最低限の実装をしていきたいと思います。

まずはプログラミングには名前が重要。この言語を ICTL と名付けることにします。Information Common Test Language の略です。英語として怪しいですが気にしないことにします。

変換手法

この言語の明確な仕様が無いので分からない点が多々ありますが、できるだけ実装が簡単になるように解釈していきたいと思います。

まず行頭の (01) はプログラムの一部なのか、それとも単に行番号を表示しているだけでプログラムの一部では無いのか…とりあえずこの部分は変換して消去してしまいます。

Angoubun = ["p","y","e","b",…(省略)…"k","b","d","r","."]
配列 Hindo のすべての要素に 0 を代入する
i を 0 から 要素数(Angoubun)-1 まで 1 ずつ増やしながら:
| bangou = 差分( Angoubun[i] )
| もし bangou != -1 ならば:
⎿ ⎿ Hindo[bangou] = Hindo[bangou] + 1
表示する(Hindo)

は Python のインデントのように必須のものなのかもしれませんが、実装上 さえあれば済みそうなのでこれも消去します。

Angoubun = ["p","y","e","b",…(省略)…"k","b","d","r","."]
配列 Hindo のすべての要素に 0 を代入する
i を 0 から 要素数(Angoubun)-1 まで 1 ずつ増やしながら:
  bangou = 差分( Angoubun[i] )
  もし bangou != -1 ならば:
⎿ ⎿ Hindo[bangou] = Hindo[bangou] + 1
表示する(Hindo)

行頭の は、行末の } に変換します。

Angoubun = ["p","y","e","b",…(省略)…"k","b","d","r","."]
配列 Hindo のすべての要素に 0 を代入する
i を 0 から 要素数(Angoubun)-1 まで 1 ずつ増やしながら:
  bangou = 差分( Angoubun[i] )
  もし bangou != -1 ならば:
    Hindo[bangou] = Hindo[bangou] + 1 } }
表示する(Hindo)

i を 0 から 要素数(Angoubun)-1 まで 1 ずつ増やしながら: とか もし bangou != -1 ならば: は、forif に機械的に変換できそうです。

Angoubun = ["p","y","e","b",…(省略)…"k","b","d","r","."]
配列 Hindo のすべての要素に 0 を代入する
for (i = 0; i <= 要素数(Angoubun)-1; i += 1) {
  bangou = 差分( Angoubun[i] )
  if (bangou != -1) {
    Hindo[bangou] = Hindo[bangou] + 1 } }
表示する(Hindo)

少し首を傾げたくなるのが、配列 Hindo のすべての要素に 0 を代入する という命令。「長さ 10 の配列のすべての要素に 0 を代入する」とかなら分かるんですが、長さ不定の配列のすべての要素に 0 を代入するとはどういうことなのか…とりあえず配列に「デフォルトの値」を持たせておき、undefined な要素にアクセスした際にはデフォルトの値に置き換えるようにします。

Angoubun = ["p","y","e","b",…(省略)…"k","b","d","r","."]
Hindo = []; Hindo.default = 0
for (i = 0; i <= 要素数(Angoubun)-1; i += 1) {
  bangou = 差分( (Angoubun[i] == undefined ? Angoubun.default : Angoubun[i]) )
  if (bangou != -1) {
    Hindo[bangou] = (Hindo[bangou] == undefined ? Hindo.default : Hindo[bangou]) + 1 } }
表示する(Hindo)

JavaScript は日本語の関数名も許容してくれるので、要素数() とか 差分() とか 表示する() はそのまま JavaScript 上で実装してしまいます。

以上の変換を replace と正規表現を使ってゴリゴリ書いていくことで、実際に実行できる JavaScript に変換することができました!

FizzBuzz を書く

では、この言語で FizzBuzz を書いてみましょう。else if が使えないのでネストが深くなってしまいますが、こんな風に書けばいいはずです。

(01)i を 1 から 100 まで 1 ずつ増やしながら:
(02)| もし i % 15 == 0 ならば:
(03)| | 表示する("FizzBuzz")
(04)| そうでなければ:
(05)| | もし i % 3 == 0 ならば:
(06)| | | 表示する("Fizz")
(07)| | そうでなければ:
(08)| | | もし i % 5 == 0 ならば:
(09)| | | | 表示する("Buzz")
(10)| | | そうでなければ:
(11)⎿ ⎿ ⎿ ⎿ 表示する(i)

これを JavaScript に変換したものがこちら。

for (i = 1; i <= 100; i += 1) {
  if (i % 15 == 0) {
    表示する("FizzBuzz")
  } else {
    if (i % 3 == 0) {
      表示する("Fizz")
    } else {
      if (i % 5 == 0) {
        表示する("Buzz")
      } else {
        表示する(i) } } } }

FizzBuzz も正しく実行することができました。これならそれなりにいろいろな計算等ができそうです。

高校関係者の方々へお願い

この実行環境は自由に使って構いません。たとえば実際に「情報」が共通テストに加わりこの言語でのプログラミング問題が出題されるようになったとしたら、共通テストに向けた演習に使うことができるでしょう。

ですが、「最初からテストと同じ言語で授業もやったほうがいいに違いない!」と考えてこの言語を「情報Ⅰ」の授業でメインに扱う言語には絶対にしないでほしいと思っています。Ruby や Python、JavaScript といった一般的なプログラミング言語を使ってください。

あなたの生徒の中には、授業をきっかけにプログラミングに興味を持つ人もいるでしょう。一般的な言語であれば、そうした生徒は自ら調べてゲームだとか GUI アプリだとかを作ったりと、授業でやったことの先へと進み出すことでしょう。

ですが、この言語には「先」がほとんどありません。せっかく興味を持った生徒に、その先の楽しさを伝えることができないのです。

せっかく始まるプログラミングの授業、一人でも多くの高校生が、プログラミングの楽しさを感じてくれることを願っています。


  1. たぶん無いと思いますけど、仮に関数型言語とかを採用する高校があった場合には不利になりそうですね。さらに無いと思いますが BrainF*ck とかを(以下略 

  2. hello worldまでが長いプログラミング言語は何ですか?に対するYukihiro Matsumotoさんの回答 - Quora 

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

framer-motionを使って遷移時にアニメーションを追加する

お題

framer-motionライブラリの使い方

・framerが提供しているコンポーネントにアニメーションを付与することができる。

・タグでJSXを包むことでページ遷移にもアニメーションをつけることができる。

導入方法

①npm install framer-motionでインストールする

②AppRouterページでswitchタグをタグとタグで囲む

包んだswitchにはlocationとkeyを設定する。locationはどこのページにいるかframer-motionに教える役割を持っており、これが設定されていないとアニメーションが機能しない。keyにはlocation.pathnameを設定。これはパス名。ここではloginやtitleが該当する。

HashRouterを使っている場合はRoute render={({ location }) => ()}で取得することができる。

AppRouter.js
import { AnimatePresence, motion } from "framer-motion"
  <Router>
    <Route render={({ location }) => (
        <AnimatePresence exitBeforeEnter initial={false}>
            <motion.div>
            <Switch location={location} key={location.pathname}>
                <Route exact path="/" render={() => <Redirect to="/top" />} />
                <Route exact path="/login" component={LoginPage} />
                <Route exact path="/title" component={TitlePage} />
            </Switch>
            </motion.div>
        </AnimatePresence>
        )}
    />
  </Router>

これで下準備は完了。後はアニメーションを設定したいページにmotion.divを入れこみます。

ログインページもといサンプルページで実装するとこんな感じになります。

Login.js
import { motion } from "framer-motion";
import { animationService } from '../../services/AnimationService';

class LoginPage extends Component {

  constructor(props) {
    super(props);
    const errors = {
      mail: "",
      password: "",
    };
    this.state = {
      animation: animationService.getAnimationMotions()
    }
  }
  render() {
    return (
      <Page>
        <LoginHeader title="ログイン" />
        <div id="login-page">
          <motion.div 
            animate={{x: 0}}
            initial={{x: 100}}
            exit={{x: -100}}
            transition={{duration: 0.2}}>
            <Button modifier="large" onClick={this.submit} disabled={this.state.isBtnDisabled}>ログイン</Button>
          </motion.div>
        </div>
      </Page>
    );
  }
}

export default withRouter(LoginPage);

motion.divのプロパティにそれぞれanimate,initial,exit,transitionと設定しています。遷移アニメーションで重要なのは始発点(initial)と終着点(exit)であり、これをinitialとexitでそれぞれx地点のどこからアニメーションが始まるかまたは終了するかを設定する必要があります。

transitionにはアニメーションが動作する時間を設定します。

以上で基本的な実装は完了。

他にもコンポーネントそのものにアニメーションを加えたりページによって遷移アニメーションを分けることもできるので組み合わせ次第でよりリッチに見えるアプリを作ることができます。

initialとかexitの値をアプリで固定して使いたい場合は別ファイルでまとめて管理すると楽になります。

まとめ

react.jsでアニメーションを描画するのってめんどくさそうと思っていましたがこのライブラリはAppRouterを多少いじることに目を瞑ればめちゃめちゃ便利ですね。

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

[CSS+Javascript] 指定した段数でピラミッド状の要素リストを自動生成する

はじめに

先日、あるWEBアプリケーションのデモ開発を行った後、UIの一部、表題内容の処理部分をパーツとして分解し、JSFiddleCodePenで公開しました。本記事はその詳細という位置づけですが、ほぼ小ネタ扱いです。

ロジック

以下が実行結果となります。処理部分のロジックは比較的単純です。


See the Pen
stCreatePyramid
by STSHISHO (@STSHISHO)
on CodePen.


1-ピラミッドの段数を引数として指定

steps = steps > 10? 10: steps段数の上限を10に設定しています。サンプルのため負数や浮動小数点への対応はしていません。

2-規定のDIV要素内にUL要素を追加

サンプルのためUL要素を挿入するDIV要素#stPyramidを固定しています。HTML内にも空のDIV要素<div id="stPyramid"></div>を記述しておきます。このDIV要素がピラミッドのコンテナになります。

const ul = document.createElement('ul')
document.querySelector('#stPyramid').appendChild(ul) //挿入位置
ul.style.width = `${steps*30+20}px` //ulのサイズを設定

3-指定段数分LI要素を追加、SPAN要素を現在の段数分追加

for (let i = 1, rl = steps; i <= rl; i++) {
  let li = document.createElement('li')
  ul.appendChild(li)
  li.style.width = `${steps*30+20}px` //li要素の幅を動的に設定

  //セル設定
  for (let j = 1; j <= i; j++) {
    let span = document.createElement('span')
    li.appendChild(span)
  }
}

あとはCSSにてDIV内の要素とLI内の要素を中心揃えにするだけです。

使用する場面やカスタマイズについて

サンプルのためUL要素を追加するDIV要素を固定していますが、任意のidを指定できるようにすれば複数のリスト自動生成に対応できます。

また、並べる要素を正方形にしていますが、CSVを読み込んでリストを自動生成する場面を想定し、データに応じたサイズやデザインに対応できるようにしても面白いと思います。

最後に

元になったWEBアプリケーションの詳細について、いずれまとめる予定でいます。

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

【Node.js】Node.jsの3大フレームワークについて

プログラミング勉強日記

2021年2月20日
Expressを使うことが多いが、他のNode.jsのフレームワークが気になったので簡単にまとめる。

Expressとは

 ExpressはNode.jsを活用したWebアプリ開発を効率よく進めることができ、よく使われている標準的なものである。
 特徴としては、情報量が多く日本語のリソースも多い。また、拡張モジュールが豊富なので追加することで柔軟な対応ができる。Expressは記述量が少なくサーバー制御が可能で複雑なシステムも簡単に拡張することができる。

Meteorとは

 Meteor(読み方:「メテオ」)フレームワークはフルスタックで、サーバー制御からフロントエンドまですべてをJavaScriptで書くことができる。MeteorはNode.jsをベースとしていて、JavaScriptとHTML, CSSだけでWebアプリを開発できるプラットフォームである。
 特徴としては、デバイスを含めてプラットフォームに依存しなく簡単に記述することができる。また、迅速な開発が可能でSPAを早く構築して公開するまでをサポートする。既存の開発そのものを変革する仕組みであったり、モバイル向けのネイティブアプリ作成も可能であったりと、魅力的な機能が豊富にある。

Sailsとは

 MVCをサポートとした開発効率を重視したフレームワークである。
 特徴としては、MVCをサポートしているRailsのような操作で開発環境を進められる。デフォルトでWeb SocketをサポートしたリアルタイムWebアプリ開発もできる。Web APIを簡単に作成して公開することもできる。

参考文献

【Node.js入門】3大フレームワーク「Express」「Meteor」「Sails」の特徴とは?
Express Web フレームワーク (Node.js/JavaScript)
オープンソースのフレームワーク/Meteorとは
Node.jsのWebフレームワーク「Sails」を使ってpub/subアプリを作ってみる

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

JavaScript 新しい演算子 nullish??とoptional?. サンプルコード付きで書いてみた

Optional Chaining(?.)

use1の値がnullかundefinedではない場合はnameを返します。
外部APIから情報を持ってくるときは、nameというプロパティが漏れている可能性も考えられます。
そこでuser1にnameは入っていますよと事前に明示し、入っていた場合そのnameプロパティを表示するようにする必要があります。
Optional Chaining(?.)を使用しない場合は、
以下のようにuser1が存在し、かつuser1の中にnameというプロパティも存在すると定義します。

const user1 = {name:"suzuki"}
const user1name = user1 && user1.name
console.log(user1name)   //suzuki

逆にOptional Chaining(?.)を使用する場合は、

const user1 = {name:"suzuki"}
const user1nameOptional = user1?.name
console.log(user1nameOptional)   //suzuki
//nameがある場合は「suzuki」が出力される
//nameがない場合はエラーにならず、undefinedが出力される

なお、Optional Chaining(?.)は何個でも使用可能です。

Nullish coalescing Operator

nullishはuser1.nameがnullかundefinedの場合、右辺を返します。user1.nameに値が入っていた場合は左辺を返します。
そのままnullかundefinedで返したくない場合に使えます。

const user1 = {name:"suzuki"}
const user1name = user1?.name ?? 'someone'
console.log(user1name)   //suzuki

const user1age = user1.age ?? 'private'
console.log(user1age)
//user1はageプロパティは持っていないので右辺の'private'出力される

またfalsyな値(0,'',null,undefined,falseなど)についてもnullishではスルーされずに出力されます。

const user1 = {name:"suzuki",age:0}
const user1age = user1?.name
const user1ageNullish = user1?.age ?? 'private'
const user1ageLogical = user1?.age || 'private'

console.log(user1ageNullish) //null,undefinedではないので0が出力される
console.log(user1ageLogical) // ageプロパティは持っていないのでprivateが出力される

||では 0 や空文字もスル-されてしまうのに対し、
nullishではスルーされず返されます。

 
 

以下はサンプルです。
console.logで出力される値は最後に書いていますのでどのような出力になるか考えてみてください。

const user1 = {name:"suzuki"}
const user2 = {name:""}
const user3 = {name:false}
const user4 = {name:0}
const user5 ={}

const name1 = user1?.name
const name2 = user2?.name || 'someone'
const name2nullish = user2?.name ?? 'someone'
const name3 = user3?.name  || 'someome'
const name3nullish = user3?.name ?? 'someone'
const name4 = user4?.name || 'someone'
const name4nullish = user4?.name ?? 'someone'
const name4optional = user4?.name
const name5 = user5?.name || 'someone'
const name5nullish = user5.name ?? 'someone'
const name5optional = user5?.name
console.log(name1) // suzuki
console.log(name2) //someone
console.log(name2nullish) //空文字
console.log(name3)  //someone
console.log(name3nullish)  //false
console.log(name4)  //someone
console.log(name4nullish)  //0
console.log(name4optional) //0
console.log(name5)  //someone
console.log(name5nullish)   //someone
console.log(name5optional)  //undefined
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptで関数型言語を作ろう(3)

前記事では、

3.add(2).add(10)

という式を解析し、式をオブジェクトで表現する「構文解析」について見てきました。

ところで、いまさらですが、普通「式」といったら、

3+2+10

と書くのが一般的なのに、なんで一般的でない書き方をしているのか、というと、

  • 作るのが(&解説するのが)面倒
  • この式の解析の仕方は他の方も解説されている

という身も蓋もない理由です。

一般的な「式」の解析方法については、上の検索欄で「構文解析」あるいは「構文解析 JavaScript」などで検索していただくとひっかかると思います。

本連載では、式の解析の仕組みはなるべく単純化して、その先のトピック、具体的に言うと型のチェックや、関数型言語ならではの機能を詳しく見てみたいと考えています。

3.add(2)はどう動くのか?

そもそも、3.add(2)という式はJavaScriptでは動作しません。これを動かすには何らかの変換が必要になります。今回はまず、この変換を支援する「ランタイムライブラリ」について見ていきます。まず、lang/runtime.tsを開いてみましょう。

lang/runtime.ts
interface INum {
    value:number,
    add:(b:INum)=>INum,
};
export const Num=(value:number):INum=>({
    value,
    add(b:INum):INum {
        return Num(value+b.value);
    },
});

これを使ってちょっと遊んでみます。

TypeScript playgroundを開いてみましょう。このリンクから開くと、すでにプログラムが書かれていると思います。

Runを押して実行してみると

LOG: {
  "value": 5
} 

という結果が出力されます。1
最後に書いてある式Num(3).add(Num(2)) で、計算を行っています。

  • INum型のオブジェクトは,次のメンバをもちます
    • value(JavaScriptの数値)
    • add(他のINum型のオブジェクトをもらって,足し算した結果をINumオブジェクトで返す関数)
  • 関数Numは,JavaScriptの数値nからINum型のオブジェクトを生成します2

つまり,今連載で扱う言語……言語になんか名前あったほうがいいですよね.リポジトリ名からとって「tinyfunc」とでもしましょうか.
tinyfuncで3.add(2)と書かれたプログラムがあったら,Num(3).add(Num(2))というJavaScript3を生成してあげればいい,ということになります.

というわけで,次回はそのJavaScriptコードを頑張って生成してみます.

理解度チェック

  • INumオブジェクトに,引き算をするsub,掛け算をするmul,割り算をするdivを追加してみて,次の計算をINumオブジェクトを使って行ってみましょう.
    • 20+30-5
    • 50-10-20
    • 50-(10-20)
    • 20*4+30*2
    • 20*(4+30)*2
    • 100/10/2
    • 100/(10/2)

  1. 本当は[LOG]って出てますがなぜか消えてしまったので[]は取っています 

  2. class使った方がシンプルなのになんで使わないの? と思った方へ.この後の実装の都合で,こちらのほうがやりやすいからです.詳しくは今後お話しますが,JSに詳しい方向けに先回りしておくと「object.f(a)(object.f)(a)が同じ挙動になるようにしたいから」です. 

  3. Num(3).add(Num(2))はTypeScript上で書いたものであって,JavaScriptで動くのか? と思った方は,PlayGround上の右側で「JS」タブを選んで,JavaScriptに変換されたコードを確認してみましょう.同じコードが出力されていると思います. 

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

[AWSxAmplifyxCognito] Amplifyでグローバルサインアウトがグローバルサインアウトしてくれない問題を解決する

はじめに

こんにちは!
認証機能を実装する場合、別のデバイスでサインアウトしたらユーザーがログインしている他の全てのデバイスでもログアウトして欲しいときがありますよね?

Amplifyではそんな要望に応えるために、singOut関数にglobalオプションがあり、signOut({global:true})のようにするとサインアウトした際に、ユーザーが持つ全トークンが無効化され、全てのデバイスからサインアウトできます。
以下ドキュメントから抜粋
image.png

と、いうのがドキュメントには書かれており、実際ユーザーが認証後に取得する3種類のトークンのうち、アクセストークンとリフレッシュトークンは即座に無効になるのですが、
実は、signOut({global:true})をしてもIDトークンは無効にならないんです!
加えて、セッションを確認するための関数たち(currentSession,userSession)はCognitoにアクセストークンやリフレッシュトークンが有効なのか問い合わせにいったりはせず、ローカルストレージにあるIdトークンの有効期限を確認するだけなんです。

つまり、currentSession,userSession関数では、1つのデバイスでグローバルサインアウトが行われていても、他のデバイスではIdトークンが有効期限切れにならない限り、ユーザーは"セッション切れ"とは判断されないんです。

対応策: 実行時にアクセストークンを使う関数を利用して、セッション切れを判断する

currentSession,userSession関数では、アクセストークン、リフレッシュトークンが有効かどうか見てくれない。
なら、それらが有効かどうかを問い合わせる関数を使えばいいのですが、その確認のためだけの関数がAmplifyリファレンスに見当たらないんです。

なので、実行時にアクセストークンを使う関数を利用して、その有効・無効を判断します。
例えば、currentUserInfo関数がそれにあたります。

この関数は,

  • サインイン状態のとき、以下のようなusername,attributes(cognitoで設定したユーザーの属性)を含むオブジェクトが返ります。
{
    "UserAttributes": [
        {
            "Name": "sub",
            "Value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        },
        {
            "Name": "email_verified",
            "Value": "true"
        },
        {
            "Name": "email",
            "Value": "xxxxxxxxx@xxxxxxxxx.com"
        }
    ],
    "Username": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
}
  • そのデバイスでサインアウトして、デバイスにトークンがないときはnull
  • 別のデバイスでサインアウトして、そのデバイスに各種トークンはあるが、アクセストークンが無効なときは、空オブジェクト{}が返ります

したがって、返り値が null または 空オブジェクト のときユーザーはサインアウト状態であるといえます。

以下はサインイン画面での実装例です。

サインイン画面側の実装例

サインインしてる時のみ、処理を行いたいので
サインアウト状態(null or 空オブジェクト)の否定である nullでない かつ 空オブジェクトでない でサインイン状態かどうかを見ています。(空オブジェクトでない はプロパティがundefinedかどうかで見ています。)

const getCurrentUserInfo = async () => {
    const currentUserInfo = await Auth.currentUserInfo();
    return currentUserInfo;
}
getCurrentUserInfo()
    .then(currentUserInfo =>{
        //以下の場合はサインアウト状態
        //1.currentUserInfo === null:そのデバイスでサインアウトして、デバイスにトークンがないとき
        //2.currentUserInfo.username(などユーザーに関するプロパティ) === undefined: 別のデバイスでサインアウトして、そのデバイスにトークンはあるが、アクセストークンが無効なとき
        if(currentUserInfo !== null && currentUserInfo.username !== undefined){
            //サインインしているときにしたい処理を書く。ログイン画面に飛ばすとか。
        }
    })
    .catch(err=> {
        console.log(err)
    })

サインイン後の画面の実装例

サインアウトしてる時のみ、処理を行いたいので
サインアウト状態(null or 空オブジェクト)でサインアウト状態かどうかを見ています。

const getCurrentUserInfo = async () => {
    const currentUserInfo = await Auth.currentUserInfo();
    return currentUserInfo
}
getCurrentUserInfo()
    .then(currentUserInfo =>{
        // 以下の場合はサインアウト状態なのでログイン画面に飛ばす。
        //1.currentUserInfo === null:そのデバイスでサインアウトして、デバイスにトークンがないとき
        //2.currentUserInfo.username(などユーザーに関するプロパティ) === undefined: 別のデバイスでサインアウトして、そのデバイスにトークンはあるが、アクセストークンが無効なとき
        if(currentUserInfo === null || currentUserInfo.username === undefined){
            //ログイン画面に飛ばす処理
        }
    })
    .catch(err=> {
        console.log(err)
        //currentUserInfoが取得できないときはログイン画面に飛ばす。
        //ログイン画面に飛ばす処理
    })

以上です。
上記の処理をページの描画ごとに行えば、
Idトークンが有効であっても、
そのデバイスでサインアウトしている場合だけでなく、別のデバイスでサインアウトしているかどうかも判定ができます。

自分の方法が唯一解ではないと思うので、別案・改善案あればぜひ共有していただければ!

終わりに

公式の見解

実は、signOut({global:true})がドキュメント通りの挙動でない問題は現在openなissueとしてあがっています。
cognito.user.signOut() does not invalidate tokens

このissueは2019年6月にopenされて現在もまだcloseされていません。このissue以外にも同様の件に関するissueは何件か見られました。

ユーザーの1人がコメントで個人的にAWSに問い合わせした際の返事を転記していて、それを信じるなら、AWS側はこの問題を認知していて現在取り組み中だそうです。。
image.png

以下のどちらかがいつか実装されるといいなと思います。できれば前者。

  • グローバルサインアウトでidトークンが即時無効になり、かつ session関数がローカルストレージのトークン有効期限ではなくcognitoに有効かどうかを確認しに行く
  • アクセストークンが有効かどうかをCognitoに問い合わせる関数が生える
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[AWSxAmplifyxCognito] Amplifyでグローバルサインアウトがグローバルサインアウトしてくれない問題の応急処置

はじめに

こんにちは!
認証機能を実装する場合、別のデバイスでサインアウトしたらユーザーがログインしている他の全てのデバイスでもログアウトして欲しいときがありますよね?

Amplifyではそんな要望に応えるために、singOut関数にglobalオプションがあり、signOut({global:true})のようにするとサインアウトした際に、ユーザーが持つ全トークンが無効化され、全てのデバイスからサインアウトできます。
以下ドキュメントから抜粋
image.png

と、いうのがドキュメントには書かれており、実際ユーザーが認証後に取得する3種類のトークンのうち、アクセストークンとリフレッシュトークンは即座に無効になるのですが、
実は、signOut({global:true})をしてもIDトークンは無効にならないんです!
加えて、セッションを確認するための関数たち(currentSession,userSession)はCognitoにアクセストークンやリフレッシュトークンが有効なのか問い合わせにいったりはせず、ローカルストレージにあるIdトークンの有効期限を確認するだけなんです。

つまり、currentSession,userSession関数では、1つのデバイスでグローバルサインアウトが行われていても、他のデバイスではIdトークンが有効期限切れにならない限り、ユーザーは"セッション切れ"とは判断されないんです。

対応策: 実行時にアクセストークンを使う関数を利用して、セッション切れを判断する

currentSession,userSession関数では、アクセストークン、リフレッシュトークンが有効かどうか見てくれない。
なら、それらが有効かどうかを問い合わせる関数を使えばいいのですが、その確認のためだけの関数がAmplifyリファレンスに見当たらないんです。

なので、実行時にアクセストークンを使う関数を利用して、その有効・無効を判断します。
例えば、currentUserInfo関数がそれにあたります。

この関数は,

  • サインイン状態のとき、以下のようなusername,attributes(cognitoで設定したユーザーの属性)を含むオブジェクトが返ります。
{
    "UserAttributes": [
        {
            "Name": "sub",
            "Value": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"
        },
        {
            "Name": "email_verified",
            "Value": "true"
        },
        {
            "Name": "email",
            "Value": "xxxxxxxxx@xxxxxxxxx.com"
        }
    ],
    "Username": "xxxxxxxxxxxxxxxxxxxxxxxxxx"
}
  • そのデバイスでサインアウトして、デバイスにトークンがないときはnull
  • 別のデバイスでサインアウトして、そのデバイスに各種トークンはあるが、アクセストークンが無効なときは、空オブジェクト{}が返ります

したがって、返り値が null または 空オブジェクト のときユーザーはサインアウト状態であるといえます。

以下はサインイン画面での実装例です。

サインイン画面側の実装例

サインインしてる時のみ、処理を行いたいので
サインアウト状態(null or 空オブジェクト)の否定である nullでない かつ 空オブジェクトでない でサインイン状態かどうかを見ています。(空オブジェクトでない はプロパティがundefinedかどうかで見ています。)

const getCurrentUserInfo = async () => {
    const currentUserInfo = await Auth.currentUserInfo();
    return currentUserInfo;
}
getCurrentUserInfo()
    .then(currentUserInfo =>{
        //以下の場合はサインアウト状態
        //1.currentUserInfo === null:そのデバイスでサインアウトして、デバイスにトークンがないとき
        //2.currentUserInfo.username(などユーザーに関するプロパティ) === undefined: 別のデバイスでサインアウトして、そのデバイスにトークンはあるが、アクセストークンが無効なとき
        if(currentUserInfo !== null && currentUserInfo.username !== undefined){
            //サインインしているときにしたい処理を書く。ログイン画面に飛ばすとか。
        }
    })
    .catch(err=> {
        console.log(err)
    })

サインイン後の画面の実装例

サインアウトしてる時のみ、処理を行いたいので
サインアウト状態(null or 空オブジェクト)でサインアウト状態かどうかを見ています。

const getCurrentUserInfo = async () => {
    const currentUserInfo = await Auth.currentUserInfo();
    return currentUserInfo
}
getCurrentUserInfo()
    .then(currentUserInfo =>{
        // 以下の場合はサインアウト状態なのでログイン画面に飛ばす。
        //1.currentUserInfo === null:そのデバイスでサインアウトして、デバイスにトークンがないとき
        //2.currentUserInfo.username(などユーザーに関するプロパティ) === undefined: 別のデバイスでサインアウトして、そのデバイスにトークンはあるが、アクセストークンが無効なとき
        if(currentUserInfo === null || currentUserInfo.username === undefined){
            //ログイン画面に飛ばす処理
        }
    })
    .catch(err=> {
        console.log(err)
        //currentUserInfoが取得できないときはログイン画面に飛ばす。
        //ログイン画面に飛ばす処理
    })

以上です。
上記の処理をページの描画ごとに行えば、
Idトークンが有効であっても、
そのデバイスでサインアウトしている場合だけでなく、別のデバイスでサインアウトしているかどうかも判定ができます。

自分の方法が唯一解ではないと思うので、別案・改善案あればぜひ共有していただければ!

終わりに

公式の見解

実は、signOut({global:true})がドキュメント通りの挙動でない問題は現在openなissueとしてあがっています。
cognito.user.signOut() does not invalidate tokens

このissueは2019年6月にopenされて現在もまだcloseされていません。このissue以外にも同様の件に関するissueは何件か見られました。

ユーザーの1人がコメントで個人的にAWSに問い合わせした際の返事を転記していて、それを信じるなら、AWS側はこの問題を認知していて現在取り組み中だそうです。。
image.png

以下のどちらかがいつか実装されるといいなと思います。できれば前者。

  • グローバルサインアウトでidトークンが即時無効になり、かつ session関数がローカルストレージのトークン有効期限ではなくcognitoに有効かどうかを確認しに行く
  • アクセストークンが有効かどうかをCognitoに問い合わせる関数が生える
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

領域に収まらない子要素をflexで折り返すには?

背景

バックエンドからデータを取得し、そのデータをもとにカードを並べたい。
しかし、通常並べるだけでは以下のように無限に横並びになってしまう。

スクリーンショット 2021-02-20 16.02.46.png

解決策

折り返したい子要素を包む親要素に以下のCSSを指定する。

display: flex;
flex-wrapper: wrap;

解決画面

以下の通り、親領域に収まらない時は折り返されるようになった。

スクリーンショット 2021-02-20 16.02.10.png

参考コード

// 折り返したい子要素を包む親要素
const CardsWrapper = styled.div`
  display: flex;
  justify-content: center;
  margin-top: 12px;
  flex-wrap: wrap; // flexでwrap指定する。
`;

// 折り返したい子要素
const CardHolder = styled.div`
  margin-right: 6px;
`;

const App = () => {
// ...

// 取得したデータ数だけカードをレンダリングする関数。
    const alignCards = (data) => {
      return (
        <CardsWrapper>
        {data.map((_data, i) => {
          return (
            <CardHolder><SampleCard name={data[i].title}/> </ CardHolder>
          )
        })}
        </CardsWrapper>
      )
  }
// ... 

  return (
    <>
      {alignCards(movieDataArraysObj)}
    </>
  )
}

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

領域に収まらない子要素をflexで折り返す

背景

バックエンドからデータを取得し、そのデータをもとにカードを並べたい。
しかし、通常並べるだけでは以下のように無限に横並びになってしまう。

スクリーンショット 2021-02-20 16.02.46.png

解決策

折り返したい子要素を包む親要素に以下のCSSを指定する。

display: flex;
flex-wrapper: wrap;

解決画面

以下の通り、親領域に収まらない時は折り返されるようになった。

スクリーンショット 2021-02-20 16.02.10.png

参考コード

// 折り返したい子要素を包む親要素
const CardsWrapper = styled.div`
  display: flex;
  justify-content: center;
  margin-top: 12px;
  flex-wrap: wrap; // flexでwrap指定する。
`;

// 折り返したい子要素
const CardHolder = styled.div`
  margin-right: 6px;
`;

const App = () => {
// ...

// 取得したデータ数だけカードをレンダリングする関数。
    const alignCards = (data) => {
      return (
        <CardsWrapper>
        {data.map((_data, i) => {
          return (
            <CardHolder><SampleCard name={data[i].title}/> </ CardHolder>
          )
        })}
        </CardsWrapper>
      )
  }
// ... 

  return (
    <>
      {alignCards(movieDataArraysObj)}
    </>
  )
}

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

LaravelでFullcalendarに登録したイベントを削除する方法

はじめに

こちらは以下の記事の続きになります。
LaravelでFullcalendarを実装する方
LaravelでFullcalendarに登録した内容を更新する方法
ディレクトリ名等も同じものを使用しているので、ご了承くださいませ。

前回までにFullcalendarにイベントの登録と更新はできるようになったので、今回は削除方法について見ていきたいと思います。

-各バージョン
-Laravel 6.x
-PHP 7.4.9
-MySQL 5.7.30
-Fullcalendar v5

削除ボタンを作成する

前回までに更新用のモーダルを開くと、登録されている情報が確認できているようにしました。
今回は更新用のモーダルに削除ボタンを追加し、このボタンを押すとデータが削除されるようにします。

modal.blade.php
<div class="modal micromodal-slide" id="modal-1" aria-hidden="true">
    <div class="modal__overlay" tabindex="-1" data-micromodal-close>
        <div class="modal__container" role="dialog" aria-modal="true" aria-labelledby="modal-1-title">
            <header class="modal__header">
                <h2>Editing my task list</h2>
                <button class="modal__close" aria-label="Close modal" data-micromodal-close></button>
            </header>
            <main>
         <form method="POST" action="{{ route('editEvent') }}">
                 @csrf
                 <input type="hidden" id="id" value="" name="id">
           <input type="text" id="edit_title" name="title" value="">
           <input type="date" id="edit_start" name="start" value="">
           <input type="color" id="edit_color" name="textColor" value="">
                  <button class="modal__btn modal__btn-primary" type="submit">変更する</button>
          </form>
        // ここから追加
              <form id="delete-event-btn" method="POST" action="{{ route('deleteEvent') }}">
                    @csrf
                    <input type="hidden" name="id">
                    <a href="#" id="delete-event" class="cancel-btn">削除する</a>
                </form> 
        // ここまで
            </main>
        </div>
    </div>
</div>

前回と同様、カレンダーのイベントを表示させるアクションであるindexアクションではAjaxで通信時のみidを渡すようになっているので(LaravelでFullcalendarを実装する方法)、idはJSで取得するようにします。

更新時にvalueの値を取得するコードを書いているので、こちらに追加していきます。

event.blade.php
<script>
  $(document).ready(function () {
    $('#calendar').fullCalendar({
      // はじめりの曜日を月曜日に変更 デフォルトは日曜日になっており、日=0,月=1になる
      firstDay: 1,
      headerToolbar: {
                     right: 'prev,next'
                     },
      events: '/index',

    eventClick: function(info){
        document.getElementById("id").value = info.id;
        document.getElementById("edit_title").value = info.title
        document.getElementById("edit_start").value = info.start._i
        document.getElementById("edit_color").value = info.textColor
        MicroModal.show('modal-1');
     }
    });

    // ここから追加
    $("#delete-event").on("click", function() {
     var form = document.getElementById("delete-event-btn");
     var eventId = document.getElementById("id").value; // 削除対象のidを前回作成した更新フォームから取得
     form.elements['id'].value = eventId; // 削除対象のidを削除フォームにもセット
     form.submit(); // 削除フォームを送信させる
     });
  });
</script>

コントローラー、ビューを作成する

先ほど削除ボタンのフォームで指定したactionを指定していきます。

web.php
Route::post('/deleteEvent', 'EventController@deleteEvent')->name('deleteEvent');

最後にコントローラーです。

EventController
public function deleteTask(Request $request) {
  // 送信されてきたidをEventテーブルに登録されているデータと紐付ける
  $task = DoneTask::find($request->input('id'));
  $task->delete();

  return redirect('/event');
}

実際に削除ボタンを押してみると、カレンダーとDBどちらからもデータが削除されてるかと思います。

これで完成です!

さいごに

今回でFullcalendarにデータの登録、更新、削除まで行うことができました!

Fullcalendarはまだまだアレンジがたくさんできるので、これからも公式マニュアルを見ながらアップデートしていきたいと思います。

最後まで読んでいただいてありがとうございました!

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

【jquery,js】入力バリデーションチェック [preventDefault,css,find] [js02_20210220]

処理の概要

送信ボタンを押下して、テキストボックスを入力されていない場合、バリデーションエラーを表示する

処理のフロー:

 (1)送信ボタンを押下
 (2)送信ボタンのイベントに紐ついて、入力ボックスの内容をチェックする
 (3)入力ボックスが一つでも空白があった場合は、エラーを赤文字で表示する

画像1

画像2

ソースコード

index.html
<body>
    <form method="GET" action="./index.html" target="_blank" id="mainForm">
        <font color="orange">バリデーションチェック</font><br><br>
        入力欄1<input type="text" name="t1" id="userText1" class="textBox" value=""><br>
        入力欄2<input type="text" name="t2" id="userText2" class="textBox" value=""><br>
        入力欄3<input type="text" name="t3" id="userText3" class="textBox" value=""><br>
        <input type="submit" id="submitButton" value="送信"><br>
        <div id="output"></div>
</body>
main.js
$(function() {
    $("#submitButton").click(function(e){
        var checkOk = true;

        $("#mainForm").find(".textBox").each(function(){
            if($(this).val() !== "") {
                $("#output").text("送信しました");
                //ボタンクリックイベントを成功とさせる
                return true
            }else{
                $("#output").text("テキストには全て入力して下さい");
                $("#output").css("color","red");
                //obj = document.getElementById("output");
                //obj.style.color = "red";

                //ボタンクリックイベントを失敗とさせる
                //return false
                e.preventDefault();
            }
        });
    });
});

ポイント

html:
(1)なし
js:
(1)クリックイベントの引数に”e”を設定して、そのイベントをpreventDefaultメソッドで処理する
(return falseでも可能)
(2)複数のオブジェクトにバリデーションをかけたい場合は、findの引数にclassを設定する
(3)バリデーションエラーは、cssメソッドを使って強調する
(4)入力ボックスの内容をeachメソッドに渡して、得られた配列の入力値をチェックする
(5)jsで、赤文字表示をする場合はstyleメソッドのcolorプロパティを使用する

参考資料

JavaScript(仕事の現場でサッと使える!デザイン教科書) p180

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

【Javascript】マウスストーカー実装法

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

パイソンのイラスト生成WebアプリをReactで作った

パイソンについて

うすうす感づいているかもしれませんが、この記事にはPython言語の話は出てきません。それでもこうしてこの記事に出会えたのも何かのご縁、ちょっとだけでも覗いていっていただけませんか。

普段Python言語を使っているソフトウェアエンジニアの方でも本物のパイソンを見たり触ったりしたことのある人は少ないのではないでしょうか。今日はそんなパイソンの魅力だけでもおぼえて帰っていただければと思います。

パイソンは日本語でニシキヘビと呼ばれていますが日本の野生では生息していません。美しい模様と、いかつい顔立ちが特徴のヘビです。

毒を持たず、鳴き声やにおいもなく、アレルギー源となる体毛もなく、餌は週に1回、寿命も10〜20年と長く飼いやすいため、犬や猫を飼えない事情がある人でも飼育できるペットとして近年人気が出ています。

なかでもボールパイソンは性格もおとなしく、大きくなりすぎないためおすすめです。

モルフとは

模様や色で分類される品種をモルフと呼びます。ボールパイソンは非常に多彩なモルフが特徴で、希少で人気のあるモルフは高額で取引されます。飼育下でのヘビの繁殖はそれほど難しくなく、好みのモルフを掛け合わせてあらたなモルフを作出する愛好家も多いです。

そのためここ数年で膨大な種類のモルフが生み出されてきました。これらのモルフの特徴をおぼえるのが大変なので作ったのが、今回紹介するウェブアプリBall Python Virtual Morph Makerです。

Ball Python Virtual Morph Maker

https://smallpinkmouse.github.io/virtualmorph/
https://github.com/smallpinkmouse/virtualmorph
screenshot.png

色や模様の特徴をGUIで設定することができます。望むパターンができたら、Renderingボタンを押すとトグロを巻いたイラストが生成されます。

フレームワークはReactを使用、グラフィックの描画はp5.js、スライダーはrc-slider、カラーピッカーはreact-colorを使っています。

設定したパラメータをjson形式でローカルに保存して、後から再現できるようにしています。

モルフの特徴

ノーマルモルフのボールパイソンは逆三角形の斑紋(Blotch)とその中の2つの点が特徴で、宇宙人の顔に見えるためAlien Headと呼ばれています。
normal_pattern.png

モハベモルフの場合、斑紋の黄色味が強くなり中の点の数もひとつになります。Keyholeとも呼ばれます。
mojave_pattern.png

スパイダーモルフの場合は、斑紋が大きくなり隙間が蜘蛛の糸のように細くなります。中の点は消失します。
spider_pattern.png

生成したイラスト

レンダリングしたイラストはこのようになります。

ノーマルモルフの頭部は黒く、目はクリっとしています。
normal.png

野生下ではニュースになるくらい希少なアルビノも、爬虫類ブリーダーの間では遺伝を管理されているため、入手しやすいポピュラーなモルフとなっています。黄色の色素は残るため体色は完全に白にはならず、また目は赤くなります。
albino.png

パイドとかパイボールと呼ばれる、印刷ミスのように模様が白く抜けるモルフも人気があります。
pied.png

スパイダーは前述のように蜘蛛の巣のような模様が特徴です。頭部や虹彩も少し色が抜けて、猫目になる個体が多いようです。
spider.png

横の斑紋が完全に消失して、背中の線だけが高速道路のセンターラインのように残るフリーウェイという品種です。
freeway.png

最後に

ボールパイソンがいかに多彩で美しい模様を持つかが伝わったでしょうか。
苦手な人もいるかと思うので、実物の写真はここまで出しませんでしたが、もし興味がわいたなら世界最大のボールパイソン情報サイトWorld of Ball Pythonsを見てみてください。
http://www.worldofballpythons.com/

最後に我が家で飼っているナムパイ君をごらんください。パステルクラウンというパステルとクラウン両方の特徴を引き継いだモルフになります。
numpy.png

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

アロー関数について簡単にまとめてみた

まずアロー関数とは、名前の通り => を用いて関数を示す記述方法です。

使用するメリットとしては
・コードの簡略化
・可読性の向上

などが挙げられます。

アロー関数についての説明に入る前に関数について少し触れていきましょう。

関数宣言(function文)

main.js
function sample() {
console.log("Hello!");
}

sample(); // Hello!

関数式(無名関数)

main.js
let sample = function() {
console.log("Hello!");
}

sample(); // Hello!

アロー関数は関数式を簡略化した記述法
先程の関数式をアロー関数を用いて記述していきます。

main.js
let sample = () => {
 console.log("Hello!");
}

sample(); // Hello!

functionを省略することができます。
違う関数式でもやってみましょう!

main.js
let sample = function(n) {
  return n*3;
}

sample(2); // 6

この関数式をアロー関数で記述します。

main.js
let sample = n => return n*3;

sample(2); // 6

めちゃくちゃ簡略化できましたね。
何が起こっているかというと
まず、先程と同じくfunctionは省略することができます。
そして引数が一つだけの場合は引数を囲っていた () を省略することができます。
さらに関数の中身の処理が一つだけの場合(今回はreturn) {} も省略することができます。

アロー関数の注意点ですが、引数を囲っていた()は引数が二つ以上の場合や引数がない場合も記述が必要です。

main.js
let sample = (a,b) => return a+b; //引数が二つ以上

sample(2,3); // 5

let sample = () => console.log("Hello!"); //引数が0の場合

sample(); // Hello!

最初は慣れるまで少し大変かもしれませんが慣れてくると見やすいコードを書くことができるようになると思いますので、この機会にマスターしましょう!

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