- 投稿日:2020-11-23T23:21:39+09:00
React - 2つのテキストボックスの入力を相互に反映する
概要
公式サイトにすでに解説がありますが、そこ扱われているサンプルソースがやや煩雑に感じました。なので、キーとなるポイントを抽出、分かりやすく短いソースで書いてみました。
この記事のサンプルソース概要
- テキストボックスが2つあり、税抜きと税込みの価格を表示します。
- いずれのテキストボックスも入力可能で、数字をタイプすると即時反映します。 (税抜きのテキストボックスに入力すると税込みのテキストボックスに即反映される。逆も同様。)
- 同じコンポーネント(MyForm)を2つ配置してあり、入力値が他のテキストボックスに干渉しないことを確認できる。
ソースコード
index.html<!DOCTYPE html> <html><head> <meta charset="UTF-8" /> </head> <body> <div id="root"></div> <!-- Load React. --> <!-- Note: when deploying, replace "development.js" with "production.min.js". --> <script src="https://unpkg.com/react@16/umd/react.development.js" crossorigin></script> <script src="https://unpkg.com/react-dom@16/umd/react-dom.development.js" crossorigin></script> <script src="script.js"></script> </body> </html>script.js// 軽減税率(8%) const taxrate = 0.08; // テキストボックス class MyInput extends React.Component { constructor(props) { super(props); } onChange(e) { // MyInputコンポーネントを配置したときの onValChange={...} に指定したメソッドがここで呼ばれる。 this.props.onValChange(e.target.value); } render() { const value = this.props.value; return ( <input type="text" value={value} onChange={(e) => this.onChange(e)} />); } } // 価格比較フォーム class MyForm extends React.Component { constructor(props) { super(props); this.state = { value1: 0, value2: 0 }; } handleChange1(val) { this.setState({ value1: val, value2: val * (1 + taxrate) }); } handleChange2(val) { this.setState({ value1: val / (1 + taxrate), value2: val }); } render() { return ( <div> 税抜き <MyInput name="taxout" value={this.state.value1} onValChange={(e) => this.handleChange1(e)} /><br /> 税込み <MyInput name="taxin" value={this.state.value2} onValChange={(e) => this.handleChange2(e)} />(軽減税率) </div> ); } } // 画面に表示 ReactDOM.render( <div> フォーム1 <MyForm name="form1" /><br /> フォーム2 <MyForm name="form2" /><br /> </div>, document.getElementById('root') );
- 投稿日:2020-11-23T23:09:49+09:00
【環境構築+α】Typescriptをいじってみたいお年頃
はじめに
こんにちは!フロントエンド大好きといっておきながらまだTypescriptをあまりいじったことがないヨシキです。今回はTypescriptの環境構築を行うとともに少し触っていけたらと思います。
環境
- Ubuntu 20.04 (Windows Sub System for Linux)
- node v14.15.1
- npm 6.14.8
- Typescript Version 3.8.3
インストールの軌跡
nodeとnpmに関しては、もともと入っていたのですが、念のため重ねてインストールしておきました。
sudo apt install nodejs npm次にn packageを使い安定板のnodeとnpmを取得します。
sudo npm install n -g sudo n stable以下のような文言をはいてくれれば成功です。
installing : node-v14.15.1 mkdir : /usr/local/n/versions/node/14.15.1 fetch : https://nodejs.org/dist/v14.15.1/node-v14.15.1-linux-x64.tar.xz installed : v14.15.1 (with npm 6.14.8)おや?様子が、、、
よーし!これで残すはメインのTypescriptのインストールのみだ!と心を躍らせていたら何故か躓きました、、、
参考記事のTypescriptインストール方法を試してみてもエラーが吐かれるばかりで全然インストールしてくれませんでした。以下が私の環境では無理だったインストール方法です。sudo npm install -g typescript ts-node npm install -g typescript sudo npm install -g typescript何かしらヒントを求め、ターミナルで以下のようなTypescript関連のコードを打ち込むと・・・
tsc --version以下のような出力がありました!
Command 'tsc' not found, but can be installed with: sudo apt install node-typescriptこれはと思い上記のコードを打ち込んでみると・・・
すんなり入りました笑
なぜ上記の参考文献のインストール方法を受け付けてくれなかったのかはわかりませんが入ってよかった!
同じようなエラーが発生してしまった方は是非お試しください。実際に動かしてみよう!
ターミナルでディレクトリを作成し、実際にコードを書いてみます!
最初なので、おなじみのやつ行っときますhello.tsconst message:string = 'Hello! TypeScript!'; console.log(message);ここでTypescriptは静的型付け言語なので、書いて、さあ!実行!とはいきません。
コンパイルという作業が必要なのです。
私はc++をはじめに触ったので、コンパイルがあるほうが自然なのですが、多くの人は動的型付け言語(PythonやJavaScript)を最初のプログラミング言語に選んでいる傾向があると思うので、最初は少しばかり慣れないかもしれませんね。次はチュートリアルに載っていたコードを少し改変して,クラスと構造体に関してのコードを書いてみました。
sample.tsinterface Point { x: number; y: number; } function printPoint(p: Point) { console.log(`${p.x},${p.y}`); } class VirtualPoint { x: number; y: number; constructor (x: number, y: number) { this.x = x; this.y = y; } } const newVPoint = new VirtualPoint(1, 6); printPoint(newVPoint);
interface
を使うことによってjavascriptではなかった(たぶん笑)構造体を宣言できるようになるんですね。これも静的型付け言語の特徴の1つなのかもしれませんね。おわりに
今回はTypescriptの環境構築を主にやってきました。
もう少し高位に扱うのなら、webpackを通してトランスパイルようにBabelをインストールして、Typescriptを扱うのが望ましいのかもしれません。webpackには結構親しみがあるので、今度はそちらのほうで試していきたいと思います。
それではお疲れ様でした!
この記事が参考になった方はぜひgoodしてってくだされ~笑参考文献
TypeScriptをWSL+Ubuntuで環境構築する
TypeScriptチュートリアル① -環境構築編-
公式チュートリアル
- 投稿日:2020-11-23T22:17:42+09:00
Mapbox GL JS でAttributionをコントロールする -調査編-
前回はMapboxにおけるAttributionについて取り扱いました。今回はMapbox GL JSにおけるAttributionのコントロールについて、Mapbox GL JS v1.13.0のソースを追いかけながら見ていきます。
Text attribution
オプションでの設定
Mapbox GL JSでは
Map
クラスをインスタンス化する際のオプションでtext attributionのコントロールが行なえます。このオプションによる制御はmap.jsの469,470行目で行われています。
map.js469 if (options.attributionControl) 470 this.addControl(new AttributionControl({customAttribution: options.customAttribution}));つまり、
attributionControl
がtrue
(デフォルト) の場合のみ、AttributionControl
をコントロールとして地図上に追加します。
AttributionControl
クラスのオプションはattribution_control.jsの9-12行目に定義されています。attribution_control.js9 type Options = { 10 compact?: boolean, 11 customAttribution?: string | Array<string> 12 };
compact
はattributionを折りたたむ表示をします。ただしこのオプションはMap
クラスをインスタンス化する際には指定する方法がありません。使用したい場合には以下のようにattributionControl: false
とした上で、新たにAttributionControl
インスタンスをaddControl
します。var map = new mapboxgl.Map({ container: 'map', // container id style: 'mapbox://styles/mapbox/streets-v11', // style URL center: [-74.5, 40], // starting position [lng, lat] zoom: 9, // starting zoom attributionControl: false }); map.addControl(new mapboxgl.AttributionControl({ compact: true, customAttribution: ['<a href="https://example.com/custom1">custom1</a>', 'custom2']}));
customAttribution
は文字列か文字列の配列です。記載した内容がそのままattributionとして表示されます。HTMLによるリンクの設定も可能です。また、SDKのドキュメントにも定義及び使用例が記載されています。
Attributionの追加処理
AttributionControl
クラスの中で実際にattributionを追加する処理は以下の部分になります。attribution_control.js146 if (this.options.customAttribution) { 147 if (Array.isArray(this.options.customAttribution)) { 148 attributions = attributions.concat( 149 this.options.customAttribution.map(attribution => { 150 if (typeof attribution !== 'string') return ''; 151 return attribution; 152 }) 153 ); 154 } else if (typeof this.options.customAttribution === 'string') { 155 attributions.push(this.options.customAttribution); 156 } 157 }文字列 / 文字列配列を判定して追加していく様子がわかります。
Mapboxのデフォルトtext attribution
何も指定しないのにデフォルトで表示されているMapboxのtext attributionはどこからやってくるのでしょうか?
スタイルを読み込んだ段階で、ソースの調査を行います。Style Specificationで定義されている通り、Styleにはソースに関する情報が記載されています。ベクタータイルであればTilesetの情報となります。これはベクタータイルではTileJSONと呼ばれるAPIで取得できます。以下のコードでは
loadTileJSON
でTileJSON APIにアクセスし、tileJSON
に結果を格納しています。vector_tile_source.js99 this._tileJSONRequest = loadTileJSON(this._options, this.map._requestManager, (err, tileJSON) => { 100 this._tileJSONRequest = null; 101 this._loaded = true; 102 if (err) { 103 this.fire(new ErrorEvent(err)); 104 } else if (tileJSON) { 105 extend(this, tileJSON); 106 if (tileJSON.bounds) this.tileBounds = new TileBounds(tileJSON.bounds, this.minzoom, this.maxzoom); 107 postTurnstileEvent(tileJSON.tiles, this.map._requestManager._customAccessToken); 108 postMapLoadEvent(tileJSON.tiles, this.map._getMapId(), this.map._requestManager._skuToken, this.map._requestManager._customAccessToken); 109 110 // `content` is included here to prevent a race condition where `Style#_updateSources` is called 111 // before the TileJSON arrives. this makes sure the tiles needed are loaded once TileJSON arrives 112 // ref: https://github.com/mapbox/mapbox-gl-js/pull/4347#discussion_r104418088 113 this.fire(new Event('data', {dataType: 'source', sourceDataType: 'metadata'})); 114 this.fire(new Event('data', {dataType: 'source', sourceDataType: 'content'})); 115 } 116 });street-v8 tilesetの場合はこんなJSONが取得できます。Attributionが設定されているのがわかりますね。
{ "attribution": "<a href=\"https://www.mapbox.com/about/maps/\" target=\"_blank\">© Mapbox</a> <a href=\"http://www.openstreetmap.org/about/\" target=\"_blank\">© OpenStreetMap</a> <a class=\"mapbox-improve-map\" href=\"https://www.mapbox.com/map-feedback/\" target=\ "_blank\">Improve this map</a>", "bounds": [ -180, -85, 180, 85 ], "center": [ 0, 0, 0 ], ...いつでもTileJSON APIを呼ぶの?
TileJSON APIを呼ぶかどうかはStyleの記述の仕方により異なります。
以下のように
url
で指定されている場合はTileJSON APIが呼ばれます。style.jsonsources: { street: { type: 'vector', url: 'mapbox://mapbox.mapbox-streets-v8' } },以下のように
tiles
で指定されている場合はTileJSON APIは呼ばれません。style.jsonsources: { street: { type: 'vector', tiles: ['http://a.tiles.mapbox.com/v4/mapbox.mapbox-streets-v8/{z}/{x}/{y}.vector.pbf?access_token=pk.eyJ1IjoieW9jaGkiLCJhIjoiY2tjZThvdWExMDV2dDJxcDgxZzBwbzlxYSJ9.M0yRA6SXDMRgXzXGuYnvsg'], tileSize: 512 } },これは
loadTileJSON
で実行される処理内でoptions.url
による分岐が発生し、urlを指定しているときのみTileJSON APIを呼ぶ実装となっているためです。load_tilejson.js34 if (options.url) { 35 return getJSON(requestManager.transformRequest(requestManager.normalizeSourceURL(options.url), ResourceType.Source), loaded); 36 } else { 37 return browser.frame(() => loaded(null, options)); 38 }Styleでのattributionの指定
Style Specificationにあるように、styleのsourceでもattributionを指定できます。
TileJSON APIでattributionを取得したかどうかに関わらず、styleで指定されたattributionによる上書きが行われます(以下の
extend(tileJSON, options)
の処理)。load_tilejson.js14 const loaded = function(err: ?Error, tileJSON: ?Object) { 15 if (err) { 16 return callback(err); 17 } else if (tileJSON) { 18 const result: any = pick( 19 // explicit source options take precedence over TileJSON 20 extend(tileJSON, options), 21 ['tiles', 'minzoom', 'maxzoom', 'attribution', 'mapbox_logo', 'bounds', 'scheme', 'tileSize', 'encoding'] 22 ); 23 24 if (tileJSON.vector_layers) { 25 result.vectorLayers = tileJSON.vector_layers; 26 result.vectorLayerIds = result.vectorLayers.map((layer) => { return layer.id; }); 27 } 28 29 result.tiles = requestManager.canonicalizeTileset(result, options.url); 30 callback(null, result); 31 } 32 };イベントチェーン
アトリビューション取得後は、イベントチェーンにより
AttributionControl
クラスへ処理が渡されます。Styleロード時に
Map
クラスに対してイベントを発行します。style.js244 _load(json: StyleSpecification, validate: boolean) { ... 252 for (const id in json.sources) { 253 this.addSource(id, json.sources[id], {validate: false}); 254 } ... 280 this.fire(new Event('data', {dataType: 'style'})); ...これを受けて
Map
クラスはstyledata
イベントを発行しますmap.js479 this.on('data', (event: MapDataEvent) => { 480 this._update(event.dataType === 'style'); 481 this.fire(new Event(`${event.dataType}data`, event)); 482 });
AttrubutionControl
クラスはこのイベントを受けた際に、attributionの変更を行います。source.attribution
にattributionが格納されています。attribution_control.js... 71 this._map.on('styledata', this._updateData); ... 136 _updateData(e: any) { 137 if (e && (e.sourceDataType === 'metadata' || e.sourceDataType === 'visibility' || e.dataType === 'style')) { 138 this._updateAttributions(); 139 this._updateEditLink(); 140 } 141 } ... 143 _updateAttributions() { ... 165 const sourceCaches = this._map.style.sourceCaches; 166 for (const id in sourceCaches) { 167 const sourceCache = sourceCaches[id]; 168 if (sourceCache.used) { 169 const source = sourceCache.getSource(); 170 if (source.attribution && attributions.indexOf(source.attribution) < 0) { 171 attributions.push(source.attribution); 172 } 173 } 174 }Mapbox wordmark
Mapbox wordmark表示・非表示は
Map
クラスのオプションでコントールすることができません。以下のコードでMapbox wordmarkを初期化しています。
map.js362 constructor(options: MapOptions) { ... 472 this.addControl(new LogoControl(), options.logoPosition); ... 510 addControl(control: IControl, position?: ControlPosition) { ... 522 const controlElement = control.onAdd(this); ...logo_control.js27 onAdd(map: Map) { 28 this._map = map; ... 39 this._map.on('sourcedata', this._updateLogo); 40 this._updateLogo(); ... 58 _updateLogo(e: any) { 59 if (!e || e.sourceDataType === 'metadata') { 60 this._container.style.display = this._logoRequired() ? 'block' : 'none'; 61 } 62 } 63 64 _logoRequired() { 65 if (!this._map.style) return; 66 67 const sourceCaches = this._map.style.sourceCaches; 68 for (const id in sourceCaches) { 69 const source = sourceCaches[id].getSource(); 70 if (source.mapbox_logo) { 71 return true; 72 } 73 } 74 75 return false; 76 }Styleを読み込んだ際、ソースに
mapbox_logo
があるかどうかで表示・非表示を切り替えていることがわかります。つまりTileJSON APIもしくはStyleのSourceにmapbox_logo: true
が含まれていればMapbox wordmarkが表示されます(Style Specificationにmapbox_logo
はありませんが…)。次回はユーザーコードで実際に挙動を見ていきます。
- 投稿日:2020-11-23T21:14:25+09:00
three.jsで歯車回してみた
立体といえば回転。回転といえば歯車。ということで歯車回してみました。(我ながらイミフ・・)
See the Pen Gears by kob58im (@kob58im) on CodePen.
ハマった点
arc
の仕様がちょっとややこしい。公式のドキュメントより抜粋:
.arc ( x : Float, y : Float, radius : Float, startAngle : Float, endAngle : Float, clockwise : Boolean ) : this
x, y -- The center of the arc offset from the last call.
radius -- The radius of the arc.
startAngle -- The start angle in radians.
endAngle -- The end angle in radians.
clockwise -- Sweep the arc clockwise. Defaults to false.直前に
arc
で移動した先の座標から、円の中心点(今回は原点(0,0))に移動するように、パラメータx
,y
を指定する必要がある。
(ドキュメントに記載されている「offset from the last call」は相対座標であることを指しているようである。)歯車の座標生成部分の抜粋(実コードから少しだけ簡略化してます)let arcShape = new THREE.Shape(); let t = 2*Math.PI/a.tooths; let dx1=0; let dy1=0; arcShape.moveTo( 0, 0 ); for(let i=0;i<a.tooths;i++) { let dx2 = -a.r3*Math.cos((i+a.duty)*t); let dy2 = -a.r3*Math.sin((i+a.duty)*t); arcShape.arc( dx1, dy1, a.r3, (i )*t, (i+a.duty)*t, false ); arcShape.arc( dx2, dy2, a.r2, (i+a.duty)*t, (i+ 1)*t, false ); dx1 = -a.r2*Math.cos((i+1)*t); dy1 = -a.r2*Math.sin((i+1)*t); }参考
- 投稿日:2020-11-23T20:19:25+09:00
【Nuxt.js】Vuex クラシックモードとモジュールモードの書き方【コピペ用】
クラシックモード
store/index.jsimport Vuex from "vuex" const createStore = () => { return new Vuex.Store({ state() { return { count: 0 }; }, getters: { getCount: state => state.count }, mutations: { incrementCountMutations(state) { state.count++; } }, actions: { incrementCountActions({ commit }) { commit("incrementCountMutations"); } } }); }; export default createStoreモジュールモード
動作条件
・index.jsがstoreオブジェクトをexportしない
・store配下にindex.jsが存在しないstore配下にcounter.jsを作成
store/counter.jsexport const state = () => ({ count: 0 }) export const getters = { getCount: state => state.count } export const mutations = { incrementCountMutations(state) { state.count++; } } export const actions = { incrementCountActions({ commit }) { commit("incrementCountMutations"); } }呼び出す(counter.jsの場合)
pages/index.vue<template> <div> state : {{ $store.state.counter.count }} <br> getters : {{ $store.getters['counter/getCount'] }} <br> <button @click="$store.commit('counter/incrementCountMutations')">incrementCountMutations</button> <br> <button @click="$store.dispatch('counter/incrementCountActions')">incrementCountActions</button> </div> </template>ヘルパー関数を使用する
pages/index.vue<template> <div> state : {{ count }} <br> getters : {{ getCount }} <br> <button @click="incrementCountMutations">incrementCountMutations</button> <br> <button @click="incrementCountActions">incrementCountActions</button> </div> </template> <script> import { mapState, mapGetters, mapMutations, mapActions } from 'vuex' export default { computed: { ...mapState('counter', [ 'count' ]), ...mapGetters('counter', [ 'getCount' ]) }, methods: { ...mapMutations('counter', [ 'incrementCountMutations' ]), ...mapActions('counter', [ 'incrementCountActions' ]) } } </script>
- 投稿日:2020-11-23T19:47:09+09:00
【おみくじ】確立変更:Mathクラス
確立を変更する方法
オンライン学習サイトでおみくじアプリを作成時にJavaScriptを利用したおみくじの運勢の出る確率を簡単に変更する方法が簡単だったので共有します。
const n = Math.random(); //定数の宣言 if (n < 0.05) { button.textContent = "大吉"; //5% } else if (n < 0.2) { button.textContent = "中吉"; //20% } else { button.textContent = "凶"; //75% } ※説明 Math.random() //0~1未満の数値をランダムで出してくれる。 //もし0~4の数値を出したい場合 const n = Math.floor(Math.random() * 5); Math.floor //小数点以下を切り捨てる。
- 投稿日:2020-11-23T19:15:25+09:00
大手日系メーカーからITベンチャーに転職して思うこと
大手日系メーカーからITベンチャーへ
何を思ったかITベンチャーへ転職をして半年が経ちました。(やっていたことはPHP, HTML, JavaScriptなど)
なんとなく半年経って思うことをまとめておこうと思います。転職して思うこと
20代の若いうちは貴重な時間なので、もしやりたいことがあるのであれば転職は早いうちにしたほうがいいと思った。
(年次が上がれば上がるほど転職を決断するのは難しかっただろう…)大手勤務時代は、やりたくないことをしてただただ時間が過ぎていく感じがした。
(まぁ、かと言ってベンチャーがそうじゃないとは言い切れないところがあるが、
大手特有の若手に対して理不尽な感じとか何事にも納得できない感じとかは今はしない)あと、周りのほとんどの人が仕事に対して情熱がなかった。
なんとかやり過ごそうとする人が多く、きつかった。
何のために仕事をして、何がしたいのか分からなかった。
向上心のある人と働きたいと思った。<環境について>
大手勤務時代は人が多くて人付き合いに疲れて、仕事に集中できなかった。
ベンチャーは人数が少なくていい。
無駄に気を使う必要がない。風通しが良い。
老害っぽい人が少ない、社内政治に巻き込まれない、興味のないゴシップに巻き込まれない(小声)
若手が多いからか気が合うのかも。。大手、特に日系メーカーだとルールが厳して、それを守らないと酷く怒られる。
中には理解に苦しむものも多く、若手は仕事しづらい場合も多い。
ベンチャーにはそもそもルールが少なく、社員同士で考えながら物事を進めていく感じがあってよい。
なんだがだいぶ自由になれた気がする。ただ、結局のところ大手でもベンチャーでも被雇用者であるわけで、ある意味では雇い主の奴隷みたいなところはある。
そこらへんのバランスは自分の中でもうまく考えてやっていかないといけないなぁと思うところですねITベンチャーに行って得たもの?
・やる気のある前向きな同僚(メーカー時代は会社に居座ろうとする考えの人が多かった)
・活気のある職場(同上)
・より主体的に行動ができる(他部署や業績不振に影響される不安から解放された、”歯車感”が減った)
・勉強すればするほど仕事にも活きてくるので勉強が楽しい
・プログラミングの技術的な話が聞けて、成長できる
・会社大きくなっていくことを考えるとなんだかロマンがある大手メーカーを辞めて失ったもの?
・年功序列で上がっていく給料
→資格取得などして自分で自分の評価を上げていく(修羅の道)
・安定(?)
→首を切られてもある程度は生きていけるように知識と技術力を身に着ける(修羅の道)
・良い世間体や社会的信用(?)
→ある意味しょうがない
・ローンが組みにくくなった
→ローンは組まない
・大企業に勤める友人とは話が合いにくくなったかも
→ある意味しょうがない
- 投稿日:2020-11-23T18:53:38+09:00
Rails 幹事向けアプリの作り方 複数レコードの同時登録編
最初に
この記事は、幹事向けのアプリを作成方法を記載します。
どんな構造を取るかアプリと言うと、2段階の複数登録を行うものです。①複数のテーブルと複数レコードを同時登録(イベントと複数の日付の登録)
②①で登録した複数レコード(日付など)の中間テーブルレコードの一括登録(出欠状態など)
開発時に、②複数のレコードを登録する方法について
参考となる情報がなく苦労しました。
そこで、作り方を公開して、同じ悩みを持った方の
助けに慣れればと思い記載致します。※UI部分については今回紹介していません。Gitにて確認お願いします。
目次
- 概要
- テーブルデータについて
- イベント(親)と複数の日程(子)、お店(子)の登録機能
- 参加者(親)と参加状況(子)の登録機能
- 参加者の参加状況の編集機能
- 参加状況の削除機能
1.アプリの概要
イベントを開催して、参加者の状況を管理。最終的にはイベントの日時・場所を決定するアプリです
ユーザーは2種類あり、「イベントの主催者」と「参加者」を想定しています。
Git:https://github.com/tsuyatsuya-april/ikang
HP:http://kyomo-ikang.com/events/1機能
主催者
・イベントの概要・候補日・候補店を登録・編集・削除する機能
2.テーブルデータについて
Userテーブル...主催者の名前・email・passwordを登録
Eventテーブル...イベントの名称と概要を登録
Scheduleテーブル...イベントの候補日を登録
Shopテーブル...イベントの開催場所候補を登録
Joinテーブル...参加者の名前を登録
DateAnswerテーブル...JoinとScheduleを親として、参加日程の状況を登録
ShopAnswerテーブル...JoinとShopを親として、開催場所の評価を登録作成するアプリでは、主に2つのフォームで下記の関係での登録を行う
Event(親)、Schedule(子)、Shop(子)のイベント登録フォーム
Join(親)、DateAnswer(子)、ShopAnswer(子)の参加者登録フォーム3.イベント(親)と複数の日程(子)、お店(子)の登録機能
イベントの登録機能を下記の小項目に沿って説明を行う。
尚、ユーザーの登録は、メジャーなので割愛させていただきます。
また、コードの一部抜粋した形で説明させていただきます。小項目
- 注目コード記載(Model,Controller,HTML,JS)
- fields_forメソッド
- name属性の修正
1.注目コード記載
model/event.rbclass Event < ApplicationRecord belongs_to :user has_many :shops, inverse_of: :event, dependent: :destroy accepts_nested_attributes_for :shops, allow_destroy: true endmodel/shop.rbclass Shop < ApplicationRecord belongs_to :event, inverse_of: :shops validates_presence_of :event endevents_controller.rbdef new @event = Event.new 1.times { @event.shops.build } end def create @event = Event.new(event_params) @event.user_id = current_user.id if @event.save redirect_to event_path(@event.id) else render "new" end end private def event_params params.require(:event).permit(:name, :description, schedules_attributes: [:savedate, :savetime], shops_attributes: [:shop_name, :shop_url, :map_url, :comment]) endevents/new.html<%= form_for @event,id:"event-new-form", local: true do |f| %> <%= render "share/error_messages", model: f.object %> <% 中略%> <div id="shop-add-btn"> <%= link_to "お店追加", "#", class:"btn-flat-border" %> </div> </div> <%= f.fields_for :shops do |shop_fields| %> <div id="new-shop-top"> <div id="new-shop"> <div id="shop-name-box"> <div id="shop-name"><p>店名(必須)</p></div> <%= shop_fields.text_field :shop_name, class:"shop-name-input"%> </div> <% 中略 %> <div id="shop-delete-box"> <%= link_to "お店削除", "#", class:"btn-flat-border-red", id:"shop-delete" %> </div> </div> </div> <% end %> </div> <div class="submit-box"> <%= f.submit "イベントの登録",class:"btn-flat-border-submit", id:"new-submit" %> </div> <% end %>main.js//お店の追加 function newShopAdd(){ nameNumberShopAdjust(); const shopParent = document.getElementById("new-shop-top"); const addShopBtn = document.getElementById("shop-add-btn"); let currentShopLength = document.querySelectorAll("#new-shop").length; let nextNum = currentShopLength; let shopHtml = ` <div id="new-shop"> <div id="shop-name-box"> <div id="shop-name"><p>店名(必須)</p></div> <input class="shop-name-input" type="text" name="event[shops_attributes][${nextNum}][shop_name]" id="event_shops_attributes_${nextNum}_shop_name"> </div> ~<中略>~`; addShopBtn.onclick = function(){ shopParent.insertAdjacentHTML("beforeend", shopHtml); shopDelete(); newShopAdd(); }; } //お店の削除 function shopDelete(){ let shopParent = document.querySelectorAll("#new-shop"); let shopDeleteBtn = document.querySelectorAll("#shop-delete"); let shopParentLength = shopParent.length; for (let i=0; i < shopParentLength; i++){ shopDeleteBtn[i].onclick = function(){ let conformShopLength = document.querySelectorAll("#new-shop").length; if(conformShopLength != 1){ return shopParent[i].remove(); } }; }; } //お店のname属性の値に入る数値の調整 function nameNumberShopAdjust(){ let saveShop = document.querySelectorAll(".shop-name-input"); let saveShopLength = saveShop.length; for(let j=0; j<saveShopLength; j++){ saveShop[j].removeAttribute("name"); saveShop[j].setAttribute("name",`event[shops_attributes][${j}][shop_name]`); } } }2.fields_forメソッド
このメソッドは、公式ドキュメントにて下記のような説明をしている。
モデルを固定してフォームを生成
form_for内で異なるモデルを編集できるようになる。つまり、今回でいうとEvent(親)とは別のShop(子)モデルの編集ができるようになるというものである。
ただし、fields_forを使う為にはいくつか準備が必要である。アソシエーションの設定
model/parent.rb親と子のモデルの双方向の関連付けができるようにする has_many :子モデル(複数形), invers_of: :(親モデル), dependent: :destroy 子モデルが同時に登録できるようにする accepts_nested_attributes_for :子モデル(複数形), allow_destroy: true(空白のフォームがあれば登録できないようにする)model/child.rb親と双方向の関連付けしてバリデーション設定などができるようにする。(例 shops.eventなど) belongs_to :親モデル, inverse_of: :子モデルコントローラの設定
controller/test.rbdef new @event = Event.new 関連した子の要素を生成する時はbuild(newのエイリアス)を使用する 1times { @event.shops.build } ちなみにtimesの数値の分だけ子の要素の登録フォームができる end 通常に保存する場合と同じ def create @event = 親モデル.new(event_params) if @event.save redirect_to 指定のパス else render アクション名 end end private def event_params params.require(:親モデル).permit(:親カラム1, :親カラム2, 子モデル名(複数系)_attributes: [:子カラム1]) .merge(user.id: :current_user.id) endビューの設定
views/test.html<%= form_for @event,id:url, local: true do |f| %> <%= f.text_field :親カラム名 %> ここで子モデルの登録ができるように設定を行う <%= f.fields_for :子モデル do |sf| %> <%= sf.text_field :子モデルカラム名 %> <% end %> <%= f.submit "イベントの登録" %> <% end %>上記のビュー・モデル・コントローラーの設定を行えば、
フォームの送信ボタンを押した後にデータが下記のようなパラメータで送られます。paramater.rb通常の場合 親モデル名 = { :first => 1, :second => 2} fields_forを使用した場合 親モデル名 = {:first => 1, :second => 2, 子モデル名 => {:first => 1, :second => 2 }}親モデルの中に子モデルがネストされてデータが受け渡され保存されるということになります。
これで複数モデルの複数レコードの保存の下地が整いました。3. name属性の修正
2では子モデルの複数のレコードが保存できる設定の説明を行ってきた。
ただし、javascriptを用いてお店と日程フォームの増減が行えるように設定をしています。
この時に保存が上手く行かなくなることがあったので、その原因と解消方法について記載します。問題
javascriptでフォームの追加を実行した後に全てのフォームが保存されない時があった。前提条件
fields_forを使った時のフォームのname属性がhtml上で下記のように変換されて表示されるtest.htmlfields_for内のinputタグの中にあるname属性の名前 event[schedules_attributes][0][savedate] event[schedules_attributes][1][savedate] event[schedules_attributes][3][savedate] 公式化 親モデル名[子モデル名(複数系)_attributes][要素番号][子モデルの対象カラム名]解消方法
javascriptを使ってデータの送信前にname属性の要素番号が被らないように調整する。main.js//(送信ボタンを押した時に要素番号を調整するメソッドを実行) function nameNumberShopAdjust(){ //お店のフォームのセレクタを取得 let saveShop = document.querySelectorAll(".shop-name-input"); //要素の数がいくらあるかを取得 let saveShopLength = saveShop.length; //要素数分だけループを実行 for(let j=0; j<saveShopLength; j++){ //フォームの既存で設定されたname属性を削除する saveShop[j].removeAttribute("name"); //フォームのname属性について、現在のループ回数を要素番号として付け直す。 saveShop[j].setAttribute("name",`event[shops_attributes][${j}][shop_name]`); } } }ここの部分に関しては、ドキュメントに記載されておらず挙動を読んで
仮説検証をたてたものですが上記方法で解決致しました。4.参加者(親)と参加状況(子)の登録機能
大項目3で指定した日程とお店について、参加状況を登録する方法について記載する。
join(親)、shop(親)、shop_answer(子)の中間テーブル、
join(親)、schedule(親)、date_answer(子)の中間テーブル
上記2つの登録を主に行う。
尚、解説はお店の方のみさせていただく小項目
- 注目コード記載(Model,Controller,HTM, routes)
- viewのname属性の調整
- 中間テーブルの親_idの格納
- event/showページで子のjoinコントローラにパラメータを渡す方法
- joinコントローラーのupdateアクション
1. 注目コード記載(Model,Controller,HTM, routes)
model/join.rbhas_many :shop_answers, dependent: :destroy accepts_nested_attributes_for :shop_answers, allow_destroy: truemodel/shop.rbhas_many :shop_answesr, dependent: :destroymodel/shop_answer.rbbelongs_to :join validates_presence_of :join belongs_to :shopevents_controller.rbdef show if params[:join_id] set_join else @join = Join.new 1.times { @join.shop_answers.build } end endjoins_controller.rbdef create @join = Join.new(join_params) if @join.save redirect_to event_path(params[:event_id]) else render "events/show" end end private def join_params params.require(:join).permit(:nickname, :email, shop_answers_attributes: [:shop_id, :status, :vote]) .merge(event_id: params[:event_id]) endevents/show.html<%= form_with model: @join, url:event_joins_path(@event.id), id:"join-form", local: true do |f| %> <div id="join-box"> <div id="join-name-label"> <label>ユーザー名</label> </div> <div class="join-users"> <%= f.text_field :nickname, id:"join-user",placeholder:"ニックネームを入力ください"%> </div> <div id="shop-answer"> <h1 id="shop-answer-title">店舗選択</h1> <div class="circle-text">説明: お店を◯×△で評価,一番良いと思う店に一番ボタンで投票下さい</div> <table id="shop-answer-table"> <tbody> <% @event.shops.each do |es| %> <%= f.fields_for :shop_answers do |shop_fields| %> <tr id="join-shops" class="bottom-line"> <th class="shop-label"> <div> <%= link_to es.shop_name, es.shop_url, target: :_blank %> </div> </th> <td class="shop-vote-balance"> <%= shop_fields.hidden_field :shop_id,class:"shop-id", value: es.id %> <%= shop_fields.hidden_field :status, id:"shops-status" %> <%= shop_fields.hidden_field :vote, id:"shops-vote" %> <div class="change-status"> <h1 class="btn btn--orange btn--circle btn--circle-a btn--shadow " id="shop-yes">◯</h1> <h1 class="btn btn--orange btn--circle btn--circle-a btn--shadow choice" id="shop-delta">△</h1> <h1 class="btn btn--orange btn--circle btn--circle-a btn--shadow cross-vote" id="shop-no">×</h1> </div> <div id="shop-change-vote"> <h1 class="btn btn--orange btn--circle btn--circle-a btn--shadow" id="shop-vote">一番</h1> </div> </td> </tr> <% end %> <% end %> </tbody> </table> </div> <div id="join-submit-box"> <%= f.submit "回答",class:"btn-flat-border", id:"join-submit-inputbox", :onclick => "return check_name()" %> </div> <% end %>routes.rbRails.application.routes.draw do root to: "events#index" resources :events do resources :schedules resources :shops resources :joins resources :comments resources :date_decisions resources :shop_decisions end end2. viewのname属性の調整
大項目3で登録した複数データに対して、複数の中間テーブルの保存を行うには下記のコードの構造をとる。
events/show.html<%= form_with model: @join, url:event_joins_path(@event.id), id:"join-form", local: true do |f| %> 親モデルのnicknameカラムの保存フォーム <%= f.text_field :nickname, id:"join-user",placeholder:"ニックネームを入力ください"%> イベントページに紐づいているお店のレコードを全て出力する。 <% @event.shops.each do |es| %> fields_forで複数レコード登録できるようにする。 <%= f.fields_for :shop_answers do |shop_fields| %> 中間テーブルの親IDを登録するフォーム。事前にid番号をvalueに格納する <%= shop_fields.hidden_field :shop_id, value: es.id %> javascriptを使って、数字1(=◯),2(=△),3(=×)が格納されるようにしている <%= shop_fields.hidden_field :status, id:"shops-status" %> javascriptを使って投票数0,1を格納する。 <%= shop_fields.hidden_field :vote, id:"shops-vote" %> onclickでユーザーのニックネームが格納されていない場合にフォームを送信できないようにする <%= f.submit "回答",:onclick => "return check_name()" %> <% end %> <% end %> <% end %>
解決方法
大項目3と同様な形でjavascriptを用いて、
出力されたお店の分、name属性の要素番号を付け直す
これで出力されたお店のレコード分だけ、中間テーブルのレコードが保存できるようになった。3. 中間テーブルの親_idの格納
中間テーブルであるshop_answerテーブルに親IDを紐付ける方法を記載する。
・1点目のjoin_idはfields_forメソッドを使用しているので親子関係となり自動的にIDが割り振られる。
・2点目のshop_idは下記の通りeachメソッドを用いて、各ID番号をshop_idのフォームに格納している。events/show.html<% @event.shops.each do |es| %> <%= f.fields_for :shop_answers do |shop_fields| %> 先ほども記述したがes.idでshop(親)のID番号をshop_answer_idに渡している。 <%= shop_fields.hidden_field :shop_id, value: es.id %> <% end %> <% end %>以上で中間テーブルの保存ができる状態になった
4. events/showページで子のjoinコントローラにパラメータを渡す方法
初学者にとってevents/showページで登録を行う場合、
events_controllerにパラメータをpostするという印象が強い。
しかし今回はjoins_controllerにパラメータをpostして登録を行うので
その場合のurl指定方法を記載する。まず下記コマンドを実行しURLの確認を行う
test.rbrails routes次に確認したURLの内joinの登録であるURLをform_withメソッドの中に適応させる
events/show.html<%= form_with model: @join, url:event_joins_path(@event.id), id:"join-form", local: true do |f| %>以上を行うことで、events/showページからjoins_controllerにデータを受け渡すことができるようになる。
5.参加者の参加状況の編集機能
events/show.htmlにて参加者毎の編集フォームを表示する方法について記載を行う。
複数の情報を出力する上で小項目
- 同一ページ内で参加者IDの情報をコントローラに受け渡す方法
- fields_forを用いた場合の編集フォームのviewを表示について。
- objectメソッド、親データの店名やURLを引き出す方法
1. 同一ページ内で参加者IDの情報をコントローラに受け渡す方法
まず、events/show.html上に参加者の編集フォームを出力するには、
コントローラに各参加者のjoin_idの数値を送り、データを抽出する必要がある。その為に、link_toメソッドを用いて、join_idをパラメータとしてコントローラに引き渡す手法を用いた。
events/show.html<% if @event.joins %> 登録されている参加者全てを出力する <% @event.joins.each do |event_join| %> ループ中の参加者のID、join.idをjoin_idというキーに格納してコントローラに渡している <%= link_to event_join.nickname, event_path(@event.id,join_id: event_join.id) %> <% end %> <% end %>コントローラではjoin_idというパラメータの有無によって
編集フォームの出力か登録ページの出力かの分岐を行っている。events_controller.rbdef show @joins = Join.all if params[:join_id] set_join else @join = Join.new 1.times { @join.shop_answers.build } end end private def set_join @join = Join.find(params[:join_id]) end編集フォームの場合は、決まった値をフォームに格納した状態で表示される。
登録フォームの場合は、新規にデータを作成できる状態で表示される
これでビューに表示できる条件が整った。2. fields_forを用いた場合の編集フォームのviewを表示について
登録時には、shop.each doとfields_forを用いたが編集時には、fields_forのみを用いれば、選択したJoinレコードに紐づくshop_answerレコードが全て出力される。
events/show.html<% unless params[:join_id] %> 新規登録の時の処理 <% else %> 編集時の処理 <%= form_with model: @join, url:event_join_path(@event.id, @join.id), id:"join-edit-form" , local: true do |f| %> ユーザー名の編集 <%= f.text_field :nickname,id:"join-edit-user", placeholder:"ニックネームを入力ください"%> fields_forを用いて、選択したJoinレコードに紐づく全てのshop_answerレコードを出力 <%= f.fields_for :shop_answers, @join.shop_answers do |shop_edit_fields| %> 編集ページでは新たに、joinのidカラムを格納するフォームを用意する。それ以外は登録と同じ。 <%= shop_edit_fields.hidden_field :id,class:"shop-answers-id" %> <%= shop_edit_fields.hidden_field :shop_id,class:"shop-edit-id" %> <%= shop_edit_fields.hidden_field :status, id:"shops-edit-status" %> <%= shop_edit_fields.hidden_field :vote, id:"shops-edit-vote" %> <% end % > <%= f.submit "更新",class:"btn-flat-border", id:"join-edit-submit-inputbox", :onclick => "return check_name()" %> <% end %> <% end %>同じ躓きをする方もいると思うので私が失敗した時の場合も下記の画像にて記載します。
3. objectメソッド、親テーブルの店名やURLを引き出す方法
小項目2では、フォームが正しく表示されたが実は1つ問題がある。
それは、fields_forでは、
「中間テーブルの子モデルに対し、親モデルのデータが引き出せない」ことである。
私のアプリでは店名やURLが表示されず、どのデータに紐づいているか判断出来なくなる。これに対して、shops.each do を用いて、店名を引き出せば良いと考えたこともあった。
しかし、結果は小項目2の最後の画像の通り、フォームが正しく表記されない。そこで、objectメソッドを用いることにした。
このメソッドを変数の後につけることで、変数に格納しているデータを取得できる。test.HTMLshop_answer = { id => 1, join_id => 1, shop_id => 1, status => 1} shop = { id => 1, shop_name => "吉野家", shop_url => "yoshinoya" } <%= f.fields_for :shop_answers, @join.shop_answers do |shop_edit_fields| %> snum = shop_id = 1 <% snum = shop_edit_fields.object.shop_id%> sn = Shop.find(1) <% sn = Shop.find(snum) %> これでfields_for内でshop_answerの親モデルのお店情報が出力できるようになった sn.shop_name = 吉野家 sn.shop_url = yoshinoya5. joinコントローラーのupdateアクション
fields_forを用いた時は、paramsの表記方法が異なる。
登録時には子モデルのID番号が必要なかったが
更新時には小モデルのID番号が必要となるまた、:_destroyをつけることで親モデルに紐づく子モデルのデータを削除することができる。
例えば、編集時に子モデルのフォームを空白に変更して更新するとデータが削除されるなど。joins_controller.rbdef update if @join.update(join_update_params) redirect_to event_path(params[:event_id]) else render "events/show" end end private 通常時 def join_params params.require(:join).permit(:nickname, :email, date_answers_attributes: [:schedule_id, :status], shop_answers_attributes: [:shop_id, :status, :vote]) .merge(event_id: params[:event_id]) end 更新時 def join_update_params params.require(:join).permit(:nickname, :email, date_answers_attributes: [:id,:schedule_id, :status, :_destroy], shop_answers_attributes: [:id,:shop_id, :status, :vote, :_destroy]) end6. 参加状況の削除機能
アソシエーションの設定で親レコードの削除に伴い紐づく子のレコードが削除されるようにする必要がある。
allow_destroy: trueは
親要素が削除された時、関連付けている情報もまとめて削除するためのオプションです。model/join.rbhas_many :date_answers, dependent: :destroy accepts_nested_attributes_for :date_answers, allow_destroy: truemodel/shop.rbhas_many :shop_answer, dependent: :destroymodel/shop_answer.rbbelongs_to :join validates_presence_of :join belongs_to :shop以上のアソシエーションをつけることで通常時と変わりなく、削除を実行することができる。
今回はビューとコントローラーの記述を割愛させていただきます。最後に
ここまで読んで下さってありがとうございます。
はじめて個人で作成したアプリなのでアラが目立つかとは存じますが
ご容赦下さいますようお願いします。また、何かご質問があればコメントお願いします。
お答えできる限りはお答え致します。参考
- 投稿日:2020-11-23T18:17:21+09:00
JavaScript BigIntを使用して小数を含む桁数上限のない四則演算
小数の誤差を出さない四則演算のBigInt使用バージョンです。
使用方法は前回と同じです。小数を扱えないBigIntで小数の計算を行う手順
BigIntは整数しか扱えませんので整数になるよう桁をずらしてから計算するわけですが、そのずらす桁数をどう決めればよいか、例としてAが123.45、Bが6.789の場合で考えてみます。
加算の場合
AとBの和の小数部分の桁数は最大でもAとBどちらかの小数の桁数の大きい方の値になりますので、その値だけAとB双方の桁をずらします。A 123.45 …小数部2桁
B 6.789 …小数部3桁 (こちらの桁数が大きいので3桁分ずらす)
↓
A 123450 …桁が足りずずらしきれない分は0を追加する
A 6789この値で和を求めます。
130239ずらした桁数分、戻します。
130.239減算の場合
加算と同じ手順で、加算の代わりに減算するだけです。乗算の場合
AとBの積の小数部の桁数は最大でAとBそれぞれの小数部の桁数を足した値になります。
乗算する際は単純に小数点を取り去った値で計算し、その後小数桁数分をずらします。A 123.45 …小数部2桁
B 6.789 …小数部3桁
(2桁+3桁で、5桁分が積を求めたあとにずらす桁数になりますが、積を求める際は単純に小数点を取り去ります)
↓
A 12345
B 6789この値で積を求めます。
83810205最初に求めておいた5桁分戻します。
838.10205除算の場合
ずらす桁数は加算・減算と同様AとBの小数部の桁数の大きい方の値ですが、最終的な商で小数まで求めたい場合は確保したい小数部の桁数の分だけAを余分にずらす必要があります。
ここでは小数部を10桁確保するものとして考えます。A 123.45 …小数部2桁
B 6.789 …小数部3桁
大きい方の桁数3がずらす値ですが、割られる数であるAはさらに必要小数桁数10を加えた13桁分ずらします。
丸めによる繰り上がりまで考慮したい場合はあらかじめ更に1桁余分にずらしておく必要がありますが、とりあえずここでは考えないものとします。
↓
A 1234500000000000 …13桁ずらした
B 6789 …3桁ずらしたこの値で商を求めます。
181838267786.124613345117101... ですが、BigIntで除算した商に小数部はありませんので
181838267786 となります。
ここからAに対して余分にずらしておいた10桁分を戻して
18.1838267786
が最終的な商になります。スクリプト
decimalcalc.js'use strict'; const decimalCalc = (function() { if(window.BigInt === undefined) { console.log('Your browser does not support BigInt.'); return; } return { // 加算 add: function(a, b) { a = this.toString(a); b = this.toString(b); if(isNaN(a) || isNaN(b)) return NaN; else if(a === 'Infinity' && b === '-Infinity') return NaN; else if(a === '-Infinity' && b === 'Infinity') return NaN; else if(/-?Infinity/.test(a)) return a; else if(/-?Infinity/.test(b)) return b; const p = Math.max(this.l(a), this.l(b)); if(p) { if(!/\./.test(a)) a += '0'.repeat(p); else a += '0'.repeat(p - (a.length - a.indexOf('.') - 1)); if(!/\./.test(b)) b += '0'.repeat(p); else b += '0'.repeat(p - (b.length - b.indexOf('.') - 1)); } a = a.replace('.', ''); b = b.replace('.', ''); let c = String(BigInt(a) + BigInt(b)); if(p) { c = c.replace(/^(-)?/, "$1" + '0'.repeat(p)); c = (c.slice(0, -p) + '.' + c.slice(-p)); c = this.resReplace(c); } return c; }, // 減算 sub: function(a, b) { a = this.toString(a); b = this.toString(b); if(isNaN(a) || isNaN(b)) return NaN; else if(a === 'Infinity' && b === 'Infinity') return NaN; else if(a === '-Infinity' && b === '-Infinity') return NaN; else if(/-?Infinity/.test(a)) return a; else if(/-?Infinity/.test(b)) return -b; const p = Math.max(this.l(a), this.l(b)); if(p) { if(!/\./.test(a)) a += '0'.repeat(p); else a += '0'.repeat(p - (a.length - a.indexOf('.') - 1)); if(!/\./.test(b)) b += '0'.repeat(p); else b += '0'.repeat(p - (b.length - b.indexOf('.') - 1)); } a = a.replace('.', ''); b = b.replace('.', ''); let c = String(BigInt(a) - BigInt(b)); if(p) { c = c.replace(/^(-)?/, "$1" + '0'.repeat(p)); c = (c.slice(0, -p) + '.' + c.slice(-p)); c = this.resReplace(c); } return c; }, // 乗算 mul: function(a, b) { a = this.toString(a); b = this.toString(b); if(isNaN(a) || isNaN(b)) return NaN; else if(/^-?[0.]+$/.test(a) && /-?Infinity/.test(b)) return NaN; else if(/-?Infinity/.test(a) && /^-?[0.]+$/.test(b)) return NaN; else if(a === 'Infinity' && b === 'Infinity') return Infinity; else if(a === '-Infinity' && b === '-Infinity') return Infinity; else if(/-?Infinity/.test(a) || /-?Infinity/.test(b)) return a * b; const p = this.l(a) + this.l(b); a = a.replace('.', ''); b = b.replace('.', ''); let c = String(BigInt(a) * BigInt(b)); if(p) { c = c.replace(/^(-)?/, "$1" + '0'.repeat(p)); c = (c.slice(0, -p) + '.' + c.slice(-p)); c = this.resReplace(c); } return c; }, // 除算 div: function(a, b, m) { a = this.toString(a); b = this.toString(b); if(isNaN(a) || isNaN(b)) return NaN; else if(/-?Infinity/.test(a) && /-?Infinity/.test(b)) return NaN; else if(/-?Infinity/.test(a) && !/-?Infinity/.test(b)) return a / b; else if(/-?Infinity/.test(b)) return 0; else if(/^-?[0.]+$/.test(a) && /^-?[0.]+$/.test(b)) return NaN; else if(/^[0.]+$/.test(b) || b === '') return Infinity; else if(/^-[0.]+$/.test(b)) return -Infinity; if(m === undefined) m = 20; if(m < 0) m = 0; else m = Math.floor(m); const p = Math.max(this.l(a), this.l(b)) + m + 1; if(!/\./.test(a)) a += '0'.repeat(p + m + 1); else a += '0'.repeat(p + m + 1 - (a.length - a.indexOf('.') - 1)); if(!/\./.test(b)) b += '0'.repeat(p); else b += '0'.repeat(p - (b.length - b.indexOf('.') - 1)); a = a.replace('.', ''); b = b.replace('.', ''); let c = String(BigInt(a) / BigInt(b)); c = c.replace(/^(-)?/, "$1" + '0'.repeat(p)); const n = c.slice(0, -(m + 1)).split(''), d = c.slice(-(m + 1)).split(''); // 小数部の必要桁数での丸め if(d[m] >= 5) { if(m > 0) { ++d[m - 1]; } else { ++n[n.length - 1]; } } d[m] = 0; // 丸めによる繰り上がり for(let i = 0; i < m; ++i) { const p = m - i; if(d[p] > 9) { d[p] %= 10; ++d[p - 1]; } } // 小数第一位の繰り上がりは整数部へ反映 if(d[0] > 9) { d[0] %= 10; ++n[n.length - 1]; } // 整数部の繰り上がり for(let i = 0; i < n.length - 1; ++i) { const p = n.length - i - 1; if(n[p] > 9) { n[p] %= 10; ++n[p - 1]; } } c = n.join('') + '.' + d.join(''); c = this.resReplace(c); return c; }, // 小数部の桁数取得 l: function(s) { let m = []; if(m = s.match(/^-?\d+\.?(\d+)?e-(\d+).*$/)) { return Number(m[2]) + (m[1] === undefined ? 0 : m[1].length); } return /e/.test(s) ? 0 : s.replace(/^[^.]+\.?/, '').length; }, resReplace: function(s) { return s .replace(/0+$/, '').replace(/\.$/, '') .replace(/^(-)?0+/, "$1").replace(/^(-)?\./, "$1" + '0.') .replace(/^-?$/, '0'); }, // 引数のキャスト用 toString: function(str) { if(typeof str !== 'string') str = String(str); str = str.replace(/^\++/, ''); if(/^(0x[\da-f]+|0o[0-7]+|0b[01]+)$/i.test(str)) str = String(Number(str)); str = str.trim(); let tmp; // 指数表記だったらパース if(tmp = str.match(/^(-?)([\d.]+)(e)([+-]?)(\d+)$/i)) { let n = tmp[2].split('.'); const s = tmp[1], f = tmp[4], e = Number(tmp[5]); if(n[1] === undefined) n[1] = ''; n[1] += '0'.repeat(e); if(f === '-') { n[0] = '0'.repeat(e) + n[0]; n[1] = n[0].slice(-e) + n[1]; n[0] = n[0].slice(0, -e); } else { n[0] += n[1].slice(0, e); n[1] = n[1].slice(e); } str = s + n[0].replace(/^0+$/, '') + '.' + n[1].replace(/0+$/, ''); str = str.replace(/^\./, '0.').replace(/\.$/, ''); } return str; }, }; }());参考
- 投稿日:2020-11-23T17:51:06+09:00
LINE上で動くwordpressプラグインを実装しました!
自己紹介
大学時代C言語を学んでから十数年以来のプログラミングを始めています。
本業はweb制作で、ホームページとLINE連携の需要がとても多く、LINEbotに興味を持ちました。
今回も練習した事のメモです。やりたい事
1.LINE公式アカウント内にLIFFでwordrpess予約プラグインを表示させる
2.wordpressのプラグインを利用して予約をする
3.予約完了後はチャットにメッセージを送る環境
- wordpress
- amelia - wordpress予約システムプラグイン
- LINE公式アカウント
- LIFF
- javascript
LIFFの設定
以下のサイトを参考にしながらLIFFの設定をしました。
https://qiita.com/ozaki25/items/5b3aedb80ab7c07618d2作成したLIFF URLをリッチメニューへ設定しました。
予約完了画面が表示されたらLINEへメッセージを送信
まず、予約完了画面が表示されたらLIFFへメッセージを送るを実装します。
毎回仮予約をして動作確認をするのは大変なので、1.リッチメニューをタップ
2.予約完了画面を表示
3.LINEへチャットメッセージを送信という動作を検証しました。
その際に、LIFFでconsole logを取得できるように、予約完了時のhtml構文のフッターにコンソールログを出力されるためのコードも追加しています。<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vConsole/3.3.4/vconsole.min.js"></script> <script> // Initialize vConsole function initVConsole() { window.vConsole = new window.VConsole({ defaultPlugins: ['system', 'network', 'element', 'storage'], maxLogNumber: 1000, onReady: function() { console.log('vConsole is ready.'); }, onClearLog: function() { console.log('on clearLog'); } }); } console.log("Hello LIFF1"); $(document).ready(async function() { initVConsole(); console.log("Hello LIFF2"); const liffId = "LIFFID"; // liffの初期化 liff.init({ liffId: liffId }); // これ以降にliffを利用した処理を書いていく // 開いている端末のos取得 console.log(liff.getOS()); console.log(liff.isInClient()); if(liff.isInClient() == true){ // ログインしてなければログインを促す // LINE内で開いた場合は特にログイン処理なしで大丈夫 if (!liff.isLoggedIn()) { liff.login(); return; } console.log("login"); // プロフィール情報取得 const profile = await liff.getProfile(); const displayName = profile.displayName; //LINEの名前 console.log(profile); console.log(displayName); let mes = displayName + "さんご予約ありがとうございました。\n自動返信メールを送付していますので受信boxをご確認ください。" liff.sendMessages([{ type: 'text', text: mes }]); console.log('メッセージ送信しました'); } }); </script>と記述
LIFFのエンドポンドにはLIFFの機能を実装させたい予約完了画面を設定しました。
これで、実装出来ているのか確認を取りながら進めていきました。
チャットにメッセージを返す
スマホから確認をすると、きちんとLIFFで取得したdisplayNameも表示されています。
wordrpess予約システムプラグインの設定
最後に、予約完了時に予約完了画面へ移行するように設定しました。
予約完了画面がLIFFで表示された時に、LINEにメッセージを送付します。LIFFのエンドポンドURLもLIFF用に作成したwebページを指定しました。
さいごに
まだまだjavaScriptに関する知識が未熟でとても時間がかかりました。
cromeのディベロッパーツールでconsoleログを確認していたので、liff.getOS()を使うとweb上でもiosモードで確認をしているとiosが戻り値として取得されているという事に気づきました。
LIFFからのアクセスかどうか切り分けるには、liff.isInClient()を使うという事を学びました。また、liff.getProfile()はawaitを利用しないと取得できないという事もわかりました。
予約した日付などをメッセージで返せると良いなと思っています。
- 投稿日:2020-11-23T17:51:06+09:00
LINE上で動くwordpress予約システムプラグインを実装しました!
自己紹介
大学時代C言語を学んでから十数年以来のプログラミングを始めています。
本業はweb制作で、ホームページとLINE連携の需要がとても多く、LINEbotに興味を持ちました。
今回も練習した事のメモです。やりたい事
1.LINE公式アカウント内にLIFFでwordrpess予約プラグインを表示させる
2.wordpressのプラグインを利用して予約をする
3.予約完了後はチャットにメッセージを送る環境
- wordpress
- amelia - wordpress予約システムプラグイン
- LINE公式アカウント
- LIFF
- javascript
LIFFの設定
以下のサイトを参考にしながらLIFFの設定をしました。
https://qiita.com/ozaki25/items/5b3aedb80ab7c07618d2作成したLIFF URLをリッチメニューへ設定しました。
予約完了画面が表示されたらLINEへメッセージを送信
まず、予約完了画面が表示されたらLIFFへメッセージを送るを実装します。
毎回仮予約をして動作確認をするのは大変なので、1.リッチメニューをタップ
2.予約完了画面を表示
3.LINEへチャットメッセージを送信という動作を検証しました。
その際に、LIFFでconsole logを取得できるように、予約完了時のhtml構文のフッターにコンソールログを出力されるためのコードも追加しています。<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vConsole/3.3.4/vconsole.min.js"></script> <script> // Initialize vConsole function initVConsole() { window.vConsole = new window.VConsole({ defaultPlugins: ['system', 'network', 'element', 'storage'], maxLogNumber: 1000, onReady: function() { console.log('vConsole is ready.'); }, onClearLog: function() { console.log('on clearLog'); } }); } console.log("Hello LIFF1"); $(document).ready(async function() { initVConsole(); console.log("Hello LIFF2"); const liffId = "LIFFID"; // liffの初期化 liff.init({ liffId: liffId }); // これ以降にliffを利用した処理を書いていく // 開いている端末のos取得 console.log(liff.getOS()); console.log(liff.isInClient()); if(liff.isInClient() == true){ // ログインしてなければログインを促す // LINE内で開いた場合は特にログイン処理なしで大丈夫 if (!liff.isLoggedIn()) { liff.login(); return; } console.log("login"); // プロフィール情報取得 const profile = await liff.getProfile(); const displayName = profile.displayName; //LINEの名前 console.log(profile); console.log(displayName); let mes = displayName + "さんご予約ありがとうございました。\n自動返信メールを送付していますので受信boxをご確認ください。" liff.sendMessages([{ type: 'text', text: mes }]); console.log('メッセージ送信しました'); } }); </script>と記述
LIFFのエンドポンドにはLIFFの機能を実装させたい予約完了画面を設定しました。
これで、実装出来ているのか確認を取りながら進めていきました。
チャットにメッセージを返す
スマホから確認をすると、きちんとLIFFで取得したdisplayNameも表示されています。
wordrpess予約システムプラグインの設定
最後に、予約完了時に予約完了画面へ移行するように設定しました。
予約完了画面がLIFFで表示された時に、LINEにメッセージを送付します。LIFFのエンドポンドURLもLIFF用に作成したwebページを指定しました。
さいごに
まだまだjavaScriptに関する知識が未熟でとても時間がかかりました。
cromeのディベロッパーツールでconsoleログを確認していたので、liff.getOS()を使うとweb上でもiosモードで確認をしているとiosが戻り値として取得されているという事に気づきました。
LIFFからのアクセスかどうか切り分けるには、liff.isInClient()を使うという事を学びました。また、liff.getProfile()はawaitを利用しないと取得できないという事もわかりました。
予約した日付などをメッセージで返せると良いなと思っています。
- 投稿日:2020-11-23T17:51:06+09:00
LINE上にwordpressの予約システムプラグインを実装してみました!
自己紹介
大学時代C言語を学んでから十数年以来のプログラミングを始めています。
本業はweb制作で、ホームページとLINE連携の需要がとても多く、LINEbotに興味を持ちました。
今回も練習した事のメモです。やりたい事
1.LINE公式アカウント内にLIFFでwordrpess予約プラグインを表示させる
2.wordpressのプラグインを利用して予約をする
3.予約完了後はチャットにメッセージを送る環境
- wordpress
- amelia - wordpress予約システムプラグイン
- LINE公式アカウント
- LIFF
- javascript
LIFFの設定
以下のサイトを参考にしながらLIFFの設定をしました。
https://qiita.com/ozaki25/items/5b3aedb80ab7c07618d2作成したLIFF URLをリッチメニューへ設定しました。
予約完了画面が表示されたらLINEへメッセージを送信
まず、予約完了画面が表示されたらLIFFへメッセージを送るを実装します。
毎回仮予約をして動作確認をするのは大変なので、1.リッチメニューをタップ
2.予約完了画面を表示
3.LINEへチャットメッセージを送信という動作を検証しました。
その際に、LIFFでconsole logを取得できるように、予約完了時のhtml構文のフッターにコンソールログを出力されるためのコードも追加しています。<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vConsole/3.3.4/vconsole.min.js"></script> <script> // Initialize vConsole function initVConsole() { window.vConsole = new window.VConsole({ defaultPlugins: ['system', 'network', 'element', 'storage'], maxLogNumber: 1000, onReady: function() { console.log('vConsole is ready.'); }, onClearLog: function() { console.log('on clearLog'); } }); } console.log("Hello LIFF1"); $(document).ready(async function() { initVConsole(); console.log("Hello LIFF2"); const liffId = "LIFFID"; // liffの初期化 liff.init({ liffId: liffId }); // これ以降にliffを利用した処理を書いていく // 開いている端末のos取得 console.log(liff.getOS()); console.log(liff.isInClient()); if(liff.isInClient() == true){ // ログインしてなければログインを促す // LINE内で開いた場合は特にログイン処理なしで大丈夫 if (!liff.isLoggedIn()) { liff.login(); return; } console.log("login"); // プロフィール情報取得 const profile = await liff.getProfile(); const displayName = profile.displayName; //LINEの名前 console.log(profile); console.log(displayName); let mes = displayName + "さんご予約ありがとうございました。\n自動返信メールを送付していますので受信boxをご確認ください。" liff.sendMessages([{ type: 'text', text: mes }]); console.log('メッセージ送信しました'); } }); </script>と記述
LIFFのエンドポンドにはLIFFの機能を実装させたい予約完了画面を設定しました。
これで、実装出来ているのか確認を取りながら進めていきました。
チャットにメッセージを返す
スマホから確認をすると、きちんとLIFFで取得したdisplayNameも表示されています。
wordrpess予約システムプラグインの設定
最後に、予約完了時に予約完了画面へ移行するように設定しました。
予約完了画面がLIFFで表示された時に、LINEにメッセージを送付します。LIFFのエンドポンドURLもLIFF用に作成したwebページを指定しました。
さいごに
まだまだjavaScriptに関する知識が未熟でとても時間がかかりました。
cromeのディベロッパーツールでconsoleログを確認していたので、liff.getOS()を使うとweb上でもiosモードで確認をしているとiosが戻り値として取得されているという事に気づきました。
LIFFからのアクセスかどうか切り分けるには、liff.isInClient()を使うという事を学びました。また、liff.getProfile()はawaitを利用しないと取得できないという事もわかりました。
予約した日付などをメッセージで返せると良いなと思っています。
- 投稿日:2020-11-23T17:51:06+09:00
LINE上でwordpressの予約システムプラグインを実装してみました!
自己紹介
大学時代C言語を学んでから十数年以来のプログラミングを始めています。
本業はweb制作で、ホームページとLINE連携の需要がとても多く、LINEbotに興味を持って学んでいます。お客様から悩み・感じている課題についてに相談を受ける事が多く、今はLINEbotを中心にどのような事が出来るのか、広く学んでいます。
今回は、LINE公式アカウントにつけられるリッチメニューの可能性を広げるLIFFに関する技術になります。
リッチメニューをタップするとwordpressで作成したページを読み込むというシンプルな設計ですが、wordpressのプラグインもLINE上でも正常に動きます。また、ただLINE上に予約システムが表示されるだけではなく、よりLINE上で動いているという体験をしてもらうために、予約完了後にはメッセージを送るという動作も実装しています。
全体の流れ
1.LINE公式アカウント内のリッチメニューにLIFFを設定し、wordrpess予約システムプラグインを表示させる
2.wordpressのプラグインを利用して予約をする
環境
- wordpress
- amelia - wordpress予約システムプラグイン
- LINE公式アカウント
- LIFF
- javascript
LIFFの設定
以下のサイトを参考にしながらLIFFの設定をしました。
https://qiita.com/ozaki25/items/5b3aedb80ab7c07618d2作成したLIFF URLをリッチメニューへ設定しました。
予約完了画面が表示されたらLINEへメッセージを送信
まず、予約完了画面が表示されたらLIFFへメッセージを送るを実装します。
毎回仮予約をして動作確認をするのは大変なので、1.リッチメニューをタップ
2.予約完了画面を表示
3.LINEへチャットメッセージを送信という動作を検証しました。
その際に、LIFFでconsole logを取得できるように、予約完了時のhtml構文のフッターにコンソールログを出力されるためのコードも追加しています。<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/vConsole/3.3.4/vconsole.min.js"></script> <script> // Initialize vConsole function initVConsole() { window.vConsole = new window.VConsole({ defaultPlugins: ['system', 'network', 'element', 'storage'], maxLogNumber: 1000, onReady: function() { console.log('vConsole is ready.'); }, onClearLog: function() { console.log('on clearLog'); } }); } //動いているのか検証用に使ったconsole.log console.log("Hello LIFF1"); $(document).ready(async function() { //動いているのか検証用に使ったconsole.log console.log("Hello LIFF2"); initVConsole(); const liffId = "LIFFID"; // liffの初期化 liff.init({ liffId: liffId }); // これ以降にliffを利用した処理を書いていく // 開いている端末のos取得 console.log(liff.getOS()); console.log(liff.isInClient()); if(liff.isInClient() == true){ // ログインしてなければログインを促す // LINE内で開いた場合は特にログイン処理なしで大丈夫 if (!liff.isLoggedIn()) { liff.login(); return; } //動いているのか検証用に使ったconsole.log console.log("login"); // プロフィール情報取得 const profile = await liff.getProfile(); const displayName = profile.displayName; //LINEの名前 //動いているのか検証用に使ったconsole.log console.log(profile); console.log(displayName); let mes = displayName + "さんご予約ありがとうございました。\n自動返信メールを送付していますので受信boxをご確認ください。" liff.sendMessages([{ type: 'text', text: mes }]); //動いているのか検証用に使ったconsole.log console.log('メッセージ送信しました'); } }); </script>以下の部分がvconsoleを表示させるために必要な部分です。
// Initialize vConsole function initVConsole() { window.vConsole = new window.VConsole({ defaultPlugins: ['system', 'network', 'element', 'storage'], maxLogNumber: 1000, onReady: function() { console.log('vConsole is ready.'); }, onClearLog: function() { console.log('on clearLog'); } }); }LIFFのエンドポンドにはLIFFの機能を実装させたい予約完了画面を設定しました。
これで、実装出来ているのか確認を取りながら進めていきました。
チャットにメッセージを返す
スマホから確認をすると、きちんとLIFFで取得したdisplayNameも表示されています。
wordrpess予約システムプラグインの設定
最後に、予約完了時に予約完了画面へ移行するように設定しました。
予約完了画面がLIFFで表示された時に、LINEにメッセージを送付します。LIFFのエンドポンドURLもLIFF用に作成したwebページを指定しました。
さいごに
まだまだjavaScriptに関する知識が未熟でとても時間がかかりました。
cromeのディベロッパーツールでconsoleログを確認していたので、liff.getOS()を使うとweb上でもiosモードで確認をしているとiosが戻り値として取得されているという事に気づきました。
LIFFからのアクセスかどうか切り分けるには、liff.isInClient()を使うという事を学びました。また、liff.getProfile()はawaitを利用しないと取得できないという事もわかりました。
予約した日付などをメッセージで返せると良いなと思っています。
- 投稿日:2020-11-23T17:07:36+09:00
配列を繰り返して取り出す処理が言語によって微妙に違う…
for文で配列を1つづつ取り出して変数に代入したいことってよくありますよね。
色々な言語による微妙な違いで脳がやられます。ここではPython,JavaScript,PHPを取り上げます。
Pythonだと
for(変数 in 配列) #繰り返したい処理inなんですよね。そしてPythonは{}なしでインデントで表します。
でみんな大好きJSだと
//配列のとき for(変数 of 配列){ //繰り返したい処理 }ofなんですよね。ちなみにオブジェクトだとinを使います。
で、PHPだと
foreach($配列 as $変数){ //繰り返したい処理 }for文ではなくforeach使ってしかもasです。
混同しそうです………
- 投稿日:2020-11-23T16:20:25+09:00
JavaScript基礎まとめ
JavaScriptの基礎を自分の備忘録&アウトプットも兼ねて記述。
そもそも、、JavaScriptはWEBページ内でページ遷移や更新をせずに画面を表示できる。
つまり、読み込む時間がないのでユーザーはストレスなく使えるということ。検証ツール(ディベロッパーツール)で確認ができる(Rubyでいうirb的なやつ)
右クリック+検証、または⌘ + option + Cで出せる1.変数定義
var(ほとんど使わないらしいから省略)
const
let の3種類・const 再代入、再定義NG
const sample = "Hello" sample = "Hello" 再代入NG const sample ="こんにちは" 再定義もNG・let
const sample = "Hello" sample = "Hello" 再代入OK const sample ="こんにちは" 再定義はNG2.テンプレートリテラル
文字の中に変数を入れるrubyだと
name = "yamada" puts #{name}さんだったのが
const name = "yamada" console.log(`${name}さん`)この``で囲うのがテンプレートリテラル
3.条件分岐
if (条件式1) { // 条件式1がtrueのときの処理 } else if (条件式2) { // 条件式1がfalseで条件式2がtrueのときの処理 } else { // 条件式1も条件式2もfalseのときの処理 }rubyとの違いは
条件式を()で囲ったり、条件式の後の処理は{}で囲うこと、
elsif ではなく else ifぐらい、、?4.繰り返し(for文)
for ([①初期化式]; [②条件式]; [③加算式]) { // 繰り返す処理の内容 }①初期化式
変数の定義をする
②条件式
処理内容を何回繰り返すか
③加算式
①で定義をした変数の増減を書くlet name = ['A','B','C','D','E'] for (let i = 0; i <= name.length; i ++) { console.log(name[i]) }①②③で考えると
①変数 i = 0
②配列の中から i 番目を取り出す(現時点ではi=0としている)
このi番目の長さがnameの配列の個数以上になるまで繰り返す
③繰り返す度にiに1ずつ加えていく、、、もし間違えている点があればコメントいただきたいです!よろしくお願いいたします。
- 投稿日:2020-11-23T15:38:34+09:00
Lazy Loadを使ってみる【現状NG】
現状
遅延表示することができていません
概要
下記を参考にLazyを使ってみました。Lazy Load何?とか 、なぜ使うの? とかも記載されてるます
Lazy Loadで画像を遅延ロードする方法手順
- lazyloadのjsを読み込む
<script src="https://cdn.jsdelivr.net/npm/lazyload@2.0.0-rc.2/lazyload.min.js"></script>
- imgタグをの画像指定をsrcからdata-srcに変更(※下記はVueで記述)
- imgタグのclassにlazyloadを追加
<!-- <img class="content_img" :src="videoInfo.img_url"> --> <img class="content_img lazyload" :data-src="videoInfo.img_url">
- bodyの最後でlazyload()を実行
- 投稿日:2020-11-23T15:24:22+09:00
メモ:超簡単にURLパラメータ事前入力
コード
window.onload = function(){ const searchParams = new URLSearchParams(decodeURI(location.search)); var input = document.getElementById('input'); input.value = searchParams.get('q'); window.history.replaceState(0, 0,'./'); };内容
ページ読み込み時に実行
window.onload = function(){ //処理 };デコードしたURLからパラメータを取得
const searchParams = new URLSearchParams(decodeURI(location.search));
q
というパラメータの値をinput
にセットvar input = document.getElementById('input'); input.value = searchParams.get('q');ブラウザのURL欄からパラメータを削除
window.history.replaceState(0, 0,'./');
- 投稿日:2020-11-23T15:10:32+09:00
GAS内のHTML間で遷移したり、パラメーターを渡したりする方法
はじめに
GAS で複数の HTML が含まれる Web アプリを作ることがあります。
そういう時に、特定の HTML を開きたいとか、HTML どうしでページ遷移したいとか、パラメーター渡したいとか思うけどやり方をいつも忘れてるので、まとめておきます。対象の方
- 基本的な JavaScript が読める方
- GAS で Web アプリを作ったことがある方
前提
以下のような構造の GAS プロジェクトを想定します。
ここで GAS プロジェクト と言っているのは、Google ドライブ上で「新規」→「その他」→「Google Apps Script」から作成されるファイルを指します。
- GAS プロジェクト
- Code.gs
- index.html
- page1.html
- page2.html
特定の HTML を開けるようにする
一般的な Web サイトのように URL から開く HTML を指定したいです。
ただし GAS の Web アプリには標準でそういった機能は提供されていません(と思います)。ないんだったら自分で作ればいいのよ!
以下のコードを書いておけば、URL から開く HTML を指定できるようになります。
Code.gsfunction doGet(e) { let page = e.parameter.page; if (!page) { page = 'index'; } return HtmlService.createTemplateFromFile(page).evaluate(); }GET でアクセスされると
Code.gs
のdoGet
関数が呼ばれるので、ここでリクエストパラメーターpage
と同じ名前の HTML を返すようにしています。
例えば https://script.google.com/xxxxx/exec?page=page1 を開けば、GAS プロジェクト内のpage1.html
が返されるようになります。HTML から別の HTML へ遷移する
GAS 内部の HTML どうしで行き来できるようにします。
まず準備として、
Code.gs
に下記の関数を追加します。
アプリの URL を返す関数です。Code.gsfunction getAppUrl() { return ScriptApp.getService().getUrl(); }a 要素を使う場合
HTML の a 要素を下記のように書きます。
index.html<a href="<?= getAppUrl() ?>?page=page1">PAGE1へ</a> <a href="<?= getAppUrl() ?>?page=page2">PAGE2へ</a>page1.html<a href="<?= getAppUrl() ?>?page=index">INDEXへ</a>
getAppUrl()
でアプリの URL が取得できるので、その後ろにリクエストパラメーターでページを指定すれば良いです。JS から遷移する場合
index.html<script> window.top.location.href = '<?= getAppUrl() ?>?page=page1'; </script>
window.top.location.href
に URL を代入すれば遷移できます。各 HTML で固有のリクエストパラメーターを使う
アクセスされる URL は↓と想定します。
https://script.google.com/xxxxx/exec?page=page1¶m1=hoge¶m2=fugaクライアントサイド
page1.html<script> google.script.url.getLocation(function(location) { const param1 = location.parameter.param1; console.log(param1); // => 'hoge' const param2 = location.parameter.param2; console.log(param2); // => 'fuga' }); </script>
google.script.url.getLocation
のコールバック関数内でリクエストパラメーターを取得できます。サーバーサイド
前に書いた
doGet
関数を修正します。Code.gsfunction doGet(e) { let page = e.parameter.page; if (!page) { page = 'index'; } // -- 変更ここから -- const template = HtmlService.createTemplateFromFile(page); if (page === 'page1') { template.param1 = e.parameter.param1; template.param2 = e.parameter.param2; } return template.evaluate(); // -- 変更ここまで -- }doGet で受け取ったパラメーターをテンプレートに設定しておけば、HTML 側でスクリプトレットを使って
param1
とparam2
を取得できます。page1.html<div> <span><?= param1 ?></span> <span><?= param2 ?></span> </div>画面ごとに使うパラメーター名は違うと思われます。
この方法だと画面ごとに分岐してパラメーターを設定する処理を書くことになります。画面ごとにいちいちやってられない!という場合は、受け取ったリクエストパラメーターをまとめて登録することもできます。
Code.gsfunction doGet(e) { let page = e.parameter.page; if (!page) { page = 'index'; } // -- 変更ここから -- const template = HtmlService.createTemplateFromFile(page); for (const key in e.parameter) { template[key] = e.parameter[key]; } return template.evaluate(); // -- 変更ここまで -- }これだとラクだし、コードもスッキリですが、意図していないリクエストパラメーターまで登録される可能性があるので、その点は注意してください。
- 投稿日:2020-11-23T14:35:33+09:00
Prettier(フォーマッタ) => ESLint(静的解析)の設定
これはなに
タイトルの内容についての2020年11月23日時点でのメモ。
やること
プロジェクトのディレクトリにてCLIで
$ npm init -y //package.json $ npm install -D prettier eslint eslint-config-prettier次に、
./node_modules/.bin
のPATHを通すPATHを通したら、
$ code -r .eslintrc.json .prettierrc
.eslintrc.json
は以下のようにする(ミニマルな形).eslintrc.json{ "env": { "es6": true }, "extends": ["prettier"] }注意として、
extends
の配列に"eslint:recommended"
を入れると失敗しました
.prettierrc
は以下のようにする(わたしのばあい)ESLintはフォーマッタとして使用しないので、このファイルに細かいルールを書いていく
ちなみに
tabwidth
はデフォルトで2なので書かなくてもいい.prettierrc{ "tabWidth": 2, "singleQuote": true }最後に、
settings.json
に以下を追加settings.json{ "editor.formatOnSave": true, "editor.defaultFormatter": "esbenp.prettier-vscode", "editor.codeActionsOnSave": { "source.fixAll.eslint": true } }ここまでやって、
VSCodeの右下にある
ESLint
とPrettier
にチェックがついて、ファイルを保存したときに両方行われていればOK.参考
- 投稿日:2020-11-23T13:57:49+09:00
初心者のためのWeb開発講座 レッスン01 プログラミング言語と開発ツール入門
MicrosoftがWeb-Dev-For-Beginnersという、Web開発初心者向けの講座を公開しています。
全24章からなる大作のレッスン講座となっています。以下はそのうち最初の第一章、Introduction to Programming Languages and Tools of the Tradeの日本語訳です。
プログラミング言語と開発ツール入門
このレッスンでは、まずプログラミング言語の基礎を学びます。
ここで取り上げた特徴は、最新のプログラミング言語のほとんどに当てはまります。
次にツールセクションでは、開発者にとって有用なソフトウェアを紹介します。Sketchnote by Tomomi Imura
事前クイズ
・コードを書かなくてもプログラムを作ることはできる?
はい / いいえ
・低レベル言語がポピュラーな分野はどこですか?
Webサイト / ハードウェア / ゲームソフト
・Web開発者と最も関連性の高いツールはどれでしょう?
Raspberry Piのようなハードウェア / ブラウザ開発者ツール / OSドキュメント
Introduction
この講座には以下が含まれています。
- プログラミングとは何か?
- プログラミング言語の種類
- プログラムの基本要素
- 開発ツール
プログラミングとは何か?
プログラミング(コーディングとも表されます)とは、コンピュータやモバイル機器などのデバイスに対して、命令を出すことです。
命令はプログラミング言語で書かれており、そしてデバイスはその命令を解釈して実行します。
この命令は色々な名称で呼ばれることがありますが、プログラム、コンピュータプログラム、アプリケーション(アプリ)、実行可能ファイル、などが一般的に使われています。プログラムとは、コードで書かれている何かです。
Webサイトも、ゲームも、スマホアプリも、全てがプログラムです。
コードを書かずにプログラムを作ることも可能ですが、作り上げたロジックは実際にはコードで書かれていて、実際にデバイスが解釈するのはコードです。
プログラムはコードを実行し、デバイスに命令を出します。
あなたが今このレッスンを読んでいるということは、文字を画面に表示するというプログラムをデバイスが実行しているということです。✅ ちょっと調べてみましょう:世界初のコンピュータプログラマは誰でしょうか?
プログラミング言語
プログラミング言語の主な目的は、開発者がデバイスに対して命令を送信する手助けをすることです。
デバイスはバイナリ(要するに0と1)しか理解することができず、そしてほとんどの開発者にとってバイナリはあまり効率的な命令手段ではありません。
プログラミング言語は、人間とコンピュータがコミュニケーションを取るための手段なのです。世の中には様々な目的のために様々なプログラミング言語が存在しています。
たとえばJavaScriptは主にWebアプリケーションのために、Bashは主にOSとの対話のために使われます。低レベル言語とは、デバイスが命令を解釈するために必要なステップ数が、高レベル言語より少ない言語のことです。
高レベル言語が人気であるのは、その読みやすさとサポート能力によるものです。
JavaScriptは高レベル言語とされています。次のコードは、高レベル言語であるJavaScriptと、低レベル言語であるARMアセンブリの違いを表したものです。
let number = 10 let n1 = 0, n2 = 1, nextTerm; for (let i = 1; i <= number; i++) { console.log(n1); nextTerm = n1 + n2; n1 = n2; n2 = nextTerm; }area ascen,code,readonly entry code32 adr r0,thumb+1 bx r0 code16 thumb mov r0,#00 sub r0,r0,#01 mov r1,#01 mov r4,#10 ldr r2,=0x40000000 back add r0,r1 str r0,[r2] add r2,#04 mov r3,r0 mov r0,r1 mov r1,r3 sub r4,#01 cmp r4,#00 bne back end信じられないかもしれませんが、両者は同じ処理をしています。
いずれもフィボナッチ数を順番に10個出力します。✅ フィボナッチ数とは、各数がその手前の二つの値の和である値の集合です。
プログラムの基本要素
プログラムにおいてひとつの命令は文と呼ばれ、文は通常は改行などの区切り文字で分かたれます。
プログラムの区切り文字は言語によって異なります。多くのプログラムは、ユーザや他から来た入力データによって出力が変わります。
データによってプログラムの動作を変えるため、プログラミング言語にはデータを一時的に保存しておく方法が用意されています。
そのデータは変数と呼ばれています。
変数とは、デバイス内のメモリにデータを保存する文のことです。
プログラムにおける変数は数学における変数と似ていて、変数には固有の名称があり、その値は処理の経過に伴って変化することがあります。プログラムには、実行されない文が現れることがあります。
これは、そうなるように開発者が設計して制御されたものであるか、予期せぬエラーが発生したかのいずれかです。
アプリケーションの制御は、堅牢で信頼性の高いプログラムを書くために必要なことです。
制御は、何らかの条件が満たされたことによって発生することが一般的です。
最近のプログラミング言語には、実行される文を制御するためにif...else
のような文が存在します。✅ これら文については次のレッスンで詳しく学びます。
開発ツール
このセクションでは、プロの開発者としての道を歩き始めるときに役立つであろうソフトウェアについて紹介します。
開発環境とは、開発者がソフトウェアを作成する際に頻繁に使用するツールや機能のセットのことです。
開発環境は、開発者が特定のニーズに合わせてカスタマイズしたり、優先順位を変更したり、異なるプログラミング言語を使用したりといった理由で次々と変わっていくこともあります。
開発環境は、それを使用する開発者の数と同じくらい千差万別です。エディタ
ソフトウェア開発において最も重要なツールのひとつが、エディタです。
エディタはコードを書く場所であり、時にはコードを実行する場所であることもあります。開発者がエディタを使う理由は、それ以外にも多々あります。
デバッグ
コードを1行1行ステップ実行することで、バグやエラーを発見しやすくします。
デバッグ機能を備えたエディタや、特定のプログラミング言語用にカスタマイズすることができるエディタも存在します。シンタックスハイライト
コードに色や書式を設定することで、コードを読みやすくします。
多くのエディタはシンタックスハイライトをカスタマイズすることもできます。拡張機能
デフォルトでは含まれない、一部の開発者向けに特化された拡張機能を追加することができます。
例えば、コードをドキュメント化してどのように動作するかを確認したいユーザがいて、タイプミスのチェックのためにスペルチェックを行いたいユーザがいて、各自が自分のほしい拡張機能をインストールすることができます。
ほとんどの拡張機能は特定のエディタでのみ動作するもので、そしてほとんどのエディタは拡張機能を検索する方法を用意しています。カスタマイズ
ほとんどのエディタは幅広いカスタマイズが可能で、開発者は自分のニーズに合わせた開発環境を作ることができます。
さらに多くのエディタでは、開発者が自分で拡張機能を作ることも可能です。有名なエディタとWeb開発向けのエクステンション
ブラウザ
もうひとつの重要なツールはブラウザです。
Web開発者は、自分のコードがWeb上でどのように実行されるかを確認するため、そしてHTML要素を視覚的に確認するためにブラウザを必要とします。多くのブラウザには開発者ツールが付属しており、アプリケーションの動作を収集・分析するために便利な機能や情報があります。
たとえば開発者ツールを使って、Webページにエラーがあった場合に、いつどこでエラーが発生したかを把握することができます。有名なブラウザ
コマンドラインツール
開発者の中には、日常の作業をGUIで行うことを好まず、コマンドラインに多くを頼る人もいます。
コードの開発には多量のタイピングが必要となるため、マウスを使うためにキーボードから手を外すことで作業の流れを中断されることを避け、キーボードショートカットを駆使して複数のファイルを操作したり複数のツールを併用したりします。多くのタスクはマウスだけでも実行可能です。
しかし、コマンドラインの利点のひとつが、マウスとキーボードで行ったり来たりせずにキーボードだけで作業を完結できるというものです。
またコマンドラインのもうひとつの利点が、カスタム設定を登録しておくことが可能ということです。
さらにマシンを新調したときに設定を移動することもできます。開発環境は個人個人によって非常に異なるため、コマンドラインの使用を避ける人もいれば、完全にコマンドラインだけしか使わない人もいます。
両方とも使う人も多いでしょう。有名なコマンドラインオプション
コマンドラインオプションは、OSによって異なります。
?はOSにプリインストールされているものです。
Windows
- Powershell ?
- Command Line (またの名をCMD) ?
- Windows Terminal
- mintty
MacOS
Linux
有名なコマンドラインツール
ドキュメント
何か新しいことを学びたいと思ったとき、その使い方を学ぶためには大抵ドキュメントに頼ることになるでしょう。
開発者は、ツールや言語を適切に使用する方法を学んだり、その仕組みについて深く調べる場合、まずはドキュメントを参照します。Web開発者向けの有名なドキュメント
✅ Web開発者の開発環境の基礎を理解したところで、Webデザイナーの開発環境との違いを見てみましょう。
? チャレンジ
いくつかのプログラミング言語について比較してみましょう。
JavaScriptとJavaの特徴は?
COBOLとGoについては?事後テスト
・Webサイトを作るときに最もよく使われるプログラミング言語はどれでしょう?
機械語 / JavaScript / Bash
・開発環境は開発者ごとに異なる?
はい / いいえ
・バグだらけのコードを修正するため、開発者は何をしますか?
シンタックスハイライト / デバッグ / コードフォーマット
レビュー & 自習
プログラマーが利用できる様々な言語について、少しだけかじってみましょう。
ひとつの言語で1行書いて実行したら、次は別のふたつの言語で同じことをやってみてください。
なにかわかりましたか?感想
初心者向け講座の最初のレッスンだけあって、一切コードは出てこず紹介だけの内容です。
本当に初めての人向けの講座ですね。
次のレッスンからはいよいよJavaScriptか、と思えば先にGitHubの使い方とアクセシビリティの話があって、その後ようやくJavaScriptというなかなか変わった構成になっています。そんなペースで大丈夫か?と思えば後半は色々なプロジェクトを作ったりしています。
全24レッスンを踏破したころには、きっといっぱしのWeb開発者になっていることでしょう。
- 投稿日:2020-11-23T13:57:49+09:00
初心者のためのWeb開発 01 レッスンの準備 - プログラミング言語と開発ツール入門
MicrosoftがWeb-Dev-For-Beginnersという、Web開発初心者向けの講座を公開しています。
全24章からなる大作のレッスン講座となっています。以下はそのうち最初の第一章、Introduction to Programming Languages and Tools of the Tradeの日本語訳です。
プログラミング言語と開発ツール入門
このレッスンでは、まずプログラミング言語の基礎を学びます。
ここで取り上げた特徴は、最新のプログラミング言語のほとんどに当てはまります。
次にツールセクションでは、開発者にとって有用なソフトウェアを紹介します。Sketchnote by Tomomi Imura
事前クイズ
・コードを書かなくてもプログラムを作ることはできる?
はい / いいえ
・低レベル言語がポピュラーな分野はどこですか?
Webサイト / ハードウェア / ゲームソフト
・Web開発者と最も関連性の高いツールはどれでしょう?
Raspberry Piのようなハードウェア / ブラウザ開発者ツール / OSドキュメント
Introduction
この講座には以下が含まれています。
- プログラミングとは何か?
- プログラミング言語の種類
- プログラムの基本要素
- 開発ツール
プログラミングとは何か?
プログラミング(コーディングとも表されます)とは、コンピュータやモバイル機器などのデバイスに対して、命令を出すことです。
命令はプログラミング言語で書かれており、そしてデバイスはその命令を解釈して実行します。
この命令は色々な名称で呼ばれることがありますが、プログラム、コンピュータプログラム、アプリケーション(アプリ)、実行可能ファイル、などが一般的に使われています。プログラムとは、コードで書かれている何かです。
Webサイトも、ゲームも、スマホアプリも、全てがプログラムです。
コードを書かずにプログラムを作ることも可能ですが、作り上げたロジックは実際にはコードで書かれていて、実際にデバイスが解釈するのはコードです。
プログラムはコードを実行し、デバイスに命令を出します。
あなたが今このレッスンを読んでいるということは、文字を画面に表示するというプログラムをデバイスが実行しているということです。✅ ちょっと調べてみましょう:世界初のコンピュータプログラマは誰でしょうか?
プログラミング言語
プログラミング言語の主な目的は、開発者がデバイスに対して命令を送信する手助けをすることです。
デバイスはバイナリ(要するに0と1)しか理解することができず、そしてほとんどの開発者にとってバイナリはあまり効率的な命令手段ではありません。
プログラミング言語は、人間とコンピュータがコミュニケーションを取るための手段なのです。世の中には様々な目的のために様々なプログラミング言語が存在しています。
たとえばJavaScriptは主にWebアプリケーションのために、Bashは主にOSとの対話のために使われます。低レベル言語とは、デバイスが命令を解釈するために必要なステップ数が、高レベル言語より少ない言語のことです。
高レベル言語が人気であるのは、その読みやすさとサポート能力によるものです。
JavaScriptは高レベル言語とされています。次のコードは、高レベル言語であるJavaScriptと、低レベル言語であるARMアセンブリの違いを表したものです。
let number = 10 let n1 = 0, n2 = 1, nextTerm; for (let i = 1; i <= number; i++) { console.log(n1); nextTerm = n1 + n2; n1 = n2; n2 = nextTerm; }area ascen,code,readonly entry code32 adr r0,thumb+1 bx r0 code16 thumb mov r0,#00 sub r0,r0,#01 mov r1,#01 mov r4,#10 ldr r2,=0x40000000 back add r0,r1 str r0,[r2] add r2,#04 mov r3,r0 mov r0,r1 mov r1,r3 sub r4,#01 cmp r4,#00 bne back end信じられないかもしれませんが、両者は同じ処理をしています。
いずれもフィボナッチ数を順番に10個出力します。✅ フィボナッチ数とは、各数がその手前の二つの値の和である値の集合です。
プログラムの基本要素
プログラムにおいてひとつの命令は文と呼ばれ、文は通常は改行などの区切り文字で分かたれます。
プログラムの区切り文字は言語によって異なります。多くのプログラムは、ユーザや他から来た入力データによって出力が変わります。
データによってプログラムの動作を変えるため、プログラミング言語にはデータを一時的に保存しておく方法が用意されています。
そのデータは変数と呼ばれています。
変数とは、デバイス内のメモリにデータを保存する文のことです。
プログラムにおける変数は数学における変数と似ていて、変数には固有の名称があり、その値は処理の経過に伴って変化することがあります。プログラムには、実行されない文が現れることがあります。
これは、そうなるように開発者が設計して制御されたものであるか、予期せぬエラーが発生したかのいずれかです。
アプリケーションの制御は、堅牢で信頼性の高いプログラムを書くために必要なことです。
制御は、何らかの条件が満たされたことによって発生することが一般的です。
最近のプログラミング言語には、実行される文を制御するためにif...else
のような文が存在します。✅ これら文については次のレッスンで詳しく学びます。
開発ツール
このセクションでは、プロの開発者としての道を歩き始めるときに役立つであろうソフトウェアについて紹介します。
開発環境とは、開発者がソフトウェアを作成する際に頻繁に使用するツールや機能のセットのことです。
開発環境は、開発者が特定のニーズに合わせてカスタマイズしたり、優先順位を変更したり、異なるプログラミング言語を使用したりといった理由で次々と変わっていくこともあります。
開発環境は、それを使用する開発者の数と同じくらい千差万別です。エディタ
ソフトウェア開発において最も重要なツールのひとつが、エディタです。
エディタはコードを書く場所であり、時にはコードを実行する場所であることもあります。開発者がエディタを使う理由は、それ以外にも多々あります。
デバッグ
コードを1行1行ステップ実行することで、バグやエラーを発見しやすくします。
デバッグ機能を備えたエディタや、特定のプログラミング言語用にカスタマイズすることができるエディタも存在します。シンタックスハイライト
コードに色や書式を設定することで、コードを読みやすくします。
多くのエディタはシンタックスハイライトをカスタマイズすることもできます。拡張機能
デフォルトでは含まれない、一部の開発者向けに特化された拡張機能を追加することができます。
例えば、コードをドキュメント化してどのように動作するかを確認したいユーザがいて、タイプミスのチェックのためにスペルチェックを行いたいユーザがいて、各自が自分のほしい拡張機能をインストールすることができます。
ほとんどの拡張機能は特定のエディタでのみ動作するもので、そしてほとんどのエディタは拡張機能を検索する方法を用意しています。カスタマイズ
ほとんどのエディタは幅広いカスタマイズが可能で、開発者は自分のニーズに合わせた開発環境を作ることができます。
さらに多くのエディタでは、開発者が自分で拡張機能を作ることも可能です。有名なエディタとWeb開発向けのエクステンション
ブラウザ
もうひとつの重要なツールはブラウザです。
Web開発者は、自分のコードがWeb上でどのように実行されるかを確認するため、そしてHTML要素を視覚的に確認するためにブラウザを必要とします。多くのブラウザには開発者ツールが付属しており、アプリケーションの動作を収集・分析するために便利な機能や情報があります。
たとえば開発者ツールを使って、Webページにエラーがあった場合に、いつどこでエラーが発生したかを把握することができます。有名なブラウザ
コマンドラインツール
開発者の中には、日常の作業をGUIで行うことを好まず、コマンドラインに多くを頼る人もいます。
コードの開発には多量のタイピングが必要となるため、マウスを使うためにキーボードから手を外すことで作業の流れを中断されることを避け、キーボードショートカットを駆使して複数のファイルを操作したり複数のツールを併用したりします。多くのタスクはマウスだけでも実行可能です。
しかし、コマンドラインの利点のひとつが、マウスとキーボードで行ったり来たりせずにキーボードだけで作業を完結できるというものです。
またコマンドラインのもうひとつの利点が、カスタム設定を登録しておくことが可能ということです。
さらにマシンを新調したときに設定を移動することもできます。開発環境は個人個人によって非常に異なるため、コマンドラインの使用を避ける人もいれば、完全にコマンドラインだけしか使わない人もいます。
両方とも使う人も多いでしょう。有名なコマンドラインオプション
コマンドラインオプションは、OSによって異なります。
?はOSにプリインストールされているものです。
Windows
- Powershell ?
- Command Line (またの名をCMD) ?
- Windows Terminal
- mintty
MacOS
Linux
有名なコマンドラインツール
ドキュメント
何か新しいことを学びたいと思ったとき、その使い方を学ぶためには大抵ドキュメントに頼ることになるでしょう。
開発者は、ツールや言語を適切に使用する方法を学んだり、その仕組みについて深く調べる場合、まずはドキュメントを参照します。Web開発者向けの有名なドキュメント
✅ Web開発者の開発環境の基礎を理解したところで、Webデザイナーの開発環境との違いを見てみましょう。
? チャレンジ
いくつかのプログラミング言語について比較してみましょう。
JavaScriptとJavaの特徴は?
COBOLとGoについては?事後テスト
・Webサイトを作るときに最もよく使われるプログラミング言語はどれでしょう?
機械語 / JavaScript / Bash
・開発環境は開発者ごとに異なる?
はい / いいえ
・バグだらけのコードを修正するため、開発者は何をしますか?
シンタックスハイライト / デバッグ / コードフォーマット
レビュー & 自習
プログラマーが利用できる様々な言語について、少しだけかじってみましょう。
ひとつの言語で1行書いて実行したら、次は別のふたつの言語で同じことをやってみてください。
なにかわかりましたか?感想
初心者向け講座の最初のレッスンだけあって、一切コードは出てこず紹介だけの内容です。
本当に初めての人向けの講座ですね。
次のレッスンからはいよいよJavaScriptか、と思えば先にGitHubの使い方とアクセシビリティの話があって、その後ようやくJavaScriptというなかなか変わった構成になっています。そんなペースで大丈夫か?と思えば後半は色々なプロジェクトを作ったりしています。
全24レッスンを踏破したころには、きっといっぱしのWeb開発者になっていることでしょう。
- 投稿日:2020-11-23T13:54:50+09:00
(JSメモ)IE未対応のUrlSearchParamsの機能をpolyfillを使わずに補完するための関数
同じような機能の関数は既に幾つかQiitaに転がっているようですが、ハッシュ(URL末尾の
#
+ 任意の文字列)などを考慮していない(最後のパラメータの値にハッシュが混入してしまう)、あるいはパラメータ部分のみ入力して使うことを想定しているものばかりだったので、「ハッシュ付きURL」「相対パスのURL」「パラメータのみ」であっても正確にパラメータをパースできる関数を書きました。関数
var _URL = function(url){ return(function(o,u,a,b,c,d){ return(d=a?(function(_){ return((a[0]=='?')?(b=_):(c=_),u.slice(0,a.index)) })(u.slice(a.index+1)):u, d==''&&(d=null), b&&(((c=b.split(/#(.*?)$/))&&(c=c.length-1?((b=c[0]),c[1]):null)),b.split('&').filter(function(v){ return v }).forEach(function(v){ (function(_){ o[_[1]]=_[2] })(v.match(/(.+?)=(.*)/)) })),{base:d,hash:c,param:o}) })({},url,url.match(/\?|#.*?$/)) };縮小版
var _URL = function(url){return(function(o,u,a,b,c,d){return(d=a?(function(_){return((a[0]=='?')?(b=_):(c=_),u.slice(0,a.index))})(u.slice(a.index+1)):u,d==''&&(d=null),b&&(((c=b.split(/#(.*?)$/))&&(c=c.length-1?((b=c[0]),c[1]):null)),b.split('&').filter(function(v){return v}).forEach(function(v){(function(_){o[_[1]]=_[2]})(v.match(/(.+?)=(.*)/))})),{base:d,hash:c,param:o})})({},url,url.match(/\?|#.*?$/))};使い方
console.log( _URL('https://www.youtube.com/watch?v=BoZ0Zwab6Oc&t=56s#contents') );{ base: "https://www.youtube.com/watch", hash: "contents", param: { v: "BoZ0Zwab6Oc", t: "56s" } }
console.log( _URL('/watch?v=BoZ0Zwab6Oc&t=56s') );{ base: "/watch", hash: null, param: { v: "BoZ0Zwab6Oc", t: "56s" } }
console.log( _URL('?v=BoZ0Zwab6Oc&t=56s#') );{ base: null, hash: "", param: { v: "BoZ0Zwab6Oc", t: "56s" } }
- 投稿日:2020-11-23T13:28:39+09:00
JavaScriptでタイマー作ってみた!
- 投稿日:2020-11-23T13:25:38+09:00
JavaScriptにおける変数の参照について
疑問1
let a = { prop1: "dog", prop2: "cat" }; let b = { prop1: "dog", prop2: "cat" }; console.log(a === b);これはtrueかfalseか?
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー疑問2
let a = { prop1: "dog", prop2: "cat" }; let b = a; console.log(a === b);これはtrueかfalseか?
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー疑問3
const a = { prop1: "dog", prop2: "cat" }; a.prop1 = "rabbit";エラーとなるのか?
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー疑問4
const a = { prop1: "dog", prop2: "cat" }; const b = a; b.prop1 = "rabbit"; console.log(a.prop1);出力されるのは dog か rabbit か
変数とデータ型
JavaScriptのデータ型は8種類
- Boolean
- Number
- String
- Undefined
- Null
- Symbol
- Bigint
- Object
プリミティブ型とオブジェクト型
データ型のうちObjectはオブジェクト型、それ以外をプリミティブ型という。
プリミティブ型の特徴
- 変数には値が格納される。
- 値を変更することができない。(イミュータブル)
オブジェクト型の特徴
- 変数には参照情報が格納される。
- 値を変更することができる。(ミュータブル)
let a = 1; a = 2 ;この場合、a は 変更できるのではなか?
メモリ空間上での参照を変更しているだけでメモリ上の値は変更されていない。(注:厳密ではなくイメージ)
メモリ3 メモリ1 メモリ2 a ----> 1 : let a = 1 2 <---- a : a = 2constがロックしているもの
constは、この参照先の変更をロックしている。
メモリ空間.メモリ3 メモリ1 メモリ2 a ----> 1 : let a = 1 2 <-❌-- a : a = 2オブジェクトのメモリ上の動き(注:厳密ではなくイメージ)
let a = { prop : "dog" }この場合
メモリ空間.メモリ3 メモリ1 メモリ2 メモリ4 メモリ5 a ----> メモリ4を参照 ----> {prop} ----> "dog"このようにオブジェクト型 a には値ではなく、参照情報が格納されている。
この時、メモリ5の内容が変わったとしてもメモリ2自体は変わらないので、同一オブジェクトを参照する。
これがイミュータブルと言われる由縁。プリミティブ型の値のコピー
let a = "dog"; let b = a; b = "cat";メモリ3 メモリ1 メモリ2 メモリ4 メモリ5 a ----> "dog" : let a = "dog" b ----> "dog" : let b = a //この時同じメモリ2を参照するのではなく、異るメモリ上に同じ値がコピーされている。 "cat" <------------------- b "dog" : b = "cat" //参照先メモリが変更される。もちろん
console.log(a) //dogaには影響しない。
オブジェクト型の値のコピー
let a = { prop:"dog" }; let b = a; b.prop = "cat";メモリ1 メモリ2 メモリ3 メモリ4 メモリ6 メモリ7 メモリ8 a --> メモリ3参照 --> {prop} --> "dog" : let a = {prop:"dog"}; b --> メモリ3参照 : let b = a; //参照情報がコピーされる。 {prop} -----------------------------> "cat" :b.prop = "cag" //aとb 同じメモリー3を参照している。よって、この場合、bのオブジェクト内部の変更はaにも影響してくる。
オブジェクト型のconstへの再代入
const a = { prop:"dog" }; a = {}メモリ空間.メモリ3 メモリ1 メモリ2 メモリ4 メモリ5 メモリ6 a ----> メモリ4を参照 ----> {prop} ----> "dog" a ----> メモリ4を参照 ----> {prop} ----> "dog" メモリ6を参照 <-❌- a {}これは constでロックさているためエラーとなる。
const a = { prop:"dog" }; a.prop = "cat"↑この場合↓
メモリ空間.メモリ3 メモリ1 メモリ2 メモリ4 メモリ5 メモリ6 a ----> メモリ4を参照 ----> {prop} ----> "dog" "cat" <---------------------------------------- {prop} "dog"constでロックしているのはあくまでオブジェクト型の変数である a に対して、オブジェクト内部の値は変更できる。
また、関数の仮引数に実引数を渡す場合、同様のコピーが行われる。
function fn(a){ } let b = 0; fn(b)と
let a = bの変数における参照の動きは同じ挙動。
つまり、fnの中で、bの値を変更した場合、プリミティブ型の場合は、呼び出し元のbには影響せず、オブジェクト型の場合は影響する。疑問を振り返る。
疑問1
let a = { prop1: "dog", prop2: "cat" }; let b = { prop1: "dog", prop2: "cat" }; console.log(a === b);これはtrueかfalseか?
「false」
a と b では 異る参照情報を保持しているため。
メモリ1 メモリ2 メモリ3 メモリ4 メモリ6 メモリ7 a ---> メモリ3を参照 ---> {略} b ---> メモリ7を参照 ---> {略} メモリ3を参照 ≠ メモリ7を参照ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
疑問2
let a = { prop1: "dog", prop2: "cat" }; let b = a; console.log(a === b);これはtrueかfalseか?
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー「true」
メモリ1 メモリ2 メモリ3 メモリ4 メモリ6 メモリ7 a ---> メモリ3を参照 ---> {略} b ---> メモリ3を参照 //オブジェクト型なので参照情報がコピーされる。 メモリ3を参照 = メモリ3を参照ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
疑問3
const a = { prop1: "dog", prop2: "cat" }; a.prop1 = "rabbit";エラーとなるのか?
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
「ならない」constでaをロックしているのは、参照情報について。参照情報が変更されるわけではない。
ーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーーー
疑問4
const a = { prop1: "dog", prop2: "cat" }; const b = a; b.prop1 = "rabbit"; console.log(a.prop1);出力されるのは dog か rabbit か
「rabbit」
const b = a;で参照情報がコピーされるので同じオブジェクトを参照する。
当然同じオブジェクトを参照しているaにも影響がでる。
- 投稿日:2020-11-23T12:29:51+09:00
【PHP】モーダル画面実装
PHPについて学習内容を備忘録としてまとめます。
モーダル画面を実装しましたので、作成方法を記載します。
上記のように投稿などをモーダル画面を使用して操作することができます。
実装方法
実装方法について記載していきます。
投稿ボタン作成
ボタンクリック時にモーダル画面を出力させるため、
投稿ボタンを作成します。header.php<ul> <li><a href="../user/user_list.php?type=all">ユーザー一覧</a></li> <li><a href="../post/post_index.php">投稿一覧</a></li> <li><a class="post_window" href="#">投稿</a></li> <li><a href="../message/message_top.php">メッセージ</a></li> : : </ul>ヘッダーに
投稿ボタン
を追加します。
このpost_window
をクリックしたときにJavaScriptの処理が開始されるようにします。JavaScriptでモーダル画面出力
先ほどの投稿ボタンがクリックされたときに、モーダル画面を出力するよう実装します。
user_page.js$(document).on('click', '.post_window', function() { //背景をスクロールできないように & スクロール場所を維持 scroll_position = $(window).scrollTop(); $('body').addClass('fixed').css({ 'top': -scroll_position }); // モーダルウィンドウを開く $('.post_process').fadeIn(); $('.modal').fadeIn(); });
post_window
をクリックしたときに処理が走るようになっています。scroll_position = $(window).scrollTop(); $('body').addClass('fixed').css({ 'top': -scroll_position });モーダル画面が出力された際に背景の画面をスクロールしないようにしています。
画面のスクロール位置を取得して、そこで位置を固定しています。$('.post_process').fadeIn(); $('.modal').fadeIn();こちらでモーダル画面を出力しています。
元のモーダル画面がないので作成していきます。モーダル画面作成
post_process.php<div class="modal"></div> <div class="post_process"> <h2 class="post_title">投稿</h2> <form method="post" action="../post/post_add_done.php" enctype="multipart/form-data"> <textarea class="textarea form-control" placeholder="投稿内容を入力ください" name="text"></textarea> <div class="post_btn"> <button class="btn btn-outline-danger" type="submit" name="post" value="post" id="post">投稿</button> <button class="btn btn-outline-primary modal_close" type="button">キャンセル</button> </div> </form> </div>投稿ボタンをクリックすると
post_add_done.php
に処理が遷移するようになっています。
post_add_done.php
はSQL文でpostテーブル
にINSERT
するようになっています。
※上記のコードは説明上不要な部分を省略しているので、トップの動作画面とは違いがあります。<div class="modal"></div>こちらの1行は後ほど説明しますが、モーダル画面が出力された際に背景を灰色にする役割を担っています。
ではこちらのモーダル画面をレイアウトしていきます。モーダル画面のレイアウト
style.css.post_process { display: none; position: fixed; z-index: 15; top: 30%; left: 50%; width: 600px; padding: 10px; background-color: #121212; border-radius: 8px; -webkit-transform: translate(-50%, -10%); transform: translate(-50%, -10%); color: #fff; font-size: 1.3rem; border: 0.3rem solid #fff; }表示する位置、背景色などを決めています。
z-index: 15;
で重なりの順序を高くており、
display: none;
で普段は表示させないようにさせ、JavaScript
の処理で出力させるようにしています。先ほど触れた、モーダル画面出力時の背景についてもレイアウトしていきます。
.modal { display: none; position: fixed; top: 0; left: 0; background-color: rgba(152, 152, 152, 0.7); width: 100%; height: 100%; z-index: 10; }こちらも位置(画面全体)、背景色を決めています。
z-index: 10;
にすることで重なり順序がモーダル画面よりも低く設定しています。上記を一通り実装すれば、トップの動作画面のように動くと思います。
参考URL
- 投稿日:2020-11-23T12:29:51+09:00
【JavaScript】モーダル画面実装
学習内容を備忘録としてまとめます。
モーダル画面を実装しましたので、作成方法を記載します。
上記のように投稿などをモーダル画面を使用して操作することができます。
実装方法
実装方法について記載していきます。
投稿ボタン作成
ボタンクリック時にモーダル画面を出力させるため、
投稿ボタンを作成します。header.php<ul> <li><a href="../user/user_list.php?type=all">ユーザー一覧</a></li> <li><a href="../post/post_index.php">投稿一覧</a></li> <li><a class="post_window" href="#">投稿</a></li> <li><a href="../message/message_top.php">メッセージ</a></li> : : </ul>ヘッダーに
投稿ボタン
を追加します。
このpost_window
をクリックしたときにJavaScriptの処理が開始されるようにします。JavaScriptでモーダル画面出力
先ほどの投稿ボタンがクリックされたときに、モーダル画面を出力するよう実装します。
user_page.js$(document).on('click', '.post_window', function() { //背景をスクロールできないように & スクロール場所を維持 scroll_position = $(window).scrollTop(); $('body').addClass('fixed').css({ 'top': -scroll_position }); // モーダルウィンドウを開く $('.post_process').fadeIn(); $('.modal').fadeIn(); });
post_window
をクリックしたときに処理が走るようになっています。scroll_position = $(window).scrollTop(); $('body').addClass('fixed').css({ 'top': -scroll_position });モーダル画面が出力された際に背景の画面をスクロールしないようにしています。
画面のスクロール位置を取得して、そこで位置を固定しています。$('.post_process').fadeIn(); $('.modal').fadeIn();こちらでモーダル画面を出力しています。
元のモーダル画面がないので作成していきます。モーダル画面作成
post_process.php<div class="modal"></div> <div class="post_process"> <h2 class="post_title">投稿</h2> <form method="post" action="../post/post_add_done.php" enctype="multipart/form-data"> <textarea class="textarea form-control" placeholder="投稿内容を入力ください" name="text"></textarea> <div class="post_btn"> <button class="btn btn-outline-danger" type="submit" name="post" value="post" id="post">投稿</button> <button class="btn btn-outline-primary modal_close" type="button">キャンセル</button> </div> </form> </div>投稿ボタンをクリックすると
post_add_done.php
に処理が遷移するようになっています。
post_add_done.php
はSQL文でpostテーブル
にINSERT
するようになっています。
※上記のコードは説明上不要な部分を省略しているので、トップの動作画面とは違いがあります。<div class="modal"></div>こちらの1行は後ほど説明しますが、モーダル画面が出力された際に背景を灰色にする役割を担っています。
ではこちらのモーダル画面をレイアウトしていきます。モーダル画面のレイアウト
style.css.post_process { display: none; position: fixed; z-index: 15; top: 30%; left: 50%; width: 600px; padding: 10px; background-color: #121212; border-radius: 8px; -webkit-transform: translate(-50%, -10%); transform: translate(-50%, -10%); color: #fff; font-size: 1.3rem; border: 0.3rem solid #fff; }表示する位置、背景色などを決めています。
z-index: 15;
で重なりの順序を高くており、
display: none;
で普段は表示させないようにさせ、JavaScript
の処理で出力させるようにしています。先ほど触れた、モーダル画面出力時の背景についてもレイアウトしていきます。
.modal { display: none; position: fixed; top: 0; left: 0; background-color: rgba(152, 152, 152, 0.7); width: 100%; height: 100%; z-index: 10; }こちらも位置(画面全体)、背景色を決めています。
z-index: 10;
にすることで重なり順序がモーダル画面よりも低く設定しています。上記を一通り実装すれば、トップの動作画面のように動くと思います。
参考URL
- 投稿日:2020-11-23T11:05:51+09:00
【JavaScript】前置演算と後置演算の違い
プログラミング勉強日記
2020年11月23日
前置演算と後置演算の違いについて学習したので、記録します。1.前置演算とは?
加算/減算の演算子を変数の前方に配置することです。
加算を
インクリメント
減算を
デクリメント
と呼びます。
script.jslet x = 1; //前置演算 ++ x;//結果:22.後置演算とは?
インクリメント/デクリメントの演算子を変数の前方に配置することです。
script.jslet x = 1; //後置演算 x ++;//結果:23.両者の違いとは
上記のコードでは、特に問題ありません。
ですが、インクリメント/デクリメントの演算子で演算した結果を、他の変数に代入する場合は、注意が必要です。
① 前置演算で他の変数に代入する場合
script.js//前置演算 let x = 1; let y = ++ x; console.log(x);//結果:2 console.log(y);//結果:2変数xを加算してから、その結果を変数yに代入しています。
② 後置演算で他の変数に代入する場合
script.js//後置演算 let x = 1; let y = x ++; console.log(x);//結果:2 console.log(y);//結果:1変数xを変数yに代入してから、変数xを加算します。
参考本
- 投稿日:2020-11-23T10:37:08+09:00
サーバから Soracom API を叩くために API Key と Token をどうやって取得するべきか
Soracom API を叩くには
API Key
とToken
のペアが必用となる。例:
curl -X GET \ --header 'Accept: application/json' \ --header 'X-Soracom-API-Key: api-xxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxx' \ --header 'X-Soracom-Token: eyJQiOiJBUraWUVDQ(以下略)' \ 'https://api.soracom.io/v1/subscribers/your_imsi/data?sort=desc'この
API Key
とToken
をサーバとかLambdaとか、要はUIの無いバックエンドサービスからどう叩くべきか?の話。
基本、公式ドキュメントをはじからはじまでちゃんと目を通せば書いてあるのだが、以下の要因からすげー迷ったので備忘録兼ねて書いとく。
- 巷にあふれる解説記事が API Reference で暫定的に取得した値を使用する前提のものが多い。
- 認証に係る要素が、
Auth Key ID
,Auth Key Secret
,API key
,Token
の 4セットあるのだが、
- 日本語と英語の表記の空目
API Key
とAuth Key ID
がまぎらわしい。- 公式APIでも
Auth Key Secret
を入れるべきフィールドをauthKey
ってたりするので、仕様書読んでて混乱する。- 先にAPIリファレンスを斜め読みして、
API Key
で調べて SORACOM API 利用ガイド の 「API Key と API Token」 だけ 読むと混乱する。
- その数項目先の 「API 呼び出し例」 まで読み進めると、認証に係る4つの要素の使い分けをちゃんと理解できる。
- Ruby 以外に公式のSDKが無い。
- 公式ドキュメントにユースケースとベストプラクティスがあんま載ってない。
Overview
- まず、プログラムアクセス用の SAMユーザを作れ。
- AWS の IAM ユーザと同じ理解。
- SAMユーザの 認証キーID (
Auth Key Id
) と 認証キーシークレット (Auth Key Secret
) は固定で持ってOK。
- AWS Secrets Manager で管理するなり、 CIで環境変数に突っ込むなりお好きにどうぞ。
- ただし、これは絶対に漏らしたらNG。漏れたら再発行する。
API Key
とToken
は SAMユーザの認証を通すことによって即時で発行して使い捨てるべきもの。ちゃんと Overview 理解できてればシンプルで使いやすいよ!
詳細
SAM ユーザ作成
- 基本は公式ドキュメントの通りでOK
- コンソールログイン用のパスワードは設定するな。何のためにSAMユーザ分けたかわからなくなる。
- SAM ユーザーの権限設定 は多分
:list*
とか:get*
とか。かならず絞るように。API Key, Token の取得
SORACOM API 利用ガイド の 1. API Key, API Token の取得 の SAM ユーザーの API Key と API Token を取得する例 を参照のこと。
具体的には以下。curl -X POST \ -H 'Content-Type: application/json' \ -d '{"authKeyId": "keyId-xxxxxxx", "authKey": "secret-xxxxxxx"}' \ https://api.soracom.io/v1/auth # Response: # 200 OK # # { # "apiKey": "API Key", # "token": "API Token", # "operatorId": "当該OperatorのID", # "userName": "SAMユーザー名" # }なお、node で axios 使う場合はこんな感じ。
const axios = require('axios'); const getTokenByAuthKey = async (authKeyId, authKeySecret, tokenTimeoutSeconds) => { const url = 'https://api.soracom.io/v1/auth'; const body = { authKeyId, authKey: authKeySecret, tokenTimeoutSeconds }; const headers = { 'Content-Type': 'application/json' }; const res = await axios.post(url, body, { headers, responseType: 'json' }); console.log(res.data); const apiKey = res.data.apiKey; const token = res.data.token; // const operatorId = res.data.operatorId; return { apiKey, token }; }; // SAM user の情報 (認証キー ID と認証キーシークレット) const authKeyId = 'keyId-xxxxxxxxxxxxxxxxxxxxxxxxxxx'; const authKeySecret = 'secret-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx'; // SAM user の認証をかけて、 API Key, Token を取得する const tokenTimeoutSeconds = 3 * 60; // 最短3分 const { apiKey, token } = await getTokenByAuthKey(authKeyId, authKeySecret); // 以降、 apiKey, token を使用して Soracom の API を呼び出しできる。理解すればこんだけ。
tokenTimeoutSeconds
は SORACOM API 利用ガイド によれば 「デフォルトでは24時間で、最短3分、最長で48時間まで指定でき」る。
- 通常のサーバちっくなデータ取得であれば 3分あれば十分すぎるので、3分で良い認識。
operatorId
は別途設定で持つでいいと思うが、auth のレスポンスで取得できるものを使うでも良さそう。
- API設計思想的にどっちを想定してるんだろう?
その他
公式の API Reference 読んでも
API Key
,Token
をどうやって取得するか書いてないので、ぐぐるじゃないですか?「API Key と API Token に移動」 するじゃないですか?
え、Token更新し続けろって言ってる?ってなるんすわ。
このページをだーっと下まで読んでいって「1. API Key, API Token の取得」まで行くと、別途SAMユーザと認証キーID/認証キーシークレットがちゃんとあるのね!って理解できる。。。
あと、流石にそろそろ公式で node 版の SDKが欲しいです... async/await 対応したやつ......
(Soracom Beam 使えってことですよねわかります)
- 投稿日:2020-11-23T10:20:34+09:00
実務経験で身につけた技術 ~JavasScript・jQuery編~
はじめに
未経験から実務経験2か月ちょっとを経て、1つのサイトを完成させることができたので(もちろん色々な人の手助けあってですが)、今後に生かすインプットと自分の備忘録としてPHP、JavaScript・jQuery、HTML・CSSの3つに分けて記録していきます。
この記事では、Javascript・jQueryについて書いていきます。
なお、身につけた技術は随時、記録・更新していきます。
他の記事については、下記のリンク先からご覧ください。
JavaScript・jQuery
$(function() {});
HTMLの読み込みが全て完了した後実行するという指定。
基本的にjqueryの記述は、HTMLが読み込まれた後に実行する必要があるので、上記の記述は必須。
jqueryを使うときには必ず書くと認識しておく。また、下記の処理も一緒のこと。(処理あればわかりやすい上記に書き換える)
参照
【jQuery】$(function() {...}) について 「意味や実行されるタイミング」$(document).ready(function{ //処理 });jQuery(document).ready(function(){ //処理 });jQuery(function(){ //処理 });チェックボックスの処理
//チェックボックス操作時 $('#check').click(function(){ if($(this).prop('checked')) { $('#btn').removeClass('disable'); //disableを外す $('#btn input').prop('disabled',false); //disabledを外す //$('#btn').html('<input type="submit" value="">'); } else { $('#btn').addClass('disable'); //disableをつける $('#btn input').prop('disabled',true); //disabledをつける //$('#btn').html('<input type="button" value="">'); } });チェックボックスがクリックされたときの処理。
prop()でチェックボックスのcheckedプロパティの状態を取得。
removeClassでcssを外す。addClassでcssをつける。(黒→オレンジ)
htmlで('#btn')タグ内のHTML要素の書き換え。prop()は、input typeの情報も取得できるし、チャックをつけたりも可能。
下記の記事がすごくわかりやすい。
jQueryのprop()でプロパティの設定・取得下記の記事を参考にしてinputタグ内のdisabledを外したりつけたりすることで、制御を書き換えた。
disabledはinput要素を無効にする。
jQueryのprop()でdisabled属性を切り替えるCSSの要素をつける
$(function() { // 「.list-item」要素に対するclickイベントを作成してください $('.list-item').click(function(){ $(this).css('color','red'); }); });クリックした時、.list-itemにcssをつけて赤色にする。
aタグにsubmitを持たせて、hiddenの値も含めて送信
<form action="" method="post" name="test" id="test"> <input type="hidden" name="page" id="page" value="form" /> <中略> <p><a href="javascript:void(0);" onclick="$('#page').val('form');$('#test').submit();return false;"></a></p> </form>form id="test"とa onclick="$('#test').submit()。
formのidをonclickでsubmit()することでformの中身を送信可能。
('').val('');でhiddenの値も一緒に送信。最後に
まとめることでなんとなく理解していた箇所をクリアに理解することができました。
まだ全然書ききれていないので、随時更新します。
今後もアウトプットした分だけインプットするよう心掛けていきます。
- 投稿日:2020-11-23T10:13:58+09:00
Svelteチュートリアルやってみた
svelteの公式サイトで学んでみる
Introduction
基本
https://svelte.dev/tutorial/basics
公式svelteでは実際にコードを書きながら学べる設計となっています。
環境構築なしですぐに書いて試せるのはありがたいですね。ちなみに...
APIドキュメントはこちら
公式のexample集はこちら
60秒で始めるquick startはこちらReactとVueに似ていると言われるSvelteですが、
Svelteは実行時に走るのではなく、ビルド時にJSを吐き出す仕組みとなっています。
なのでフレームワークによってパフォーマンスを落とす心配がありません。SvelteではReactなどと同じようにcomponentをコードの単位として扱います。
コンポーネントはHTMLやCSS,JSをカプセル化した再利用可能な自己完結したコードブロックになります。
コンポーネントは.svelte
の拡張子を付けたファイルに記述します。App.svelte<h1>Hello world!</h1>追加データ
先ほどの静的なマークアップにデータを追加してみます。
`name`という変数を追加します。App.svelte<script> let name = 'world' </script> <h1>Hello {name}!</h1>記述がとてもシンプルですね。
動的な属性
テキスト同様に、中括弧で要素の属性を制御することも可能です。
App.svelte<script> let src = 'tutorial/image.gif'; </script> <img src={src}>しかし、上記の記述だと
A11y: <img> element should have an alt attribute (5:0)
と行ったエラーが発生します。
a11y
はaccessibility
の略ですが、alt属性がないという警告を出してくれています。
svelteはアクセシビリティに配慮した警告も出してくれるというので驚きです。
下記のようにalt属性を追加すればOKです。App.svelte<script> let src = 'tutorial/image.gif'; </script> <img src={src} alt="男のダンス">ちなみに、属性名と値が同じ値であれば下記のようにショートハンドを使うことができます。
<img {src} alt="男のダンス">スタイル
HTML同様
<style>
タグが使用可能です。
styleは記述したコンポーネントにスコープされるので、他のコンポーネントのcssとの重複などを気にせず済みます。App.svelte<style> p { color: purple; font-family: 'Comic Sans MS', cursive; font-size: 2em; } </style> <p>This is a paragraph.</p>ネストされたコンポーネント
実際に実装する場合はコンポーネントをネストしていくことになるかと思います。
Nested.svelte<p>This is another paragraph.</p>上記ファイルコンポーネントを単純にimportすればそのまま使用可能です。
App.svelte<script> import Nested from './Nested.svelte' </script> <p>This is a paragraph.</p> <Nested />呼び出した側(ここではApp)の
<style>
は、呼び出された側(ここではNested)のスタイルには影響を与えません。
また、コンポーネントの名前は他のHTMLタグと区別するため、必ず最初の文字が大文字である必要があります。Reactのようにexport文も書かないでOKなので、コード量が減りますね。
HTMLタグ
通常は文字はプレーンテキストとして解釈されるので、
<strong>
のようなタグはHTMLとして解釈されません。
HTMLとして解釈させたい場合は{@html ... }
という特殊な記法を使います。App.svelte<script> let string = `この文章は <strong>HTMLを含む!!!</strong>`; </script> <p>{@html string}</p>Svelte側ではセキュリティ対策を行なっていないので、 外部データを流し込む際はXSSなどに要注意です。
アプリを作る
実際に自分のエディタでsvelteを使うには、環境構築が必要です。
rollup用にはrollup-plugin-svelteが、
webpack用にはsvelte-loaderがそれぞれ準備されています。
環境構築についての公式の解説は https://svelte.dev/blog/svelte-for-new-developers に乗っています。
こちらの記事では割愛しますが、非常に簡単に構築可能のようです。
エディタのセッティング方法の公式解説はこちらになります。設定が終われば、svelteのコンポーネントをimportし、
new
演算子でインスタンス化して使用可能です。App.svelteimport App from './App.svelte'; const app = new App({ target: document.body, props: { // 後ほど学習します answer: 42 } });reactivity
svelteはアプリケーションの状態とDOMを同期するための reactivity(反応性?)のシステムが中心となります。
App.svelte<script> let count = 0; function handleClick() { count += 1; } </script> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button>ボタンを押すと
handleClick
が発動し、countが+1され、それが描画にも同期されています。宣言
コンポーネントの状態が変わるとSvelteは自動でDOMをアップデートしてくれますが、
コンポーネントの状態の中には他の状態に依存するものあります。(例えばfullname
がfirstname
とlastname
の状態から成り立つような時)
そのような場合は下記のようなリアクティブ宣言を使います。let count = 0; $: doubled = count * 2見慣れない書き方ですが、この宣言によって参照している値が変わったら再計算させることができます。
App.svelte<script> let count = 0; $: doubled = count * 2 function handleClick() { count += 1; } </script> <button on:click={handleClick}> Clicked {count} {count === 1 ? 'time' : 'times'} </button> <p> {count} doubled is {doubled} </p>上記のコード実行結果では、ボタンを押して
count
を追加させると、しっかりdoubled
も変更できているのが確認できます。ステートメント
リアクティブ宣言は値以外でも使用可能です。
$: console.log(`the count is ${count}`)上記を
<script>
タグ内に記述すると、count
が変更される度にconsole.logが走ります。ステートメントをグループ化することも可能です。
$: { console.log(`the count is ${count}`); alert(`I SAID THE COUNT IS ${count}`); }コードブロックの前に置くことも可能です。
$: if (count >= 10) { alert(`カウントしすぎ!`); count = 9; }配列やオブジェクトの変更
変数の変更を検知する都合上、svelteは
push
やsplice
といったオブジェクトや配列自体を変更しない更新を検知することができません。
なので、変更する際は必ず新しいオブジェクトや配列になるように配慮しなければいけません。function addNumber() { numbers = [...numbers, numbers.length + 1]; }プロパティへの代入は、値そのものへの代入と同様に変更を検知できます。
ただし、変更を検知したい変数名はまとまって使われていなければなりません。const foo = obj.foo foo.bar = 'baz' $: test = obj.foo.barこの場合、 obj.foo.barは変更されていますが、
testの再計算は行われません。
この制約はハマりそうなので要注意です。props
今まではcomponent内のデータのみを扱ってきましたが、実際には他のコンポーネントから
データを受け取ったり、データを子のコンポーネントに渡す必要もあります。
このようなデータをprop
と呼びます。(propertiesの略です)
Svelteではexport
というキーワードを使用してpropsを渡せるようになります。Nested.svelte<script> export let answer; </script> <p>答えは {answer}</p>App.svelte<script> import Nested from './Nested.svelte'; </script> <Nested answer={42}/>描写結果答えは 42デフォルト値
propにデフォルト値を設定することもできます。
Nested.svelte<script> export let answer = 100; </script> <p>答えは {answer}</p>App.svelte<script> import Nested from './Nested.svelte'; </script> <Nested />描写結果答えは 100スプレッドprops
オブジェクトをpropsとして持っているのであれば、スプレッド構文を使ってまとめて子に値を渡すことも可能です。
const pkg = { name: 'svelte', version: 3, speed: 'めちゃ早' } <Info {...pkg} />逆に子コンポーネント側で、
親から与えられたprops全てを$$props
で取得することが可能です。
これはexport
演算子を使用していないものでも取得できます。
svelte側での最適化が難しいので非推奨となっていますが、場合によっては便利でしょう、と説明に書かれていました。ロジック
HTMLにはifやloopといった制御をする機能はありませんが、svelteにはあります。
ifブロック
if文は
{#if 条件式} ~ {/if}
で実装可能です。{#if user.loggedIn} <button on:click={toggle}> Log out </button> {/if} {#if !user.loggedIn} <button on:click={toggle}> Log in </button> {/if}なんとなくpugなどのHTMLテンプレートエンジンっぽい書き方ですね。
else
もちろんelseもあります。
{#if user.loggedIn} <button on:click={toggle}> Log out </button> {:else} <button on:click={toggle}> Log in </button> {/if}
#
文字はblockの始まりを、
/
文字はブロックの終わりを、
:
はブロックが続けられることを表しています。else-if
else-if文は下記のように記述します。
{#if x > 10} <p>{x} は 10より大きい</p> {:else if 5 > x} <p>{x} は 5より小さい</p> {:else} <p>{x} は 5 と 10の間</p> {/if}each
各アイテムに対して同じ処理を行いたい時はeachを使用します。
App.svelte<script> let cats = [ { id: 'J---aiyznGQ', name: 'Keyboard Cat' }, { id: 'z_AbfPXTKms', name: 'Maru' }, { id: 'OUtn3pvWmpg', name: 'Henri The Existential Cat' } ]; </script> <ul> {#each cats as cat} <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}"> {cat.name} </a></li> {/each} </ul>配列だけでなく,配列ライクなオブジェクト(lengthプロパティを持ったもの)も同様に
each
が使えます。
さらにさらに、indexも取得可能です。{#each cats as cat, i} <li><a target="_blank" href="https://www.youtube.com/watch?v={cat.id}"> {i + 1}: {cat.name} </a></li> {/each}分割代入も可能です。
<ul> {#each cats as { id, name } }, <li><a target="_blank" href="https://www.youtube.com/watch?v={id}"> {name} </a></li> {/each} </ul>keyed each blocks
デフォルトではeachの値を変更すると、ブロックの最後を追加/削除して調整されます。
例えば配列の先頭を削除した場合、一番最後の要素が消えてしまう挙動となります。
これを改善するために、各要素にユニークなkeyをつけます。{#each things as thing (thing.id)} <Thing current={thing.color}/> {/each}Await
非同期処理を同期的に行う際にはawaitを使います。
下記では、 あるpromiseの処理を同期的に実行する文です。{#await promise} <p>...waiting</p> {:then number} <p>ナンバーは {number}</P> {:catch error} <p style="color: red">{error.message}</p>構文は
Promise
と同じなのでわかりやすいかと思います。
Promise解決時に何も表示を行いたいくない場合は、下記のように最初のブロックを省略可能です。{#await promise then value} <p>値は {value} </p> {/await}イベント
Domイベント
すでに紹介したように、
on:
ディレクティブを使用してイベントの登録が可能です。<div on:mousemove={handleMousemove}> The mouse position is {m.x} x {m.y} </div>インラインハンドラ
インラインでハンドラを設定することももちろん可能です。
<div on:mousemove="{e => m = { x: e.clientX, y: e.clientY }}"> The mouse position is {m.x} x {m.y} </div>
"
は無くてもいいのですが、これをつけることで環境によってはハイライトがついて見やすい、とのことです。reactなどでは実行時負担になるためインラインハンドラは避けるべき、
と言われていますが、svelteの場合コンパイラがいい感じに調整してくれるので、インラインでもOKです。イベント修飾子
イベントの振る舞いを変更するためにイベント修飾子を使用することができます。
例えば、once
修飾子を使えば、一度だけイベントが走るように制御できます。App.svelte<script> function handleClick() { alert('clicked') } </script> <button on:click|once={handleClick}> Click me </button>他にも色々な修飾子があります。
名前 作用 preventDefault ハンドラーが走る前に event.preventDefault()
を走らせるstopPropagation event.stopPropagation
を走らせる(イベントの伝播を止める)passive スクロール系のイベントのパフォーマンスをあげる(svelteが安全とみなすとと自動で入れてくれる) capture バブリングフェーズでは無くキャプチャフェーズでイベントが発火する once 一度だけイベントが走るようにする self event.targetが自身のDOMだった場合のみ発火する 修飾子をチェインすることも可能です。
on:click|once|capture={...}コンポーネントイベント
コンポーネントはeventをdispatchすることもできます。
Inner.svelte<script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); function sayHello() { dispatch('message', { text: 'Hello!' }); } </script>App.svelte<script> import Inner from './Inner.svelte'; function handleMessage(event) { alert(event.detail.text); } </script> <Inner on:message={handleMessage}/>
createEventDispatcher
で作成したdispatch
を使用します。
dispatchの第一引数に指定した文字列のイベントが発火し、第二引数で与えた値がevent.detail
に与えられます。
createEventDispatcher
はコンポーネントが最初にインスタンスされた時にしか呼び出せない点に注意です。つまり、setTimeout
のコールバックで呼び出したりすることはできません。イベントフォワーディング
DOMイベントと異なり、コンポーネントイベントはバブリングしません。
なので、深くネストしているコンポーネントのイベントを走らせたい場合、中間のコンポーネントはイベントを転送する必要があります。例として、 App > Outer > Innter とコンポーネント入れ子になっている場合を考えます。
Innerのコンポーネントには、先ほどのようにmessage
のイベントがdispatchされています。これを解決するには、Outerコンポーネントにイベントハンドラーをつけてしまうことです。
Outer.svelte<script> import Inner from './Inner.svelte'; import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); function sayHello() { dispatch('message', event.detail); } </script> <Inner on:message={forward}/>
event.detail
には、 Innerから渡されてきた{ text."Hello!"}
が格納されてきますので、
それをそのままAppにdispatchしています。Inner.svelte<script> import { createEventDispatcher } from 'svelte'; const dispatch = createEventDispatcher(); function sayHello() { dispatch('message', { text: 'Hello!' }); } </script> <button on:click={sayHello}> Click to say hello </button>App.svelte<script> import Outer from './Outer.svelte'; function handleMessage(event) { alert(event.detail.text); } </script> <Outer on:message={handleMessage}/>しかしこれだとコード記述量が多くなるので、Svelteはショートハンドを準備しています。
Outerコンポーネントのようにフォワードを行うコンポーネントは、下記の記述だけでOKです。Outer.svelte<script> import Inner from './Inner.svelte'; </script> <Inner on:message/>DOMイベントのフォワーディング
イベントのフォワーディングはDOMイベントでも同様に働きます。
CustomButton.svelte<button on:click> Click me </button>バインディング
Textインプット
svelteはデータフローが上から下へなるように設計されています。
しかし、便利である時はこのフロールールを破ることがあるようです。
例えば<input>
で上から下のルールを守る場合、on:inputハンドラーをつけ、event.target.valueを渡し...という煩雑さがあります。愚直な例<script> let name = 'world'; function test(event) { name = event.target.value } </script> <input on:input={test}> <h1>Hello {name}!</h1>記述をシンプルにするために、inputの場合は
bind:value
というディレクティブが使用可能です。<input bind:value={name}>これは、nameを更新すると、入力値が更新されるのと同時に、入力値を変更するとnameも更新されるという
双方向のデータ更新を可能にします。App.svelte<script> let name = 'world'; </script> <input bind:value={name}> <h1>Hello {name}!</h1>数字のinput
DOMでは全てが
string
型で扱われます。
これはtype="number”
やtype="range"
などといった数字を扱いたいときに不便です。Svelteでは、これをいい感じにしてくれていて、数字をDOMに入れてもうまく働くようにしてくれます。
<input type=number bind:value={a} min=0 max=10> <input type=range bind:value={a} min=0 max=10>checkbox
チェックボックスでは
input.value
ではなくinput.checked
ディレクティブを使用します。<input type=checkbox bind:checked={yes}>グループinput
ラジオボタンのように互いの値に関係性があるようなinputの場合、
bind:group
ディレクティブを使います。<input type=radio bind:group={scoops} value={1}>ラジオボタンなんかはEach文がよく使えるケースの1つですね。
<script> let scoops = 1; let menu = [ 'Cookies and cream', 'Mint choc chip', 'Raspberry ripple' ] </script> <h2>Flavours</h2> {#each menu as flavour} <label> <input type=checkbox bind:group={flavours} value={flavour}> {flavour} </label> {/each}textarea input
textareaはtext inputと同様に
bind:value
を使用します。<textarea bind:value={value}></textarea>textareaに限った話ではないですが、この場合は名前が被っているので略すことが可能です。
<textarea bind:value></textarea>selectのバインディング
selectの
bind:value
を使用します。<script> let questions = [ { id: 1, text: `Where did you go to school?` }, { id: 2, text: `What is your mother's name?` }, { id: 3, text: `What is another personal fact that an attacker could easily find with Google?` } ]; let selected; let answer = ''; </script> <select bind:value={selected} on:change="{() => answer = ''}"> {#each questions as question} <option value={question}> {question.text} </option> {/each} </select>optionのvalueはオブジェクトでOKなのが変わっています。
selectedの初期値が設定されていないので、リスト最初の値が自動的にデフォルト値となります。
ただし、seletedが初期化されるまでundefinedとなるので、seletedの参照には注意が必要となります。まとめ
長くなってきたので途中までにしましたが、公式ドキュメントはまだまだ記述がたっぷりです。
svelteは他のフレームワークと違い、状態管理(ReduxやVuexに当たるもの)がデフォルトで組み込まれているそうです。
現在他ライブラリに比べると普及度は低いですが、非常に使いやすく、仮想DOMのオーバーヘッドもない良いフレームワークだと思いました。