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

TypeScriptを使って嬉しかったこと

新人「先輩、TypeScriptのコーディングできました!」

先輩社員「どれどれ」
先輩社員「いやそこら中コンパイルエラーだらけ...なのは型システムが働いてる証拠だ!」
先輩社員「そうだろ?」

先輩社員「型は...ガードレールだ」
先輩社員「進むべきじゃない場所へ進もうとしたら、ちゃんとブロックしてくれる...」
(ぺこぱ風)

ってことで、静的型付言語って良いですよね。
今回はTypeScriptを使ってみて嬉しかったことを書いてみます。

登場人物

ワイ・・・ワイ(36歳)
社長・・・社長
ハスケル子・・・インターンの中学2年生

今日から新しいプロジェクト開始

社長「おーい、やめ太郎、ハスケル子ちゃん」
社長「新しいお仕事を獲得してきたで」
社長「技術記事投稿サイトを作る案件や」

ワイ「おお〜、さすが社長はんや」

社長「おおきにやで」
社長「ほんで、そのお仕事の話なんやけど」
社長「クライアントはんから一つ要望があんねん」
社長「Reactを使って作ってくれ、って言われてんねや」
社長「やめ太郎、ハスケル子ちゃん、Reactはイケるか?」

ワイ「やった事なくはないですわ」
ハスケル子「私も大丈夫です」

TypeScriptはどうする?

社長「TypeScriptはどうしよか?」
社長「そこはクライアントはんからは何も言われてへんねやけど」

ワイ「TSはよう分からんから、今回はやめときまひょ」
ワイ「型とかよう読まれへんし」

ハスケル子「私は絶対TypeScript有りでやりたいです」
ハスケル子「型がないとコード読むの面倒なので...」

型がないと面倒、とは

ワイ「いや、型があるほうが型情報を読まなあかんから面倒やん」

ハスケル子「いえ、型が書いてあるほうがコードを把握しやすいです」

ワイ「ええ...何で...?」

例えば:マイページ

ハスケル子「例えば、技術投稿サイトのマイページみたいなのを作るとしますね」

スクリーンショット 2020-01-12 20.44.38.png

ハスケル子「↑こんな感じのやつです」

ワイ「ほうほう」

ハスケル子「で、ユーザ情報を表示するためのUserInfoっていうコンポーネントを作るとします」
ハスケル子「TypeScriptなしで書くとすると、コードは↓こんな感じです」

/components/UserInfo.jsx
const UserInfo = ({ user }) => (
  <div>
    <section>
      <h2>ユーザ情報</h2>
      <p>アカウント名:{user.account}</p>
      <p>名前:{user.name}</p>
    </section>
    <section>
      <h2>記事一覧</h2>
      <ol>
        {
          user.articles.map(article => (
            <li>
              <a href={article.url}>{article.title}</a>
            </li>
          ))
        }
      </ol>
    </section>
  </div>
)

ワイ「なるほどな」

ハスケル子「やめ太郎さんなら、このコンポーネントのコードを見て、」
ハスケル子「どんな風に使うべきコンポーネントなのか読み取れますか?」

ワイ「そら簡単やで」
ワイ「余裕で読み取れますがな
ワイ「まず...」

/components/UserInfo.jsx
const UserInfo = ({ user }) => (

ワイ「↑こう書いてあるから」
ワイ「このUserInfoコンポーネントは」
ワイ「親コンポーネントからuserっていうpropsを受け取ってる...」
ワイ「っちゅうことが分かるわ」

ワイ「他にも...」

/components/UserInfo.jsx
<h2>ユーザ情報</h2>
<p>アカウント名:{user.account}</p>
<p>名前:{user.name}</p>

ワイ「↑こんなコードがあるから」
ワイ「親から受け取ったuserっていうpropsは、accountnameというプロパティを持ったオブジェクトである...」
ワイ「ってこともわかるで」

ワイ「あとは...」

<h2>記事一覧</h2>
<ol>
  {
    user.articles.map(article => (
      <li>
        <a href={article.url}>{article.title}</a>
      </li>
    ))
  }
</ol>

ワイ「↑こんな部分があるな...」
ワイ「せやから、userarticlesっちゅうプロパティも持っとんな」
ワイ「mapメソッドを使っているということは、そのuser.articlesは配列やということが分かるで」
ワイ「ほんで、その配列の中身はオブジェクトやな」
ワイ「urltitleというプロパティを持ったオブジェクトっちゅうことや」

ハスケル子「はい、そんな感じですよね」
ハスケル子「まとめると、どんな風に使うべきコンポーネントだと言えますか?」

ワイ「ええ...?」
ワイ「まあ、まとめるならば」

const user = {
  account: 'Yametaro',
  name: 'やめ太郎',
  articles: [
    {
      title: "記事名1",
      url: "/article1.html"
    },
    {
      title: "記事名2",
      url: "/article2.html"
    }
  ]
};

ワイ「propsとして↑こんなオブジェクトを渡して使うべきやな」

ハスケル子「そうですね」

TypeScriptの場合はどうか

ハスケル子「次に、TypeScriptを使った場合のコードを見てみます」

/components/UserInfo.tsx
type Props = {
  user: User
}

type User = {
  account: string,
  name: string,
  articles: Article[]
}

type Article = {
  title: string,
  url: string
}

const UserInfo: React.FC<Props> = ({ user }) => (
  <div>
    <section>
      <h2>ユーザ情報</h2>
      <p>アカウント名:{user.account}</p>
      <p>名前:{user.name}</p>
    </section>
    <section>
      <h2>記事一覧</h2>
      <ol>
        {
          user.articles.map(article => (
            <li>
              <a href={article.url}>{article.title}</a>
            </li>
          ))
        }
      </ol>
    </section>
  </div>
)

ハスケル子「↑こんな感じです」

ワイ「ほら、やっぱ型が書いてあるぶん長いやん...」

ハスケル子「そりゃあコード量は少し増えますけど」
ハスケル子「可読性の面でメリットもありますよ」
ハスケル子「例えば...」

/components/UserInfo.tsx
type Props = {
  user: User
}

ハスケル子「↑ここを見ると」
ハスケル子「このコンポーネントはuserというpropsを受け取る」
ハスケル子「そしてそのuserUser型の値である」
ハスケル子「...ということが分かります」

ワイ「なるほどな、受け取るprops一覧がここで分かるわけやな」
ワイ「今回はuser一個だけってことか」
ワイ「でも、なんやのUser型って」
ワイ「number型とかstring型なら知っとるけど」

ハスケル子「それは↓この部分に書いてあります」

/components/UserInfo.tsx
type User = {
  account: string,
  name: string,
  articles: Article[]
}

ハスケル子「User型の値は、accountという文字列と」
ハスケル子「nameという文字列...」
ハスケル子「そしてarticlesという配列を持っているよ、ってことがわかります」

ワイ「ほうほう、つまり...」
ワイ「User型っていうのはハスケル子ちゃんの自作の型ってこと?」

ハスケル子「そうです」
ハスケル子「自分で新しい型を定義して名前を付けることができるんです」

ワイ「ほー、なんか...」
ワイ「Userってのはこんなやつですよ、みたいな」
ワイ「世界観的なものが分かりやすいな」

ハスケル子「そうですね」

ワイ「ほんで、Userが持ってるaccountnamestring型なのは分かったけど」
ワイ「Article[]ってのはどういう型なん?」

ハスケル子「Article[]っていうのは」
ハスケル子「Article型の値が入ってる配列だよ、って意味です」

ワイ「Article型ってのは、また自作の型やな?」

ハスケル子「はい」

/components/UserInfo.tsx
type Article = {
  title: string,
  url: string
}

ハスケル子「↑この部分でArticle型を定義しています」

ワイ「なるほど」
ワイ「Article型の値は、titleurlというプロパティを持ってますよ...」
ワイ「ほんで、titleurlも文字列ですよ...」
ワイ「っちゅうことやな」

ハスケル子「そうです」

型が書いてあると、何が嬉しいか

ハスケル子「TypeScriptなしの場合は」
ハスケル子「コンポーネントの中身を全部読んで...」

「なるほど、このコンポーネントにはuserというオブジェクトを渡して使うんだな」
「そのuserは、accountnamearticlesというプロパティを持ってるんだな」
articlesmapメソッドを持っているようだから、配列だな・・・!」

ハスケル子「...っていうことがようやく分かったじゃないですか」

ワイ「せやな」

ハスケル子「でもTypeScriptありの場合だと」

/components/UserInfo.tsx
type Props = {
  user: User
}

type User = {
  account: string,
  name: string,
  articles: Article[]
}

type Article = {
  title: string,
  url: string
}

ハスケル子「↑この型定義の部分を見ただけで...」

const user = {
  account: 'Yametaro',
  name: 'やめ太郎',
  articles: [
    {
      title: "記事名1",
      url: "/article1.html"
    },
    {
      title: "記事名2",
      url: "/article2.html"
    }
  ]
};

ハスケル子「↑こんなpropsを受け取るコンポーネントだな!」
ハスケル子「ってことが分かるじゃないですか」
ハスケル子「コンポーネントの実装部分を読まなくても!」

ワイ「おお...」
ワイ「やっぱリーダブルやなぁ...」

社長「(何がやっぱやねん...)」

ワイ「仕事やと、一つの案件のコーディングを複数人で担当することも多いから」
ワイ「人の作ったコンポーネントを使う機会とかも多いしな」
ワイ「せやから、読み取りやすいのはええよなぁ」

ワイ「あと思ったんやけど」
ワイ「型ってなんか、コメントみたいな効果もあるんやね」
ワイ「このプロパティには文字列を入れてくださいな、みたいな」
ワイ「コード内に仕様書が書いてある感じがええな」

ハスケル子「そうですよね」
ハスケル子「しかも、ちゃんと守らなきゃ前に進めない...」
ハスケル子「強制力を持ったコメントですよね」
ハスケル子「型定義の通りにpropsを渡さないとエラーが出て、コンパイルが通らない...」
ハスケル子「だから、マウスでぽちぽちページを見て回る前にミスに気付けるんです」

ワイ「ああ、普通はコードを実行してみるまで」
ワイ「エラーが出るかどうか分からへんもんなぁ...」
ワイ「それはええかも...」

他にも嬉しいことが

ハスケル子「そういえば、やめ太郎さんって」
ハスケル子「nameっていう単語をよくnamaeってスペルミスするじゃないですか」

ワイ「まぁ、比較的するな」

ハスケル子「そういうタイポ対策にもTypeScriptが役立ったりしますよ」
ハスケル子「例えば...」

/components/UserInfo.tsx
<p>名前:{user.namae}</p>

ハスケル子「↑こんな風にタイポしちゃったとしたら...」

Property 'namae' does not exist on type 'User'. Did you mean 'name'?
(プロパティ「namae」はタイプ「User」に存在しません。 「name」という意味ですか?

ハスケル子「なんて教えてくれるんです」

ワイ「おお、まじか...」
ワイ「まさにワイのための機能やん...」

ハスケル子「そもそもタイポしなくなるようなメリットもありますよ」

スクリーンショット 2020-01-12 22.14.27.png

ハスケル子「↑こんな風に、user.まで打つと入力候補が出てくるので」
ハスケル子「その中から選択すればいいんです」

ワイ「おお...」
ワイ「もう、使わん理由ないやん...」

ワイ「ハスケル子先生...!!」
ワイ「TypeScriptがしたいです......」

社長「ほな、決まりやな!」

そんなこんなで終業時刻

ワイ「ハスケル子ちゃん、今日も色々教えてくれてありがとうやで...」

ハスケル子「教えるの楽しいから全然いいですよ」

ワイ「ハスケル子ちゃんは凄いなぁ...まだ中学生なのに色々知ってて...」
ワイ「チート過ぎて、見てると自分が情けなくなってくるわ...」
ワイ「負け過ぎてるんやもん...」

ハスケル子「私だって同じですよ」
ハスケル子「いつも友達に嫉妬してます...」
ハスケル子「でも...誰かに負けてても、誰かより劣っていても」
ハスケル子「今の自分から精一杯生きるしかないんです」
ハスケル子「自分なりの精一杯をやりましょう」

ワイ「ハスケル子ちゃん...!」

ハスケル子「...もしくは、死ぬとか...」

ワイ「いや死を提案すな!

〜おしまい〜

続編もよろしくやで

4歳娘「パパ、実行時エラーの出ないフロントエンド言語ってなーんだ?」

おまけ:簡単にReact + TypeScriptを始めるには

React + TypeScriptを触ってみたい人は、

npx create-next-app --example with-typescript my-app-name
cd my-app-name
npm run dev

↑この3つのコマンドを打ってから
http://localhost:3000/にアクセスするだけやで!
(もちろんNode.jsは先にインストールしといてや!)

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

初心者による DOM イベントを使った処理

概要

ブラウザ上では、さまざまなイベントが発生します。クライアントサイドのjavascriptでは、それらのイベントに応じて処理をします。これをイベント駆動型モデルという。

イベント一覧

javascriptがキャッチするイベントを一覧にしてみる

読み込み関係

abort : 画像の読み込みを中断した時
load : ページ/画像の読み込みが終了した時
unload : ほかのページに移動するとき

マウス関係

click : クリック時
dbclick : ダブルクリック時
mousedown : マウスボタンを押したとき
mouseup : マウスボタンを離したとき
moousemove : マウスポインターが移動した時
mouseover : マウスポインターが要素に乗った時
mouseout : マウスポインターが要素から外れた時
mouseenter : マウスポインターが要素に乗った時
mouseleave : マウスポインターが要素から外れた時
contextmenu : コンテキストメニューが表示される前

要素がネストしている場合に注意!
mouseover/mouseroutは親要素から子要素への出入りで反応する。
mouseenter/mouseleaveは親要素から出ない限り反応しない。

キー関係

keydown : キーを押した時
keypass : キーを押している時
keyup : キーを離した時

フォーム関係

change : 内容が変更されたとき
reset : リセットボタンが押されたとき
submit : サブミットボタンが押されたとき

その他

resize : 要素のサイズを変更した時
scroll : スクロールした時

イベントドリブンモデル(イベント駆動型モデル)

イベント駆動型モデルをなすためには、

  1. どの要素で発生したのか
  2. どのイベントが発生したのか
  3. どのハンドラー/リスナーに関連付けるか

を定義しておく必要がある。
javascriptでは3の関連付けを行うために以下の方法を提供している。

  1. タグ内の属性として宣言する
  2. 要素オブジェクトのプロパティとして宣言する
  3. addEventListenerメソッドを使う

タグ内の属性として宣言する

一番シンプルな実装方法。宣言方法は

<タグ名 onイベント名="Javascriptのコード" />

となる。一般的にはコードを直接書くのではなく関数を別で定義して呼び出す形となる。

sample.html
<input type="text" value="" onClick="on_click()" />
sample.js
function on_click() {
 alert('クリックされました!')
}

なるべく、htmlファイルとjsファイルで分けたほうが良い。

要素オブジェクトのプロパティとして宣言する

htmlファイルには一切書かず、jsファイルで設定する方法。

sample.js
obj.onEvent = function() {
 //コード
}
 //obj : windowオブジェクトもしくは要素オブジェクト
 //Event : イベント名

obj.onEvent = func
 //func : 関数の定義はべつにしてある場合

関数の定義をその場でするか、別にしてあるかの違いで二種類の書き方がある。

sample.html
<button id="btn" value="">ボタン</button>
sample.js
//windowがロードされたときにイベントハンドラーを定義
window.onload = function() {
 document.getElementById("btn").onclick = function() {
  window.alert("ボタンがクリックされました!")
 }
}

この書き方には注意点が3つある。
1つ目は、onClickのようにイベント名を大文字にするのではなく、onclickのように小文字にする必要があるということ。
2つ目は、プロパティとして登録するのは関数オブジェクトである必要があるため、window.load = get のように関数呼び出しにしてはいけない。
3つ目は、要素を取得する場合、window.loadの配下ではないといけない。ページが読み込まれる前に探してしまう場合がある。

addEventListeneメソッドで宣言する

onClickのようなイベントハンドラーでは、「同一の要素/同一のイベントに対して複数のイベントハンドラーを紐づけられない」という問題があります。
ここで登場するのが、イベントリスナーです。イベントリスナーは「同一要素の同一イベントに対して、複数のイベントハンドラーを紐づけできるイベントハンドラー」です。
イベントリスナーを登録するのは、addEventListenerの役割です。

sample.js
elem.getElementById(type, listener, capture)
 //elem : 要素オブジェクト type : エベントの種類
 //listener : イベントに対して実行すべき処理
 //capture : イベントの方向

で実装することができる。

sample.html
<input id="btn" type="button" value="ダイアログ表示" />
sample.js
//ページがロードされたタイミングで処理しなさい
document.addEventListener('DOMContentLoad', function() {
 document.getElementById('btn').addEventListener('click', function() {
  window.alert('ボタンがクリックされました')
 }, false)
}, false)

ここで使われている、onloadイベントハンドラーはコンテンツ本体とすべての画像がロードされたところで実行、DOMContentLoadedイベントリスナーはコンテンツ本体がロードされたところで実行、という違いがある。
画像が使われているというような特別な理由がない限り、DOMContentLoadedイベントリスナーで表すのが基本となっている。

参考資料

山田祥寛様 「javascript本格入門」

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

JavaScript で現在開いているページのドメイン・ホスト名を取得する

JavaScript で、現在開いているページのドメイン・ホスト名を取得する。

  • Documentインターフェイスのdomainプロパティを利用して取得する。
const domain = document.domain;
  • locationオブジェクトのhostプロパティを利用して取得する。
const domain = location.host;
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのイベント

イベントリスナー

イベントリスナーの追加

js
イベントターゲット.addEventListener(イベント名,リスナー,[オプション])

イベントリスナーの削除

js
removeEventListener(イベント名,リスナー,[オプション])

イベントリスナーを一度だけ呼び出す

addEventListenerのオプション 内容
capture キャプチャーフェーズで取得するかどうか(真偽値)
once リスナーの呼び出しを一回にするか(真偽値)
passive パッシブイベントかどうか(真偽値)

イベントの情報自体を取得する

MouseEventなどイベント自体の情報を取得する

eventは任意の文字列です。

js
button.addEventListener("click",(event)=>{
  console.log(event);
})

イベントが発生した要素を取得する

eventは任意の文字列です。

js
button.addEventListener("click",(event)=>{
  console.log(event.tareget);
})
js
//オプション指定
const option = {
  once: true
}

const clickAlart = () => {
  alert("クリックされた");
}

const button = document.querySelector("button");
button.addEventListener("click", clickAlart, option)

クリックイベント

イベント 内容
click 左ボタンシングルクリック
dblclick 左ボタンダブルクリック
contextmenu 右ボタンクリック
mousedown マウスのボタンを押した時
mouseup マウスのボタンを離した時
html
<button>ボタン</button>
js
const button = document.querySelector("button");
  button.addEventListener("click",()=>{
  console.log("クリックされた");
})

マウスイベント(クリックイベント以外)

イベント 内容
mousemove 対象エリア内でマウスを動かした時
mouseenter 対象エリアにカーソルが入った時
mouseleave 対象エリアからカーソルが出た時
mouseover 対象エリアにカーソルが入った時(バブリングあり)
mouseout 対象エリアからカーソルが出た時(バブリングあり)
mousewheel マウスホイールを回転させた時

バブリング:イベントが親要素や先祖要素に伝わることです。

js
対象エリア.addEventListener("mousemove",()=>{
 message.innerHTML="マウスが動いた";}
)
html
<div class="target""></div>
<p class="message"></p>
css
.target {
  height: 100px;
  background-color: red;
}
js
const message = document.querySelector(".message");

const target = document.querySelector(".target");

target.addEventListener("mousewheel", () => {
  message.innerHTML = "赤い領域内でマウスホイールが動いた";
})

マウスの座標を取得

マウスの座標 内容
event.clientX ブラウザ左上を基準としたX座標
event.clientY ブラウザ左上を基準としたY座標
event.offsetX 要素左上を基準としたX座標
event.offsetY 要素左上を基準としたY座標
event.pageX ページ左上を基準としたX座標
event.pageY ページ左上を基準としたY座標
event.screenX 画面左上を基準としたX座標
event.screenY 画面左上を基準としたY座標
js
対象エリア.addEventListener("mousemove",(event)=>{
  console.log(event.clientX,event.clientY);
});
html
<div class="target"></div>
css
.target {
  height: 100px;
  background-color: red;
}
js
//ターゲットの領域を取得
const target = document.querySelector(".target");

//ターゲットの領域内でマウスカーソルが動くたびに座標を表示
target.addEventListener("mousemove", (event) => {
  console.log(event.clientX, event.clientY);
})

タッチイベント

イベント 内容
touchstart タッチ開始時
touchmove タッチポイント移動時
touchend タッチ終了時
html
<p class="target"></p>
<p class="message"></p>
css
.target {
  height: 100px;
  background-color: red;
}
js
const text = document.querySelector(".target");
const message = document.querySelector(".message");

text.addEventListener("touchstart", () => {
    message.innerHTML = "タッチ開始";
});
text.addEventListener("touchmove", () => {
    message.innerHTML = "タッチムーブ";
});

text.addEventListener("touchend", () => {
    message.innerHTML = "タッチ終了";
});

タッチイベントの情報を取得する

プロパティ 内容
changedTouches タッチ情報の配列
clientX ブラウザ左上基準のX座標
clientY ブラウザ左上を基準としたY座標
offsetX 要素左上を基準としたX座標
offsetY 要素左上を基準としたY座標
pageX ページ左上を基準としたX座標
pageY ページ左上を基準としたY座標
screenX 画面左上を基準としたX座標
screenY 画面左上を基準としたY座標
タッチイベントとマウスイベントの違い 内容
タッチイベント 複数同時に起こる場合がある
マウスイベント 複数同時に起こらない
html
<p class="target"></p>
<p class="message"></p>
css
.target {
  height: 100px;
  background-color: red;
}
js
const target = document.querySelector(".target");
const message = document.querySelector(".message");

target.addEventListener("touchmove", () => {
  const touch = event.changedTouches;

  message.innerHTML = `${touch[0].pageX.toFixed(2)}<br>${touch[0].pageY.toFixed(2)}`;

});

toFixed(2)で小数点第2位までになるように四捨五入しています。

テキスト選択時

イベント 内容
selectstart テキスト選択開始時
html
<p class="text">ああああああああああああああ</p>
js
//ターゲットの取得
const text = document.querySelector(".text");

text.addEventListener("selectstart", () => {

  console.log("文字列の選択が開始された");

});

キーボードイベント

イベント 内容
keydown キーが押された時
keypress キーが押された時(文字キーのみ=修飾キー以外)
keyup キーが離された時

1つのキーを押して離した時、keydown→keypress→keyupの順番に発生します。

html
<textarea></textarea>
html
const textarea = document.querySelector("textarea");

textarea.addEventListener("keypress", () => {

  console.log("キーが押された");

});

入力されたキーを判定する

プロパティ 内容
key ボタンの値
keyCode ボタンのコード
code キーの名前
repeat キーを押しっぱなしにしているかどうか(真偽値)
isComposing 入力が未確定かどうか(真偽値)
html
<textarea></textarea>
js
const textarea = document.querySelector("textarea");


const keytype1 = (event) => {
  console.log(`値:${event.key}`);
  console.log(`コード:${event.keyCode}`);
  console.log(`名前:${event.code}`);
  console.log(`未確定?:${event.isComposing}`);

  console.log(`alt?:${event.altkey}`);
}

textarea.addEventListener("keyup", keytype1)


const keytype2 = (event) => {
  console.log(`押しっぱなし?:${event.repeat}`);
}

textarea.addEventListener("keydown", keytype2)

イベントを発火させる

メソッド 内容
dispatchEvent(イベント) イベントを発生させる
new Event("イベント名",[{detail:値}]) イベントを生成する

クリックしなくても、3秒後にクリックイベントを発火させる

html
<p class="target"></p>
js
const target=document.querySelector(".target");
target.addEventListener("click",()=>{
  target.innerHTML="クリックされた";
});

setTimeout(()=>{
  target.dispatchEvent(new Event("click"));
},3000);

デフォルトのイベントをキャンセルする

メソッド 内容
preventDefault() イベントのデフォルトの挙動をキャンセル

マウスホイールを無効化

js
document.querySelector(".target").addEventListener("wheel",(event)=>{
  event.preventDefault();
})

タッチ開始を無効化

js
document.querySelector(".target").addEventListener("touchstart",(event)=>{
  event.preventDefault();
})

ドラッグ&ドロップ

ドラッグする要素のイベント 内容
dragstart 要素のドラッグ開始時
drag ドラッグしている時
dragend ドラッグ終了時
ドラッグを受ける要素のイベント 内容
dragenter ドラッグしながら要素に入った時
dragover ドラッグしながら要素に存在するとき
dragleave ドラッグしながら要素から出た時
drop 要素をドロップしたとき
ドラッグのイベント情報 内容
event.dataTransfer.files ドロップされたファイル情報
html
<div id="startArea">
  <div id="target" draggable="true">
    <p id="targetword">Drag me!</p>
  </div>
</div>

<div id="goalArea">
</div>
css
body {
  position: relative;
}

#target {
  width: 98px;
  height: 98px;
  background-color: darkcyan;
  border: solid 1px #000;
  box-sizing: border-box;
}

#targetword {
  width: 100px;
  height: 100px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: white;
  font-weight: bold;
  margin: 0;
}

#startArea {
  width: 100px;
  height: 100px;
  margin: 10px 0;
  border: solid 1px black;
  position: fixed;
  top: 10px;
  left: 10px;
  box-sizing: border-box;
}

#startword {
  display: flex;
  align-items: center;
  justify-content: center;
  color: orange;
  font-weight: bold;
  width: 100px;
  height: 100px;
  margin: 0;
}

#goalArea {
  width: 100px;
  height: 100px;
  margin: 10px 0;
  border: solid 1px black;
  position: fixed;
  bottom: 10px;
  right: 10px;
  box-sizing: border-box;
}

.dragging {
  display: none;
}

.dragged {
  background-color: orange;
}
js
//ターゲットの取得
const target = document.querySelector('#target');

//スタートエリアの取得
const startArea = document.querySelector('#startArea');

//ゴールエリアの取得
const goalArea = document.querySelector('#goalArea');

//ターゲットのドラッグが開始された時
target.addEventListener('dragstart', () => {
  console.log("dragstartイベント");
});

//ドラッグ中の時
target.addEventListener('drag', () => {
  console.log("dragイベント");

  //ターゲットを非表示にする
  target.classList.add("dragging");

});

//ドラッグが終了した時
target.addEventListener('dragend', () => {
  console.log("dragendイベント");

  //ターゲットを表示にする
  target.classList.remove("dragging");

});


//スタートエリアに入った時
startArea.addEventListener('dragenter', () => {
  console.log("dragenterイベント");

  //スタートエリアに色を着ける
  startArea.classList.add("dragged");

});

//ゴールエリアに入った時
goalArea.addEventListener('dragenter', () => {
  console.log("dragenterイベント");

  //ゴールエリアに色を着ける
  goalArea.classList.add("dragged");
});


//スタートエリアから離れた時
startArea.addEventListener('dragleave', (event) => {
  console.log("dragleaveイベント");

  //スタートエリアの背景色を戻す
  startArea.classList.remove("dragged");

});

//ゴールエリアから離れた時
goalArea.addEventListener('dragleave', (event) => {
  console.log("dragleaveイベント");

  //ゴールエリアの背景色を戻す
  goalArea.classList.remove("dragged");

});


//スタートエリア上にある時
startArea.addEventListener('dragover', (event) => {
  console.log("dragoverイベント");

  //dropの前処理
  event.preventDefault();
});

//ゴールエリア上にある時
goalArea.addEventListener('dragover', (event) => {
  console.log("dragoverイベント");

  //dropの前処理
  event.preventDefault();


});


//スタートエリアでドロップされた時
startArea.addEventListener('drop', () => {
  console.log("dropイベント");

  //ターゲットをスタートエリアに追加
  startArea.appendChild(target);

});

//ゴールエリアでドロップされた時
goalArea.addEventListener('drop', () => {
  console.log("dropイベント");

  //ターゲットをゴールエリアに追加
  goalArea.appendChild(target);

});

印刷

イベント 内容
afterprint 印刷設定を閉じた時
beforeprint 印刷直前

例:印刷時に印刷用クラスを適用する

html
<p class="normal" id="sample">Lorem, ipsum dolor sit amet consectetur adipisicing elit. Rerum doloribus ex nostrum sunt magni, itaque veniam sapiente provident velit quo vero, quaerat vitae porro cumque. Sunt dolore impedit saepe praesentium!</p>
css
.normal {
  color: red;
}

.print {
  color: blue;
}
js
const sample = document.querySelector("#sample");

window.addEventListener("afterprint", () => {

  //印刷用クラス削除
  sample.classList.remove("print");

});

window.addEventListener("beforeprint", () => {

  //印刷用クラス追加
  sample.classList.add("print");

});

読み込み完了

イベント 内容
load リソース含め全ての読み込み完了時
DOMContentLoaded DOMツリーの構築完了時
js
window.addEventListener("load", () => {
  console.log("ページ読み込み完了");
})

window.addEventListener("DOMContentLoaded", () => {
  console.log("DOMツリーが準備できた");
})

読み込みエラー

イベント 内容
error 読み込み時にエラーが発生した時
html
<img src="xxxxxxxxxxxxxxx.jpg">
js
const img = document.querySelector("img");

img.addEventListener("error", () => {
  console.log("画像表示エラー");
});
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

docsifyで数式を書けるようにする

はじめに

docsifyは、ドキュメントWebページを作成するツールです。静的なHTMLをジェネレートするのではなく、Markdownをパースして表示するSPAのように動作します。

セットアップ

macOS Catalina 10.15.2 で作業しました。docsifyは、npmでインストールできます。

$ npm i docsify-cli -g

initコマンドで、ドキュメントの雛形を作ります。

$ dosify init hoge

serveコマンドで、ローカルにサーバーが立ち上がりプレビューが見ることができます。もととなるMarkdownファイルが変更されると、スグに反映されます。

$ docsify serve hoge

Serving /Users/yoshoku/hoge now.
Listening at http://localhost:3000

数式を書けるようにする

数式の表示には、docsify-katexを利用します。KaTeXは、WebページでTeXによる数式をレンダリングするライブラリで、有名なMathJaxよりも速く動作するそうです。docsify-katexは、このKaTeXをdocsifyで利用できるようにしたものです。

セットアップ

利用は簡単で、initコマンドで作成されたindex.htmlに、scriptタグをbodyタグ内の末尾などに追加するだけです。

... 省略

  <!-- CDN files for docsify-katex -->
  <script src="//cdn.jsdelivr.net/npm/docsify-katex@latest/dist/docsify-katex.js"></script>
  <link rel="stylesheet" href="//cdn.jsdelivr.net/npm/katex@latest/dist/katex.min.css"/>
</body>
</html>

これでOKです。

数式の書き方

\$でくくります。\$が一つであれば行内に、\$が二つであれば改行して、数式をレンダリングします。

改行する感じの数式
$$\mathbf{x}\in\mathcal{R}^{d}$$
です。

改行しない感じの数式 $\mathbf{x}\in\mathcal{R}^{d}$ です。

これが、以下のようになります。

math.png

おわりに

docsifyは気軽に書けていいですね。

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

朝会のfacilitatorをランダムに決めるslack bot

これは何か

  • 平日の決まった時間に、randomに1人、Daily Scrum (≒ 朝会)のFacilitatorを決めるslack botの作り方

背景

  • Scrum開発を円滑に進めていく上で、Scrum Masterに依存した運用体制はScrum teamの目指すべき姿ではない
  • その課題を解決する1つの手段として、Daily ScrumのFacilitatorを回すような運用を入れてみたら、メンバーが能動的になった

アウトプットイメージ

  • 平日の決まった時間に、SpreadSheetから1人randomに選択し、特定のSlackにpostする

SpreadSheet

スクリーンショット 2020-01-12 20.45.53.png

Slack Channel

スクリーンショット 2020-01-12 20.42.55.png

ソースコード

var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
var lastrow = sheet.getLastRow();
var lastcol = sheet.getLastColumn();
var sheetdata = sheet.getSheetValues(1, 1, lastrow, lastcol);
var calJa = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com');

function Facilitator(){
  var today = new Date();
  var weekInt = today.getDay();
  if(weekInt == 0 || weekInt == 6){
    return false;
  }
  else if(calJa.getEventsForDay(today).length > 0){ 
    return false;
  }
  else{ 
    var row = Math.floor(Math.random() * 7) + 2;
    var Name = sheetdata[row][0];  
    var row2 = Math.floor(Math.random() * 7) + 2;
    var Face = sheetdata[row2][1];
    var slackAccessToken = 'xxxxx(slack workspaceで一意に持っているので、要確認)';
    var slackApp = SlackApp.create(slackAccessToken);
    var channelId = "xxx-yyy-2020(postしたいslack channel名)";
    var Message = slackApp.postMessage(channelId,"Today's facilitator is…" + Name +"-san"+ " " + "Let's GoGo!" + Face);
  }
}

ざっくり構成解説

SpreadSheetにアクセスして、記述してある内容を取得する

var sheet = SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()
var lastrow = sheet.getLastRow();
var lastcol = sheet.getLastColumn();
var sheetdata = sheet.getSheetValues(1, 1, lastrow, lastcol);
var calJa = CalendarApp.getCalendarById('ja.japanese#holiday@group.v.calendar.google.com');
  • SpreadsheetApp.getActiveSpreadsheet().getActiveSheet()でGASからアクティブなSpreadSheetにアクセスします
  • getLastRow()で、アクセスしたSpreadSheetの最後の行を取得します
  • getLastColumn()で、同様に最後の列を取得します
  • getSheetValues(startrow, startcol, lastrow, lastcol)で、アクセスしているSpreadSheet内の値全体を取得します
    • 前述のgetLastRow(), getLastColumn()対象範囲内の最後の行と列を動的に取得できるので、メンバーが増える or 減る(行++ or 行--)、投稿したい要素が増える or 減る(列++ or 列--)場合でも大丈夫
  • CalendarApp.getCalendarById('省略')では、Google Calenderの 日本の祝日カレンダーにアクセスしています

SpreadSheetから、特定のNameとFaceを選択して、Slack Channelに送る

function Facilitator(){
  var today = new Date();
  var weekInt = today.getDay();
  if(weekInt == 0 || weekInt == 6){
    return false;
  }
  else if(calJa.getEventsForDay(today).length > 0){ 
    return false;
  }
  else{ 
    var row = Math.floor(Math.random() * 8) + 2;
    var Name = sheetdata[row][0];  
    var row2 = Math.floor(Math.random() * 8) + 2;
    var Face = sheetdata[row2][1];
    var slackAccessToken = 'xxxxx(slack workspaceで一意に持っているので、要確認)';
    var slackApp = SlackApp.create(slackAccessToken);
    var channelId = "xxx-yyy-2020(postしたいslack channel名)";
    var Message = slackApp.postMessage(channelId,"Today's facilitator is…" + Name +"-san"+ " " + "Let's GoGo!" + Face);
  }
}
  • today.getDay()で、指定された日付の「曜日」を取得している
    • 返される値は0~6で、0 == 日曜日, 1 == 月曜日... ,6 == 土曜日となります
  • weekInt == 0 || weekInt == 6で、休日(土曜日 or 日曜日)は送らないようにしている
  • calJa.getEventsForDay(today).length > 0で、祝日のイベントがカレンダー上に1つでもある時は送らないようにしている
  • Math.floor(Math.random() * 8) + 2では、最大で9、最小で2となる行番号を1つ取得して、1列目に適用している
    • Math.floor()で、()内の値を小数点以下を切り捨てる
    • Math.random()で、0以上1未満 (0は含むが1は含まない)の間でrandomな値を返す
    • 次に出てくるrow2の中身も同じことをしている
  • slackAccessToken周りは、GASとSlackではじめるチャットボット〜初心者プログラマ向け〜辺りを参考にする
  • channelId = "xxx-yyy-2020(postしたいslack channel名)"ではpostしたいSlack Channel名を#抜き で記述する
  • slackApp.postMessage(channelId,"Today's facilitator is…" + Name +"-san"+ " " + "Let's GoGo!" + Face)で、実際に該当のSlack Channelにpostするが、textで直接記述している部分は変更してもらって大丈夫(ex. "Today's facilitator is…", "-san" etc)

トリガー

  • Daily Scrumが毎朝12:30~12:45だったので、余裕を持って、10:00~11:00の間に発火するように設定してます

スクリーンショット 2020-01-12 21.46.54.png

参考

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

【Jest】Vueのテストを書こう!

Vueのテストを書こう!

Vue.jsのコンポーネントのテストについてナレッジを貯めていきます。(随時追加)

  1. テキスト表示についてのテスト
  2. DOM要素についてのテスト
  3. 子コンポーネントをスタブにしてテスト
  4. コンポーネントのfactory関数を定義する

テキスト表示についてのテスト

import { mount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {
  it('renders a HelloWorld', () => {
    const wrapper = mount(HelloWorld)

    expect(wrapper.text()).toMatch("HelloWorld!!")
  })
})

DOM要素についてのテスト

import { mount } from '@vue/test-utils'
import HelloWorld from '@/components/HelloWorld.vue'

describe('HelloWorld.vue', () => {
  it('renders a HelloWorld', () => {
    const wrapper = mount(HelloWorld)
    expect(wrapper.html()).toMatch("<div><p>HelloWorld!!</p></div>")
  })
})

子コンポーネントをスタブにしてテスト

shallowMountメソッドで子コンポーネントをスタブ(代替えの偽物)としてマウントします。
mountだと子コンポーネントまですべてマウントされます。

const shallowWrapper = shallowMount(Parent)
console.log(shallowWrapper.html())
  • ログの結果

子コンポーネントが<vuecomponent-stub />とスタブとなって出力されています。

<div><vuecomponent-stub></vuecomponent-stub></div>

コンポーネントのfactory関数を定義する

一番上にvaluesオブジェクトをまとめてdataにして、新しいwrapperインスタンスを返すファクトリ関数を宣言します。

このようにすると、すべてのテストでconst wrapper = shallowMount(Foo)を複製する必要がありません

import { shallowMount } from '@vue/test-utils'
import Foo from './Foo'

const factory = (values = {}) => {
  return shallowMount(Foo, {
    data () {
      return {
        ...values
      }
    }
  })
}

describe('Foo', () => {
  it('welcome メッセージを描画する', () => {
    const wrapper = factory()

    expect(wrapper.find('.message').text()).toEqual("Welcome to the Vue.js cookbook")
  })

  it('usernameが7未満のときエラーを描画する', () => {
    const wrapper = factory({ username: ''  })

    expect(wrapper.find('.error').exists()).toBeTruthy()
  })

  it('usernameが空白のときエラーを描画する', () => {
    const wrapper = factory({ username: ' '.repeat(7) })

    expect(wrapper.find('.error').exists()).toBeTruthy()
  })

  it('usernameが7文字かそれ以上のとき、エラーが描画されない', () => {
    const wrapper = factory({ username: 'Lachlan' })

    expect(wrapper.find('.error').exists()).toBeFalsy()
  })
})

参考URL

Vue.js 公式サイト

vue-test-utils 公式サイト

vue-testing-handbook
➡︎より実践的な使い方がわかる

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

React Native (Expo) を使ってはじめてのモバイルアプリ制作 きっかけから制作、公開まで

昨年末12月28日にはじめて制作したアプリを App Store に公開しました。
icon.jpg
"tagTimeLog Lite"

タグを使って時間を記録するアプリです。かなり単純なアプリです。
モバイルアプリを開発したきっかけから制作、公開までをまとめました。

きっかけ

複数の仕事が重なり、それぞれのプロジェクトごとに実際にどれだけの時間を消費しているのか計測したいと思い、既存のアプリを探していくつか試して見たのですが、自分が欲しいと思うようなシンプルで手軽なアプリが見つかりませんでした。
アプリを探すのにも疲れたので「仕方ない、自分で作るか」ということで一念発起してアプリ開発を思い立ちました。
仕事で使っている WordPress でも導入された React の勉強もしなければならなかったので、 React を使ってアプリを作れば一石二鳥ということで無駄になることはないと自分を納得させて開発を開始しました。

仕様

自分が欲しいと思ったアプリの仕様。

  • 操作がわかりやすい
  • シンプル
  • 少ないタップ数でログが取れる
  • 動作が早い
  • ログイン不要
  • エキスポート機能
  • メモを取れるようにする
  • ログの時間を修正できるようにする

最初からこのようにリスト化するようなことはせず、何となくこんな感じでとぼんやり考えながら作っていきました。
制作途中であれも欲しい、これもあったらいいなと思いつくことがありましたが、元々の「シンプルで手軽」という基本コンセプトととりあえず早く公開することを優先して機能を絞り込みました。
そもそも技術がないので複雑なことはできませんし。

集計自体はスプレッドシートで行う予定でしたので、CSVファイルへのエキスポートは必須でした。

ブラウザベースかモバイルネイティブか

React を使用することを前提としていましたが、React 自体を全く理解しておらず、React Native(モバイル用フレームワーク) 以前の話だったので、 React を使ってブラウザベースで制作することにしました。

JavaScript の再学習

これまで jQuery を何となく使ってきた程度の JavaScript の知識だったので、React を学習するには、 JavaScript の知識が全く足りず、まずは JavaScript の学習から始めました。
React においては ES6以降 を使っての制作が基本なので ES6 を中心に JavaScript を学習しなおしました。

JavaScript の学習に使用したWebサイトや書籍

React の学習

JavaScript をある程度学習できたので React の学習です。
個人的には、React の重要なポイントは state のように思いました。
あとは JavaScript (ES6) の知識で書ける感じです。
なので、もし、React の学習に躓いている方がおられたら JavaScript の知識について少し見直してみても良いかもしれません。
少し難しく感じたのは、ルーティングのところでの props や値の受け渡しです。
コンポーネントの構成をどうすればよいのか迷いました。
あと、 JSX は慣れるしかないと思います。

React の学習に使用したWebサイトや書籍

React の勉強には公式サイトが最も役に立ちました。
本などは少し古かったりするので、先ずは公式サイトで勉強し、わからないところを本で補うようにしました。

React を使ってブラウザベースのアプリを制作

Create React App を使って作成しました。
勉強しながらの制作だったので、あらゆる所でつまずきましたが、公式サイトや書籍、Web 上の情報を参考にしながら一定ペースを保って完成までたどり着きました。
アプリ制作のおかげで React の概要については、おおよそ理解できたように思います。

使用したライブラリー、データベース

  • ライブラリー: react-router-dom
  • データベース: IndexedDB

ライブラリーは 、react-router-dom を入れました。
データベースは、ブラウザ用のローカルストレージ IndexedDB を使いました。

react-router-dom の勉強には「速習 React 速習シリーズ」、 IndexedDB の勉強には MDN が非常に参考になりました。というか、IndexedDB には、 MDNしかないと思います。

ブラウザベースからモバイルへ

最初に希望していた機能は、ほぼこのブラウザベースのアプリで満たしており、完成後しばらくアプリを使用していました。
しかし、使用していて少し不安になることがありました。
それは、データベースの IndexedDB です。

IndexedDB は個人的には使いやすく非常によいデータベースだと思うのですが、ブラウザ依存のデータベースなので、例えば何かの理由でブラウザをアンインストールしてしまうとデータベースも消えてしまいます。
私が Web サイト制作を仕事にしていることもありブラウザの設定を変えるなど、荒い(?)使い方をする場合もあるのでブラウザ依存のデータベースを使用していることが少し不安でした。

そこで、React の思想「Learn Once, Write Anywhere」を思い出し、専用のアプリであればアンインストールするようなこともないため、「仕方ない、React Native を使ってモバイルアプリを作るか。」ということでモバイルアプリの制作を開始しました。

React Native (Expo) を使ってネイティブモバイルアプリを制作

React で作成したアプリを React Native で作り直しです。
React Native であっても一部ネイティブ言語を触る必要があります。
ネイティブ言語は触りたくない(勉強したくない)ので、 Expo を利用することにしました。
Expo は、実装できる機能の範囲が狭まるようですが、最初から最後まで JavaScript だけでアプリ公開できるようになる React Native 向け支援ツールだそうです。

React と React Native の違いとしては、根本的なところは React とほぼ一緒だと思いますが、見た目の構築とルーティング(画面遷移)の設定は、まったく違うように思いました。
全体的に組みなおす必要がありました。
モバイルと比較して Web のほうが自由度が高いので、もし、モバイルと Web で、見た目を同じにしようと考えているのであれば、モバイルから作成したほうが良いように思いました。

React Native (Expo) の学習に使用したWebサイトや書籍

React Native については、Web 、書籍ともに日本語化されているものが少なく、基本的には英語で書かれている公式サイトを見て勉強しました。
英語がわからないので理解するのにかなり時間がかかってしまいましたが、Google 翻訳など利用しながら、とりあえず制作できるところまでは理解できました。

使用したライブラリー、データベース

package.json の中身です。

  • expo
  • expo-ads-admob
  • expo-sharing
  • expo-sqlite
  • papaparse
  • react
  • react-dom
  • react-native
  • react-native-elements
  • react-native-gesture-handler
  • react-native-keyboard-aware-scroll-view
  • react-native-reanimated
  • react-native-svg
  • react-native-swipe-list-view
  • react-native-web
  • react-navigation
  • react-navigation-stack
  • react-navigation-tabs
  • sentry-expo

ライブラリー、データベースで気になったところ

Expo

JavaScript だけでアプリの制作から申請まですべてできるのは大変ありがたいです。
ただ、ちょっと気になるのはアプリのファイルサイズが大きくなるということです。
Expo を使用すると仕方がないことらしいのですが、確かにより複雑なことができる他の Swift などで作られたであろうと思われるアプリと比較すると、ちょっと大きいなという感じがしました。
今後ブラッシュアップして小さくできるか確認していきたいと思います。

react-navigation

React から React Native に移行して最も戸惑ったのがルーティング(ページ遷移)です。
React の "react-router-dom" とまったく違うように感じました。

react-native-swipe-list-view

当初、"react-native-swipeout" を使っていたのですが、expo をアップデートしたところエラーが出るようになったので、"react-native-swipe-list-view" に変更しました。

ライブラリーは、大変便利でありがたいのですが、アップデートに対応されていないと代替ライブリーがない場合は、アプリ自体が更新できないことになってしまうので、依存度はなるべく低くした方が良いと思いました。

expo-ads-admob

ローカル環境でのテスト広告や publish 後の正式広告は、すぐに表示されるのですが、iOS App Store 公開、申請後はすぐに表示されず、2、3日後に表示されるようになりました。
他の方のブログなどを見ていると普通にそれぐらい待たされることがあるようです。

sqlite

データベースは、 sqlite を使いました。当初は、 realm を使いたいと思っていましたが、Expo が対応していなかったので仕方なく sqlite にしました。
sqlite を使ってみたところ特に問題なく使えてますので、とりあえず良かったです。

App Store (iOS) への申請と公開

担当者によって厳しさが違う?

App Store (iOS) への一度目の申請は、一発 OK で何の修正命令もなく翌日には公開となりました。
Apple の審査は、厳しいと思っていたので意外でした。
公開翌日に見た目のところを少し調整してアップデートし、審査申請に出したところエラー表示され修正命令が来ました。
修正内容は「アプリの名前がガイドライン違反してるので名前変えろ」(英語わからないのでおそらくこんな感じ)でした。ガイドラインでは「アプリの名前に金額を表すようなワードは入れるな」ということらしいです。
現在のアプリ名は "tagTimeLog Lite" なのですが、最初の申請時は "tagTimeLog Free" という名前で申請していました。
この "Free" が金額を表しているようです。
私の勉強不足だったので反省なのですが、いくらなんでも最初の申請時に気づかなかったのかなと思いました。
Apple の審査が早くなったとどこかで読みましたが、担当者によって能力に差があり、一部で審査が甘くなっているのかもしれません。

名前を変更し、再審査を依頼、2、3日後に無事通過して公開となりました。

思いつきから制作、公開までの期間

思いつきから公開まで、ほぼ一年かかりました。
通常の受注仕事しながらなので、途中一ヶ月くらいまったく触れなかたということもあるので、ざっくりですが、実質4、5ヶ月くらいでしょうか。

特に学習面で React については、公式含めて教材となるような、日本語で記載されている Web サイトがたくさんあるのですが、React Native では日本語がガクッと減って、ほぼ英語で記載されている Web サイトでした。英語が苦手な私は React Native の学習段階でかなり時間を取られた印象です。

アップデートの予定

今後は下記内容を予定してます。

  • Android 版作成
  • 他の形式による時間表示
  • ダークモード (iOS)
  • 有料版作成 ほか

以上

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

【Svelte】Svelte環境をDockerで作ってみた

概要

完成品はこちら

詳しい話はまたブログとかに書きます。

躓いたと

SvelteのみHostとContainerのlocalhostに繋ぐのにすこし手間取りました。
ReactやVueを造ったときは、localhostのままポート番号だけあわせておけばよかったのですが・・・・
ここに書いてあるとおり、sirvの仕様でそのままだとHost側のlocalhostからContainer側のlocalhostにはつながらない。
なので、sirvコマンドでContainerのhostを指定する必要がありました。

sirv public build --host 0.0.0.0

これで、Host側のlocalhostとContainer側の0.0.0.0が繋がるので、初期画面が表示されるようになります。

Sapperは他同様にport:3000同士を繋げば問題なくContainerと繋がります

まとめ

去年くらいから少し話が出てきたSvelte。
まだまだこれから感ありますが、ReactやVueを刺激するような存在になってくれると面白いです。

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

【Svelte】Svelte環境をDockerで作ってみた【Sapper】

概要

完成品はこちら

詳しい話はまたブログとかに書きます。

躓いたと

SvelteのみHostとContainerのlocalhostに繋ぐのにすこし手間取りました。
ReactやVueを造ったときは、localhostのままポート番号だけあわせておけばよかったのですが・・・・
ここに書いてあるとおり、sirvの仕様でそのままだとHost側のlocalhostからContainer側のlocalhostにはつながらない。
なので、sirvコマンドでContainerのhostを指定する必要がありました。

sirv public build --host 0.0.0.0

これで、Host側のlocalhostとContainer側の0.0.0.0が繋がるので、初期画面が表示されるようになります。

Sapperは他同様にport:3000同士を繋げば問題なくContainerと繋がります

まとめ

去年くらいから少し話が出てきたSvelte。
まだまだこれから感ありますが、ReactやVueを刺激するような存在になってくれると面白いです。

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

チームの時間管理できるWebチャートを作成したかった

初めに

前職にてヘルプデスクのリーダーを行っていた際、メンバーが今何のタスクを行っているか視覚的に把握できれば、もっとスムーズに休憩時間の調整や緊急度や重要度を意識したタスク管理ができそうだなと思っていました。
そこで時間管理チャートの作成に取り掛かったのですが、いろいろな理由で没になったため供養したいと思います。

元ネタ

コピペでWebに埋め込めるシンプルなガントチャート時間割作ってみた
理想にとても近いOSSが公開されていたので、改変して利用させていただくことにしました。

画面

キャプチャ1.PNG

機能

  • タスクをクリックして選択
  • タスクの新規作成・編集・削除
  • 現在の時刻をラインで表示
  • 30分単位で時間軸をスライド可能
  • 画面サイズ自動変更

頑張りましたがデザインがダサいのはどうしようもなかったです。
(元ネタの否定ではありません)
(作成中)

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

JavaScriptで元の配列を変更せずに並び替えした配列を作る

JavaScriptで配列を並び替えするときに使用するsortメソッドは実行した配列自身を変更してしまう。

const ary    = [1, 2, 3, 4, 5];
const sorted = ary.sort((a, b) => b - a);;

console.log(ary);    // => [ 5, 4, 3, 2, 1 ] 元の配列まで変更されてしまう!
console.log(sorted); // => [ 5, 4, 3, 2, 1 ]

元の配列は変更せずに並び替えした配列を作成したい場合は、以下のようにすればいい。
concatメソッドで配列を複製してからsortメソッドを行うイメージ。

const ary    = [1, 2, 3, 4, 5];
const sorted = ary.concat().sort((a, b) => b - a);;

console.log(ary);    // => [ 1, 2, 3, 4, 5 ]
console.log(sorted); // => [ 5, 4, 3, 2, 1 ]

参考

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

エンジニア研修

概要

今回弟がエンジニアデビューするということで、macbookAir(gold)を買ってあげました。
これから9か月間、私の方で研修をおこない、それなりのエンジニアになってもらおうかと思います。

そこで弟のように、エンジニアを始めたばかりの人の参考になるように、「研修内容」、「弟が躓いた箇所」や「参考文献など」を記事にしていこうかと思います。
普段私は自分のブログサイトで発信していましたが、誰も見てくれないので、qiitaに書いていくことにしました。
https://campbel.love/が私のサイトです。たまには見てくださいな(笑)

弟はそこまで忍耐力もなく、また、ほとんど初心者なので「挫折をしないカリキュラム」、「スムーズな理解」に重点を置くつもりです。まさにゆとり!
なので、少し遠回りをしてしまうかもしれません。
例えば、Dockerを用いてもらいたいけど、初学者の弟には難しいのでMampから始めてもらう。みたいな。(最終的にはdockerにしてもらいますが。)

偉そうにカリキュラムは組みましたが、私自身も勉強途中で全部を理解はしていません。弟と一緒に勉強して知識を高めていきたいと思っています。
*もし、こんなやり方の方がいい、これは間違っているなどありましたら、コメントをお願いいたします。弟と私のために。

私は文章を書くのがすこぶる苦手なので、わかりづらい事や誤字脱字もあるかと思いますが、どうぞよろしくお願い致します。

予定のカリキュラム

文法の理解などは基本はdotinstallをやってもらいます。(有料なので私が支払うことになりそう。。。)
わからないことがあれば随時きくように。

1ヶ月~2ヶ月
・php、html、css、js、mysqlを理解する。(jsから初めてもらおうかなと)
・mampの設定
・macの操作、mampになれる。

3ヶ月〜5ヶ月
・jqueryもしくはvueをつかう。vueが使えるようになったら最高。ちなみに私は使用したことがない。
・gitを使えるようにする。
・local環境下でいいのでlaravelでlaravelでサイトを作れるようにする。
「掲示板的なやつ」もしくは「画像投稿サイト」でいい。
デザインパターンはしっかり行う。
リレーション、トランザクションもしっかり行こなう。
ログイン認証もしっかり行う
時間があればrest api作成も行う。
・hostsの理解など

ここらへんで一区切り。。。

6ヶ月〜7ヶ月
・cicdを使う(cercleciでいいかと)
・awsを使う
・laravelのテスト仕様書も書けるようにする。

8ヶ月
・localとawsでdockerを使う。

9ヶ月
kubernetesを使用する??これは未定。。。

これでかなりレベルの高いエンジニアであると僕は思うが、参考にしているエンジニアチャンネルでは、これが最低ラインだと言っていました。。。(kubernetesは除く)
エンジニアの敷居が上がってきているのかしら?٩( ᐛ )و
しかし、専門学生が2年通うよりも全然最強だと思います!!
ちなみに私はこれらのことを、もちろんこれだけでなく他の勉強もしながらですが、数年かけました。それを弟は9ヶ月で行うため、厳しい道のりになるとおもわれます。弟よ!がんばれ〜!!( ^ω^ )

私の課題として、「awsでdockerを使用する」、「kubernetesを使用する」はまだやったことがないので、できるようにしておきます!

なぜmacなのか

一言で言うと使っている人が多いから。
他には、サーバーではlinuxを使用することが多いが、macはwinよりもlinuxに近いからとか、bashが使えるからとか、かっこいいから。

賛否両論だと思いますが、とにかくmacを買うことをお勧めします。
高いなと思うかもしれませんが、分割での購入も可能で、2年の分割だと利子はなんと0円です。
あと学割があって最大22000円引きで購入が可能。2020年2月からは新学期キャンペーンで3万ほどのヘッドフォンがついてきますよ!お得です!
参考

1日目はマックの設定

1 appleidの作成、osが最新かどうかのチェック
2 アプリのダウンロード(詳細は以下)
3 chromeの拡張のダウンロード(詳細は以下)

macにダウンロードするアプリ

ブラウザ
「こんなにいらないやろ」と思うかもしれないが、”エンジニア”には必要。(アプリ欄を見てこれらがないと笑われてしまう。)
chrome
firefox
opera

開発に必要なアプリ
サイバーダッグ: サーバーのファイルをいじれる
iterm2: ターミナルの拡張版
sequel pro: mysqlをいじる
vs code: エディタ(高級なメモ帳)
postman: apiを可視化
xcode: 主にアイフォンのアプリを作成するのに使用。アイフォンのアプリを作成しなくとも必要。
mamp: mac、apache、mysql、phpの略。windowsだとxamp。パソコン内に仮想のサーバーを立てることができる。
mampでの開発は時代遅れのゴミで、本来ならば(というよりできるエンジニアは)Dockerを使うべきだが設定はmampの方が断然楽なので、初め(6ヶ月くらい)はmampで対応する。
composer: phpのライブラリ環境コマンド。これは必須。なくても開発はできるが、使用していないのはゴミエンジニアである。

チャットアプリ
line: line
chatwork: よく使われるチャットツール
slack: よく使われるチャットツール

その他便利なアプリ
app cleaner: アプリをきれいに削除できる
cd to: フォルダから直で移動できる。
clipy: 有料。コピーしたものの履歴が残る。数百円なので絶対に入れた方がいいと思われるアプリの一つ。使ったら世界が変わったように感じた。弟にも買ってあげた。

これらはまだ使用しないと思われるので、まだインストールしなくていい
brew cask
git
anyenv: phpしか使わないならば、いらない。python、node.jsをやる場合は入れた方がいい。

chromeにダウンロードする拡張の機能

開発はおもにchromeで行うため、chromeの設定はとても大事です。
ダウンロード先はここ
https://chrome.google.com/webstore/category/extensions?hl=ja

開発に必要なアプリ
Clear Cache: キャッシュの削除
EditThisCookie: クッキーの編集
Page load time: ページが表示されるまでの時間計測
Quick Javascript Switcher: jsのオンオフ切替。1クリックでできるため、めちゃめちゃ便利です。

あると便利なアプリ
Wappalyzer: 自分が閲覧したサイトがどの技術で作られているか確認ができる。
Google のクロム ™ のための時計: chromeに時計を表示させる。
ブックマークサイドバー: ブックマークがマウスオーバーで開ける。お勧めの設定は「マウスを左に持っていき右クリックで表示させる。」

個人的にお勧めなアプリ
Douga Getter: 動画のダウンロード
ストリームレコーダー: 動画のダウンロード

とりあえず1日目はここまで。お疲れ様です。

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

[Nuxtjs] base64エンコードする画像のファイルサイズの上限を変更する

TL; DR

  • nuxt.config.js のbuildプロパティで、base64エンコードできる画像のファイルサイズの上限を変更できる。

概要

  • nuxtjsでwebサービスの自社プロダクトの開発と運用をやっているエンジニアです。
  • ある日、ロゴの画像を変更してほしいとデザイナーから依頼が来ましたが、staticディレクトリにある画像を差し替え社内開発環境にリリースしまいしたが、ブラウザキャッシュが効いてしまい、昔のロゴが表示されてしまいました。
  • 何かクエリバラメーターをつけるなどして、画像のパスを変更すれば解決するのですが、何か別の解決方法がないかを探したところ、assetsに静的ファイルをおけば、webpackが静的ファイルをbase64エンコードし、htmlやcssに埋め込みをしてくれるみたいでした。が、試したところ、どうやらbase64エンコードしてくれる画像のファイルサイズがデフォルトで1KBと決まっているみたいなので、変更したいと思ったのですが、やり方を探しても見つからなかったので、せっかくならと思い、記事にまとめました。

実際にどうやるか

結論から話すと、下のように書くと、base64エンコードしてくれる静的ファイルのサイズ上限を変更できました。

nuxt.config.js
module.exports = {
  extend(config, { loaders }) {
    // これで1MB以下のassetsディレクトリにある静的ファイルがbase64エンコードされます。
    loaders.imgUrl.limit = 100000
  },
}

この loadersimgUrl がどこから来たからというと、下のドキュメントに記載されていました。
https://ja.nuxtjs.org/api/configuration-build/#loaders

// loadersの中身
{
  file: {},
  fontUrl: { limit: 1000 },
  imgUrl: { limit: 1000 }, // デフォルトは1KB
  pugPlain: {},
  vue: {
    transformAssetUrls: {
      video: 'src',
      source: 'src',
      object: 'src',
      embed: 'src'
    }
  },
  css: {},
  cssModules: {
    localIdentName: '[local]_[hash:base64:5]'
  },
  less: {},
  sass: {
    indentedSyntax: true
  },
  scss: {},
  stylus: {},
  vueStyle: {}
}

全部のコードを追えていないため、 imgUrl のプロパティがどうurl-loader の設定と紐づいているかまではわかっていないのですが、プロパティの名前から予測することができたため、試したところうまくbase64エンコードされました。

ちなみに、base64エンコードされるとhtmlではこのようになります。

スクリーンショット 2020-01-12 17.27.18.png

まとめ

  • これで今後画像のパスを無理やり変更しなくても、画像によってエンコードした結果が変わるので、ブラウザキャッシュでファイルの中身が変更されないという問題は回避されるのかなと思いました。内容が少しニッチな気持ちもしますが、同じ問題で困った人の役に立てればと思い、qiitaにまとめました。最後まで読んでいただき、ありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強の記録その8: スプレッド構文&レスト構文を利用した配列オブジェクトの操作

スプレッド構文を利用して2つの配列を結合

const/let 定数名/変数名 = [...配列名]とすることで、2つの配列を結合することができます。
例としては、以下のような2つの配列があった場合は、const b = [55, 80, ...a]と記述することで、2つの配列を結合することができます。 
この方法をスプレッド構文と呼びます。

index.js
const a = [10, 40, 12, 50];
const b = [55, 80, ...a]; 

console.log(b);
//[55, 80, 10, 40, 12, 50]

レスト構文を利用して、配列の要素を別の定数へ代入

配列の要素を別の定数に代入したいという場合はレスト構文が役に立ちます。

下の例ではconst [a, b, ...other] = scores2とすることで、scores2の1番前の要素から順にaとbに要素を代入しています。
scores2の残りの要素は、otherという定数に配列として格納されます。これをレスト構文と呼びます。

ちなみに配列の要素を別々の変数や定数に格納することを分割代入と呼びます。

index.js
const scores1 = [10, 40, 12, 50];
const scores2 = [55, 80, ...scores1];
const [a, b, ...other] = scores2; 

console.log(a);
//=>55
console.log(b);
//=>80
console.log(other);
//=>[10, 40, 12, 50]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強の記録その8: スプレッド構文&レフト構文を利用した配列オブジェクトの操作

スプレッド構文を利用して2つの配列を結合

const/let 定数名/変数名 = [...配列名]とすることで、2つの配列を結合することができます。
例としては、以下のような2つの配列があった場合は、const b = [55, 80, ...a]と記述することで、2つの配列を結合することができます。 
この方法をスプレッド構文と呼びます。

index.js
const a = [10, 40, 12, 50];
const b = [55, 80, ...a]; 

console.log(b);
//[55, 80, 10, 40, 12, 50]

レフト構文を利用して、配列の要素を別の定数へ代入

配列の要素を別の定数に代入したいという場合はレフト構文が役に立ちます。

下の例ではconst [a, b, ...other] = scores2とすることで、scores2の1番前の要素から順にaとbに要素を代入しています。
scores2の残りの要素は、otherという定数に配列として格納されます。これをレフト構文と呼びます。

ちなみに配列の要素を別々の変数や定数に格納することを分割代入と呼びます。

index.js
const scores1 = [10, 40, 12, 50];
const scores2 = [55, 80, ...scores1];
const [a, b, ...other] = scores2; 

console.log(a);
//=>55
console.log(b);
//=>80
console.log(other);
//=>[10, 40, 12, 50]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScriptのUIライブラリ ReactのHook使用してToDoアプリを作成してみました

はじめに

この記事ではJavaScriptのライブラリであるReactとReactの機能であるhookを使用して簡単なToDoアプリの実装を行います。
以前に書いた記事をベースに書き換えるので、そちらも参考にしてみてください。
JavaScriptのUIライブラリ ReactでToDoアプリを作成してみました
Reactのドキュメントチュートリアル(三目並べ)を一通り行った後の練習になるように書きたいと思います。
環境構築に関しては、create-react-appを使用して作成しています。
環境構築に関しては以前書いた記事があります。
ソースコード

目次

  1. コンポーネントの確認
  2. 各コンポーネントの解説
  3. まとめ

1. コンポーネントの確認

すぐに動かせる環境を置いておきます。


See the Pen
ReactToDo with Hook
by oq-Yuki-po (@oq-yuki-po)
on CodePen.



ReactはUIのパーツをコンポーネントという独立した一つの部品とみなして構成していきます。
今回の例では下記の様に分割しました。
以前は、これらのコンポーネントを全てクラスで定義して行きましたが今回は関数コンポーネントで定義して行きます。

ReactToDoApp.png

ToDoアプリケーションを構成するコンポーネントは全部で4つあります。

  • ToDo
    ToDoアプリケーションの全体を表します

  • TaskAdd
    新しいタスクの追加を行います

  • TaskList
    追加されたタスクをリストにして表示します

  • TaskItem
    一つのタスクを表します

2. 各コンポーネントの解説

2-1. ToDo.js

2-1-1. ソース全体

ToDo.js
import React, { useState, useReducer } from "react";
import TaskAdd from './TaskAdd';
import TaskList from './TaskList';

function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return [
        ...state,
        action.NewTask
      ];
    case 'delete':
      let TaskIndex = 0;
      for (var i = 0; i < state.length; i++) {
        if (state[i].key.toString() === action.TaskId.toString()) {
          TaskIndex = i;
        }
      }
      return state.filter((_, index) => index !== TaskIndex);
    default:
      return state;
  }
}

function ToDo() {
  const [TaskId] = useState(0)
  const [ToDoList, dispatch] = useReducer(reducer, [])
  return (
    <main className='todo-component'>
      <TaskAdd id={TaskId} dispatch={dispatch} TaskList={ToDoList}/>
      <TaskList TaskList={ToDoList} />
    </main>
  );
}

export default ToDo; 

2-1-2. モジュールのインポート

import
import React, { useState, useReducer } from "react";
import TaskAdd from './TaskAdd';
import TaskList from './TaskList';

useStateuseReducerがhookと呼ばれるものです。
これを使用することで、クラスコンポーネントで行なっていた状態管理が関数コンポーネントでも行えます。

2-1-3. ToDoコンポーネント

ToDoコンポーネント
function ToDo() {

  const [TaskId] = useState(0)
  const [ToDoList, dispatch] = useReducer(reducer, [])

  return (
    <main className='todo-component'>
      <TaskAdd id={TaskId} dispatch={dispatch} TaskList={ToDoList}/>
      <TaskList TaskList={ToDoList} />
    </main>
  );
}

クラスで定義していた際は、constructorを定義していたと思います。
以前の記事では、下記の様に定義していました。

ToDoコンポーネントのconstructor
constructor(props) {
  super(props);
  this.state = {
    TaskList: [],
    TaskId: 0
  };
  this.deleteTask = this.deleteTask.bind(this);
  this.addTask = this.addTask.bind(this);
}

関数でコンポーネントを定義する際は以下の様になります。

ToDoコンポーネント(クラス版)
const [TaskId] = useState(0)
const [ToDoList, dispatch] = useReducer(reducer, [])

stateを定義する際にuseStateを使用します。
const [状態管理したい変数, 状態を変更する関数] = useState(状態管理したい変数の初期値)
TaskIdは子コンポーネントに渡して、そちらで管理するので変更する関数を定義していません。
(定義してる場所が、そもそもどうなの?みたいなツッコミはご勘弁を。後々にAPIとの連携を見越した実装だと解釈お願いします。。。)
const [状態管理したい変数, 状態を変更する関数] = useState(初期値)
配列や複雑なロジックをstateに持たせる時は、TaskListの様にuseReducerを使用します。
const [状態管理したい変数, reducerで定義した関数(dispatch)] = useReducer(reducer, 状態管理したい変数の初期値);
公式の引用

通常、useReducer が useState より好ましいのは、複数の値にまたがる複雑な state ロジックがある場合や、
前の state に基づいて次の state を決める必要がある場合です。また、useReducer を使えば
コールバックの代わりに dispatch を下位コンポーネントに渡せるようになるため、
複数階層にまたがって更新を発生させるようなコンポーネントではパフォーマンスの最適化にもなります。

引用にも書いてある通り、<TaskAdd id={TaskId} dispatch={dispatch} TaskList={ToDoList}/>
dispatchをTaskAddに渡しています。

2-1-4. reducerの定義

reducerの定義
function reducer(state, action) {
  switch (action.type) {
    case 'add':
      return [
        ...state,
        action.NewTask
      ];
    case 'delete':
      let TaskIndex = 0;
      for (var i = 0; i < state.length; i++) {
        if (state[i].key.toString() === action.TaskId.toString()) {
          TaskIndex = i;
        }
      }
      return state.filter((_, index) => index !== TaskIndex);
    default:
      return state;
  }
}

reducerの定義を行います。
stateは状態管理したい変数、今回はTaskListが入っています。
actiondispatchで、この関数を実行するときに指定したパラメータが格納されています。
action.typeで、どの操作なのかを判定するのに使用しています。
adddeleteはそれぞれ、TaskListに新規にタスクを追加、指定したタスクを削除の処理を行なっています。

2-2. TaskAdd.js

2-2-1. ソース全体

TaskAdd.js
import React, { useState } from "react";
import Task from './Task'

function TaskAdd(props) {

    const [NewTask, setTask] = useState('')
    const [TaskId, setTaskId] = useState(props.id)
    const [ErrorMessage, setErrorMessage] = useState('')

    function handleClick() {
        if (NewTask === '') {
            setErrorMessage('入力が空です。')
            return 0
        }
        for (var i = 0; i < props.TaskList.length; i++) {
            if (props.TaskList[i].props.name === NewTask) {
                setErrorMessage('タスク名が重複しています。')
                return 0
            }
        }
        props.dispatch({
            type: 'add',
            NewTask: <Task key={TaskId} id={TaskId} name={NewTask} dispatch={props.dispatch} />
        })
        setTaskId(TaskId + 1)
        setTask('')
        setErrorMessage('')
    }

    return (
        <section className='task-creator'>
            <h2>Task Add</h2>
            <input className='task-item-text' type="text" placeholder="Task" value={NewTask}
                onChange={(e) => setTask(e.target.value)} />
            <button className='task-add-btn' type="button" onClick={handleClick}>
                Add
                </button>
            <p className='error-msg'>{ErrorMessage}</p>
        </section>
    )
}

export default TaskAdd; 

2-2-2. stateの定義

stateの定義
const [NewTask, setTask] = useState('')
const [TaskId, setTaskId] = useState(props.id)
const [ErrorMessage, setErrorMessage] = useState('')

Task.jsと同じ様に定義しています。
const [状態管理したい変数, 状態を変更する関数] = useState(状態管理したい変数の初期値)

2-2-3. handleClick

TaskAdd.jsのhandleClick
function handleClick() {
    if (NewTask === '') {
        setErrorMessage('入力が空です。')
        return 0
    }
    for (var i = 0; i < props.TaskList.length; i++) {
        if (props.TaskList[i].props.name === NewTask) {
            setErrorMessage('タスク名が重複しています。')
            return 0
        }
    }
    props.dispatch({
        type: 'add',
        NewTask: <Task key={TaskId} id={TaskId} name={NewTask} dispatch={props.dispatch} />
    })
    setTaskId(TaskId + 1)
    setTask('')
    setErrorMessage('')
}

定義したstateにアクセスする際はthis.state.NewTaskの様に行なっていましたが
hookでは単純にNewTaskでアクセスできます。(this.stateの呪縛から開放される!!)

stateの更新の際はthis.setState({ ErrorMessage: '入力が空です。' })ではなく
state定義時の関数をそのまま使用できます。
つまりsetErrorMessage('入力が空です。')と書けます。

タスクの追加時にはTask.jsからpropsとして受け取ってあるdispatchを使用しています。

props.dispatch({
    type: 'add',
    NewTask: <Task key={TaskId} id={TaskId} name={NewTask} dispatch={props.dispatch} />
})

typeNewTaskはactionで取れる様に追加しています。

2-2-4. render

TaskAdd.jsのrender
return (
    <section className='task-creator'>
        <h2>Task Add</h2>
        <input className='task-item-text' type="text" placeholder="Task" value={NewTask}
            onChange={(e) => setTask(e.target.value)} />
        <button className='task-add-btn' type="button" onClick={handleClick}>
            Add
            </button>
        <p className='error-msg'>{ErrorMessage}</p>
    </section>
)

this.stateで取得することが無くなったので、少しスッキリしたと思います。

2-3. Task.js

2-3-1. ソース全体

Task.js
import React, { useState } from "react";

function Task(props) {

    const [isDone, setStatus] = useState(props.isDone)
    const task_id = 'task-id-' + props.id.toString()
    const css_label = 'task-item-label'
    const css_isDone = `${css_label} isDone`
    const css_Wip = `${css_label} WorkInProgress`

    return (
        <li className='task-item-row'>
            <input id={task_id} className='task-item-checkbox' type='checkbox'
                onChange={() => (isDone === true) ? setStatus(false) : setStatus(true)}>
            </input>
            <label htmlFor={task_id} className={(isDone === true) ? css_isDone : css_Wip}>
                {props.name}
            </label>
            <i className="material-icons icon" onClick={() => props.dispatch({type:'delete', TaskId:props.id})}>
                delete
            </i>
        </li>
    );
}
export default Task; 

2-3-2. stateの定義

stateと定数の定義
const [isDone, setStatus] = useState(props.isDone)
const task_id = 'task-id-' + props.id.toString()
const css_label = 'task-item-label'
const css_isDone = `${css_label} isDone`
const css_Wip = `${css_label} WorkInProgress`

stateとclassNameの定数を定義しているのみです。

2-3-3. render

Task.js
return (
    <li className='task-item-row'>
        <input id={task_id} className='task-item-checkbox' type='checkbox'
            onChange={() => (isDone === true) ? setStatus(false) : setStatus(true)}>
        </input>
        <label htmlFor={task_id} className={(isDone === true) ? css_isDone : css_Wip}>
            {props.name}
        </label>
        <i className="material-icons icon" onClick={() => props.dispatch({type:'delete', TaskId:props.id})}>
            delete
        </i>
    </li>
);

onChange={() => (isDone === true) ? setStatus(false) : setStatus(true)}で単純なif文を省略しています。
onClick={() => props.dispatch({type:'delete', TaskId:props.id})}でpropsで受け取ったdispatchを使用してタスクの削除を行なっています。

2-4. TaskList.js

TaskList.js
import React from "react";

function TaskList(props) {
    return (
        <section className='task-list'>
            <h2>Task List</h2>
            <ul>
                {props.TaskList}
            </ul>
        </section>
    );
}

export default TaskList; 

特にクラスと関数で違いは無いでしょうか、強いて言えばクラスで書くより短いくらいでしょうか??

3. まとめ

Reactのhookを使用して、クラスコンポーネントを関数コンポーネントのみで書き換えてみました。
スッキリ書き換えられるのはメリットに感じました。
ドキュメントを読む限りでは、完全に互換性がある訳では無いそうなので更に勉強が必要そうです。。。
最後まで見てくださり、ありがとうございます。
質問や、指摘は大歓迎ですので、よろしくお願いします。

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

JavaScriptでテーブルを自動で生成

以下のサンプルソースは、forループでテーブルを自動生成し、生成されたテーブルを指定した行数ずつ表示するページング機能を実装しています。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>サンプル</title>
    <meta charset="utf-8">
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>

    <style>
    div#paging
    {
      text-align: center;
      /* スクロールに対してページングボックスの位置を固定 */
      position: fixed;
      /* 位置を指定 */
      bottom: 0;
      right:45%;
    }

    div#pagingbox{
      background: #FFF;
    }

    th{
      /* ヘッダ背景塗りつぶし */
      background: #eee;
    }
   th,td {
     /* 枠線を1本線指定 */
      border: solid 1px;
      width:auto;
   }

   table{
     /* 枠線を1本線指定 */
      border: solid 1px;
      border-collapse:  collapse;
      white-space: nowrap;
    }

    footer{
      position: fixed;
      width: 100%;
      background-color: #C0C0C0;
      padding: 15px 0;
      bottom: 0; /*下に固定*/
    }
    </style>

  </head>




  <body>
    <!--テーブル生成位置-->
    <div id ='maintable'></div>



    <!--ページングボタン配置-->
    <footer>
      <div id="paging">
        <table>
          <tr>
            <tb><button id="prevbtn" type="button"><</button></tb>
              <tb>
                <span id="currentpage">currentpage</span>
                  /
                <span id="lastpage">lastpage</span>
              </tb>
            <tb><button id="nextbtn" type="button">></button></tb>
          </tr>
        </table>
      </div>
    </footer>


    <script>
      // table要素を生成
      var table = document.createElement('table');
      // tr部分のループ
      for (var i = 0; i < 700; i++) {
        // tr要素を生成
        var tr = document.createElement('tr');
        // th・td部分のループ
        for (var j = 0; j < 50; j++) {
            // 1行目のtr要素の時
            if(i === 0) {
              // th要素を生成
              var th = document.createElement('th');
              // th要素内にテキストを追加
              th.textContent = i + '-' + j;
              // th要素をtr要素の子要素に追加
              tr.appendChild(th);
            } else {
              // td要素を生成
              var td = document.createElement('td');
              // td要素内にテキストを追加
              td.textContent = i + '-' + j;
              // td要素をtr要素の子要素に追加
              tr.appendChild(td);
            }
        }
        // tr要素をtable要素の子要素に追加
        table.appendChild(tr);
        }
      // 生成したtable要素を追加する
      document.getElementById('maintable').appendChild(table);
    </script>


    <script>// ページング機能
    jQuery(function($) {
      var page = 0;
      var displayrows = 30;// 1ページ当たり表示する行の数

      function draw() {// ページの表示
        $('#lastpage').html(Math.ceil(($('tr').size()-1)/displayrows));
        $('#currentpage').html(page + 1);
        $('tr').hide();
        $('tr:first,tr:gt(' + page * displayrows + '):lt(' + displayrows + ')').show();// 変数を使用する場合は' +  + 'を忘れずに
      }
      $('#prevbtn').click(function() {// 1ページ後進
        if (page > 0) {
          page--;
          draw();
        }
      });
      $('#nextbtn').click(function() {// 1ページ前進
        if (page < ($('tr').size() - 1) /displayrows - 1) {
          page++;
          draw();
        }
      });
      draw();//初回表示
    });
    </script>
  </body>
</html>


実行結果
無題.png

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

JavaScriptでテーブルを動的に生成

以下のサンプルソースは、forループでテーブルを自動生成し、生成されたテーブルを指定した行数ずつ表示するページング機能を実装しています。

<!DOCTYPE html>
<html lang="ja">
  <head>
    <title>サンプル</title>
    <meta charset="utf-8">
    <script src="http://ajax.googleapis.com/ajax/libs/jquery/1.7.0/jquery.min.js"></script>

    <style>
    div#paging
    {
      text-align: center;
      /* スクロールに対してページングボックスの位置を固定 */
      position: fixed;
      /* 位置を指定 */
      bottom: 0;
      right:45%;
    }

    div#pagingbox{
      background: #FFF;
    }

    th{
      /* ヘッダ背景塗りつぶし */
      background: #eee;
    }
   th,td {
     /* 枠線を1本線指定 */
      border: solid 1px;
      width:auto;
   }

   table{
     /* 枠線を1本線指定 */
      border: solid 1px;
      border-collapse:  collapse;
      white-space: nowrap;
    }

    footer{
      position: fixed;
      width: 100%;
      background-color: #C0C0C0;
      padding: 15px 0;
      bottom: 0; /*下に固定*/
    }
    </style>

  </head>




  <body>
    <!--テーブル生成位置-->
    <div id ='maintable'></div>



    <!--ページングボタン配置-->
    <footer>
      <div id="paging">
        <table>
          <tr>
            <tb><button id="prevbtn" type="button"><</button></tb>
              <tb>
                <span id="currentpage">currentpage</span>
                  /
                <span id="lastpage">lastpage</span>
              </tb>
            <tb><button id="nextbtn" type="button">></button></tb>
          </tr>
        </table>
      </div>
    </footer>


    <script>
      // table要素を生成
      var table = document.createElement('table');
      // tr部分のループ
      for (var i = 0; i < 700; i++) {
        // tr要素を生成
        var tr = document.createElement('tr');
        // th・td部分のループ
        for (var j = 0; j < 50; j++) {
            // 1行目のtr要素の時
            if(i === 0) {
              // th要素を生成
              var th = document.createElement('th');
              // th要素内にテキストを追加
              th.textContent = i + '-' + j;
              // th要素をtr要素の子要素に追加
              tr.appendChild(th);
            } else {
              // td要素を生成
              var td = document.createElement('td');
              // td要素内にテキストを追加
              td.textContent = i + '-' + j;
              // td要素をtr要素の子要素に追加
              tr.appendChild(td);
            }
        }
        // tr要素をtable要素の子要素に追加
        table.appendChild(tr);
        }
      // 生成したtable要素を追加する
      document.getElementById('maintable').appendChild(table);
    </script>


    <script>// ページング機能
    jQuery(function($) {
      var page = 0;
      var displayrows = 30;// 1ページ当たり表示する行の数

      function draw() {// ページの表示
        $('#lastpage').html(Math.ceil(($('tr').size()-1)/displayrows));
        $('#currentpage').html(page + 1);
        $('tr').hide();
        $('tr:first,tr:gt(' + page * displayrows + '):lt(' + displayrows + ')').show();// 変数を使用する場合は' +  + 'を忘れずに
      }
      $('#prevbtn').click(function() {// 1ページ後進
        if (page > 0) {
          page--;
          draw();
        }
      });
      $('#nextbtn').click(function() {// 1ページ前進
        if (page < ($('tr').size() - 1) /displayrows - 1) {
          page++;
          draw();
        }
      });
      draw();//初回表示
    });
    </script>
  </body>
</html>


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

JavaScript勉強の記録その7: 配列オブジェクトの作成・追加・削除

配列の作成方法

[]内にカンマ区切りでデータを入れることで配列を作ることができます。
コンソールに出力してみると、配列の情報が見れます。

index.js
const scores = [80, 90, 40];
console.log(scores);
//(3) [80, 90, 40]
//0: 80
//1: 90
//2: 40
//length: 3
// .....

配列の要素へのアクセス 

配列の各要素へはインデックス番号を指定してあげることによってアクセスすることができます。

index.js
const scores = [80, 90, 40];

console.log(scores[0]);
//80
console.log(scores[1]);
//90
console.log(scores[2]);
//40

ループ処理を使い各要素へアクセス

ループ処理を使って、各要素を取り出すこともできます。
配列オブジェクトにはlengthというプロパティというのがありますので、配列名.lengthとすることで配列の長さを取得することができます。
以下の例では、lengthを利用してループ処理をし、各要素を取り出しています。

index.js
const scores = [80, 90, 40];

console.log(scores.length);
//3

for (let i = 0; i < scores.length; i++) {
  console.log(`it is ${scores[i]}`);
}

//it is 80
//it is 90
//it is 40

データの追加および削除

配列オブジェクトに用意されてあるpushメソッドとspliceメソッドを利用することによって要素の追加および削除ができます。

index.js
const scores = [80, 90, 40];

scores.push(15,20); 
//配列の後ろから15と20を加える
console.log(scores);
//[80, 90, 40, 15, 20] 

scores.splice(1, 2);
//配列の1番目から2個の要素を削除する
console.log(scores);
//[80, 15, 20]

scores.splice(1, 1, 30, 70);
//配列の1番目の要素から1つの要素を削除して、30と70を入れる
console.log(scores);
//[80, 30, 70, 20]
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JSファイル net::ERR_ABORTED 404 (Not Found)と言われた時の対処方法

JSファイルが net::ERR_ABORTED 404 (Not Found)と言われた時の対処方法

Laravelでの開発でのスクリプトを、bladeへの直書きではなくて、外部JSファイルとして外出しした書き方をしている場合、コンソール画面にて、net::ERR_ABORTED 404 (Not Found)となりました。

原因:JSファイルの置き場所が間違っていた!!

JSファイルのbladeへの呼び出し方によって、ファイルの置き場所にお作法があるそうなんです。

JSファイルの置き場所の「お作法」

ケース1: JSファイルを作成して読み込む場合は、public/jsの下に配置

ケース2: JSファイルを作成し、Laravel Mixでコンパイルして読み込む場合は、resources/js/assets/の下に配置

ということでした。

反省

基礎からコツコツ学習している人にとっては、当然のことなんだと思うんですが、手当たり次第にLaravelを触っている身としては、こんな初歩的な事も知らなかったのです。お恥ずかしい・・・。

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

Indexed Database APIを使ってスタンドアロンな付箋アプリを作ってみる

記事の趣旨

せっかくIndexed Database APIについて勉強したので,それを使ってブラウザアプリを作ってみた.

1. 作ったアプリと技術要素

ブラウザにスティッキー(付箋紙)っぽいメモを残していけるツール.本当はカンバン方式の管理ツールを作りたかったんだけれど,力尽きたのでいったん保留.画面キャプチャは以下のとおり.

kanban01.png

使用した技術要素は以下のとおり.Vue.jsは初めて触るので,書籍と睨めっこしながらの対応.

  • HTML
  • CSS
  • Vue.js
  • Indexed Database API(敢えてライブラリ未使用)

ソースコードはGithubのリポジトリに置いているのでご自由にどうぞ.動作は「Chrome 79.0.3945.117」で確認.Edgeでも動作するがレイアウトが若干崩れる.

2. 操作方法

直感で扱えるレベルだと思うけれど,とりあえず以下のとおり.

  1. cloneなりzip downloadなりでローカルに落としてきたらkanban.htmlを開く.
  2. 付箋紙を新たに作りたいときは左下のプラスマークをクリック.
  3. 付箋紙はドラッグ&ドロップで自由に移動可能.
  4. 付箋紙をダブルクリックすると編集ウィンドウ(後述)が開く.
  5. 編集ウィンドウでURLを登録しておけば,付箋紙左下の「URL」ボタンクリックでその画面に新タブで遷移可能.

kanban02.png

3. 苦戦したポイント

スキル低いので,初歩的だったり,間抜けなものばかりだが,恥を忍んで書いていく.

3.1 Promise

以下の処理は,「DBへのコネクションを確立する」->「チケットを全件取得する」->「グローバル変数にチケット情報を詰め込む」という意図のコードになる.このうち,createConnection()およびgetAllTickets()は,Promiseオブジェクトを返却する関数である.先日の記事でも述べたとおり,Indexed Database APIは非同期処理であるため,Promiseを活用することで同期を図っている.

    created: function() {
        createConnection().then(
            getAllTickets()
        ).then(
            (allTickets) => {
                allTickets.forEach(
                    ticket =>
                    {
                        tickets.push({
                            id: ticket.id,
                            text: ticket.text,
                            url: ticket.url,
                            categoryId: ticket.categoryId,
                            left: ticket.left,
                            top: ticket.top,
                            zIndex: ticket.zIndex,
                            deleted: false
                        })
                    }
                )
            }
        ).catch(
            (reason) => alert(reason)
        )
    }

さて,上記コードは一部が不正で,これだと同期処理にならない.どこかわかるだろうか.

答えは3行目.getAllTicket()と書いているが,ここはgetAllTicketと,関数オブジェクトを指定しないといけない.

promise.prototype.then()は,あくまでメソッドであり,その引数指定するのはコールバック関数.ものすごく間抜けなミスだが,うっかりif/elseの構文と同じような感覚で記述してしまい,上記のミスに至ってしまった.

3.2 Bubbling

今回の付箋紙オブジェクトは,複数のレイヤで構成されているので,Bubblingを意識してイベントをハンドリングしないといけない.が,そもそもその辺りを全然理解しておらず,序盤はかなり意図しない挙動に苛まれた.たぶん今も多少バグは残っていると思われる.

Bubblingについては,こちらのページの説明がとてもわかりやすいと思う.定義を引用させていただくと以下のとおり.

要素上でイベントが起きると、最初にその上のハンドラが実行され、次にその親のハンドラが実行され、他の祖先に到達するまでそれらが行われます。

今回でいうと,例えば「URL」ボタンをクリックすると,続けて付箋紙自体にセットされたマウスダウンイベントが処理されることになる.後者は付箋紙の移動に関わる処理なので,付箋紙がワープするなどの謎挙動が頻発していた.

Bubblingを防ぎたい場合は,event.stopPropagation()を呼び出すことで,それ以降の親オブジェクトへの伝播を抑制することができる.

4. ペンディングしている事項

  • 付箋紙の色の変更機能
    • categoryIdという死にパラメータがその名残
  • ファイルサーバやローカルへのショートカットボタン
    • エクスプローラの直接操作は無理そうなので,パスをクリップボードにコピーする処理を想定
  • ゴミ箱アイコンへのドロップによる付箋紙削除処理
    • 面倒臭くなって今は削除ボタンで代替
  • カンバンボード化と,付箋紙へのステータス(ToDo,WIP,Done)導入
    • そもそもそれを目指したアプリだったので
  • 複数タブ対応
    • 現在は複数タブでこのアプリを開いたら何が起きるか不明
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

何でもランキングにしてツイートできる「Cappps(キャップス)」を公開した【個人開発】

ランキングを作ってTwitterでツイートできるサービス「Cappps」を作ったので、公開してみました。
どうにか飽きずにリリースまで持っていけました。
今まで作っては飽き作っては飽きで、完成できなかったので、リリースできてよかったです。

リリースしたもの

Cappps 何でもランキングにしてみよう!
ランキングを作ってツイートできるサービスです。
作ったランキングをシェアすることもできます。

見た目

top.png

ランキング作成画面

create.png

作った理由

食べ物のおすすめランキングサイトを作っていました。
でも、いちいちHTML書くの嫌だからシステムにしちゃおう。
        ↓
いっそのことwebサービスにしちゃおう。

っていう流れでできました。

どうでも良い機能

  • 視聴回数

投稿者のモチベアップにつながるかな と思ってつけました。

  • ジャンル分け

これは完全に暇だったからです。
セレクトボックスから選んでジャンルをつけられます。

作りたいもの

お問い合わせ

流石に作ったほうが良いですね。
匿名のほうが問い合わせやすいですし。

アクセスランキング

これも作りたいけど、インデックスしてないのでつらそうです。

使った言語

フロントエンド

  • JavaScript

ライブラリ使ってない素のJSです。
あと対してJSは使ってないです。
ランキング表示部だけ、慣れてるJavaScriptで書きました。

  • CSS

エラーメッセージがないときの消去を疑似要素:emptyでやりました。

サーバーサイド

  • PHP

こちらも素のPHPです。
データベースがよくわからないので、テキストファイルに書き出して保存していました。

一応ジャンル分けもできるようになってます。

感想

最低限の機能だけでも案外ものになるなと思いました。
なんならまだTwitterカードの動作が不安ですけれど...
投稿する というところが動いた時点でリリースしました。

こんくらい雑でも動けばいいです。なんなら動かなくてもいいです。

今、アプリでもサービスでも完成して作り込んでいる方は、とりあえず最初のリリースをしてみてください。
あなたも、ユーザーも、WinWinになるでしょう。

宣伝

好きなゲームでも、嫌いな授業でも、何でも良いです。
ぜひ、ランキングを作ってみてください。
Cappps

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

【個人開発Webサービス】何でもランキングにしてツイートできる「Cappps(キャップス)」をリリースしました

ランキングを作ってTwitterでツイートできるサービス「Cappps」を作ったので、公開してみました。
どうにか飽きずにリリースまで持っていけました。
今まで作っては飽き作っては飽きで、完成できなかったので、リリースできてよかったです。

リリースしたもの

Cappps 何でもランキングにしてみよう!
ランキングを作ってツイートできるサービスです。
作ったランキングをシェアすることもできます。

見た目

top.png

ランキング作成画面

create.png

作った理由

食べ物のおすすめランキングサイトを作っていました。
でも、いちいちHTML書くの嫌だからシステムにしちゃおう。
        ↓
いっそのことwebサービスにしちゃおう。

っていう流れでできました。

どうでも良い機能

  • 視聴回数

投稿者のモチベアップにつながるかな と思ってつけました。

  • ジャンル分け

これは完全に暇だったからです。
セレクトボックスから選んでジャンルをつけられます。

作りたいもの

お問い合わせ

流石に作ったほうが良いですね。
匿名のほうが問い合わせやすいですし。

アクセスランキング

これも作りたいけど、インデックスしてないのでつらそうです。

使った言語

フロントエンド

  • JavaScript

ライブラリ使ってない素のJSです。
あと対してJSは使ってないです。
ランキング表示部だけ、慣れてるJavaScriptで書きました。

  • CSS

エラーメッセージがないときの消去を疑似要素:emptyでやりました。

サーバーサイド

  • PHP

こちらも素のPHPです。
データベースがよくわからないので、テキストファイルに書き出して保存していました。

一応ジャンル分けもできるようになってます。

感想

最低限の機能だけでも案外ものになるなと思いました。
なんならまだTwitterカードの動作が不安ですけれど...
投稿する というところが動いた時点でリリースしました。

こんくらい雑でも動けばいいです。なんなら動かなくてもいいです。

今、アプリでもサービスでも完成して作り込んでいる方は、とりあえず最初のリリースをしてみてください。
あなたも、ユーザーも、WinWinになるでしょう。

宣伝

好きなゲームでも、嫌いな授業でも、何でも良いです。
ぜひ、ランキングを作ってみてください。
Cappps

下のようなランキングを作れます。

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

【個人開発Webサービス】何でもランキングにしてツイートできる「Cappps(キャップス)」をリリース

ランキングを作ってTwitterでツイートできるサービス「Cappps」を作ったので、公開してみました。
どうにか飽きずにリリースまで持っていけました。
今まで作っては飽き作っては飽きで、完成できなかったので、リリースできてよかったです。

リリースしたもの

Cappps 何でもランキングにしてみよう!
ランキングを作ってツイートできるサービスです。
作ったランキングをシェアすることもできます。

見た目

top.png

ランキング作成画面

create.png

作った理由

食べ物のおすすめランキングサイトを作っていました。
でも、いちいちHTML書くの嫌だからシステムにしちゃおう。
        ↓
いっそのことwebサービスにしちゃおう。

っていう流れでできました。

どうでも良い機能

  • 視聴回数

投稿者のモチベアップにつながるかな と思ってつけました。

  • ジャンル分け

これは完全に暇だったからです。
セレクトボックスから選んでジャンルをつけられます。

作りたいもの

お問い合わせ

流石に作ったほうが良いですね。
匿名のほうが問い合わせやすいですし。

アクセスランキング

これも作りたいけど、インデックスしてないのでつらそうです。

使った言語

フロントエンド

  • JavaScript

ライブラリ使ってない素のJSです。
あと対してJSは使ってないです。
ランキング表示部だけ、慣れてるJavaScriptで書きました。

  • CSS

エラーメッセージがないときの消去を疑似要素:emptyでやりました。

サーバーサイド

  • PHP

こちらも素のPHPです。
データベースがよくわからないので、テキストファイルに書き出して保存していました。

一応ジャンル分けもできるようになってます。

感想

最低限の機能だけでも案外ものになるなと思いました。
なんならまだTwitterカードの動作が不安ですけれど...
投稿する というところが動いた時点でリリースしました。

こんくらい雑でも動けばいいです。なんなら動かなくてもいいです。

今、アプリでもサービスでも完成して作り込んでいる方は、とりあえず最初のリリースをしてみてください。
あなたも、ユーザーも、WinWinになるでしょう。

宣伝

好きなゲームでも、嫌いな授業でも、何でも良いです。
ぜひ、ランキングを作ってみてください。
Cappps

下のようなランキングを作れます。

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

【個人開発サービス】たった6時間で作った、なんでもランキングにしたがるお前ら向けのサービス「Cappps(キャップス)」をリリースした

ランキングを作ってTwitterでツイートできるサービス「Cappps」を作ったので、公開してみました。
どうにか飽きずにリリースまで持っていけました。
今まで作っては飽き作っては飽きで、完成できなかったので、リリースできてよかったです。

リリースしたもの

Cappps 何でもランキングにしてみよう!
ランキングを作ってツイートできるサービスです。
作ったランキングをシェアすることもできます。

見た目

top.png

ランキング作成画面

create.png

作った理由

食べ物のおすすめランキングサイトを作っていました。
でも、いちいちHTML書くの嫌だからシステムにしちゃおう。
        ↓
いっそのことwebサービスにしちゃおう。

っていう流れでできました。

どうでも良い機能

  • 視聴回数

投稿者のモチベアップにつながるかな と思ってつけました。

  • ジャンル分け

これは完全に暇だったからです。
セレクトボックスから選んでジャンルをつけられます。

作りたいもの

お問い合わせ

流石に作ったほうが良いですね。
匿名のほうが問い合わせやすいですし。

アクセスランキング

これも作りたいけど、インデックスしてないのでつらそうです。

使った言語

フロントエンド

  • JavaScript

ライブラリ使ってない素のJSです。
あと対してJSは使ってないです。
ランキング表示部だけ、慣れてるJavaScriptで書きました。

  • CSS

エラーメッセージがないときの消去を疑似要素:emptyでやりました。

サーバーサイド

  • PHP

こちらも素のPHPです。
データベースがよくわからないので、テキストファイルに書き出して保存していました。

一応ジャンル分けもできるようになってます。

感想

最低限の機能だけでも案外ものになるなと思いました。
なんならまだTwitterカードの動作が不安ですけれど...
投稿する というところが動いた時点でリリースしました。

こんくらい雑でも動けばいいです。なんなら動かなくてもいいです。

今、アプリでもサービスでも完成して作り込んでいる方は、とりあえず最初のリリースをしてみてください。
あなたも、ユーザーも、WinWinになるでしょう。

宣伝

好きなゲームでも、嫌いな授業でも、何でも良いです。
ぜひ、ランキングを作ってみてください。
Cappps

下のようなランキングを作れます。

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

【JavaScript】getElementByIdでnullが返ってきたときに確認すること

はじめに

凡ミスで長い時間つまずいたのでメモ。
あと役に立ちそうなサイトもメモ。

jsのコードの場所を確認

外部ファイルを読み込むにしろ, htmlの中に書くにしろ, どこに書くかは割と重要ですね。
外部ファイルでつまずいたのでそちらの事例を書きます。

<script src="menu.js"></script>
<div id="menu"></div>
var menu = document.getElementById('menu');

この例だと変数menunullになってしまいます。
理由は簡単ですが, <div id="menu"></div>の前にjsファイルを読み込んでいるので, menuというid属性なんて知らないわよ! どこにあるの!ってなってしまいます。
ってわけなので, 順番を入れ替えてあげればなおります。

<div id="menu"></div>
<script src="menu.js"></script>

その他

他の事例はこちらのサイトが参考になるかも

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

jQuery の基礎

つい先日下記の2つの記事を書きました。
JavaScriptをHTMLファイルにどのように適用するかというものですね。

『JavaScriptをWEBサイト(HTMLファイル)に適用する方法』
https://qiita.com/kibinag0/items/7843727e4328881ae964

『JavaScript と DOM 応用編』
https://qiita.com/kibinag0/items/6e9b9169dcd261bd9ae1

今回その続編のjQuery編です。

DOMで document.querySelector("h1") とかって記載していたんですけど、何とjQueryを使用すると、、、そのコードが $("h1") で済むみたいです。時短かよ。
ちなみに "$ = jQuery" という意味で jQuery("h1")としても大丈夫見たい。

jQuery.js
// 今までのDOM Selector 
document.querySelector("h1")

// jQuery での Selector
$("h1")

しかもh1タグが複数ある場合は、document.querySelecterAll("h1")とかやらなきゃいけなかったのも、全て$("h1")で引っ張ってきてくれるようになっています。

なんと便利な。そんな便利なjQueryの基礎をメモっていきます。
今回も、AngelaさんのUdemyのコースです。→Udemyリンク

まずはjQueryを使用できる環境を作る

jQuery公式サイト の中からGoogle Hosted Libraries を探し、最新のjQueryのscriptタグを探しに行きます。google developer

qiita.html

// ①Google Developersから取得し、jQueryを読み込む

<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script>

// ②jQuery 読み込んだ後に、javascriptシートを読み込む

<script src="index.js" charset="utf-8"></script>

// ちなみに場所は、bodyタグ内の一番最後 だから、こうなるはず

</body>

jQuery で CSS を編集・追加・消去

css("プロパティ","値") で編集できます。

直接編集・追加しちゃうやり方はこちら。

qiita.js
// h1タグのcss編集・追加

$("h1").css("color","brown");

// 複数のcssを変更・追加
$("h1").css({"color":"brown","text-align":"left"});


// ちなみにプロパティだけを選択すると値を返してくれる
console.log($("h1").css("color"));

新たにクラスを追加・消去する場合

もしクラスを作って、そのクラスを適用させたい、もしくは、すでに適用されているクラスを外したいといった場合は、こんな感じ。

今回は h1タグに"oshan"というクラスを適用・外す場合。

qiita.js
// oshan クラスを適用する

$("h1").addClass("oshan");

// すでに適用されているoshanクラスを外す

$("h1").removeClass("oshan");

// oshan と stylish 2つのクラスを適用したい場合

$("h1").addClass("oshan stylish");


jQuery でテキストを変える

次にjQueryを使用してテキストを変更する2つの方法。

text()でテキストのみを変更する。

こちらはhelloだとしたら、その"hello"部分のみを変更しています。

qiita.js
// button内のテキストを"OMG!!"変更する

$("button").text("OMG!!");


.htmlでそのタグ自体を変更する。

こちらはhelloのタグも含めた全ての部分を編集しています。だから、タグなんかも入れちゃえるのですね。

qiita.js
// buttonのタグ自体を編集する

$("button").html("<em>OMG!!</em>")

jQueryで属性(attributes)を編集する

次に、jQueryで属性を編集する方法です。属性(attributes)ってなに?って方のために説明しておくと、属性とはaタグのhrefとか、imgタグのsrc, altのようなものです。

それを attr()で変更できます。

qiita.js
// 属性とはタグの中のhrefやsrc, altなどのこと。
<a href="">
<img src="" alt="">

// aタグのsrc属性をgoogle.comに変更するよ

$("a").attr("src","https://www.google.com/");

jQuery で add Event Listener を実装する

以前のDOM, JavaScript応用編でクリックされた時やキーボードが押された時に何かしらの処理を実行させるaddEventListenerのjQuery版を見て行きます。

クリックされたときは click()で

qiita.js
// h1がclickされた時に関数で色をpurpleに変更させる

$("h1").click(function(){
   $("h1").css("color","purple")
});

キーボード入力されたときは keypress()で

qiita.js
// keyboard入力があった時に、h1の色をpurpleに変更する

$("body").keypress(function(){
   $("h1").css("color","purple")
});

まぁでも、on() で全部対応できるみたい

上記のようにclick()とか、keypress()とかでもevent別に対応できるけど、on()を使用すると、その全てに対応できるみたい。

on(イベント、適応する関数)

qiita.js
// h1がclick された時にcssの関数を適用
$("h1").on("click", function(){
    $("h1").css("color","red");
});

// mouseover された時にcssの関数を適用
$("h1").on("mouseover", function(){
    $("h1").css("color","red");
});

ただイベントを書き換えるだけ。。便利かよ。

ちなみに、clickとかkeypressとかmouseoverとかのイベント一覧↓
https://developer.mozilla.org/ja/docs/Web/Events

jQueryで直接要素をHTMLに追加する

今まではすでにあるHTML要素にCSSの変更を加えたり、HTML内のテキストを編集したりという処理でしたが、jQueryではHTMLファイルに要素自体を加えることも可能になっています。

qiita.js

// beforeは、こうなる
// <button>NEW</button> <h1>Hello World</h1>
$("h1").before("<button>NEW</button>");


// afterは、こうなる
// <h1>Hello World</h1> <button>NEW</button> 
$("h1").after("<button>NEW</button>");


// prependは、こうなる
// <h1> <button>NEW</button> Hello World</h1>
$("h1").prepend("<button>NEW</button>");


// appendは、こうなる
// <h1>Hello World <button>NEW</button> </h1>  
$("h1").append("<button>NEW</button>");


逆にHTMLファイルから要素を除去する場合はこんな感じ。

qiita.js
// 逆に何か要素をHTMLファイルから消去するときは
$("button").remove();

// ちなみに、これ button 要素が全部消え去りますのでご注意を 笑

jQueryで簡単なアニメーションを作成する

指定した要素をhide(隠す)したり、show(表示)させたり、toggle(表示・隠す)したり、fadeOut(フェードアウト)したり、fadeIn(フェードイン)したり、fadeToggle(フェードイン・アウト)したり、slideUp(スライドイン)したり、slideDown(スライドアウト)したり、いろんなアニメーションを使用することができます。

qiita.js
// button をclick したときに h1 を hide するとき

$("button").on("click", function(){

   $("h1").hide();

});

ちなみに、上記の"hide"部分をshow, fadeIn, fadeOut, slideUp, slideDownなどに変えればそれぞれの処理を適用できます。

また、animate()を使用すると、cssなどを適用して、アニメーションを作ることができます。
下記は、アニメーションを使用して 透明性(opacity)を0.5にしています。animateの場合は、中に入れる値が数字でなければならないことに注意です。

qiita.js
$("button").on("click", function(){

   $("h1").animate({opacity: 0.5});

});

複数のアニメーションを順番に適用する

複数のアニメーションを適用することもできます。やってみると意外とかっこいい。

qiita.js
// slideUpしてslideDownして、最後に透明性を0.5にしてます。

$("h1").slideUp().slideDown().animate({opacity: 0.5});


以上でjQueryの基礎終わりです。

1つ1つを暗記するのではなく、「こんなのがあるのか」とざっくり覚えておくとググりながら使えるよ!ってAngelaさんが言ってました笑

確かに〜。

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

JavaScript勉強の記録その6: 色々な関数の定義方法

関数の定義

JavaScriptにはいくつか関数定義の方法があります。

関数宣言

一番ポピュラーなやり方として、関数宣言があります。

以下の例ではsumという関数を定義した後、sumの呼び出しを行なっております。

index.js
function sum(a, b, c) {
  return a + b + c;
}

console.log(sum(1,1,1));
//3

関数式

変数や定数に代入するような形で関数を定義することもできます。
以下の例では定数であるsumに関数を代入し、定数sumに()を付けて実引数を渡しています。

index.js
const sum = function(a, b, c) {
  return a + b + c;
}

console.log(sum(1,1,1));
//3

アロー関数

関数式をより短く書く方法も用意されてあり、それをアロー関数と呼びます。
書き方としては、functionと書く代わりに=>を{}と繋げてあげればOKです。

index.js
const sum = (a, b, c) => {
  return a + b + c;
}

console.log(sum(1,1,1));
//3

もっと短くアロー関数

さらに短く書く方法も用意されてあり、処理結果を返すだけであれば{}とreturnを削ることもできます。

index.js
const sum = (a, b, c) => a + b + c;

console.log(sum(1,1,1));
//3

もっともっと短くアロー関数

引数が1つだけの場合は()すらも省略できる書き方も用意されてあるようです。
以下の例ではコメントアウトされてある関数式と、コメントアウトされていない部分は同じ意味になります。

index.js
//const doble = function(a) {
  //return a * 2;
//}

const doble = a => a * 2;

console.log(doble(2));
//4
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

JavaScript勉強の記録その5: breakとcontinueを使ったループ処理

breakとcontinueを使ったループ処理

breakやcontinueを利用して、ある条件の時にはループ処理を抜けたり、次のループ処理に入ったり等、制御をすることができます。 
まず、以下の例では変数が3の倍数の時にコンソールに「こんにちは」と表示します。

index.js
for (let i = 1; i <= 10 ; i++) {
  if (i % 3 === 0) {
    console.log('こんにちは');
  }
  console.log(i);
}

// 1
// 2
// こんにちは
// 3
// 4
// 5
// こんにちは
// 6
// 7
// 8
// こんにちは
// 9
// 10

上記の例では3の倍数の時には、「こんにちは」と「3の倍数」が出力されてありますが、「3の倍数」の時は、数字は表示せず、「こんにちは」だけ表示したいときもあるかと思います。
そのような時は、continueという命令を加えることで、制御を加えることができます。

以下の例ではcontinueが呼ばれた時点で次のループ処理に入ることができますので一番下のconsole.log(i)は呼ばれません。

index.js
for (let i = 1; i <= 10 ; i++) {
  if (i % 3 === 0) {
    console.log('こんにちは');
    continue;
  }
  console.log(i);
}

// 1
// 2
// こんにちは
// 4
// 5
// こんにちは
// 7
// 8
// こんにちは
// 10

最後に、ある条件に当てはまった時点でループ処理を抜けたい、という時もあるかと思います。
そう言う時はbreakという命令を使えば、その時点でループ処理を抜けられます。

index.js
for (let i = 1; i <= 10 ; i++) {
  if (i % 3 === 0) {
    console.log('こんにちは');
    break;
  }
  console.log(i);
}

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