- 投稿日:2019-07-16T23:29:56+09:00
JavaScriptとは
JavaScriptとは
JavaScriptについての忘備録です。
JavaScriptについてわかった気になり、痛い目を見たので
ひっそり、シンプルに、アウトプットします。参考サイト:MDN web docs / JavaScript とは
1.JavaScriptとは
動的にコンテンツを更新したり、マルチメディアを管理したり、その他多くのことができるスクリプト言語。
(例)・GitHubでソースコードを見る
・ライブ実行で実際の動きを見る2.実際に何ができるのか
(1)JavaScriptでできること
・値を変数に格納する。
・文字列を操作する。
・Webページの動きを作る。 など
-> これらはJavaScriptからすぐに使えるように、作られている
( = Application Programming Interface (API) と呼ばれている )(2)API(大まかに2種類)
1.ブラウザ API
・Webブラウザに組み込まれている。
・コンピュータを取り巻く環境からデータを取り出したりする。
(例)DOM (Document Object Model) API,Geolocation API,Canvas,
WebGL,HTMLMediaElementなど2.サードパーティ API
・ブラウザには組み込まれていない。
・Web上からコードと情報を探さなければならない。
(例)Twitter API,Google マップ APIなど3.JavaScript を動かすと何が起きるのか
(1)ブラウザのセキュリティ
・それぞれのタブ内でコードは完全に分かれて実行されている。
= タブで動いているコードは、
他のタブや他のWebサイトのコードに干渉できない。(2)JavaScript の実行順序
・たいていは先頭から最後に向かって順番に実行されるが、
どの順番で実行されるかということに気を付ける必要がある。
(例)var para = document.querySelector('p'); para.addEventListener('click', updateName); function updateName() { var name = prompt('名前を入力して下さい'); para.textContent = 'Player 1: ' + name; }(3)インタープリタとコンパイルコード
【インタープリタ言語】
・JavaScriptはインタープリタ言語。
・コードが上から下に実行されてコードの実行結果がすぐに返ってくる。
・ブラウザが実行する前にコードを何らかの形に変換する必要はない。【コンパイル言語】
・コンピュータで実行する前に他の形式に変換 (コンパイル) しなければならない。(4)サーバーサイドコードとクライアントサイドコード
【サーバーサイドコード】
・サーバー上で実行され、結果がブラウザにダウンロードされて表示される。
・PHP、Python、Ruby、ASP.NET、そしてJavaScriptなど。【クライアントサイドコード】
・ユーザのコンピュータ上で実行されるコード。
・Webページを見ているとき、ページのクライアントサイドコードがダウンロードされて、ブラウザで実行されて表示される。
・このJavaScriptモジュールのことを、クライアントサイドJavaScriptと言う。(5)動的コードと静的コード
【動的】
・①クライアントサイドのJavaScript と、②サーバーサイドの言語。
①クライアント上のブラウザでHTMLのテーブルを生成したり、
そのテーブルにサーバーから指示を受けデータを追加したり、
Webページ上でユーザにテーブルを表示したりするなどして、動的にコンテンツを生成する。
②データベースからデータを取得して、動的にコンテンツを生成する。【静的】
・動的に更新されるコンテンツを含まないWebページ。
・常に同じコンテンツを表示するページ。4.ページにJavaScriptを追加する方法
(1)JavaScript をページの中に書く
▼▼index.html
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <title>Apply JavaScript example</title> </head> <body> <button>Click me</button> <script> function createParagraph() { var para = document.createElement('p'); para.textContent = 'ボタンが押されました!'; document.body.appendChild(para); } var buttons = document.querySelectorAll('button'); for (var i = 0; i < buttons.length ; i++) { buttons[i].addEventListener('click', createParagraph); } </script> </body> </html>(2)JavaScript をページの外に書く
▼▼index.html
<!DOCTYPE html> <html lang="en-US"> <head> <meta charset="utf-8"> <title>Apply JavaScript example</title> </head> <body> <button>Click me</button> <script src="script.js"></script> </body> </html>▼▼script.js
function createParagraph() { var para = document.createElement('p'); para.textContent = 'ボタンが押されました!'; document.body.appendChild(para); } var buttons = document.querySelectorAll('button'); for (var i = 0; i < buttons.length ; i++) { buttons[i].addEventListener('click', createParagraph); }(3) 【NG】 HTMLにJavaScriptのハンドラーを混ぜる
【NG】 HTMLの途中にJavaScriptのコードが書かれている例
▼▼index.html<button onclick="createParagraph()">押してください</button>▼▼script.js
function createParagraph() { var para = document.createElement('p'); para.textContent = 'ボタンが押されました!'; document.body.appendChild(para); }->なぜだめか
・HTMLをJavaScriptで汚している。
・onclick="createParagraph()"
という属性を、
JavaScript を適用したいボタン全てに書かなければならず、非効率。【OK】 純粋なJavaScriptでかくとこんな感じ
▼▼script.jsvar buttons = document.querySelectorAll('button'); for (var i = 0; i < buttons.length ; i++) { buttons[i].addEventListener('click', createParagraph); }(4)スクリプトの読み込み方針
・
async
:依存関係なしでスクリプトを単独で実行できる。
・defer
:ブラウザで実行する順序で、対応するscript要素を配置する。5.最後に
プログラミング言語の初学者さん、同志として一緒にJavaScriptの学習楽しみましょう。
もしご指摘等ありましたら、教えていただけるとうれしいです。
- 投稿日:2019-07-16T22:54:37+09:00
export defaultってなんだろう
問題
突然ですが、ここにこんなSampleコンポーネントがあります。(この記事はちょっとしたReact要素を含んでいますがReactを知らなくても多分わかります。)
sample.tsxexport const Sample: FC<Props> = () => { return(<div>sample</div>); }これは、いかにもこんな感じでimportして使えそうです。
sampleContainer.tsximport { Sample } from './sample.tsx'; import { connect } from 'react-redux'; const mapStateToProps = .... ; // 割愛 export default connect(mapStateToProps)(Sample);さて、ここでexport defaultされているこの子はいったいどのようにimportしたら他の場所で使えるのでしょうか。
答え
答えは以下のようになります。
routing.tsximport SampleContainer from './sampleContainer.tsx'; const participantRoutes: Routes = [ { path: "/activity", component: SampleContainer } ];ファイル名と対応しているのかな?と一見思うかもしれませんが、これは唯一の回答ではありません。これでもいいのです。
routing.tsximport SC from './sampleContainer.tsx'; const participantRoutes: Routes = [ { path: "/activity", component: SC } ];と言うか、なんでもいいのです。(Syntax上はですが)
なんででしょうか。考えてみたらとても自明なことだし納得のいくことなのですが、ReactでReduxするまでこれを意識しなければいけない場面がなかったので全く気づかず、不思議に思っていました。
解説
ポイントは
export default
です。これは文字通り、そのファイルのデフォルトとして後ろに続くものをexportするよ、という意味になります。
export default
されたものを使いたい時、importする側は任意の名前をつけることができます。それは、これをデフォルトにするよ、と宣言しているからです。
これは自然に理解できると思いますが、デフォルトを複数指定することはできません。なので、1ファイル内にさっきのconnectのようなexport default
宣言を複数書くとエラーが出ます。
なるほど、だから名前を好きにつけてもちゃんとconnectが使えたのですね詳しい説明: MDN Export
- 投稿日:2019-07-16T22:24:00+09:00
【JavaScript】innerHTM、textContentの違い
はじめに
今回はinnerHTMLプロパティとtextContentプロパティの違いについて紹介します。
両方ともJavaScriptの要素内のテキストにアクセスできるプロパティです!
私はよくどっちがどっちだかわからなくなるので、ここでまとめておきます!両者の違い
● innerHTML
"HTMLを解釈して出力する"のがinnerHTMLです。
● textContent
"HTMLを解釈せずにそのまま文字として出力する"のがtextContentです。
以下にコードの例を載せておきます!
test.html<p id="p1">ボタンを押して下さい</p> <input type="button" value="textContent" onclick="clickBtn1()"> <input type="button" value="innerhtml" onclick="clickBtn2()"> <script> function clickBtn1(){ const p1 = document.getElementById("p1"); p1.textContent = "<b>ボタンを押しました</b>"; } function clickBtn2(){ const p1 = document.getElementById("p1"); p1.innerHTML = "<b>ボタンを押しました</b>"; } </script>上記のコードは、ボタンをクリックすると、innerHTMLとtextContentでそれぞれ文字が出力される記述です。
innerHTMLの方をクリックした場合は
ボタンを押しました
と、<b></b>
タグが反映されて太文字で文字が出力されます!textContentの方をクリックした場合は、HTMLタグが読み込まれないので
<b>ボタンを押しました</b>
と、タグもそのまま出力されます!※こちらのコードは以下のサイトから引用させて頂きました!
こちらのサイトでは、それぞれのボタンをクリックした時のデモもあります!是非ご覧ください!
https://itsakura.com/js-textcontent-innerhtmlさいごに
innerHTMLとtextContentの違いでした!
そもそもどっちがどっちかわからない場合は、innerHTMLの文字の意味を考えると覚えやすいです。
文字そのままでinner
は"内なる"という意味なので、
innerHTML
は直訳すると"HTMLを内に入れる"的な感じになります。
つまりinnerHTML
は"HTMLタグを読み込むよ"って解釈できます!
わからなくなったらこれでいきましょう!
- 投稿日:2019-07-16T20:26:27+09:00
Electron を Custom URL Scheme で起動する
やりたいこと
Custom URL Scheme から Electron を起動したい、値も渡したい
例えばブラウザで、
itmss://music.apple.com/jp/album/ignition-single/1378858551
にアクセスすると iTunes が起動し、特定のページに遷移するこれと同じことを実現したい
前提
- Electron version v5.0
- electron-builder v20.40.2
手順
Mac
package.json の build に以下を追記する
"build": { ... "protocols": { "name": "myApp IM URL", "schemes": [ "my-appname" ] }, ... }ビルド後に
.dmg
イメージからアプリケーションを Mac にインストールすることで、 URL Scheme から Electron を起動することができるWindows
package.json の build に以下を追記する
"build": { ... "nsis": { "include": "build/installer.nsh", "perMachine": true }, ... }
build/installer.nsh
ファイルを追加する!macro customHeader !system "echo '' > ${BUILD_RESOURCES_DIR}/customHeader" !macroend !macro preInit ; This macro is inserted at the beginning of the NSIS .OnInit callback !system "echo '' > ${BUILD_RESOURCES_DIR}/preInit" !macroend !macro customInit !system "echo '' > ${BUILD_RESOURCES_DIR}/customInit" !macroend !macro customInstall !system "echo '' > ${BUILD_RESOURCES_DIR}/customInstall" DetailPrint "Register My AppName URI Handler" DeleteRegKey HKCR "My AppName" WriteRegStr HKCR "My AppName" "" "URL:My AppName" WriteRegStr HKCR "My AppName" "URL Protocol" "" WriteRegStr HKCR "My AppName" "EveHQ NG SSO authentication Protocol" "" WriteRegStr HKCR "My AppName\DefaultIcon" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME}" WriteRegStr HKCR "My AppName\shell" "" "" WriteRegStr HKCR "My AppName\shell\Open" "" "" WriteRegStr HKCR "My AppName\shell\Open\command" "" "$INSTDIR\${APP_EXECUTABLE_FILENAME} %1" !macroend !macro customInstallMode # set $isForceMachineInstall or $isForceCurrentInstall # to enforce one or the other modes. !macroendビルド後、 Setup ファイルからインストールすることで、 URL Scheme から Electron を起動することができる
Custom URI Scheme を Electron 側で取得する
メインプロセス
Mac OS と Windows で書き方が違う
// for Mac OS app.on('open-url', (e, url) => { // URL Scheme から開いたときにここが実行される webContents.send('customUri', url); }); // アプリの二重起動を防ぐ(for windows) const gotTheLock = app.requestSingleInstanceLock(); if (!gotTheLock) { // すでにウィンドウを開いていた場合、新しい window は quit app.quit(); } else { // 2つめのウィンドウが開かれた時のイベントを定義する app.on('second-instance', (event, commandLine, workingDirectory) => { // mainWindow.webContents.send('log', 'second instance!!'); commandLine.forEach(cmd => { // URIスキームのみを探して、レンダラプロセスに送る if (/my-appname:\/\//.test(cmd)) { mainWindow.webContents.send('customUri', cmd); } }); // すでにメインウィンドウがある場合、それにフォーカスする if (mainWindow) { if (mainWindow.isMinimized()) { // 最小化してた場合 mainWindow.restore(); // restore } mainWindow.focus(); // フォーカス } }); }レンダラプロセス
/** * custom uri をメインプロセスから受け取る */ ipcRenderer.on('customUri', (event, uri) => { console.log('customUri:', uri); });参考
https://electronjs.org/docs/all#apprequestsingleinstancelock
- 投稿日:2019-07-16T19:36:44+09:00
ついてくるヘッダーを簡単に作る。
<!-- 省略 --> <style> body { z-index: 0; margin: 0; pasding: 0 } #header { position: fixed; top: 0; left: 0; z-index: 100 } </style><!-- CSSでかぶるところがあってら消してください --> <script src=//code.jquery.com/jquery-3.3.1.min.js></script><!-- jQueryをすでに読み込んでいる場合は不要です --> <script> $(function() { $('body').css('padding-top',$('#header').outerHeight()); }); </script> </head> <body> <header> <div id=header> <!-- 省略 --> </div> </header> <!-- 省略 -->間違ってたらごめんなさい。
7/17 追記
一応解説しときます。
最初にstyle要素のz-index
でbodyの上に#headerを重ねてposition:fixed;
で画面上部に固定。
jQueryで#headerの高さを取得してbodyにpadding-topをその分だけあててます。
やっぱり間違ってたらごめんなさい。
- 投稿日:2019-07-16T19:31:59+09:00
【JavaScript】axios0.19.0で共通のクエリパラメータが正常にセットできない問題と対応方法
結論
v0.19.0限定のバグのようです。
axiosのGithub上のissueで既に議題になっています。暫定対応になりますが、v0.18.0にダウングレードして利用することで回避できます。
問題のコード
以下のようなコードを書いた時に発生します。
axios.create({ params: { apiKey: "abcdefghijklmnopqls" } }); axios.get("https://xxx.com/api/posts"); // 期待される実行結果 // https://xxx.com/api/posts?apiKey=abcdefghijklmnopqlsへのアクセス // 実際の実行結果 // https://xxx.com/api/postsへのアクセスAPIにアクセスする際に、常にクエリパラメータにAPIキーの指定が必要なシステムがあり、毎度
get
関数を呼び出す際にapiKeyを指定したくない。といった場合に、
create
関数で共通する情報を予め設定できます。
...が、axios@0.19.0
のバグでうまく付与されないようです。
暫定対応
issueを見ると、修正のPRが取り込まれたバージョンがぼちぼちリリースされる雰囲気ですが、一応それまでの暫定対応を書いておきます。
get関数にベタ書きaxios.get("https://xxx.com/api/posts?apiKey=abcdefghijklmnopqls");※ メンテナンス性最悪なので非推奨。
OR
axios@0.18.0
にダウングレードCDN<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script> ↓ <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js">npm# 今のaxiosのアンインストール(自身の環境に合わせて--saveオプション、--save-devオプションを付与して下さい) npm uninstall axios # 0.18.0版のインストール npm install axios@0.18.0v0.19.0じゃなければいけない理由がなければこちらを推奨
一応issueをウォッチして、修正版がでたら追記します。
- 投稿日:2019-07-16T19:31:59+09:00
【JavaScript】axios@0.19.0で共通のクエリパラメータが正常にセットできない問題と対応方法
結論
v0.19.0限定のバグのようです。
axiosのGithub上のissueで既に議題になっています。暫定対応になりますが、v0.18.0にダウングレードして利用することで回避できます。
問題のコード
以下のようなコードを書いた時に発生します。
axios.create({ params: { apiKey: "abcdefghijklmnopqls" } }); axios.get("https://xxx.com/api/posts"); // 期待される実行結果 // https://xxx.com/api/posts?apiKey=abcdefghijklmnopqlsへのアクセス // 実際の実行結果 // https://xxx.com/api/postsへのアクセスAPIにアクセスする際に、常にクエリパラメータにAPIキーの指定が必要なシステムがあり、毎度
get
関数を呼び出す際にapiKeyを指定したくない。といった場合に、
create
関数で共通する情報を予め設定できます。
...が、axios@0.19.0
のバグでうまく付与されないようです。
暫定対応
issueを見ると、修正のPRが取り込まれたバージョンがぼちぼちリリースされる雰囲気ですが、一応それまでの暫定対応を書いておきます。
get関数にベタ書きaxios.get("https://xxx.com/api/posts?apiKey=abcdefghijklmnopqls");※ メンテナンス性最悪なので非推奨。
OR
axios@0.18.0
にダウングレードCDN<script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.19.0/axios.min.js"></script> ↓ <script src="https://cdnjs.cloudflare.com/ajax/libs/axios/0.18.0/axios.min.js"></script>npm# 今のaxiosのアンインストール(自身の環境に合わせて--saveオプション、--save-devオプションを付与して下さい) npm uninstall axios # 0.18.0版のインストール npm install axios@0.18.0v0.19.0じゃなければいけない理由がなければこちらを推奨
一応issueをウォッチして、修正版がでたら追記します。
- 投稿日:2019-07-16T19:22:31+09:00
Tensorflow.js + Chart.js で学習の様子を可視化する
はじめに
TensorFlowなどの機械学習用ライブラリを使って実際に画像認識モデルとか実装してみるものの、
精度がいくらですよとか、ラベルがいくつですよみたいな、数値の結果しか基本的には得られない。
可視化の機能もあるとはいっても、精度の遷移をグラフにしたものとかで、
とくに全くの初学者にはいまいちピンとこないんじゃないかと思う(思わない?)。結局、機械学習(とかディープラーニング)ってなにをやってるの?
学習ってなに?
っていうと、与えたデータに対してうまいぐあいにフィッティングする曲線を自動的に獲得してるってことになる。
(データの分類だとちょっと違うけど考え方によってはまあね)なので、機械学習がどのようなものなのかよりイメージしやすいように、
学習の様子、曲線がデータにフィッティングしていく様子を可視化してみる。
機械学習にはTensorflow.jsを使う。
データの描画にはChart.jsを使う。学習可視化の方針
- 学習用データとそれに対するモデル推論結果の二種類のデータ群をChart.jsで描画する。
- Tensorflow.jsで学習用データに対するモデルをつくる。
- モデルの学習処理中に(1 epochごとに)描画処理を呼ぶ。
- 描画処理にてモデルによる推論結果を取得し、描画するデータを更新する。
実装
全体
https://codepen.io/dldemo/full/maJXGb
もしくはSee the Pen Tensorflow.js + Chart.js by demo (@dldemo) on CodePen.
モデルの学習処理
const EPOCHS = 100; const BATCH_SIZE = 10; const trainModel = async () => { const f = await nnModel.fit(dh.convertTensorDataX(), dh.convertTensorDataY(), { epochs: EPOCHS, batchSize: BATCH_SIZE, callbacks: { onEpochEnd: updateDrawing } }); };上記コードの
callbacks
部分、onEpochEnd: updateDrwing
が 1 epoch ごとにupdateDrawing
の処理を呼ぶための設定。
updateDrawing
に描画処理を記述すればいい。詳細は後述する。
nnModel
は学習モデル。
ちなみにdh.hogehoge
は描画する学習用データのTensor型を返している。Tensorflow.jsでモデルをつくる
モデルの作成
最適化手法の設定
const optimConfig = { optimizer: 'adam', loss: 'meanSquaredError' }レイヤーの設定
const sequentialDenseModelConfig = { layers: [{ units: 1, inputShape: [1] },{ units: 20, activation: 'relu' },{ units: 20, activation: 'relu' },{ units: 20, activation: 'relu' },{ units: 20, activation: 'relu' },{ units: 1, activation: 'linear' }] }モデル作成
上記の最適化手法およびレイヤーの設定を用いてモデルを作成、コンパイルする。
ここではtf.sequential
を用いた単純なモデルをつくっている。const createSequentialDenseModelFromConfig = (compile=true) => { let model = tf.sequential(); for (key in sequentialDenseModelConfig['layers']) { model.add(tf.layers.dense(sequentialDenseModelConfig['layers'][key])); } if (compile) { model.compile(optimConfig); } return model; };モデルの推論
つくったモデルに対して実際に入力を与えて結果を得ることを推論と呼んだりする。
モデルによる予測処理。tidyPredict = (model, x) => tf.tidy(() => model.predict(x));
tf.tidy
を使うことでうまくメモリ管理をしてくれる。
x
に学習用データを与えることでデータに対する予測結果を得られる。描画処理でつかう。Chart.jsでデータを描画する
もろもろの設定
const color = Chart.helpers.color; const chartColors = { red: '#FF0000', blue: '#0000FF' }; const scatterData = { datasets:[{ label: 'train dots', borderColor: chartColors.red, backgroundColor: color(chartColors.red).alpha(0.2).rgbString(), pointRadius: 10, data: dh.originalData, type: 'scatter' },{ label: 'predict line', borderColor: chartColors.blue, backgroundColor: color(chartColors.blue).alpha(0.2).rgbString(), data: dh.packPredictedDatasets(nnModel), type: 'scatter' }] };
datasets
に描画するデータを設定する。
ここでは学習用データとモデルの推論結果の二種類を設定する。
一つ目が学習用データ。二つ目がモデルの推論結果を使ったデータ。
お好みで配色やグラフの種類を設定できる。
type: 'scatter'
で散布図となる。
data
の項目に描画するデータを設定する。
データの型は以下のように x 座標と y 座標の二つのパラメータで定めたデータの配列でよい。class ChartDataModel { constructor(x, y) { this.x = x; this.y = y; } }
dh.packPredictedDatasets(nnModel)
で、学習用データ x とその推論結果 y をzip化して取得している。
※ Chart.js とTensorflow.jsで扱う型が異なるため、型変換してまとめている。const zip = (array1, array2) => array1.map((_, index) => [array1[index], array2[index]]); packPredictedDatasets = (model) => { const tensorData = this.convertTensorDataX(); return zip( Array.from(tensorData.dataSync()), Array.from(this.tidyPredict(model, tensorData).dataSync()) ).map(([x, y]) => new ChartDataModel(x, y)); }描画
以下のようにお決まりの構文を記述すれば上記で設定した内容に従い、
canvas
タグに対してグラフを描画してくれる。const ctx = document.getElementById('canvas').getContext('2d'); window.chart = new Chart(ctx, { type: 'scatter', data: scatterData, option:{ title: { display: true, text: 'Chart.js Scatter Chart' }, scales: { xAxes: { ticks: { min: DOMAIN_MIN, max: DOMAIN_MAX, } } } } });
DOMAIN_hoge
は描画する x 軸の変域。描画データの更新
前述したモデルの学習処理にて 1 epoch ごとに呼ぶ
updateDrawing
処理では、
Chart.js で描画するデータを上書き更新処理をおこなうことで更新内容が反映される。const updateDrawing = async (epoch, log) => { window.chart.data.datasets[1].data = dh.packPredictedDatasets(nnModel); window.chart.update(); };おわりに改善メモ
- 最適化手法を選択化
- 分類の可視化
- データのアップロード
- 画面上でモデル作成
などなど。。
- 投稿日:2019-07-16T17:47:39+09:00
Slackメッセージ 10000 件制限対応: 古いメッセージを一括で削除する
Slackのフリープランで10000件制限をやりくりする
Slackはフリープランだと、10000件しかメッセージを保存できません。
古いものが見えなくなっても困らないけど、中には残しておきたいメッセージもありますよね。
そこで、スター付きのメッセージと、ピン留めしたメッセージは、残して置けるようにしました。目次
- 結論
- 手順
- 2-1. 実行環境を用意
- 2-2. ソースをダウンロード
- 2-3. あなたのSlackチームへのアクセス情報を設定
- 2-4. (オプション) プロキシの設定
- 2-5. 実行
1. 結論
最古の1000件を削除するプログラムを定期的に実行する
プログラムは、JavaScriptで書く
実行は、コマンドプロンプトから、Node.jsで行う2. 手順
2-1. 実行環境を用意
Node.jsをインストール
下図の中央左のボタンをクリックしてダウンロード
全部デフォルト選択でインストール2-2. ソースをダウンロード
Gitをインストールしている人は、以下のコマンドを実行
git clone https://github.com/teraoka-k/SlackWebAPI-deleteOldChat.git
Gitをインストールしていない人は、ここからzip形式でダウンロードできます
2-3. あなたのSlackチームへのアクセス情報を設定
ここからSlackのトークンを発行
緑色の「Create token」ボタンを押す
※Slack Teamの管理者権限のユーザが望ましい(閲覧権限のあるメッセージしか削除できないため)
間違って普通のユーザで、トークンを発行してしまっても、
「Re-issue token」ボタンを押せば、何度でもトークンを発行できるので大丈夫トークンが発行されたら、全選択して、コピーする
(※上図のxoxp-68094...部分がトークンです)
2-2.でダウンロードしたソースの、deleteOldMsgButPinnedOrStarred.jsをお好きなエディターで開く
下記Stringの値を先ほどコピーしたトークンで置き換えdeleteOldMsgButPinnedOrStarred.jsconst token = 'paste your token here';2-4. (オプション) プロキシの設定
仕事先だと、プロキシを設定する必要あり
Chromeを立ち上げて、右上の三点リーダから設定を開く
proxyと検索ボックスに入力して、Open proxy setting をクリック
LANの設定をクリックすると、アドレスとポートが見れるので、それぞれコピーする
以下のコメントを解除deleteOldMsgButPinnedOrStarred.js// const proxiedRequest = request.defaults({'proxy': 'http://${your-address}:${your-port-number}'});
先ほどコピーしたアドレスとポートを設定
deleteOldMsgButPinnedOrStarred.jsconst proxiedRequest = request.defaults({'proxy': 'http://your-corp-address:8080'});deleteOldMsgButPinnedOrStarred.jsを保存して閉じる
2-5. 実行
deleteOldMsgButPinnedOrStarred.jsがあるディレクトリで、コマンドプロンプトを開く
下記コマンドを実行
node deleteOldMsgButPinnedOrStarred.js
削除対象のチャット一覧が出力される
削除してよければ、下記のコメントアウトを解除して、再度実行すれば削除されるdeleteOldMsgButPinnedOrStarred.js/** delete chat */ // top1000OldestChatList.forEach(chat => // deleteChat(chat.channel, chat.ts).then(msg => // console.log(msg) // ) // );備考
Node.jsは、プロキシ環境下で動かないので、以下の設定が、追加で必要かもです
コマンドプロンプトで下記コマンド実行cmd for proxy setting of npm call npm -g config set proxy http://your-corp-address.co.jp:8080 call npm -g config set https-proxy http://your-corp-address.co.jp:8080 call npm -g config set registry https://registry.npmjs.org/ call npm config list※なお上記コマンドの2行目と3行目は、各自のプロキシのアドレスとポートに置き換えてください
4行目は固定です(ただしhttpsかhttpかは、Node.jsのバージョンによって替わったことがあります)
- 投稿日:2019-07-16T17:47:39+09:00
Slack10000 件制限: スター&ピンどめ以外1000件削除
Slackのフリープランで10000件制限をやりくりする
Slackはフリープランだと、10000件しかメッセージを保存できません。
古いものが見えなくなっても困らないけど、中には残しておきたいメッセージもありますよね。
そこで、スター付きのメッセージと、ピン留めしたメッセージは、残しておけるようにしました。目次
- 結論
- 手順
- 2-1. 実行環境を用意
- 2-2. ソースをダウンロード
- 2-3. あなたのSlackチームへのアクセス情報を設定
- 2-4. (オプション) プロキシの設定
- 2-5. 実行
1. 結論
最古の1000件(スター&ピンどめ以外)を削除するプログラムを定期的に実行する
プログラムは、JavaScriptで書く
実行は、コマンドプロンプトから、Node.jsで行う2. 手順
2-1. 実行環境を用意
Node.jsをインストール
下図の中央左のボタンをクリックしてダウンロード
全部デフォルト選択でインストール2-2. ソースをダウンロード
Gitをインストールしている人は、以下のコマンドを実行
git clone https://github.com/teraoka-k/SlackWebAPI-deleteOldChat.git
Gitをインストールしていない人は、ここからzip形式でダウンロードできます
2-3. あなたのSlackチームへのアクセス情報を設定
ここからSlackのトークンを発行
緑色の「Create token」ボタンを押す
※Slack Teamの管理者権限のユーザが望ましい(閲覧権限のあるメッセージしか削除できないため)
間違って普通のユーザで、トークンを発行してしまっても、
「Re-issue token」ボタンを押せば、何度でもトークンを発行できるので大丈夫トークンが発行されたら、全選択して、コピーする
(※上図のxoxp-68094...部分がトークンです)
2-2.でダウンロードしたソースの、deleteOldMsgButPinnedOrStarred.jsをお好きなエディターで開く
下記Stringの値を先ほどコピーしたトークンで置き換えdeleteOldMsgButPinnedOrStarred.jsconst token = 'paste your token here';2-4. (オプション) プロキシの設定
仕事先だと、プロキシを設定する必要あり
Chromeを立ち上げて、右上の三点リーダから設定を開く
proxyと検索ボックスに入力して、Open proxy setting をクリック
LANの設定をクリックすると、アドレスとポートが見れるので、それぞれコピーする
以下のコメントを解除deleteOldMsgButPinnedOrStarred.js// const proxiedRequest = request.defaults({'proxy': 'http://${your-address}:${your-port-number}'});
先ほどコピーしたアドレスとポートを設定
deleteOldMsgButPinnedOrStarred.jsconst proxiedRequest = request.defaults({'proxy': 'http://your-corp-address:8080'});deleteOldMsgButPinnedOrStarred.jsを保存して閉じる
2-5. 実行
deleteOldMsgButPinnedOrStarred.jsがあるディレクトリで、コマンドプロンプトを開く
下記コマンドを実行
node deleteOldMsgButPinnedOrStarred.js
削除対象のチャット一覧が出力される
削除してよければ、下記のコメントアウトを解除して、再度実行すれば削除されるdeleteOldMsgButPinnedOrStarred.js/** delete chat */ // top1000OldestChatList.forEach(chat => // deleteChat(chat.channel, chat.ts).then(msg => // console.log(msg) // ) // );備考
Node.jsのパッケージマネージャーnpmは、プロキシ環境下で動かないので、以下の設定が、追加で必要かもです
コマンドプロンプトで下記コマンド実行cmd for proxy setting of npm call npm -g config set proxy http://your-corp-address.co.jp:8080 call npm -g config set https-proxy http://your-corp-address.co.jp:8080 call npm -g config set registry https://registry.npmjs.org/ call npm config list※なお上記コマンドの2行目と3行目は、各自のプロキシのアドレスとポートに置き換えてください
4行目は固定です(ただしhttpsかhttpかは、npmのバージョンによって替わったことがあります)
- 投稿日:2019-07-16T16:22:13+09:00
脱jQueryしてみて、憧れのVue.jsでLPコーディングしたとき必要だった知識たち
こんちは、森高千里の「17才」を聴きながらサーモン春巻き食べてます。
Perfumeがカバーしたら素敵になると思ってます。先日、
LPを禁jQueryで、ピュアJsでコーディングしてみました。
(ちなみに私はjQuerもLoveです。)
トキメキ不足だったのでとても楽しかったです!皆さん試したいのではないでしょか!その時のTips私的メモです。
欲しい情報が、公式やいろんなブログに散らばってたので改良してまとめました。(ps.世間を知るたび無力な自分の記事投稿って怖いですよね)
ひとまずVue.jsのハローワールドを初体験しました。
vue.html<div id="app"> {{ message }} </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> //el:にはVue.jsであれこれしたい部分のIDを new Vue({ el: '#app', data: { message: '憧れのVue,js なんかオシャレ' } }) </script>これでHTMLの{{ message }}にjs側のmessageのテキストが表示されました!
お次は、クリック検知。死ぬほど使うonclickです。
click.js$("#target").on("click",function(){ $(this).text("へロー"); });jQuery だとこうですよね。
click.html<div id="app"> <button v-on:click="changeBg">背景変更</button> </div> <script> // v-on:click="ここ" で関数名を登録するようです。 // methods:内に内容を記述 new Vue({ el: '#app', data: { message: 'ハロー Vue.js!' }, methods: { changeBg: function () { document.body.style.backgroundColor = 'red'; } } }) </script>el: '#app'の中に入ってなきゃダメみたいです。
v-on:click="ここ" で関数名を登録するようです。
methods:の中にどんどん関数を登録してくわけですね! おっけー!toggleClassの検知 これがきっと味噌や!
onclickしたらtoggleClassをvue.jsでやります!!
click.js$("#target").on("click",function(){ $(".target").toggleClass("active"); });jQuery だとこうですよね。
sample:vue.html <div id="app"> <p class="target" v-bind:class='{active:isActive}'>表示されるエリア</p> <button v-on:click='isActive=!isActive'>クラスtoggleボタン</button> </div> <style> .target{ transition:3s all; opacity:0; } .active{ opacity:1.0; } </style> <script> data: { message: '憧れのVue,js なんかオシャレ', isActive: false, } </script>簡単なので落ち着いて解説読んでください
v-on:click='isActive=!isActive'の指定で、
クリックするたびに
isActiveがtrueかfalseに切り替わり(toggle)
.activeがついたり消えたりする仕組みです!
vueの方はisActive: false,のフラグを書いてあげるだけですスクロールイベント,リサイズイベントの検知 これもめっちゃ使いますよね!
click.js// スクロール $(window).on("scroll",function() { console.log($(this).scrollTop()); }); // リサイズ var w = 0; $(window).on('load resize',function(){ w = $(window).width(); if(w < 768){ // スマホごにょごにょ } });jQuery だとこうですよね。
そして肝心のvueバージョン!
これはもうシンプルに、これでいいのかな。。vue.html<div id="app"> <p style="height:4000px;"></p> </div> <script src="https://cdn.jsdelivr.net/npm/vue"></script> <script> //el:にはVue.jsであれこれしたい部分のIDを new Vue({ el: '#app', data: { scrollY: 0, windowWidth: 0, loadWidth:0, }, //mounted DOMにアクセスできるようになった状況 mounted: function(){ window.addEventListener('scroll', this.handleScroll); window.addEventListener('resize', this.handleResize); window.addEventListener('load', this.handleLoadSize); }, methods: { handleResize: function(){ this.windowWidth = window.innerWidth; console.log(this.windowWidth); }, handleScroll: function() { this.scrollY = window.scrollY; console.log(this.scrollY); }, handleLoadSize: function(){ this.loadWidth = window.innerWidth; console.log(this.loadWidth); } } }) </script>感想は、超絶楽しかったけど、なんかもう途中で何やってんだ間でてきて、普通のシンプルなLPはjQueryとCSSで綺麗に書いて作ればいいと思いました。笑
使い方が甘いのかな!毎回データ取ってきて値が変わったりするインタラクティブなやつは向いてるんだなと思いましたが、どうなんでしょかね。フレームワークだしな、、。泣
あんまフレームワークやらんから、、泣しかし記事にしてアウトプットすると整理されて理解が深まります。
以上、ご静聴有難うごいました。
お前こっちのが楽だぞとか、ここ違うぞとかあれば教えてください。
(プラグインとか調べて、使いこなせばもっと楽なんだろなとこっそり気づいてます笑)最後になりましたが、僕はjQuery好きです。
- 投稿日:2019-07-16T16:05:05+09:00
ReactNativeの開発環境を構築する話(Windows編)
はじめに
以前に書いたReactNativeの開発環境を構築する話(Mac編)のWindows版です。
前提
・AndroidStudio
React NativeでAndroidアプリ @ Windows 備忘録
・Node.js
[Node.js] Node.js の導入(Windows編)
・VSCode
Visual Studio Code (Windows版) のインストール各種設定・インストール
1. yarnとreact-native-cliインストール
npm install -g yarn yarn -g add react-native-cli2. 必須モジュールインストール
ここは各プロジェクトによって内容が異なってくるかと思います。
お好きなようにアレンジしてください。react-native init【プロジェクト名】 —version 0.59.9 cd【プロジェクト名】 yarn add —dev typescript yarn add —dev react-native-typescript-transformer yarn add —dev @types/react @types/react-native yarn tsc —init —pretty —jsx react mkdir src move App.js src/App.tsx copy nul rn-cli.config.js yarn add —dev prettier yarn add —dev tslint tslint-react yarn add prettier —dev tslint-config-prettier tslint-plugin-prettier copy nul tslint.json copy nul images.d.ts3. tsconfig.jsonを修正
下記の値を追加・修正します。
tsconfig.json{ "compilerOptions": { "lib": ["es2015"], "allowSyntheticDefaultImports": true, }, "exclude": [ "node_modules" ] }4. index.jsのApp参照先を修正
App.jsをsrcに動かしたのでindex.jsを修正します。
index.js/** * @format */ import {AppRegistry} from 'react-native'; // import App from './App'; // del import App from './src/App'; // add import {name as appName} from './app.json'; AppRegistry.registerComponent(appName, () => App);5. rn-cli.config.jsとpackage.jsonを修正
※0.57以降ではrn-cli.config.jsは不要と聞いたこともあります。
rn-cli.config.jsmodule.exports = { getTransformModulePath() { return require.resolve("react-native-typescript-transformer"); }, getSourceExts(){ return["ts","tsx"]; } }package.jsonは以下を追加
package.json"scripts": { "run-ios": "react-native run-ios", "run-android": "react-native run-android" },6. tslint.jsonとimages.d.tsを修正
tslin設定も正直お好みです。
tslint.json{ "extends": ["tslint:recommended", "tslint-react", "tslint-config-prettier"], "rules": { "jsx-no-lambda": false, "member-access": false, "interface-name": false, "prefer-for-of": false, "ordered-imports": false, "object-literal-sort-keys": false, "no-console": false }, "linterOptions": { "exclude": [ "config/**/*.js", "node_modules/**/*.js", "coverage/lcov-report/*.js" ] } }images.d.tsdeclare module '*.svg'; declare module '*.png'; declare module '*.jpg'; declare module '*.jpeg'; declare module '*.gif'; declare module '*.bmp'; declare module '*.tiff';7.実行
Androidエミュレータを起動した状態で下記コマンドを実行
yarn run-android感想
Macの場合と特に差異がないため問題なく動くかと思いますが、
何か記載漏れ等ありましたらご連絡ください。また、ここで記載した内容が少しでも参考になれば幸いです。
- 投稿日:2019-07-16T13:49:50+09:00
【Firebase Hosting】SPAなサイトでPage Not foundとでた
React.js(SPA)で構築したサイトをFirebase Hostingを用いてデプロイすると、
以下のように、404 Page Not Found
と表示された。firebase.jsonでのHostingの設定に問題があったらしい。
変更前
firebase.json{ "storage": { "rules": "storage.rules" }, "hosting": { "public": "build", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ] } }変更後
firebase.json{ "storage": { "rules": "storage.rules" }, "hosting": { "rewrites": [ { "source": "**", "destination": "/index.html" } ], "public": "build", "ignore": [ "firebase.json", "**/.*", "**/node_modules/**" ] } }これを追加した。
"rewrites": [ { "source": "**", "destination": "/index.html" } ],参照: 公式ドキュメント
- 投稿日:2019-07-16T13:46:25+09:00
46日目。Javascriptで配列を作ってまとめて更新、メンテしやすくなりました。
品物のリストをずらっと表示しているホームページの改修をしています。
今後もしょっちゅう入れ替えがありそうなのでJavaScriptをつかってみました。
覚えたら使ってみたいじゃないですか!?test.jsconst Targets = [ ["butaman.png", "横浜", "ぶたまんケーキ","2019/07/11 11:00","出荷済み"], ["takoyaki.png", "大阪", "たこやきケーキ","2019/07/11 14:00","製造中"], ・・・ (中略) ・・・ ["higuma.png", "札幌", "ひぐまケーキ","2019/07/12 11:00","スタンバイ"] ] let str = ""; for (i=0; i<Targets.length; i++ ){ Target = Targets[i] str = str + `<div class='frame'>`; str = str + `<img class='left' src="${Target[0]}" >`; str = str + `<div class='store'>${Target[1]}</div>`; str = str + `<div class='prod'> ${Target[2]}</div>`; str = str + `<div class='time'> ${Target[3]}</div>`; str = str + `<img class='right' src="${Target[4]}">`; str = str + `</div>` } const firstscript = function() { document.getElementById('eid').innerHTML = str; }; window.addEventListener("DOMContentLoaded", firstscript);品目がたくさんあると更新が大変!
HTMLにずらっと書かれていたものを、配列にまとめて更新しやすくしてみました。
これをforでくるくる回してHTMLに返しています。
document.getElementById
だけだと更新されなかったのでaddEventListener
で開いたときに起動させています。
【JavaScript入門】addEventListener()によるイベント処理の使い方!最初はPHPでフォームを作ってと考えたのですが、サーバーのメンテが難しそうなので見送り。
次にCSVファイルを読み込む作戦を試したのですが、Chromeではセキュリティ上原則禁止。見送りました。
onclick()でコンテンツを変えられないかな?と思ったのですが、複数箇所のクリック画像がぜんぶ同時に更新される謎現象がわからなくて見送り。
Javascriptでローカルファイル(file://)を読み込んでみる
【JavaScript入門】すぐわかる!画像を切り替える方法
あきらめて一行ずつコツコツ更新を進めつつ
Progateを復習してみたら配列に変数をどんどん入れて、forでくるくる回す方法がちょうど出てきました。さっそく使ってみたらいい感じになりました。この方法にたどりつくまでが、えらい大変でした。。。
所要時間
・PHPで試作・・・2時間
・CSV方式の試作・・・1時間
・onclickの試作・・・1時間
・Progate Javascript1,2の復習・・・1時間
・配列に格納、forで回す・・・1時間
- 投稿日:2019-07-16T13:32:35+09:00
あなたのWEBサイトを爆速でダークモードにする(Darkmode.js)
はじめに
macOS Mojaveからダークモードが搭載され、i0S13でもダークモードが採用される予定です。流行っているこのダークモードは、省エネ・目に優しいといったメリットがあるそうですが、個人的には、単純に「かっこいい」と思っています。(素人から見ると、「こいつ・・できるな・・」を演出してくれる、と勝手に思っています)
爆速でWEBサイトをダークモードに Darkmode.js
アプリによっては、すでにダークモードや黒背景ができるようになっているものがありますが、自分のWebサイトを爆速でダークモードにしてくれる Darkmode.js というのを見つけましたので、やってみました。
といってもメチャクチャ簡単で、たったの4行。
CSS: mix-blend-mode を使用しているとのことなので、Can I USE でサポートを調べておくとよいかと思います。
追加するコード
sample.html<script src="https://cdn.jsdelivr.net/npm/darkmode-js@1.3.4/lib/darkmode-js.min.js"></script> <script> new Darkmode().showWidget(); </script>オプション
sample.html<script src="https://cdn.jsdelivr.net/npm/darkmode-js@1.3.4/lib/darkmode-js.min.js"></script> <script> var options = { bottom: '64px', // default: '32px' right: 'unset', // default: '32px' left: '32px', // default: 'unset' time: '0.5s', // default: '0.3s' mixColor: '#fff', // default: '#fff' backgroundColor: '#fff', // default: '#fff' buttonColorDark: '#100f2c', // default: '#100f2c' buttonColorLight: '#fff', // default: '#fff' saveInCookies: false, // default: true, label: '?', // default: '' autoMatchOsTheme: true // default: true } const darkmode = new Darkmode(options); darkmode.showWidget(); </script>サンプル
サンプル1
サンプル2
サンプル3
余談
Qiitaタイトルに「爆速」をつけるのに、とても憧れていたので、今回使ってみました。。
参照URL
https://coliss.com/articles/build-websites/operation/javascript/add-dark-mode-to-your-site.html
- 投稿日:2019-07-16T13:12:38+09:00
Vue + AWS Amplifyでシンプルな画像共有アプリを作成する (作成〜デプロイ) #2
前 → Vue + AWS Amplifyでシンプルな画像共有アプリを作成する (概要) #1
〜この記事でやること〜
- VueProjectの作成
- Amplifyの導入
- Auth ( Cognito )
- Storage ( S3 )
- 作成したprojectをAmplifyConsoleからBuild & Deploy
VueProjectの作成
まずはProjectを作成します。
■ 前提条件
- Node.js
- npm
がインストールされていること
■ 参考
Node.js / npm をインストール (Mac環境)
Node.js / npmをインストールする(for Windows)
とりあえず Ubuntu で新しい Node.js, npm をインストールVueCLIのinstall
$ npm install -g @vue/cliVueProjectの作成
今回は全部盛りにしていますが各自好きなように選択してください。
※この記事は
class-style
ではなくobject-style
で書きます$ vue create <your-project-name> Vue CLI v3.9.2 ? Please pick a preset: Manually select features ? Check the features needed for your project: Babel, TS, PWA, Router, Vuex, CSS Pr e-processors, Linter, Unit, E2E ? Use class-style component syntax? No ? Use Babel alongside TypeScript (required for modern mode, auto-detected polyfill s, transpiling JSX)? Yes ? Use history mode for router? (Requires proper server setup for index fallback in production) Yes ? Pick a CSS pre-processor (PostCSS, Autoprefixer and CSS Modules are supported by default): Sass/SCSS (with node-sass) ? Pick a linter / formatter config: Standard ? Pick additional lint features: Lint on save, Lint and fix on commit ? Pick a unit testing solution: Jest ? Pick a E2E testing solution: Cypress ? Where do you prefer placing config for Babel, PostCSS, ESLint, etc.? In package. json ? Save this as a preset for future projects? No起動してみる
$ yarn serve
このように表示されればOK
GitHubへpush
Remoteリポジトリの追加
$ git remote add origin <your-remote-repository>
Push
$ git add . $ git commit -m "first commit" $ git push origin masterAmplifyの導入
Amplifyを導入するにあたって
develop
ブランチを作成しておきます。developブランチの作成
$ git checkout -b developAmplify-CLIのinstall
$ npm install -g @aws-amplify/cli $ amplify -v 1.8.1次に、AWSアカウントを紐付けます。以下のコマンドを打つとAmplifyで使用するユーザーを新規作成することが出来ます。
詳しくはAWS Amplify クイックスタートを参考にしてください。$ amplify configure
もし既にAWSのユーザーがあり、そちらを使う場合はこの作業は必要ありません。
AWS Backendの設定
Amplify-CLIのプロジェクトの設定を選択することが出来ます。
amplifyの環境名はdev
にしています。$ amplify init ? Enter a name for the project <project name> ? Enter a name for the environment dev ? Choose your default editor: Visual Studio Code ? Choose the type of app that you're building typescript Please tell us about your project ? What javascript framework are you using vue ? Source Directory Path: src ? Distribution Directory Path: dist ? Build Command: yarn build ? Start Command: yarn serve設定が終わるとしばらく動いた後にS3にbucketが作成されます。
これはDeploy用のCloudFormationTemplateを配置するbucketになります。
Amazon Cognitoの追加
ユーザー管理・認証をしたいのでAmazonCognitoを使えるようにします。
commandで簡単に作成出来ます。$ amplify auth add // Manualを選択 Do you want to use the default authentication and security configuration? Manual configuration // IAM controlをするかどうか Select the authentication/authorization services that you want to use: User Sign-Up, Sign-In, connected with AWS IAM controls (Enables per-user Storage features fo r images or other content, Analytics, and more) Please provide a friendly name for your resource that will be used to label this category in the project: <label-name> Please enter a name for your identity pool. <identity-pool-name> Allow unauthenticated logins? (Provides scoped down permissions that you can cont rol via AWS IAM) No // GoogleアカウントやTwitterアカウントでのサインアップ(今回はなし) Do you want to enable 3rd party authentication providers in your identity pool? No Please provide a name for your user pool: <user-pool-name> // サインイン時に使える属性(今回はユーザー名) Warning: you will not be able to edit these selections. How do you want users to be able to sign in? Username // 多要素認証 Multifactor authentication (MFA) user login options: OFF // Emailを使って登録変更する Email based user registration/forgot password: Enabled (Requires per-user email e ntry at registration) // アカウント認証コード送付用の文面 Please specify an email verification subject: Your verification code Please specify an email verification message: Your verification code is {####} // パスワードポリシー(デフォルト使用) Do you want to override the default password policy for this User Pool? No // 登録時に必要な属性の選択(今回はEmailのみ) Warning: you will not be able to edit these selections. What attributes are required for signing up? (Press <space> to select, <a> to tog gle all, <i> to invert selection)Email Specify the app's refresh token expiration period (in days): 30 Do you want to specify the user attributes this app can read and write? No // OAuth(今回はなし) Do you want to enable any of the following capabilities? (Press <space> to select , <a> to toggle all, <i> to invert selection) Do you want to use an OAuth flow? No // Lambdaトリガー(今回はなし) ? Do you want to configure Lambda Triggers for Cognito? No Successfully added resource <project-name> locallyこれでOKです。
S3 Storageの追加
今回は画像共有アプリなので画像を保存するbucketを用意します。
$ amplify storage add // Imageが含まれている方を選択 ? Please select from one of the below mentioned services Content (Images, audio, v ideo, etc.) ? Please provide a friendly name for your resource that will be used to label this category in the project: <label-name> ? Please provide bucket name: <bucket-name> // 認証済みユーザーのみがアクセスできる ? Who should have access: Auth users only // どこまでの権限を与えるか ? What kind of access do you want for Authenticated users? create/update, read, de lete // Lambdaトリガー(今回はなし) ? Do you want to add a Lambda Trigger for your S3 Bucket? No Successfully added resource <label-name> locallyリソースの作成
amplifyの状態を確認します。
$ amplify status Current Environment: dev | Category | Resource name | Operation | Provider plugin | | -------- | ------------- | --------- | ----------------- | | Auth | resourcename | Create | awscloudformation | | Storage | resourcename | Create | awscloudformation |AuthとStorageがCreateの状態になっていますが、あくまでLocalの設定が追加されただけでリソースは作成されません。
実際にリソースを作成するには$ amplify push
を実行します。色々走った後に、
✔️ All resources are updated in the cloud
となればOKです。
Cloud上にS3 bucketとCognitoのUserpoolが作成されているのが確認できます。変更をGitHubにpush
$ git add . $ git commit -m "Add amplify ~" $ git push origin developAmplify Consoleを使ってDeploy
いよいよdeployです。
まずはAmazon Amplify Consoleを開きましょう。アプリの作成をクリックするとGitプロバイダーを選択する項目があるのでGitHubを選択してcontinueをクリック
対象のリポジトリとブランチを選択して次へをクリック
バックエンドの設定の画面が表示されます。
Amplifyの環境名を選択し、AmplifyConsole用のロールを選択します。無い場合はCreate new role
を選択「アプリの作成」を押すと早速ビルドが始まります。
ビルドが終わり、プロビジョンから検証までオールグリーンであればDeploy成功です!
ドメインのところに書かれているURLを開いて以下のように表示されればOK!Basic認証を追加
このままだと誰でもアクセス出来てしまうので簡単な認証を追加することでそれを防ぎます。
Amplify Console左メニュの「アクセスコントロール」をクリック
「アクセスの管理」をクリック
Apply a global password
を ON にしてusername
とpassword
を設定してSave
をクリック
キャッシュをクリアしてからURLを開いて以下のように表示されればOKです!
終わりに
VueProjectの作成からリソースの生成、ビルド&デプロイまで行いました。
次回はUIデザインを詰めていこうと思います。
ありがとうございました!前 → Vue + AWS Amplifyでシンプルな画像共有アプリを作成する (概要) #1
次 → Vue + AWS Amplifyでシンプルな画像共有アプリを作成する (UIデザイン) #3
- 投稿日:2019-07-16T13:05:55+09:00
【web開発� PHP】同ページにHTTPを行いたい!
はじめに
webサービスを開発する際に便利だと思ったので書き起こしています。ButtonやAnchorを用いて同ページに変数を送信(HTTP)したいときに使います。例えば、ページ上のカテゴリボタンを押すとそのページ上でそのカテゴリのみのコンテンツが絞られたものが表示されたり。
以下のコード例では同ページ内に変数を渡す部分を記述しています。解決法
こちらではPOST送信で書いておりますが、GETでも可。
PHPファイル先頭に以下を記述。もしPOST送信があればdataを受け取りPOSTデータを削除するだけのシンプルなコードです。if (isset($_POST["btnid"])) { $data = $_POST["btnid"]; unset($_POST); }さて、以下のコードが鍵となります。こちらはform要素ですが、画面上には表示されないようになっております。valueには後に送信したい変数を格納するのでデフォルトでは空白。
<form name="form1"> <INPUT type="hidden" id="btnid" name="btnid" value=""/> </form>以下のコードではid="id"である要素をクリックするとvalueに送信したい変数を格納して送信するというものであり、これが実行されると、もう一度index.phpの先頭行から処理ををするため\$data変数に$_POST["btnid"]が送信されるというシンプルなものです。
$("#id").on("click", function(e){ $("#btnid").val(data); //送信したいデータをvalue値として登録 let form1 = document.forms["form1"]; form1.method = "POST"; //HTTP方式の決定 form1.action = "index.php"; //遷移先を同ファイルに設定 form1.submit(); //送信 return false; });未解決事項
色々調べてみたものの、一番よく見かけたのはAjaxでの送信でしたが今回はうまく実装できませんでした。AjaxはGoogle検索する時に、文字を少しタイプすると下に検索予想が出てくる機能に使われているようで、常にHTTP方式で送信している感じらしいです。おそらく、今回実装できなかったのは要素がクリックした時にのみ発火されるというものに相性が良くなかったのかなと思いますが、今後の実装でもう一度勉強しようと思います。
- 投稿日:2019-07-16T13:05:55+09:00
【web開発】PHPで同ページにHTTPを行いたい!
はじめに
webサービスを開発する際に便利だと思ったので書き起こしています。ButtonやAnkerを用いて同ページに変数を送信(HTTP)したいときに使います。例えば、ページ上のカテゴリボタンを押すとそのページ上でそのカテゴリのみのコンテンツが絞られたものが表示されたり。
以下のコード例では同ページ内に変数を渡す部分を記述しています。解決法
こちらではPOST送信で書いておりますが、GETでも可。
PHPファイル先頭に以下を記述。もしPOST送信があればdataを受け取りPOSTデータを削除するだけのシンプルなコードです。if (isset($_POST["btnid"])) { $data = $_POST["btnid"]; unset($_POST); }さて、以下のコードが鍵となります。こちらはform要素ですが、画面上には表示されないようになっております。valueには後に送信したい変数を格納するのでデフォルトでは空白。
<form name="form1"> <INPUT type="hidden" id="btnid" name="btnid" value=""/> </form>以下のコードではid="id"である要素をクリックするとvalueに送信したい変数を格納して送信するというものであり、これが実行されると、もう一度index.phpの先頭行から処理ををするため\$data変数に$_POST["btnid"]が送信されるというシンプルなものです。
$("#id").on("click", function(e){ $("#btnid").val(data); //送信したいデータをvalue値として登録 let form1 = document.forms["form1"]; form1.method = "POST"; //HTTP方式の決定 form1.action = "index.php"; //遷移先を同ファイルに設定 form1.submit(); //送信 return false; });未解決事項
色々調べてみたものの、一番よく見かけたのはAjaxでの送信でしたが今回はうまく実装できませんでした。AjaxはGoogle検索する時に、文字を少しタイプすると下に検索予想が出てくる機能に使われているようで、常にHTTP方式で送信している感じらしいです。おそらく、今回実装できなかったのは要素がクリックした時にのみ発火されるというものに相性が良くなかったのかなと思いますが、今後の実装でもう一度勉強しようと思います。
- 投稿日:2019-07-16T12:37:48+09:00
RPGツクールMVでCanvas描画処理が反映されない原因と対処方法
概要
- RPGツクールMVのスクリプトで、Sprite.bitmap.contextを用いてCanvas操作を行う際、context側でclearするのではなく、bitmap側でclear等をしないと描画が正常に行われない。
発端
- 昔々ある日のこと、とあるスーパーツクラーがRPGツクールMV上で直線を描くためのプラグインを作っていました。
スーパーツクラーは瞬く間に下記のようなコードを作成しました。
Sprite_Line_Straight.prototype.update = function() { Sprite.prototype.update.call( this ); // Sprite.bitmap.contextを操作して線を描画する let context = bitmap.context; // NOTE: 前フレームで描画した内容を一旦クリアする context.clear(); // NOTE: 左上から右下へ直線が毎フレーム伸びていく描画処理 context.beginPath(); context.moveTo( 0, 0 ); context.lineTo( ( 10 * this.i ) , ( 10 * this.i ) ); context.stroke(); context.closePath(); this.i ++; }しかし実際に上記のコードを実行しても、線が全く表示されません。
ところが
this.i
の値は正常にインクリメントされており、処理自体は動いています。にもかかわらず全く線が描画されないので、スーパーツクラーはブチギレ寸前になりました。
スーパーツクラーは怒りのあまり創造した世界を破壊しようと、プロジェクトをゴミ箱に配置しかけました。
原因
- 単にSprite.bitmap.context側の描画メソッドを実行しても、Bitmapの描画は反映されない。
- Bitmapの描画を反映するためには、clearを含めたBitmap側の描画メソッドを実行する必要がある。
方法
- clear
let bitmap = this._lineCanvas.bitmap; bitmap.clear();- clearRect
let x = 0; let y = 0; let width = 816; let height = 624; let bitmap = this._lineCanvas.bitmap; bitmap.clearRect( x, y, width, height );なんで?
- Bitmapの変更内容が実際に描画されるのは、Bitmap._baseTexture.update()が実行された時。
Bitmap.prototype.createBaseTexture = function(source){ this._baseTexture = new PIXI.BaseTexture(source); … };Bitmap.baseTexture.update()が実行されるのは、Bitmap._setDirty()が呼び出され、Bitmap.dirtyがtrueになった時。
Bitmap.prototype._setDirty = function() { this._dirty = true; }; Bitmap.prototype.checkDirty = function() { if (this._dirty) { this._baseTexture.update(); this._dirty = false; } };Bitmap._setDirty()は実行されるのは、Bitmap.clear()を含めた、Bitmapに定義された描画関連のメソッドが呼ばれた時。
Bitmap.prototype.clear = function() { this.clearRect(0, 0, this.width, this.height); }; Bitmap.prototype.clearRect = function(x, y, width, height) { this._context.clearRect(x, y, width, height); this._setDirty(); };つまりBitmap.context側のメソッドを呼び出しても、Bitmap._setDirty()が実行されないので、描画が行われなかった!
- これ初見で気づくやつおるか~?
- なお、Bitmap.clear系メソッドは、Bitmap.context.clearRectをラップしているので、クリア処理内容に差異はない。
正しい書き方は?
Sprite.bitmap.context側のメソッドを実行する一連の処理の前に、Sprite.bitmap.clear()を実行しておく。
Sprite_Line_Straight.prototype.update = function() { // 実はこれを呼んでもBitmap._setDirty()が実行されるわけではない。 // TODO: スプライトの状態によっては呼び出されるかも?要検証。 Sprite.prototype.update.call( this ); // 前フレームで描画した内容を一旦クリアする let bitmap = this.lineCanvas.bitmap; bitmap.clear(); // クリアされるのが嫌ならSprite.bitmap.setDirty()を呼び出すとか? // bitmap._setDirty(); // Sprite.bitmap.contextを操作して線を描画する // NOTE: 左上から右下へ直線が毎フレーム伸びていく描画処理 let context = bitmap.context; context.beginPath(); context.moveTo( 0, 0 ); context.lineTo( ( 10 * this.i ) , ( 10 * this.i ) ); context.stroke(); context.closePath(); this.i ++; }結果
- 原因を突き止めたスーパーツクラーは一瞬のうちにコードを修正、無事に直線を引くプラグインを作成することができました。
- 落ち着きを取り戻したスーパーツクラーはプロジェクトの削除作業を中断し、世界に平和が訪れましたとさ、めでたしめでたし。
- 投稿日:2019-07-16T12:26:41+09:00
VSCodeを使ったJavaScriptのモジュール抽出
背景
ブラウザ向けJavaScriptの開発でも、BrowserifyやWebpackといったモジュールバンドルツールを作うことで、ファイル単位でモジュールを分割した開発が可能になりました。
すでにあるソースコードを、モジュールにわける操作は、機械的に実施できますが、手間です。
VSCodeのリファクタリング機能を使うことで、機械的な手間を軽減することができます。方法
注意:今回はすでにファイル内にprivateなstatic関数がある状況を対象とします。
privateなstatic関数を抽出する方法は対象としません。対象のサンプル
ファイル
以下のように、exportする関数とは別に、privateなstatic関数を含むモジュールがあります。
このprivateなstatic関数をモジュールとして抽出します。index.jsexport default function(annotationData, modeAccordingToButton) { return function(selectionModel) { const modifications = selectionModel.all().map((e) => annotationData.getModificationOf(e).map((m) => m.pred)) updateModificationButton(modeAccordingToButton, 'Negation', modifications) updateModificationButton(modeAccordingToButton, 'Speculation', modifications) } } function updateModificationButton(modeAccordingToButton, specified, modificationsOfSelectedElement) { // All modification has specified modification if exits. modeAccordingToButton[specified.toLowerCase()] .value(doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement)) } function doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement) { if (modificationsOfSelectedElement.length < 0) { return false } return modificationsOfSelectedElement.length === modificationsOfSelectedElement.filter((m) => m.includes(specified)).length }
updateModificationButton
関数と、doesAllModificaionHasSpecified
関数をモジュールとして抽出していきます。モジュール抽出
updateModificationButton関数
VSCodeでは、privateなstatic関数 をフォーカスすると、ヒントアイコンが表示されます。
ヒントアイコンをクリックすると新しいファイルへ移動します
と提案が表示されます。これをクリックすると選択したprivateなstatic関数が別ファイル
updateModificationButton.js
に分かれます。updateModificationButton.jsimport { doesAllModificaionHasSpecified } from "./index"; export function updateModificationButton(modeAccordingToButton, specified, modificationsOfSelectedElement) { // All modification has specified modification if exits. modeAccordingToButton[specified.toLowerCase()] .value(doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement)); }選択した
updateModificationButton
関数は、新しく作成されたupdateModificationButton.js
に移動されています。index.jsimport { updateModificationButton } from "./updateModificationButton"; export default function(annotationData, modeAccordingToButton) { return function(selectionModel) { const modifications = selectionModel.all().map((e) => annotationData.getModificationOf(e).map((m) => m.pred)) updateModificationButton(modeAccordingToButton, 'Negation', modifications) updateModificationButton(modeAccordingToButton, 'Speculation', modifications) } } export function doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement) { if (modificationsOfSelectedElement.length < 0) { return false } return modificationsOfSelectedElement.length === modificationsOfSelectedElement.filter((m) => m.includes(specified)).length }代わりに、
index.js
にはimport { updateModificationButton } from "./updateModificationButton";
の一文が追加されます。相互import問題
気になる点は
doesAllModificaionHasSpecified
関数の前にexport
が追加されている点です。
updateModificationButton
関数内でdoesAllModificaionHasSpecified
関数を使っているため、新しく作ったupdateModificationButton.js
からdoesAllModificaionHasSpecified
を参照する必要があるためです。
updateModificationButton.js
を確認するとimport { doesAllModificaionHasSpecified } from "./index";
文があるのがわかります。
updateModificationButton.js
とindex.js
で、お互いにimportしあっていて気持ち悪いです。doesAllModificaionHasSpecified
気にせず
doesAllModificaionHasSpecified
も新しいファイルへ移動します
。すると
doesAllModificaionHasSpecified
関数はdoesAllModificaionHasSpecified.js
ファイルにわかれます。
updateModificationButton.js
は次のように修正されます。import { doesAllModificaionHasSpecified } from "./doesAllModificaionHasSpecified"; export function updateModificationButton(modeAccordingToButton, specified, modificationsOfSelectedElement) { // All modification has specified modification if exits. modeAccordingToButton[specified.toLowerCase()] .value(doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement)); }
updateModificationButton.js
はdoesAllModificaionHasSpecified.js
を参照し、index.js
への参照はなくなりました。
一的に相互import問題は発生しますが、すべてのprivateなstatic関数をモジュールとして抽出すれば、自然に解消されます。その他
一つの関数をexportするときは名前付きエクスポートより、デフォルトエクスポートの方が望ましいと考えています。
次のように修正します。index.jsimport updateModificationButton from "./updateModificationButton"; export default function(annotationData, modeAccordingToButton) { return function(selectionModel) { const modifications = selectionModel.all().map((e) => annotationData.getModificationOf(e).map((m) => m.pred)) updateModificationButton(modeAccordingToButton, 'Negation', modifications) updateModificationButton(modeAccordingToButton, 'Speculation', modifications) } }updateModificationButton.jsimport doesAllModificaionHasSpecified from "./doesAllModificaionHasSpecified"; export default function(modeAccordingToButton, specified, modificationsOfSelectedElement) { // All modification has specified modification if exits. modeAccordingToButton[specified.toLowerCase()] .value(doesAllModificaionHasSpecified(specified, modificationsOfSelectedElement)); }doesAllModificaionHasSpecified.jsexport default function(specified, modificationsOfSelectedElement) { if (modificationsOfSelectedElement.length < 0) { return false; } return modificationsOfSelectedElement.length === modificationsOfSelectedElement.filter((m) => m.includes(specified)).length; }まとめ
VSCodeのリファクタリング機能を使うことで、JavaScriptのファイル分割の手間が軽減できました。
これによってBrowserifyやWebpackをつかった、モジュール分割の恩恵を受けやすくなります。
小さな独立したファイルにわけることで、以下のような恩恵が受けられます。
- ソースコードの影響範囲が明確になり、修正しやすくなる
- 複数人で修正作業をした時に、衝突が起きる範囲が狭くなる
参考
- 投稿日:2019-07-16T11:48:56+09:00
短いURLでおねだりしよう
背景
- amazonの商品ページをチャットでやりとりする機会があり、長いURLがウザかった。メールに貼りつけると折り返されてURLが途切れてしまうこともあるかと(いまどき無い!?)
- wikiを引用するときもURLが長い
例
amazon, wiki のURLは短くできる。(≠短縮URL)
amazonは商品コードで表示できる
https://www.amazon.co.jp/dp/B07P6VVPLV
https://www.amazon.co.jp/dp/B077YCQ265wikiは記事のidで表示できる
https://ja.wikipedia.org/?curid=4698短縮URLはサービス経由で短くするので、サービス終了になると使えなくなってしまう。
以下はアイルトンセナのwikiのURLを短くしたもの
https://bit.ly/30tuVICamazon, wiki のURLを短くするツールはあったけどめんどくさい
chrome extension で短いURLをサクっとコピーできるようにしよう
3年ぶりくらいにchrome extension に着手。下記ブレストも参照
仕様
- 右クリメニューでクリップボードにコピー
- アイコンでクリップボードにコピー
- 対象外のサイトでは右クリメニューを出さない
- 対象外のサイトではアイコンを無効化 ← 実現できず@TODO
ブレスト
右クリメニューはサクっと使えるので決まり。以前、作ったこともある。
貼り付けが目的なので、クリップボードにコピーしたい。→調べたら可能だったので採用
アイコンは使用しないつもりが、勝手に表示されるので、押したらコピーできるようにした。
右クリメニューを押したときコピーされたか分からないので、メッセージを出そうと思ったが、それもウザいので、対象のサイトのみ右クリメニューが出るようにした。
コピーできてないことがある(wikiのみ)ので、アイコンにコピー状況を表示するようにした。(原因は、contensとbackgroundの通信失敗)
対象外のサイトでアイコン無効化ができなかった。エクステンションは他にも使用しており、アイコンは表示しないつもりなのでOK(^^;
アイコンでコピーもわりと便利。
スマホでも使いたい→無理でした
IEでも使いたい→しらん
ファイル構成
root_folder
│ manifest.json
│
├─html
│ options.html
│
├─img
│ icon-128x128.png
│ icon-16x16.png
│ icon-48x48.png
│
├─js
│ background.min.js
│ content.min.js
│
└─_locales
├─en
│ messages.json
└─ja
messages.jsonソース
- manifest.json
permissions はトップドメインを*にできないため、https://www.amazon.*/* はエラーとなる。
manifest.json{ "name": "short url for amazon, wiki", "short_name": "short url", "version": "1.0.0", "manifest_version": 2, "description": "short url to clipboard", "author": "ctrlzr", "default_locale": "ja", "permissions": [ "https://www.amazon.co.jp/*", "https://www.amazon.com/*", "https://www.amazon.it/*", "https://www.amazon.fr/*", "https://www.amazon.de/*", "https://www.amazon.es/*", "https://*.wikipedia.org/wiki/*", "activeTab", "background", "contextMenus", "clipboardRead" ], "content_scripts": [{ "matches": [ "https://www.amazon.co.jp/*", "https://www.amazon.com/*", "https://www.amazon.it/*", "https://www.amazon.fr/*", "https://www.amazon.de/*", "https://www.amazon.es/*", "https://*.wikipedia.org/wiki/*" ], "js": ["js/content.min.js"] }], "background": { "scripts": ["js/background.min.js"] }, "browser_action": { "default_icon": "img/icon-16x16.png", "default_title": "short url" }, "options_ui": { "page": "html/options.html", "chrome_style": true }, "icons": { "128": "img/icon-128x128.png", "48": "img/icon-48x48.png", "16": "img/icon-16x16.png" } }
- content.js
chrome.runtime.onMessage.addListener で background のメッセージを受け取り、wikiの記事idをhtml内から取得する。
content.jschrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) { var res = null; if (!msg.target) { // } else { if ('wiki' === msg.target) { res = get_wgArticleId(); } else { console.log(msg.target+' does not supprted'); } } sendResponse(res); }); function get_wgArticleId() { let scripts = document.querySelectorAll('script'); for (let i=0; i<scripts.length; i++) { let script = scripts[i]; let result = script.innerHTML.match(/"wgArticleId":(\d+)/); if (result) { return result[1]; } } return null; }
- background.js
cm_click でasyncしてるのは、wikiでhtmlを捜索するのに、contents_script と通信するため。
どのイベントが走るか console.log で確認して必要なイベントを見極めた。
chrome.tabs.onActivated:タブを切り替えたとき
chrome.tabs.onUpdated:画面遷移、再読み込みしたとき
chrome.browserAction.onClicked:アイコンをクリックしたとき以下は非同期処理
chrome.tabs.sendMessage
chrome.tabs.getSelectedbackground.jsvar cmid = null; var cm_click = async function(data, tab) { var color = [0, 0xaa, 0, 100]; let txt = await get_short_url[data.menuItemId].call(this, data.pageUrl, tab.id); if (!txt) { txt = data.pageUrl; color = [0xff, 0, 0, 100]; } let textArea = document.createElement('textarea'); textArea.value = txt; document.body.appendChild(textArea); textArea.select(); document.execCommand('copy'); document.body.removeChild(textArea); chrome.browserAction.setBadgeText({text:"copy", tabId: tab.id}); chrome.browserAction.setBadgeBackgroundColor({color: color}); }; var icon_click = async function(tab) { var data = { menuItemId: "wiki", pageUrl: tab.url }; cm_click(data, tab); }; var get_short_url = { amazon: function(url) { let pos = url.indexOf('/dp/'); if (-1 === pos) { return null; } let pos2 = url.indexOf('/', pos+4); if (-1 === pos2) { pos2 = url.indexOf('?', pos+4); } let id = null; if (-1 < pos2) { id = url.substring(pos+4, pos2); } else { id = url.substring(pos+4); } let path = getBaseUrl(url)+'/dp/'+id; return path; }, wiki: function(url, tab_id) { return new Promise(function(resolve, reject) { chrome.tabs.sendMessage(tab_id, {target: "wiki"}, function(id) { if (id) { let path = getBaseUrl(url)+'/?curid='+id; resolve(path); } else { resolve(null); } }); }); } }; function getBaseUrl(url) { let pos = url.indexOf('/', 8); return url.substring(0,pos); } var cm = function() { chrome.tabs.getSelected(null,function(tab){ let target = null; if (!tab.url) { } else if (tab.url.match(/^https:\/\/www\.amazon\.[^/]+\/([^\/]*\/|)dp\//)) { target = 'amazon'; } else if (tab.url.match(/^https:\/\/.*\.wikipedia.org\//)) { target = 'wiki'; } else { // } if (cmid) { chrome.contextMenus.remove(cmid); cmid = null; } if (target) { let options = { id: target, title: "short url to clipboard", contexts: ['page'], onclick: cm_click }; cmid = chrome.contextMenus.create(options); } }); } chrome.tabs.onActivated.addListener(function (tabId) { cm(); }); chrome.tabs.onUpdated.addListener(function (tabId) { cm(); }); chrome.browserAction.onClicked.addListener(function(tab){ if (!cmid) { return; } cm_click({menuItemId: cmid, pageUrl: tab.url}, tab); });
- その他のソースはほぼ使用していないので省略
注意点
公開は有料(5$)
限定公開は無料という情報もあったが、現在(2019年7月)は、有料。
20本まで5$manifest.json の permissions に <all_urls> , tab を使用しない
chrome extension の公開には審査があり、<all_urls> にすると審査が長くなる。tab は activeTab を推奨される。権限ありありでどんなことができるかは、こちらを参照
ブラウザ拡張の権限でどこまで(悪いことを)できるのか?とその対策【デモあり】document は content_scripts のみ
manifest.json の content_scripts に指定したjsのみ、documentを読める。
backend で document.getElementById('honya') としても想定した値は返らない。tabs は background のみ
content_scripts 側で、chrome.tabs がエラーになってハマった。
非同期を前提に
普通にjsで書いていたら非同期通信を余儀なくされ、構成が変わってしまい、aync await で回避する羽目に。
自動更新は使えない
2017年12月以降、自動更新する機能は制限された。
以前は、manifest.json に update_url を記載すれば自動更新できた。らしい。公開
2019年7月16日 11:37現在 審査中
最大60分なのに、まだ公開されない...jsをmin化したのがよくない!?
申請依頼ページが開けないので、詳細はのちほど。拒否られた
名前にChromeと入っていてはダメっぽい
拒否理由は分からないが、この記事からアップロードしたzipファイル名がよくないと推測。
extension_short_url.zip → short_url.zip に変えて再審査中
- 投稿日:2019-07-16T11:22:54+09:00
【JavaScript】voidについて
今日、
void
というものを初めて見知ったので、調べたことをまとめてみます。voidとは?
void
演算子は、式を評価して、絶対にundefined
を返します。
一体どういうところで使うんでしょうか。この演算子は、「戻り値が undefined であってほしい場所に、それ以外の戻り値を持つ式を挿入したい場合」に有用です。
[参考:MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/void)ふむふむ。
...?
ということで、実際の使用例をみてみます。voidの活躍場所<a href="Javascript:void(0)">リンク</a>javascript:擬似プロトコル
これは
http:
とかfile:
とかと同じURIスキームの1つです。javascript: から始まる URI をサポートしたブラウザに於いて、それは、URI 内のコードを評価し、戻り値が undefined でなければ、返された値にページコンテンツを置き換えます。
[参考:MDN](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Operators/void)リンク内でJavaScriptが実行できちゃうんですね。
MDNでは控えめなイベントハンドラ以外多用しない方が良いと書かれていることにも留意しておきます。つまり上の例の
Javascript:void(0)
でアンカータグ本来の動きを抑制しています。
わざわざイベントハンドラでpreventDefault
を設定しなくて良いので楽ですね。ギモン:Javascript:undefinedじゃだめ?
これは昔の仕様では
undefined
が予約語じゃなかった為、
undefinedに値を定義したりできてしまっていたからみたいです。昔のundefinedvar undefined = 5; //最悪 var num = 5; if(num === undefined){ alert('未定義です'); //定義してるのに実行されちゃう }昔のものと互換性をつけるに、
void(0)
で本来のundefinedを保証しています。なのでこの例に限らず、確実に
undefined
を保証したい場合は、
void(0)
に置き換えたほうが良いんじゃないかと思います。
- 投稿日:2019-07-16T08:38:36+09:00
Web API の CSRF 対策まとめ
セキュリティ脆弱性診断などでたまに CSRF について指摘されることがあります。
今まではトークン発行して対応すれば良いんでしょ? と思ってましたが、SPA のように非同期通信が前提の場合はどう対処するべきなんだろう、と疑問が出たりしたので、調べてみた結果をまとめてみました。CSRFとは
Cross Site Request Forgeries(クロスサイトリクエストフォージェリ)の略で、
サービス利用者の正規権限を利用して、意図しないタイミングでサービスの機能を実行させる攻撃手法のことを指します。
2005年に mixi 日記で発生した「ぼくはまちちゃん」で一躍有名になりました。大量の「はまちちゃん」を生み出したCSRFの脆弱性とは? - ITmedia エンタープライズ
CSRF が発生する原因
サービスの機能を実行するプログラムへのリクエストの検証が権限情報のみであった場合に発生します。
CSRF は正規ユーザの権限を使って実行されるので権限情報のみの検証では不十分です。
権限情報の他にも正規のルートかつ正規のタイミングであるかを同時に検証する必要があります。既存の API が CSRF 対策されているかチェックする
攻撃者が他サイトから正規ユーザのアクセスを利用して API に直接リクエストを送る方法は大きく分けて2つあります。
- JavaScript(非同期通信)によるリクエスト
- ブラウザの標準動作(同期通信)によるリクエスト
例えば、https://example.com/api/message/update という 更新API に
text
というパラメータでリクエストを送るケースの場合、
認証済みの状態で以下の内容を含むページにアクセスをして、更新が実行されてしまうと CSRF 対策がなされてないことになります。1. JavaScript(非同期通信)によるリクエスト
Web API の場合、ほとんどは JavaScript による非同期通信による送信が使われることになります。
ページに以下のような内容を仕込んでおくと API に向けてデータを送信することができます<script> var xhr = new XMLHttpRequest(); xhr.open('POST','https://example.com/api/message/update'); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded'); xhr.withCredentials = true; xhr.send('text=csrf%20test'); </script>JavaScript には Same Origin Policy という仕様があり、クロスドメインで非同期通信を実行すると CORS policy エラーが発生します。
ドメインを跨いだ JavaScript の通信に制限がかかるため API 側がレスポンスにAccess-Control-Allow-Origin
ヘッダを付与してクロスドメインの通信を許可しない限り、JavaScript はレスポンスの内容を読み取ることができません。一見この仕様により CSRF 対策になるのでは…? と思われますが、Same Origin Policy はクロスドメインのJavaScript のレスポンス情報をブラウザが読み取らせないようにしているだけで、リクエスト自体は API に到達しています。 CSRF はリクエストが成立すればレスポンスは不要なので、結局 API 側は更新を実行する前に認証情報の以外のチェックを行う必要があります。
2. ブラウザの標準動作(同期通信)によるリクエスト
例えば
<form>
要素による送信です。実際に実行する際には不可視の iframe 内で処理するなどしてユーザに実行を気づかせないようにします。<form action="https://example.com/api/message/update" method="POST"> <input type="hidden" name="text" value="csrf test"> </form> <script>document.forms[0].submit()</script>また API が GET を受け付けている場合は
<img>
要素に差し込んでリクエストを送ることもできます。<img src="https://example.com/api/message/update?message=csrf%20test">また、同期通信は Same Origin Policy の制約を受けませんが、通信後の内容は正規サイトの管轄となるため、同期通信では外部サイトから正規サイトの情報にアクセスすることができません。
CSRF の対策方法
CSRF 対策として大きく分けて2つのアプローチがあります。
- トークンを発行してリクエストの正当性を検証してから実行する
- プリフライトリクエストを検証してから実行する
1. トークンによる対策
事前にトークンを発行しておき、更新APIでは認証情報のほかにトークンチェックを行い、正規のリクエストであるかを検証します。
外部サイトからはトークン API へのリクエストは送れても、CORS エラーにより発行されたトークンを読み取ることができません。
更新 API のリクエストも正規に発行されたトークンが必要になり、外部サイトからはトークンの内容を知る術がないため、リクエストは失敗します。
この仕組みは CSRF 対策としては古くから使われていて、既存のフレームワークの仕組みに取り込まれていたりしているため、分かり易い仕組みではあるものの、
- サーバ側にトークンを保存する必要がある
- クライアント側の API アクセス前にトークン API への手続き処理が必要になる
といった手間がありそれらの開発・運用をするためのコストが発生してしまうのがデメリットです。
2. プリフライトリクエスト検証による対策
ブラウザは JavaScript でクロスドメインの非同期通信を行う際に一定の条件を満たすと実際の送信の前に
OPTIOIN
メソッドを使って プリフライトリクエストを発行します。(一定の条件の詳細は プリフライトリクエスト - MDN を参照してください)
この プリフライトリクエストが正規のルートからのリクエストであるかを検証することで CSRF 対策 とすることができます。
CSRF 対策としてプリフライトリクエストを発生させるには、カスタムヘッダを付与する方法が一般的です。また、リクエストの検証は以下の条件をチェックします。
- 付与したカスタムヘッダが存在すること
Host
ヘッダが正規サービスのホスト名であることOrigin
ヘッダが許可した接続元のホスト名(API のレスポンスヘッダに含まれるAccess-Control-Allow-Origin
ヘッダと同じ)であることこれらの条件を満たさないリクエストはエラーとして返却することで、本来のリクエスト処理は中断され実行されなくなります。
また、<form>
による同期送信はクロスドメインにおいてもプリフライトリクエストを発生させずに本来のリクエストを直接送信することはできますが、同期送信はカスタムヘッダを付与できないため、リクエストチェックの条件「付与したカスタムヘッダが存在すること」を満たせずエラーとなります。
結果として意図しない外部サイトからのリクエストを全て弾き、CSRF の成立を阻むことができます。プリフライトリクエストによる対策がトークンによる対策よりも優れているのは、
- サーバ側でトークン API の開発やトークン管理が不要
- 既存の API に一律的に
OPTION
メソッドの受付と検証処理を追加するだけでよい- クライアント側でトークン取得や送信の処理が不要
- クライアント側は API にカスタムヘッダを付与するだけでよい
といった点です。トークンによる対策よりも開発コスト、運用コストを削減できます。
逆にデメリットとしては同期通信(<form>
による送信)に対応できない点です。
SPA 構成のサービスなどでは、想定外(JavaScript 以外から)のアクセスを一律排除できるので寧ろメリットになりますが。動作検証サンプル
CSRF の対策が行われていないAPIと、プリフライトリクエストによる CSRF 対策が行われている API の動作サンプルを作ってみました。
https://github.com/okamoai/example-csrf
サンプルの詳細はリポジトリの README.md をご確認ください。
まとめ
これから既存の API、もしくは新規のサービス開発で CSRF 対策を求められた場合、
- API との非同期通信がメインになる SPA のようなプロジェクトではプリフライトリクエストによる対策
- 同期的な送信で処理される従来型のプロジェクトではトークンによる対策
で対応するのが良さそうです。
トークンによる対策は SPA のプロジェクトにも適用はできますが、プリフライトリクエストで対応した方がフロントエンド、バックエンド共に対応コストが少なくて良さそうですね。
参考
- 投稿日:2019-07-16T08:13:50+09:00
Chrome App で最小限のテキストエディタを作る
Chromebook を入手したのでちょっとしたテキストエディタを作る。ローカルのファイルを開いて、編集して、保存するだけの最小限のもの。こんな感じのやつ。Chrome 75.0 で作成。
GoogleChromeLabs が開発しているシンプルな text-app を参考にした。こちらはエディタ部分に CodeMirror を使っておりシンタックスハイライトが出来、また複数ファイルを同時に開く程度のことはできる。
https://github.com/GoogleChromeLabs/text-appまずマニフェストファイル。テキストエディタなのでファイルの書き込み許可が必要。
manifest.json{ "manifest_version": 2, "name": "MinimalEditor", "version": "0.1", "app": { "background" : { "scripts": ["background.js"] } }, "permissions": [{"fileSystem": ["write"]}] }続いて
background.js
だが、これはindex.html
をウィンドウとして開くだけ。background.jschrome.app.runtime.onLaunched.addListener( function() { chrome.app.window.create('index.html', { id: 'main', bounds: {width: 640, height: 640} }); });続いて
index.html
は、ヘッダーに「開く」「上書き保存」「名前をつけて保存」とファイル名の<span>
要素を並べ、その下にエディタ本体の<textarea>
を置く。CSS はいいかんじに書く。index.html<!DOCTYPE html> <html> <head> <meta charset='utf-8'> <link rel='stylesheet' href='style.css'> </head> <body> <div class='wrap'> <div class='header'> <a href=# id='menu-open'>Open</a> <a href=# id='menu-save'>Save</a> <a href=# id='menu-saveas'>Save As</a> <span id='filename'></span> </div> <textarea id='editor'></textarea> </div> <script src='third_party/jquery/jquery-3.4.1.min.js'></script> <script src='app.js'></script> </body> </html>最後に
app.js
だが面倒なので$(document).ready()
の内部に全部ぶちこむ形で書いている。Chrome FileSystem API を使用。半端にES6が混じってるけど気にしないでほしい。app.jsfunction MinimalEditorApp() {} let app = new MinimalEditorApp(); $(document).ready(function () { $("#menu-open").on("click", function (e) { e.preventDefault(); chrome.fileSystem.chooseEntry({type: "openFile"}, function(entry) { app.entry = entry; $("#filename").text(app.entry.name); console.log(app.entry); app.entry.file(function(file) { let reader = new FileReader(); reader.onload = function(e) { $("#editor").text(e.target.result); }; reader.readAsText(file); }); }); } ); $("#menu-save").on("click", function(e) { e.preventDefault(); const content = $("#editor").val(); app.entry.createWriter(function(writer) { let blob = new Blob([content], {type: 'text/plain'}); writer.onwrite = function() { writer.onwrite = function() {}; writer.write(blob); }; writer.truncate(blob.size); console.log("Saved!"); }, function(error) { console.log("Writing Error"); }); }); $("#menu-saveas").on("click", function(e) { e.preventDefault(); const content = $("#editor").val(); chrome.fileSystem.chooseEntry({type: "saveFile"}, function(entry) { app.entry = entry; app.entry.createWriter(function(writer) { let blob = new Blob([content], {type: 'text/plain'}); writer.onwrite = function() { writer.onwrite = function() {}; writer.write(blob); }; writer.truncate(blob.size); console.log("Saved!"); }, function(error) { console.log("Writing Error"); }); }); }); });とりあえずこれで一応テキストエディタとして動くが、なんのエラー処理もしていないので新規ファイルを「上書き保存」するとエラーを吐くし、編集中にウィンドウを閉じるとなんの警告も出ずに消える。
- 投稿日:2019-07-16T05:59:15+09:00
【Programming News】Qiitaまとめ記事 July 15, 2019 Vol.1
筆者が昨日2019/7/15(月)に気になったQiitaの記事をまとめました。
Programming News
2019年
7月15(月)
Java
- Spring Boot
Kotlin
Python
- Beginner
- Tips
Ruby
- Beginner
- Apps
Rails
- Beginner
- OAuth
- Tips
C#
Android
Swift
- Summary
- Apps
- Tips
Flutter
- Tips
Vue.js
- Beginner
- Apps
Vuex
React
- Beginner
- Tips
JavaScript
- Beginner
Node.js
- Tips
Laravel
- Beginner
- Tips
jQuery
- Tips
Go言語
- Beginner
Unity
- Beginner
Bot
HTML
MySQL
- Beginner
AI
Git
- Beginner
- Tips
Azure
インフラ
- 仮想環境
- ubuntu
- ubuntu環境構築 #### Network
- Tips
RPA
Docker
- Beginner
Linux
Google Apps Script
- Beginner
- Tips
- Apps
Server Side
Develop
PowerShell
Vim
- Beginner
UML
- PlantUML
Raspberry
Security
- SSL
資格
- AWS
- 基本情報
- ネットワーク
更新情報
Kotlin
- Kotlin入門
Java
- Java 10
IDE
- 投稿日:2019-07-16T05:26:33+09:00
Vue.jsと<input type=range>でSoundCloudみたいなシークバーを作る
挨拶
こんにちはkaijiです。
最初に、初投稿かつ今までWebをほとんどやったことのない人なので、色々と間違っていたり不足している部分があるかと思います。もしそういった点があった時はコメントで優しく教えてもらえると嬉しいです。経緯・注意
個人で開発しているサイトでオーディオを再生するためシークバーを実装しようとして色々調べてたのですが、HTML5のAudioタグで作っていたり、inputの標準のUIで作っているものだったり、オリジナルの見た目で作っていてもプログレスバーまで作っているものはほとんど見当たらなかったので、Vue.js上でJavascriptのWeb Audio APIとinputタグを使って自分で作ってみました。
ここでは、シークバーを作ることをメインにしているので、再生、停止、ボリューム調整等の基本的な動作に関しては他のサイトとかを参考にしてください。全体的な流れ
- シークバーの見た目を作る
- 実際にシークさせてみる
- 現在の再生時間と、全体の再生時間を表示させる(分:秒表示)
- 再生済み用のプログレスバーを作り、元のinputタグに重ねる
といった感じです。
シークバーの見た目を作る
シークバーの見た目に関しては、基本好きなように作ってねっていう感じなんですが、いくつか注意点があるので、それについて書きます。
まずinputタグにはデフォルトで見た目が設定されています。(Mac版Chromeの場合はこんな感じ↓)
ですが、これだとブラウザごとに見た目が変わってしまったり、サイトによってはデザイン的に浮いてしまうことがあるため(ちなみにこれはHTML5のオーディオタグにも言える)今回は自分でCSSを書いて見た目を作っていくのですが、その前にこのデフォルトの見た目を表示させないように<style>
内に以下のCSSを書いてみましょう。input[type=range] { -webkit-appearance: none; } input[type=range]::-webkit-slider-thumb { } input[type=range]::-ms-tooltip { display:none; } input[type=range]::-moz-range-track { } input[type=range]::-moz-range-thumb { }ここでは
input[type=range]
の見た目を表示させないでくださいと各ブラウザ用に書いています。
こうすることで、真っさらなinput[type=range]
ができたので、ここからは実際に見た目を作っていきます。
なぜ-ms
だけthumbの設定がないのかや、-moz
には何も書いていないのか等は以下のページにわかりやすく書かれていたので、そちらを参考にしてみてください。input type=range タグをカスタマイズするために
実際にシークさせてみる
次は実際にシークをさせてみましょう。
ここではWeb Audio API上でオーディオファイルが再生できる状態になっていることを前提に話をするので、まだ実装できてない人は他の投稿やリファレンス等をみて実装してからもう一度来てください。今回はVue.jsを使っているので双方向データバインディングのできるv-modelを現在の再生時間、v-bindを全体の再生時間を表示させるために使っていきます。
まず
<script>
内でオーディオのcurrentTime(現在の再生時間)とdurationTime(全体の再生時間)を取得します。const audio = new Audio export default { data() { return { currentTime: 0, durationTime: 0 } }, mehods:{ play() { audio.src = //オーディオのURLとか audio.addEventListener("loadedmetadata", function () { return { durationTime: audio.duration.toFixed(0) } }); audio.addEventListener("timeupdate", function () { return { currentTime: audio.currentTime.toFixed(0) } }); audio.addEventListener("ended", function () { return { currentTime: 0, durationTime: 0 } }); audio.play(); }, seek() { audio.currentTime = this.currentTime; } } }ここでやっていることは
audio.src
で取得したオーディオデータからaddEventListener
を使ってメタデータを抽出できたタイミングでaudio.durationTime
を取得し、その値を返させています。
それと同じように再生中にaudio.currrentTime
を取得しその値を返させています。
また再生が終了したら両方の値を0(初期値)にしています。そして下に書かれている
seek()
というメソッドではv-modelの特性である双方向データバインディングを使ってthumbを移動させるたびにその位置(時間)まで実際に再生されている音源のaudio.currentTime
を移動させています。また
audio.durationTime
やaudio.currrentTime
を取得するときtoFixed(0)
と書いていますが、これは取得してきた値を整数に変換しています。
なぜこんなことをするのかというと、標準で取得してくる再生時間はミリ秒(1/1000秒)で表されているため、今回作るようなプレイヤーの場合あまり適している形とは言えません。
そのため今回は整数で表すようにしていますが、もし作るプレイヤーがミリ秒まで表示できるものであって欲しいならtoFixed(n)
を書く必要はありませんし、toFixed(n)
はnの値を変えることによって0.1秒単位(その場合nは1になる)などもっと細かい値にすることも可能なので、自分の用途に合わせて調整してみてください。
詳細は下記のURLから見てくださいでは次に
<template>
内に記述していきます。
<a v-on:click=play>再生</a>
<input type="range" v-model="currentTime" v-on:input="seek" v-bind:max="durationTime"/>最初のaタグやinputの
type="range"
は単に処理を呼び出したり、inputのタイプを指定しているだけなので気にしないでください。まずv-modelを使ってcurrentTimeを取得しています。なんども言っていますが、v-modelは双方向データバインディングが可能なため、値が変化する度にリロード等の処理をせず、直接表示される値を変化させることが可能です。
そしてv-on:input="seek"
はシークバーに触れる(thumbが動く)度に先ほど記述したseek()メソッドが呼び出されます。
最後にv-bind:max="durationTime"
はシークの最大値をdurationTimeにしています。そのほか最小値やステップは記述していないためデフォルトの値が使われますので、これも必要に応じて設定してみてください。現在の再生時間と、全体の再生時間を表示させる(分:秒表示)
v-modelを使ったcurrentTime(現在の再生時間)、v-bindを使ったdurationTime(全体の再生時間)を反映させる処理を見てきた皆さんであれば、おそらくinputに値を反映させたように、文字にも同じように反映させればいいとすぐにわかったと思いますが、今回は少し発展して分:秒(mm:ss)で時間を表示していきたいと思います。
const audio = new Audio export default { data() { return { currentTime: 0, durationTime: 0, convertedDurationMin: "00", convertedDurationSec: "00", convertedCurrentMin: "00", convertedCurrentSec: "00" } }, mehods:{ play() { audio.src = //オーディオのURLとか audio.addEventListener("loadedmetadata", function () { const durationMin = Math.floor(audio.duration.toFixed(0) / 60); const durationSec = audio.duration.toFixed(0) % 60; return { durationTime: audio.duration.toFixed(0), convertedDurationMin: ("00" + durationMin).slice(-2), convertedDurationSec: ("00" + durationSec).slice(-2) } }); audio.addEventListener("timeupdate", function () { const currentMin = Math.floor(audio.currentTime.toFixed(0) / 60); const currentSec = audio.currentTime.toFixed(0) % 60; if ((currentMin > this.convertedCurrentMin && audio.currentTime !== 0) || (currentMin < this.convertedCurrentMin)) { return { currentTime: audio.currentTime.toFixed(0), convertedCurrentMin: ("00" + currentMin).slice(-2), convertedCurrentSec: ("00" + currentSec).slice(-2) } } else { return { convertedCurrentSec: ("00" + currentSec).slice(-2) } } }); audio.addEventListener("ended", function () { return { currentTime: 0, durationTime: 0, convertedDurationMin: "00", convertedDurationSec: "00", convertedCurrentMin: "00", convertedCurrentSec: "00" } }); audio.play(); } } }オーディオから取得したdurationTimeを整数に変換し、その値を60で割った数を
durationMin
、60で割った数の余りをdurationSec
とします。
covertedDurationMin
へは"00"にdurationMin
を加算した要素から最後の2つを取り出して返し、
covertedDurationSec
へは"00"にdurationSec
を加算した要素から最後の2つを取り出して返しています。わかっている人もいると思いますが、"00は"文字列のため、それに
durationMin
を加算すると9分以下なら3桁(Ex.009)、10分以上なら4桁(Ex.0010)となってしまいmm表記にはなりませんが、最後の二つの要素のみを取り出せば、9分以下の時は10の位が0になり、10分以上の時はdurationMin
と同じ値になるので、mm表記にすることができます。
covertedDurationSec
にも同じことが言えるので、これでdurationTimeをmm:ss表記にすることができました。currentTimeにも同じことが言えますが、currentTimeは値がどんどん変わっていくため、それも考慮してコードを書くと上記のようなコードになります。
currentMin
がconvertedCurrentMin
より大きく、かつaudio.currentTime
が0でない場合というのはcurrentMin
が加算されるタイミング(currentSec
が0になるタイミングとも言える)の時呼び出されるものです。
thumbを移動させないシークであればこれで問題ありませんが、今回はthumbを移動するため、それに加えてcurrentMin
がconvertedCurrentMin
より大きいときという条件を加えました。こうすることにより1分以上戻った時でも正常に表示できるようになりました。次に
<template>
内に記述していきます。
<span v-text="convertedCurrentMin + ':' + convertedCurrentSec"></span>
<span v-text="convertedDurationMin + ':' + convertedDurationSec"></span>正直そのまますぎて説明することがないので注釈を一つ。ここではspanタグを使っていますが、もちろん別のタグを使っても構いません。
例えばSoundCloudではdurationTimeはaタグになっていて、一度クリックすると残り時間の表示になり、もう一度クリックすると従来通り全体の再生時間を表示することができます。再生済み用のプログレスバーを作り、元のinputタグに重ねる
まず↓のようなinputと同じサイズのプログレスバーを作ります。
<span class="progress" id="progress"></span>
もちろんタグはdiv等でも構いません。次に
style
を書いていきます。span.progress { width: /*width*/ background: linear-gradient(/*YourFavoriteColor*/, /*YourFavoriteColor*/) no-repeat; background-size: 0; position: absolute; pointer-events: none; }まずプログレスバーに
position: absolute
を加える等をして、inputの上に重ねます。
background: linear-gradient(/*YourFavoriteColor*/, /*YourFavoriteColor*/) no-repeat;
でプログレスバーの色をグラデーションで指定し、backgroud-size
を使ってプログレスの度合いを表しています。
また最後のpointer-events: none;
はプログレスバーの操作を無効化して、その下に配置されているシークバーの操作をできるようにしています。これを指定することにより、プログレスバーが真上に重なっても操作ができるようになりました。ちなみにプログレスを表示する項目が1つであれば、spanを足さずに直接inputに書くこともできます。
しかしこういったWebサービスではストリーミング再生、つまり再生済みだけでなく読み込み済みのプログレスも作る必要があることが多いため、今回は別のタグにして複数のプログレスを重ねられるようにしました。次に
<script>
のtimeUpdate内に記述していきます。const percent = audio.currentTime.toFixed(0) / audio.duration.toFixed(0) * 100; document.getElementById("progress").style.backgroundSize = percent + "%";currentTimeをdurationTimeで割った数に100を掛けた数を定数
percent
とします。
定数percent
の範囲は0 <= percent <= 100なので、background-sizeの値をwidthに対しての百分率とすることができます。そして、span.progress
のbackground-size
に定数parcent
に"%"を足した値を反映させます。そうするとthumbに従ってプログレスバーが動いているように見えるかと思います。最後に
長々と書いてきましたが、これで終わりです。初めてな上に眠気と戦いながら書いたので、ミスや分かりづらいところがあったかもしれないですが、少しでも読んでくださった方の参考になれば幸いです。
- 投稿日:2019-07-16T03:11:30+09:00
ASAP Vue SPA入門:コンポーネント
Vue CLI3 で作成した SPA(Single Page Application)プロジェクト上で、段階的に Vue.js を学んで行きましょう。
今回はコンポーネント編です。
TodoList を作りながらコンポーネントの使い方を学びます。前提事項
ASAP Vue SPA入門:Vue の基本 が完了していること。
ページの追加
新規に Todo List ページを作成します。
- TodoList.vue を作成
- router.js を修正
- App.vue にナビゲーションを追加
やり方を忘れてしまった人は「ページの追加」を振り返ってください。
src/views/TodoList.vue<template> <div class="todolist"> <h1>Todo</h1> <ul v-if="todos.length"> <li v-for="todo in todos" :key="todo.id" >{{ todo.text }}</li> </ul> <p v-else> TODO 一覧はありません </p> </div> </template> <script> let nextTodoId = 1; export default { data() { return { todos: [ { id: nextTodoId++, text: "Learn Vue" }, { id: nextTodoId++, text: "Learn about single file components" }, { id: nextTodoId++, text: "Learn about conponents" } ] }; } }; </script> <style> h1 { text-align: center; } .todolist { max-width: 400px; margin: 0 auto; text-align: left; } </style>コンポーネントの作成とプロパティによる値渡し
Todo List から Todo List Item を別コンポーネントに切り出します。
TodoListItem.vue コンポーネントを作成します。
src/components/TodoListItem.vue<template> <!-- todo プロパティのテキストを表示 --> <li>{{ todo.text }}</li> </template> <script> export default { // 親コンポーネントから todo プロパティを受け取る props: ["todo"] }; </script>親コンポーネントから子コンポーネントに値を渡すにはプロパティを使用します。
上記ではtodo
プロパティを受け取れるよう宣言しています。TodoList.vue を下記の様に修正します。
TodoList.vue<template> <div class="todolist"> <h1>Todo</h1> <ul v-if="todos.length"> <!-- TodoListItem コンポーネントを使います --> <!-- v-bind を使って動的に todo プロパティを渡します --> <TodoListItem v-for="todo in todos" :key="todo.id" :todo="todo" /> </ul> <p v-else> TODO 一覧はありません </p> </div> </template> <script> // TodoListItem コンポーネントを import します import TodoListItem from "../components/TodoListItem"; export default { // 使用するコンポーネントを Vue に伝えます components: { TodoListItem }, data() { // ... } }; </script> <style> /* ... */ </style>JavaScript のところで先程作成した
TodoListItem.vue
をインポートして、Vue オブジェクトのcomponents
属性に設定しています。こうすることで、HTML のところで、TodoListItem
タグが使用できるようになります。HTML のところでは
li
タグの代わりにTodoListItem
タグを使用するように変更しました。子コンポーネントに値を渡すためにtodo
プロパティにtodo
オブジェクトをバインドしています。プロパティのバリデーション
プロパティは以下のように記述することでバリデーションチェックをすることができます。
export default { props: { // 基本的な型の検査 (`null` と `undefined` は全てのバリデーションにパスします) propA: Number, // 複数の型の許容 propB: [String, Number], // 文字列型を必須で要求する propC: { type: String, required: true }, // デフォルト値つきの数値型 propD: { type: Number, default: 100 }, // デフォルト値つきのオブジェクト型 propE: { type: Object, // オブジェクトもしくは配列のデフォルト値は // 必ずそれを生み出すための関数を返す必要があります。 default: function () { return { message: 'hello' } } }, // カスタマイズしたバリデーション関数 propF: { validator: function (value) { // プロパティの値は、必ずいずれかの文字列でなければならない return ['success', 'warning', 'danger'].indexOf(value) !== -1 } } } }
TodoListItem.vue
もバリデーションチェックをするよう修正します。TodoListItem.vueprops: { todo: { type: Object, // オブジェクト型であること required: true // 必須項目 } }イベント
親コンポーネントから子コンポーネントへはプロパティを使って値を渡すことができますが、子コンポーネントから親コンポーネントへ値を渡すことは出来ません。その代わりにイベントを使って通信することができます。
イベントの発火
TodoListItem.vue
に削除ボタンを設け、remove
イベントを発火するよう修正します。TodoListItem.vue<li> {{ todo.text }} <button @click="$emit('remove', todo.id)">x</button> </li>
botton
がクリックされると$emit('イベント名', ペイロード)
でイベントを発火します。イベントハンドリング
TodoList.vue
でremove
イベントをハンドリングして Todo を削除します。TodoList.vue<!-- remove イベントをハンドリングして removeTodo メソッドをコールします --> <TodoListItem v-for="todo in todos" :key="todo.id" :todo="todo" @remove="removeTodo" />TodoList.vuemethods: { removeTodo(idToRemove) { this.todos = this.todos.filter(todo => { return todo.id !== idToRemove; }); } }
removeTod
メソッドにはremove
イベントが発火された時のペイロードが引数として渡されます(この場合は todo の id です)。演習
TodoInputText.vue
コンポーネントを作成して、TodoList.vue
でそれを使って TODO を追加できるようにしてください。次回
Coming Soon...
- 投稿日:2019-07-16T02:15:32+09:00
自力でリバーシ(オセロ)を作っていく#01
前回の投稿はこちら
自力でリバーシ(オセロ)を作っていく#00これからの記事の書き方
これからの記事のフォーマットを決めておこうと思います。
自分が描きやすく、また見返しやすくするためです。
- 導入(これはないかも
- 進捗
- 作った処理の流れ説明
- 詰まった部分
- できれば改善したい部分
- 学んだこと(今後の予想なども)
こんな感じでやってみます
今回の進捗
実装完了した処理は
- 8*8のマス描画
- クリックで石置ける
- ヨコではさまれたら色変更この3つだけです笑
前回の、必要な処理列挙と3つの実装だけで5時間くらいかかっちゃいました。作った処理の流れ説明
- 8*8のマス描画
- クリックで石置ける
- ヨコではさまれたら色変更
この3つについて、順に書いていきます
8*8のマス描画
今回はまず8*8マスの描画をするところから取り掛かったのですが、いきなりかなり悩みましたね〜
後から操作しやすいHTMLにしたかったので、何がいいのか結構考えました。
- テーブルで作るか
- ひとマスをcanvasで作り 、繰り返すか
- シンプルにdivだけでいくか
僕が思いついたのはこの3つでしたね。
テーブルでの作成は、タテヨコが制御しやすいかな?とか思って選択肢に出したのですが、よく考えると全然そんなことなさそうなので不採用にしました。canvasで全体のマス描画という案も一瞬浮かんだのですが、クリックイベントが使いにくくないそうなので即却下しました。
ひとマスをcanvasで作ってそれをループさせる案をとりあえず採用したのですが、僕の力不足でうまくいかず断念しました(canvas使いたかったぁ…こんな感じで最終的にdivで作成していくことに決定しました(消極的選択!
divでの作成に決めるとマス描画は一瞬でした笑
早くjavascriptでの処理に取り掛かりたい気持ちでいっぱいでしたね。html<div class=”row”> <div class=”rectangle”></div> *8 ・ ・ ・ </div>これを8個作って、テキトーにborderとサイズを決めて終わりです。
ついでに、黒石と白石をそれぞれ、black-stone/white-stoneというクラスの擬似要素で作成ました。
クリックや色変更の時に、このクラスの有無で制御する予定です。クリックしたら石置ける
ここからは、「クリックで石が置ける」、「ヨコではさまれた時に色が変わる」処理について考え始めました。
まず、
- 石を置いた場所
- 左右斜めにある石
を簡単に取得できるようにしたかったので、ここをどう処理するか考えました。
全てのマスにidをふる案が最初に思いついたのですが、これではあまりにもやっつけすぎるというか、アホっぽい気がしたのでやめました。
結局左下から順に
(x1,y1)/(x1,y2)
というようなクラスをふることになりました。
(x1,y7) (x2,y7) (x3,y7) (x1,y6) (x2,y6) (x3,y6) (x1,y5) (x2,y5) (x3,y5) (x1,y4) (x2,y4) (x3,y4) (x1,y3) (x2,y3) (x3,y3) (x1,y2) (x2,y2) (x3,y2) (x1,y1) (x2,y1) (x3,y1) ここからがやっとJSでの処理ですね。
とりあえず簡単に考えるために黒石のみの操作で進めていきます。
まず
className('rectangle')でマスを全部取得して、クリックされたマスを取得するために次のようにしました。JSfor (let i = 0; i < rectangleObject.length; i++) { rectangleObject[i].addEventListener('click', () => { //石置いて、はさめたら白色をひっくり返す処理 putBlackStone(rectangleObject[i]); }, false); }putBlackStone(obj)を考えていきます。
まずクリックされたマスにclass="black-stone"を追加したいのですが、その前に
すでにそのマスに石が置いてないかどうか
を確認してから、なければ石を置くような処理にします。なので、ここではclassList.containsを使用して次のようにしました。
JSfunction putBlackStone(obj) { if (obj.classList.contains('white-stone') == true) { alert('そこにはすでに白石があります'); }else if (obj.classList.contains('black-stone') == true) { alert('そこにすでに黒石があります'); }else { obj.classList.add('black-stone'); //はさんだ石の色を変更する処理 reverseStone(obj); } }これでひとまず、クリックされたマスに黒石を置く処理は完了です。
次にはさんだ色を変更するreverseStone(obj);の中身を考えていきます。ヨコではさまれたら色変更
この部分でだいぶ苦戦しました。
ていうか、正直まだ未完成です笑処理の流れとしては
1. クリックされたマスの座標取得
2. 同じy座標(水平方向)で黒石があるか確認
3. 黒石があれば間に白石があるか確認
4. 黒石間が白石で隙間なく埋まっているか確認
5. 埋まっていれば白石を黒石に変更この処理の流れを考えるのが今回の挑戦の目的なので、苦戦しましたがかなり楽しかったですね。
詰まった部分
今回の作業で詰まった箇所は次の点です。
- Canvasの繰り返し描画
- クリックされた隣のマスの取得Canvasの繰り返し描画
Canvasでひとマス描画して、それをjavascriptのループを使用して8*8のマスを作成ようと考えていました。
Canvasの初使用でウキウキのまま、まず
<canvas class="rectangle"></>
を出力するためにdocument.writeの使用を検討しました。
調べているとdocument.writeはHTML5では非推奨ということなので使用をやめ、innerHTMLを使ってみることにしました。
ですが、<div></div>
のなかに入れ子で出力がうまくできず断念しました…
ここはまたリベンジしたいですね。クリックされた隣のマスの取得
ここは当然用意していた座標の出番だ、とニッコリしていたのですが、実際隣を取得するには
クリックしたマスのx座標に±1したx座標を作らないといけないというところで詰まりました。結局以下の感じの処理でいきました。若干ゴリ押しなのかな?
- クリックしたマスのx座標クラスを取得
- 文字列のx座標の数字部分をperseIntで数値として取得
- 演算ができるのであとは自由に
JS//例)element = ["rectangle","x5","y5"] //例)numの中身はx5 var num = element.match(/[0-9]+/); //例)xnumの中身は数値型の5 var xnum = parseInt(num, 10);できれば改善したい部分
- クリックしたマスの左右上下の取得方法をもっとスマートにできる気がした
- 「ヨコではさまれたら色変更」が、今は同じ処理を何度も書いてしまっているので関数にしてスッキリさせる。
学んだこと(今後の予想なども)
文字列から数値にできるのは結構便利だなあと思った。
今回のように座標をクラスとして使用してもなんとかなったのは、文字列を数値に変換できたからなので笑
できなかったらまた別の方法に変更することになってたと思います。同じように変数名を動的に作るためにevalがすごく役に立った。とはいえ、あまり推奨されていないようなので、今後使用を検討しようと思います。
縦ではさまれた時の、横の時と同じようにしてできそうなのでいいのですが、斜めが怖いなあ
ただ、やっぱり処理の流れを考えるのはすごく楽しいので今後も楽しみながら進めていきたいと思います。恥ずかしいけど、ここまでのコードも貼っておこう
See the Pen
reversi#01 by 谷口 (@taniguchi06)
on CodePen.
最後まで読んでいただいたきありがとうございました。
- 投稿日:2019-07-16T00:51:22+09:00
自力でリバーシ(オセロ)を作っていく#00
この記事なんなの?
まず最初に、これからの投稿の目的を書いておきます。
発信用というよりも、自分のアウトプット用です。
そして、あわよくば他の人に投稿を見てもらって、ダメなところをボコボコに叩かれたいという願望があります。(お願いします!!基本的に自分のリバーシ作りの中で考えていることや、詰まったところを書いていくことになると思います。
どうぞよろしくお願いいたします。自力とは?
タイトルにある「自力で」という文言ですが、これが意味するのは
「リバーシの作り方を検索せずに、0から自分だけで進め方や、実装方法を考えていく」
ということです。
つまり検索するのは「javascript クラスから 要素取得」みたいな具体的なことだけで、「javascript リバーシ 作り方」といったことは検索しません。
ボードゲーム盤の作り方みたいな大きい塊の検索は基本無しでトライするつもりです。僕のレベル感
コーディングのお仕事ではだいたいjQueryを使用していて、最近は意識的にJavascriptで書くようにしているのですが、よく使う処理をなんども使っていてあまり熟達していない感じです。
なので、今回が過去最高のJavascript使用作品となりますね。
ただ、今回のリバーシ作成の目的はJavascriptの学習というよりも、処理を自分で考えるという気持ち良さ欲しさのためです笑
頭を使いたい!ただそれだけで始めました笑リバーシ作りの始まり
リバーシ作りに必要な処理
まず、リバーシになるために必要な処理を「〜できる」という形式で列挙していきます。
- 8*8のマスを描画できる
- クリックで石を置くことができる
- 間なく別の色の石にはさまれたら色変更できる(タテ・ヨコ・ナナメ)
- 何もはさめない場所に石を置くのをブロックできる
- 全てのマスが埋まるか、どちらも石を置けなくなったら終了にできる
- 1つ石を置いたら相手のターンに変更できる
- 石を置く場所がなければスキップできる
とりあえず思いつくのはこれくらいですかね。
まずはこれらを実装していこうと思います。実装順序
先ほど挙げた処理をどれからやっていくか決めます。
- 8*8のマスを描画
- クリックで石を置ける
- タテ・ヨコではさまれたら色変更
- ナナメではさまれたら色変更
- 一つ石を置いたら相手のターンに変更できる
- 何もはさめない場所に石を置けなくする
- 石を置く場所がなければスキップ
- 全てのマスが埋まるか、どちらも石を置けなくなったら終了にできる
- CPU作成
こんな感じで順番にやっていこうと思います。
実際の実装は次の記事で書き始めようと思います。
自力でリバーシ(オセロ)を作っていく#01