- 投稿日:2020-08-18T23:51:43+09:00
【第2回】「みんなのポートフォリオまとめサイト」を作ります~REST API編~ 【Cover】成果物URL: https://minna.itsumen.com
ワイ 「この記事のカバーです」
https://qiita.com/kiwatchi1991/items/58b53c5b4ddf8d6a7053バックナンバー
【第1回】「みんなのポートフォリオまとめサイト」を作ります~プロトタイプ作成編~
成果物
リポジトリ
フロントエンド
https://github.com/yuzuru2/minna_frontend
バックエンド
https://github.com/yuzuru2/minna_backend
コレクション定義(テーブル定義)
ワイ 「今回はNoSQLのMongoDBを使ってます」
ワイ 「コレクションとはRDBでいうテーブル的なやつです」
RDB MongoDB スキーマ データベース テーブル コレクション カラム フィールド レコード ドキュメント ①Users(ユーザ)
uid: unique
物理名 論理名 型 uid ユーザID string name 名前 string twitterUrl TwitterのURL string githubUrl GitHubのURL string createdAt 作成時間 Date updatedAt 更新時間 Date src/mongoose/collection/users.tsimport * as mongoose from 'mongoose'; import Schema from 'src/mongoose'; const model_name = 'users'; interface _interface { uid: string; name: string; twitterUrl: string; githubUrl: string; createdAt: Date; updatedAt: Date; } interface i_model extends mongoose.Document {} interface i_model extends _interface {} const model = mongoose.model( model_name, new Schema({ uid: { type: String }, name: { type: String, minlength: 1, maxlength: 15 }, twitterUrl: { type: String }, githubUrl: { type: String }, createdAt: { type: Date }, updatedAt: { type: Date }, }).index({ uid: 1 }, { unique: true }) ); // 作成 export const create = async (params: Pick<i_model, 'uid'>) => { const _data: _interface = { uid: params.uid, name: '名無し', twitterUrl: '', githubUrl: '', createdAt: new Date(), updatedAt: new Date(), }; return (await model.insertMany([_data])) as i_model[]; }; // 抽出 export const find = async (params: Pick<i_model, 'uid'>) => { const _data: Pick<i_model, 'uid'> = { uid: params.uid }; return (await model.find(_data)) as i_model[]; }; // 更新 export const update = async ( uid: string, params: Pick<i_model, 'name' | 'twitterUrl' | 'githubUrl'> ) => { return await model.updateOne( { uid: uid }, { $set: { ...params, updatedAt: new Date() } } ); };②Products(ポートフォリオ)
_id: unique
物理名 論理名 型 _id ポートフォリオのID string uid ユーザID string type ポートフォリオのタイプ number title ポートフォリオのタイトル string url ポートフォリオのURL string repo リポジトリのURL string createdAt 作成時間 Date updatedAt 更新時間 Date src/mongoose/collection/products.tsimport * as mongoose from 'mongoose'; import Schema from 'src/mongoose'; const model_name = 'products'; const pagingNum = 5; interface _interface { uid: string; type: number; title: string; url: string; repo: string; createdAt: Date; updatedAt: Date; } interface i_model extends mongoose.Document {} interface i_model extends _interface {} const model = mongoose.model( model_name, new Schema({ uid: { type: String }, type: { type: Number, min: 0, max: 5 }, title: { type: String, minlength: 1, maxlength: 30 }, url: { type: String, minlength: 1, maxlength: 100 }, repo: { type: String, maxlength: 100 }, createdAt: { type: Date }, updatedAt: { type: Date }, }) ); // 作成 export const create = async ( params: Pick<i_model, 'uid' | 'type' | 'title' | 'url' | 'repo'> ) => { const _data: _interface = { uid: params.uid, type: params.type, title: params.title, url: params.url, repo: params.repo, createdAt: new Date(), updatedAt: new Date(), }; return (await model.insertMany([_data])) as i_model[]; }; // 更新 export const update = async ( id: string, uid: string, params: Pick<i_model, 'type' | 'title' | 'url' | 'repo'> ) => { return await model.updateOne( { _id: id, uid: uid }, { $set: { ...params, updatedAt: new Date() } } ); }; // 削除 export const deleteProduct = async (id: string, uid: string) => { return await model.deleteOne({ _id: id, uid: uid }); }; // 全投稿数 export const countAll = async () => { return model.find({}).countDocuments(); }; // ジャンル別投稿数 export const countType = async (type: number) => { return model.find({ type: type }).countDocuments(); }; // タイトル別投稿数 export const countTitle = async (title: string) => { return model.find({ title: { $regex: title } }).countDocuments(); }; // ユーザ別投稿数 export const countUser = async (uid: string) => { return model.find({ uid: uid }).countDocuments(); }; // ページング全投稿 export const pagingAll = async (num: number) => { return await model.aggregate([ { $match: {}, }, { $lookup: { from: 'users', localField: 'uid', foreignField: 'uid', as: 'users_info', }, }, { $sort: { createdAt: -1 }, }, { $skip: num * pagingNum, }, { $limit: pagingNum }, { $project: { _id: '$_id', type: '$type', title: '$title', url: '$url', repo: '$repo', name: '$users_info.name', uid: '$uid', createdAt: '$createdAt', updatedAt: '$updatedAt', }, }, ]); }; // ページングタイプ別 export const pagingType = async (num: number, type: number) => { return await model.aggregate([ { $match: { type: type, }, }, { $lookup: { from: 'users', localField: 'uid', foreignField: 'uid', as: 'users_info', }, }, { $sort: { createdAt: -1 }, }, { $skip: num * pagingNum, }, { $limit: pagingNum }, { $project: { _id: '$_id', type: '$type', title: '$title', url: '$url', repo: '$repo', name: '$users_info.name', uid: '$uid', createdAt: '$createdAt', updatedAt: '$updatedAt', }, }, ]); }; // ページングタイトル別 export const pagingTitle = async (num: number, title: string) => { return await model.aggregate([ { $match: { title: { $regex: title }, }, }, { $lookup: { from: 'users', localField: 'uid', foreignField: 'uid', as: 'users_info', }, }, { $sort: { createdAt: -1 }, }, { $skip: num * pagingNum, }, { $limit: pagingNum }, { $project: { _id: '$_id', type: '$type', title: '$title', url: '$url', repo: '$repo', name: '$users_info.name', uid: '$uid', createdAt: '$createdAt', updatedAt: '$updatedAt', }, }, ]); }; // ページングユーザ別 export const pagingUser = async (num: number, uid: string) => { return await model.aggregate([ { $match: { uid: uid, }, }, { $lookup: { from: 'users', localField: 'uid', foreignField: 'uid', as: 'users_info', }, }, { $sort: { createdAt: -1 }, }, { $skip: num * pagingNum, }, { $limit: pagingNum }, { $project: { _id: '$_id', type: '$type', title: '$title', url: '$url', repo: '$repo', name: '$users_info.name', uid: '$uid', createdAt: '$createdAt', updatedAt: '$updatedAt', }, }, ]); };REST API
ユーザ作成・ログイン
リクエストURL
Post /v1/create/userリクエストヘッダー
Authorization: Firebase Authorizationで発行されたjwttoken Content-Type: application/jsonリクエストパラメーター
{}レスポンス
{}
ポートフォリオ投稿
リクエストURL
Post /v1/create/productリクエストヘッダー
Authorization: Firebase Authorizationで発行されたjwttoken Content-Type: application/jsonリクエストパラメーター
{ // ポートフォリオのタイトル title: string; // ポートフォリオのURL url: string; // ポートフォリオのリポジトリURL repo: string; // 0: Webアプリ // 1: スマホアプリ // 2: デスクトップアプリ // 3: スクレイピング // 4: ホムペ // 5: その他 type: number; }レスポンス
{}
ユーザプロフィール更新
リクエストURL
Put /v1/update/userリクエストヘッダー
Authorization: Firebase Authorizationで発行されたjwttoken Content-Type: application/jsonリクエストパラメーター
{ // ユーザ名 name: string; // GitHubのURL githubUrl: string; // TwitterのURL twitterUrl: string; }レスポンス
{}
ポートフォリオ更新
リクエストURL
Put /v1/update/productリクエストヘッダー
Authorization: Firebase Authorizationで発行されたjwttoken Content-Type: application/jsonリクエストパラメーター
{ // ポートフォリオのID id: string; // ポートフォリオのタイトル title: string; // ポートフォリオのURL url: string; // ポートフォリオのリポジトリURL repo: string; // 0: Webアプリ // 1: スマホアプリ // 2: デスクトップアプリ // 3: スクレイピング // 4: ホムペ // 5: その他 type: number; }レスポンス
{}
ポートフォリオ削除
リクエストURL
Delete /v1/cancel/productリクエストヘッダー
Authorization: Firebase Authorizationで発行されたjwttoken Content-Type: application/jsonリクエストパラメーター
{ // ポートフォリオのID id: string; }レスポンス
{}
ユーザプロフィール参照
リクエストURL
Get /v1/find/user/:uidリクエストヘッダー
Authorization: null以外の値リクエストパラメーター
{ // ユーザID uid: string; }レスポンス
{ // ユーザ名 name: string; // TwitterのURL twitterUrl: string; // GitHubのURL githubUrl: string; }
ページング全投稿(5件)
リクエストURL
Get /v1/paging/all/:numリクエストヘッダー
Authorization: null以外の値リクエストパラメーター
{ // 1ページ目は1, 2ページ目は2.... num: string; }レスポンス
{ // 件数 count: number; list: { // ポートフォリオのID _id: string; // 0: Webアプリ // 1: スマホアプリ // 2: デスクトップアプリ // 3: スクレイピング // 4: ホムペ // 5: その他 type: number; // ポートフォリオのタイトル title: string; // ポートフォリオのURL url: string; // ポートフォリオのリポジトリURL repo: string; // 投稿者名 name: string[]; // ユーザID uid: string; // 作成日 createdAt: Date; // 更新日 updatedAt: Date; }[]; }
ページングポートフォリオのタイトル別(5件)
リクエストURL
Get /v1/paging/title/:title/:numリクエストヘッダー
Authorization: null以外の値リクエストパラメーター
{ // 1ページ目は1, 2ページ目は2.... num: string; title: string; }レスポンス
{ // 件数 count: number; list: { // ポートフォリオのID _id: string; // 0: Webアプリ // 1: スマホアプリ // 2: デスクトップアプリ // 3: スクレイピング // 4: ホムペ // 5: その他 type: number; // ポートフォリオのタイトル title: string; // ポートフォリオのURL url: string; // ポートフォリオのリポジトリURL repo: string; // 投稿者名 name: string[]; // ユーザID uid: string; // 作成日 createdAt: Date; // 更新日 updatedAt: Date; }[]; }
ページングタイプ別(5件)
リクエストURL
Get /v1/paging/type/:type/:numリクエストヘッダー
Authorization: null以外の値リクエストパラメーター
{ // 1ページ目は1, 2ページ目は2.... num: string; // 0: Webアプリ // 1: スマホアプリ // 2: デスクトップアプリ // 3: スクレイピング // 4: ホムペ // 5: その他 type: string; }レスポンス
{ // 件数 count: number; list: { // ポートフォリオのID _id: string; // 0: Webアプリ // 1: スマホアプリ // 2: デスクトップアプリ // 3: スクレイピング // 4: ホムペ // 5: その他 type: number; // ポートフォリオのタイトル title: string; // ポートフォリオのURL url: string; // ポートフォリオのリポジトリURL repo: string; // 投稿者名 name: string[]; // ユーザID uid: string; // 作成日 createdAt: Date; // 更新日 updatedAt: Date; }[]; }
ページングユーザ投稿別
リクエストURL
Get /v1/paging/user/:uid/:numリクエストヘッダー
Authorization: null以外の値リクエストパラメーター
{ // 1ページ目は1, 2ページ目は2.... num: string; uid: string; }レスポンス
{ // 件数 count: number; list: { // ポートフォリオのID _id: string; // 0: Webアプリ // 1: スマホアプリ // 2: デスクトップアプリ // 3: スクレイピング // 4: ホムペ // 5: その他 type: number; // ポートフォリオのタイトル title: string; // ポートフォリオのURL url: string; // ポートフォリオのリポジトリURL repo: string; // 投稿者名 name: string[]; // ユーザID uid: string; // 作成日 createdAt: Date; // 更新日 updatedAt: Date; }[]; }
src/route/index.tsimport * as Express from 'express'; import * as Cors from 'cors'; import * as DotEnv from 'dotenv'; import Constant from 'src/constant'; // route--- import create_friend from 'src/route/create/friend'; import create_user from 'src/route/create/user'; import create_product from 'src/route/create/product'; import paging_all from 'src/route/paging/all'; import paging_title from 'src/route/paging/title'; import paging_type from 'src/route/paging/type'; import paging_user from 'src/route/paging/users'; import update_product from 'src/route/update/product'; import update_user from 'src/route/update/user'; import cancel_friend from 'src/route/cancel/friend'; import cancel_product from 'src/route/cancel/product'; import find_user from 'src/route/find/user'; // route--- DotEnv.config(); const app = Express(); const router = Express.Router(); // middleware--- app.use(Cors({ origin: process.env.ORIGIN_URL })); app.use('/.netlify/functions/api', router); app.use(Express.urlencoded({ extended: true })); app.use( (req: Express.Request, res: Express.Response, next: Express.NextFunction) => { req.headers.authorization !== undefined ? next() : res.sendStatus(403); } ); app.use((_, __, res: Express.Response, ___) => { res.sendStatus(500); }); // middleware--- // routing--- // ユーザ作成 router.post(Constant.API_VERSION + Constant.URL['/create/user'], create_user); // 投稿 router.post( Constant.API_VERSION + Constant.URL['/create/product'], create_product ); // フォローする router.post( Constant.API_VERSION + Constant.URL['/create/friend'], create_friend ); // ページング全投稿 router.get( Constant.API_VERSION + Constant.URL['/paging/all'] + '/:num', paging_all ); // ページングタイプ別 router.get( Constant.API_VERSION + Constant.URL['/paging/type'] + '/:type' + '/:num', paging_type ); // ページングタイトル別 router.get( Constant.API_VERSION + Constant.URL['/paging/title'] + '/:title' + '/:num', paging_title ); // ページングユーザ別 router.get( Constant.API_VERSION + Constant.URL['/paging/user'] + '/:uid' + '/:num', paging_user ); // プロフィール router.get( Constant.API_VERSION + Constant.URL['/find/user'] + '/:uid', find_user ); // 更新 ユーザ router.put(Constant.API_VERSION + Constant.URL['/update/user'], update_user); // 更新 記事 router.put( Constant.API_VERSION + Constant.URL['/update/product'], update_product ); // フォローはずす router.delete( Constant.API_VERSION + Constant.URL['/cancel/friend'], cancel_friend ); // 投稿削除 router.delete( Constant.API_VERSION + Constant.URL['/cancel/product'], cancel_product ); // routing--- export default app;
- 投稿日:2020-08-18T23:33:23+09:00
VuePressにTailwind CSSを導入する方法
最低限トップページをVuePressで表示させる
VuePressでトップページを表示させる手順は公式ドキュメントに沿って行ってください。
この時点でのディレクトリ構成は以下の通りです。
. ├── docs │ ├── README.md ├── package.jsonTailwind CSSの導入
手順は基本的にTailswind CSSの公式ドキュメントに記載された通りです。
- npm or yarnでTailwind CSSをインストールする
ルートディレクトリで以下コマンドを実行
# Using npm npm install tailwindcss # Using Yarn yarn add tailwindcss
- ./docs/.vuepress/styles/index.styl を作成
mkdir ./docs/.vuepress mkdir ./docs/.vuepress/styles touch ./docs/.vuepress/styles/index.stylちなみに、上記ディレクトリ構成でindex.stylを配置すると、vuepressが自動的にindex.stylを認識、デフォルトのCSSファイルとして扱ってくれます。
この時点でのディレクトリ構成は以下の通りです。
. ├── docs ├── README.md ├── .vuepress ├── styles ├── index.styl ├── package.json
- ./docs/.vuepress/styles/index.styl に Tailwind CSSの設定を追加
./docs/.vuepress/styles/index.styl に以下を追加
@tailwind base; @tailwind components; @tailwind utilities;自分で全体に効かせるCSSを書きたいときは、
@tailwind components; と @tailwind utilities; の間に記述しましょう。@tailwind base; @tailwind components; (ここにCSSを記述) @tailwind utilities;
- ./docs/.vuepress/config.js を作成して設定を追加
touch ./docs/.vuepress/config.js./docs/.vuepress/config.js の内容を以下の通りにします。
module.exports = { plugins: [ // ... require('tailwindcss'), require('autoprefixer'), // ... ] }この時点でのディレクトリ構成は以下の通りです。
. ├── docs ├── README.md ├── .vuepress ├── styles ├── index.styl ├── config.js ├── package.json
- サーバーを再起動して確認
もしサーバーを起動している状態なら再起動しましょう。
これでVuePressでTailwind CSSが使えるようになりました!
- 投稿日:2020-08-18T23:31:47+09:00
[メモ] JavaScript(ES2015)の基礎
始めに
JavaScriptについて勉強したときのメモです。
気になったところについてしか書いてないです。
実務でJavaScriptを使ったことのない人間が書いてるのでご注意。文(Statement)
末尾のセミコロン(;)は省略可能だけど、つけた方が良さそう。
文字列リテラル
シングルクォート、ダブルクォートが使えるが特に違いは無い。文字列中にシングルクォート/ダブルクォートを含める場合は以下のような書き方ができる。
let s1 = "シングルクォート(')"; let s2 = 'ダブルクォート(")';テンプレート文字列
文字列中に変数の値を簡単に含めることができる。
C#の文字列補完に似てると思った。オブジェクトリテラル
オブジェクトのキーはダブルクォートで括らなくてもいい。
分割代入
Pythonのパッキング/アンパッキングと似たようなことができる。
(右辺の値が評価されてから代入が行われている様子。これもPythonと同様)Node.js でモジュールをインポートするときにたまに見る左辺の{}は分割代入の構文によるもの。下の場合だとpeerモジュールの中のExpressPeerServerの参照だけを取得している。
const express = require('express'); const { ExpressPeerServer } = require('peer'); const app = express(); app.get('/', (req, res, next) => res.send('Hello world!'));論理積(&&),論理和(||)
ショートサーキット演算である点に注意する。
日付
- 投稿日:2020-08-18T23:22:01+09:00
javascript 2つの配列の平均値
複数の数値が定義されている配列data1とdata2から「data1とdata2を合わせた平均値」と「平均値以上の数値」を表示するプログラムを作成する。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>課題</title> <script> var data1 = [59, 39, 100, 2, 15, 40, 84, 97]; var data2 = [63, 18, 64, 97, 50, 98]; var sum = 0; for(var i = 0; i < data1.length; i++) { sum += data1[i]; } for(var j = 0; j < data2.length; j++) { sum += data2[j]; } var ave = sum/(data1.length+data2.length); document.write('<p>data1とdata2を合わせた平均値:'+ave+'</p>'); document.write('<p>平均値以上の数値は以下</p>') for(var i = 0; i < data1.length; i++) { if(data1[i]>=ave) { document.write('<p>data1:'+data1[i]+'</p>'); } } for(var j=0; j<data2.length; j++){ if(data2[j]>=ave) { document.write('<p>data2:'+data2[j]+'</p>'); } } </script> </head> <body> </body> </html>初めて学習したときはこれでもかなり苦戦ししました。for文とif文をある程度理解していればかんたんになりますねぇ。
- 投稿日:2020-08-18T22:45:42+09:00
[JavaScript] reduceは可読性が悪くループで置き換え可能なので使うべきではない
reduceは使いこなすのが難しい?
reduceを見ても、なんだこりゃ?という感じで、使うのが難しいなと思っていました。
なので、自分が reduce を使いこなせないとよいプログラマではなく、reduce を使いこなすのがよいプログラマなのか?と感じたこともありました。
で、下記のような話を目にしました。
なぜGuidoはmap() やfilter(),reduce() そしてlambdaをPython 3から取り除きたかったのですか? - Quora
Pythonの作成者であるGuido van Rossumは、関数型プログラミングがあまり好きではありません。このことは周知の事実です。
Guido:「私は現実世界でのコードの読みやすさと有用性を重視しています。map()とfilter()が適切な場合もいくつかありますが、他のケースではPythonのリスト内包表記があるのです。わたしがreduce()を嫌いになったのは、それを使うとほとんど常に「(a)sum()を実装するのにしか使われていない」「(b)読み取り不可能なコードになってしまう」ということからです。
Python作成者の方はとても天才の部類にはいる方だと思いますが、その人でも難しいというか、読み取り不可能なコードになるということをすっぱりと見抜いていたのですね。
私は Python についてはよく知らないのですが、リンク先記事でのGuido氏の判断は見事だと思います。
JavaScriptではsumは組み込み関数ではないですが、関数化してしまえばいいわけですし、sumをreduceではかけなくてもループでその場で即座に書けない人はいないですよね。そして、sumする以外の場面でreduceが有効な場面はたしかにほぼ見当たりません。
reduceに起因する不具合は解決方法を見つけるのが非常に難しい
下記のリンクのその先のリンクを見ていただくと、split('.')した後の reduceをつかっているコードで、実に読みにく見ただけでは不具合があるかどうかわからないコードが見つかります。(見ただけで不具合あると見抜ける人います?テストコードを書いているのでテストを通過させてみると、どこに不具合があるのかわかりますよ。)
JavaScript オブジェクトの深くネストされているプロパティに安全にアクセスする getProperty - Qiita
単純なループ処理で書かれている場合なら不具合があったとしても比較的簡単にみつかります。ループ回数を知るのも簡単です。ループ内にconsole.logを仕込んで、indexとその時の変化している各種値を取得するのは容易です。
reduceはループ回数が読み取りにくい
MDNで紹介されている非常に簡単なサンプルです。
Array.prototype.reduce() - JavaScript | MDNconst array1 = [1, 2, 3, 4]; const reducer = (accumulator, currentValue) => accumulator + currentValue; // 1 + 2 + 3 + 4 console.log(array1.reduce(reducer)); // expected output: 10 // 5 + 1 + 2 + 3 + 4 console.log(array1.reduce(reducer, 5)); // expected output: 15reduceを知っている人はわかるのですが、初見でreducerが何回実行されるのか、を知るのはとてもわかりにくいです。配列の個数が固定だからまだわかるかもしれませんが、現実のコードでは配列長は変化するものですので、より複雑です。
また、reduceの第二引数の有無によってreducerの実行回数が変わり、currentValueが何回目のreducerの実行によって呼び出されるのかが変わるというコードは読みにくくて仕方ありません。
日本語で説明するのも難しい。
reduce は簡単にループで置き換え可能
ともかく、reduceを使わなくてもおそらく全ての場面においてループ処理で記載でき、そのほうが読みやすいので、わざわざ奇妙キテレツなreduceを使う必要がありません。
関数型的な、array.forEachや、array.map、array.filter、array.find、array.findIndex、これらは使いやすく直感的なので私はよくつかいます。なので、関数型的なコードを否定するわけではないです。
ただ、その中の処理としても、reduce は圧倒的に使いにくく読みにくく、置き換え可能なので使う必要がない。ということです。
reduce は forEach, for ,for of などで置き換え可能です。そして置き換えたほうが可読性があがり、修正も機能拡張も何もかも使いやすくて汎用的です。ループを抽象化してforを使うべきではない。というわけわからないこだわりを持った人が結構いる。
forではなくreduceを無理やり使ったような、パズル構文で「こんな変な構文使い粉得る俺すげー」的な人気記事を、両手の指の数くらいは見てきたと思います。
for を避ける意味がわかりません。for も forEach も、for of でもなんでも、読みやすければ採用しましょう。動作も早いです。
読みにくいreduceを使わずともプログラムは書けますし、読みやすいコード書く人が増えて欲しいので、積極的にreduceを使わない人が増えて欲しいなと思いました。
- 投稿日:2020-08-18T22:34:49+09:00
JavaScriptでいま何歳?
いま何歳?
年齢とか何周年?を計算するためのコードを書いたのでメモ。Dateオブジェクトを使う
const now = new Date() const thisYear = now.getFullYear() const thisMonth = now.getMonth() + 1 const today = now.getDate() const calcAge = birthday => { let target = thisYear * 10000 + thisMonth * 100 + today return Math.floor((target - birthday) / 10000) } console.log(`いま${calcAge(20001221)}歳です`)参考:MDN: Date
Moment.jsを使う
moment()を呼び出すと、DateオブジェクトをラップするMomentオブジェクトを生成してくれる。
import moment from 'moment' const now = moment() const birthday = moment('2000-12-21') const calcAge = now.diff(birthday, 'year') console.log(`いま${calcAge}歳です`)Dateオブジェクトを使うよりも、シンプルに日付関連を扱えそう。
参考:公式ドキュメント: Moment.js Documentation
- 投稿日:2020-08-18T21:43:55+09:00
開発体験を変える! Firefox DevTools Tips 7 - 1 選
本記事は、開発体験を変える! Chrome DevTools Tips 7選 にインスパイアされ、Firefox の実装状況を確認したものになります。
Chrome との比較のため、元記事を引用しながら説明します。1.
$0
で選択中のDOM要素の取得特定の要素に何かしたいという時には、要素のIDやclassを確認してConsoleで
document.querySelector("#xxx")
で取得するというのが一般的だと思います。実はそれはカーソル選択と$0
で代替できます。
Classや、IDがついていない特定のDOMを取得したい時とかにも使えるので地味に便利です。手順
- カーソルで取得したい要素を選ぶ
- コンソールタブで
$0
を入力Chrome の Console Utilities API と、ほぼ同等のものが Firefox にも用意されています。
Helper commands の一例
$(selector, element)
CSS セレクター文字列セレクターを検索し、一致する要素から派生した最初のノードを返します。指定しない場合、要素はデフォルトでドキュメントになります。 document.querySelector() と同等、または \$ 関数が存在すれば、\$ 関数を呼び出します。$('#hoge')
$$(selector, element)
CSSセレクター文字列セレクターを検索し、一致する要素から派生した DOM ノードの配列を返します。指定しない場合、要素はデフォルトでドキュメントになります。これはdocument.querySelectorAll()に似ていますが、NodeListではなく配列を返します。$$('div')
$x(xpath, element, resultType)
要素のコンテキストで XPath xpath 式を評価し、一致するノードの配列を返します。指定しない場合、要素はデフォルトでドキュメントになります。 resultType パラメータは、返される結果のタイプを指定します。
XPathResult 定数または対応する文字列:「number」、「string」、「bool」、「node」、または「nodes」。
指定しない場合は、ANY_TYPEが使用されます。$x("//p")2. DOM変化からのDebugger起動(Break on)
DevToolsでのデバッグというとソースコード上に
debugger
を仕込んでというのがありますが、ソースコードを何もいじらずDevTools単体でも実行できます。1つはインスペクタータブからの
ブレークポイントの設定
です。
何か表示が変わるDOM要素を選択して右クリックでブレークポイントの設定
を選択、表示されるサブメニューでいずれかの項目にチェックを入れます。
その状態で画面上にて表示の変化のトリガーとなる動作を行うと、DOMの変化の実行前に処理が止まり、該当変化のイベント処理からステップ実行が可能になります。ブレークポイントの設定のオプションは以下の通りです。
option名 内容 サブツリーの変更 現在選択されているノードの子が削除または追加されたとき、または子の内容が変更されたときにトリガーされます。子ノード属性の変更、または現在選択されているノードへの変更ではトリガーされません 属性の変更 現在選択されているノードで属性が追加または削除されたとき、または属性値が変更されたときにトリガーされます ノードの削除 現在選択されているノードが削除された時にトリガーされます 手順
- インスペクタータブをひらく
- 下位要素が変化する、または自身の属性が変わる or 破棄される要素を選ぶ
- 右クリックで
ブレークポイントの設定
を選択、サブメニューのいずれかにチェックを入れる- 画面上で変化のトリガーとなる操作をする
3. Exceptionの発生箇所で自動停止
エラー発生時に自動的にブレークポイントを設定することも可能です。
「console上にエラー出てるけど、何処で発生しているんだろう?」という時にわざわざ該当箇所を探しブレークポイントを設定する必要はありません。一瞬でデバッグできます。手順
- デバッガータブを開く
- 右側のブレークポイントペインにある
ブレークポイント → 例外で停止
にチェックを入れる- エラーの発生する動作を行う
4. snippetの登録
良く使うスクリプトをsnippetとして登録する機能です。
登録したスクリプトはコントロールパネルから実行できます。私はちょっとしたデバッグ用のスクリプトなどを登録しています。これは結構手順が違いました。
手順
- コンソールタブを開きます
ctrl
+b
でマルチラインモードに切り替えます。- マルチラインモードで自由にスクリプトを作成
ctrl
+s
で名前を付けて保存ctrl
+o
で読み込み5. ScreenShot(フルページ or ノード)
スクロール分を含んだフルページのスクリーンショットを撮りたい時ってありますよね。そんな時はおそらくChrome拡張を使っている人が多いと思います。
でも実はDevTools単体でもできます。他にも、Screenshot Capture node screenshot
を選べばElementsの選択画面で選択しているノードの範囲でのスクリーンショットもお手軽に撮れます。これは、Firefox だとアドレスバーにある三点リーダから選択できます。
自由エリア選択も可能です。手順
- アドレスバーにある三点リーダからスクリーンショットを撮るを選択
6. APIリクエストの実行形式でのコピー
APIのテストや別クライアントでの操作の際に、APIリクエストをcURLやその他の形式で使いたいって時あると思います。そんな時もDevToolsが使えます。
ネットワークタブからリクエストを選び、右クリックでCopyを選択するだけで様々な実行形式のコードベースが取得可能です。手順
- ネットワークタブを開く
- コピーしたいリクエストを選択して右クリック
- コピーのサブメニューから指定の形式を選ぶ
7. 使用していないCSS、JSの調査
DevTools単体で静的解析的に、ロードしているものの未使用のCSS、JSを調べることも可能です。
※ あくまでページ単体での調査なので、全ページで使う共通スクリプトをbunldeしていると値が悪く出たりします。そこは注意が必要です。残念ながら、Firefox ではできないようです。
CSSカバレッジapx. 画面上で自由にテキスト編集
これはChrome DevToolsの機能という訳ではないのですが、分かりやすく凄いので紹介します。
DevToolsのConsoleタブ上で以下のコードを実行すると、画面上の要素を自由に改変できるようになります。
文字列を編集したり、画像を削除したり色々できます。こっちはできます。
document.designMode = "on"ちょっと表現を変えてデザインを見てみたりとかする時に便利です。
ちなみに$0と組み合わせて、選択中の要素を編集可能にしたい場合は以下でも出来ます。
$0.contentEditable = 'true'終わりに
以上「開発体験を変えるChrome DevTools Tips 7選」からの「開発体験を変える! Firefox DevTools Tips 7 - 1 選」でした。
思っていたよりも、同等の機能がありますね。
(なので、思っていた以上にパクリ記事になってしまいました^^;)カバレッジの件は、個人的に結構残念です。
- 投稿日:2020-08-18T21:32:46+09:00
【React】コンポーネントとPropsについて解説してみた
前書き
これを通して読めば、「コンポーネントとProps」について理解できると思います。ご指摘ありましたらコメントよろしくお願いします?♂️
この記事で作っていくもの
質素ですが、プロフィール一覧ページを
React
を使って実装していきます!!
出来るだけ丁寧かつ噛み砕いて解説していきますね?♂️コンポーネントとは
一言でいうならUI(みため)の部品のことです。パッと思いつく例は、下記のとおり。
- ブログの記事
- プロフィール欄
- Twitterみたいな投稿
これらをコンポーネント化するメリットは、下記のようなReactコンポーネントのメリットを享受できるからです!
- 再利用可能(共通化)
- 管理のしやすさ
例えば、ツイッターのような投稿に一つ一つコードを書いていたら大変ですよね?
そんなときにコンポーネント化しておけば、使い回しがきくのです!!ちなみに、コンポーネントには二種類あります。それが「クラスコンポーネント」と「関数コンポーネント」です。それぞれ解説していきますね。
クラスコンポーネント
People.jsxclass People extends React.Component { render() { return ( <h1>みんなのプロフィールが見れるよ!</h1>; {/* ここにそれぞれのプロフィールを並べる(あとで解説) */} ) } }こんな感じで定義することができます。文字通り、
class
の継承によってコンポーネントが作成されていますね。ちなみにクラスコンポーネントはstate
やライフサイクルメソッドを持つことができます。(ここら辺は別の記事で解説しようかなって思います。)関数コンポーネント
People.jsxconst People = () => { return ( <h1>みんなのプロフィールが見れるよ!</h1>; {/* ここにそれぞれのプロフィールを並べる(あとで解説) */} ) };今度は関数コンポーネントで書いてみました。どうでしょうか?スッキリしましたよね。これが関数コンポーネントの大きな利点です。ですが、関数コンポーネントは
state
やライフサイクルメソッドを持つことはできません。(補う方法はあります。)ちなみに、関数コンポーネントが推奨されていたりします。
ではでは、次に子供のコンポーネント(プロフィールの内容)を作っていきましょう!
「ところで、プロフィールの内容ってそれぞれ違いますよね。どう管理するんですか??」
そんな時には登場するのが
Props
です!!Propsってなに?
親のコンポーネントから子供のコンポーネントに値を渡すための仕組みです。少し分かりにくいので、コードをみながら解説していきますね。
別ファイル(
Profile.jsx
)に子コンポーネント(ここではPeople.jsx
が親)を作っていきましょう。Profile.jsx// 子コンポーネント const Profile = () => { return ( <> <h2>〇〇さんのプロフィールです!</h2> <p>年齢は〇〇歳</p> <p>身長は〇〇〇cm</p> <p>性格は〇〇</p> </> ); };子コンポーネントを作ったので、親コンポーネントで呼び出してみましょう!
People.jsx// 親コンポーネント const People = () => { return ( <> <h1>みんなのプロフィールが見れるよ!</h1>; <Profile /> </> ) };こんな感じで
<子コンポーネント名/>
の形で呼び出すことができます。実際のブラウザでは下記のように表示されます。(ちなみに
<>,</>
はReact.Fragment
のシンタックスシュガーです。ここでは解説しないので、気になる方はググってみてください。)味気ないですよね。穴だらけなので、それぞれのプロフィールに対応できないですよね...
では、
Props
で値を親コンポーネントから子コンポーネントに渡していきましょう!!まずは親コンポーネントを管理しているファイルを変更していきましょう。People.jsx// 親コンポーネント const People = () => { return ( <> <h1>みんなのプロフィールが見れるよ!</h1>; <Profile name="山田" age={20} height={170} personality="優しい" /> </> ) };こんな感じで子コンポーネントを呼び出している箇所で属性を設定することで、値を渡すことができます。今度は子コンポーネントを管理しているファイルで値を受け取っていきましょう!!
Profile.jsx// 子コンポーネント const Profile = (props) => { return ( <> <h2>{props.name}さんのプロフィールです!</h2> <p>年齢は{props.age}歳</p> <p>身長は{props.height}cm</p> <p>性格は{props.personality}</p> </> ); };まず、子コンポーネントを定義している箇所の引数に
props
を渡しましょう。このprops
の中身を擬似的に表現してみると下記のようになります。// 以下は擬似的なコードです。 props = { name: "山田", age: 20, height: 170, personality: "優しい" }こんな感じで
props
の中にはオブジェクトの形でkey
と値が格納されているというイメージが分かりやすいと思います?あとは使いたい箇所で
props.key
で値を使うことができます。ちなみにブラウザで見ると下記みたいになります。ここまでで一旦、完了です。ではこれからコンポーネントをカスタマイズしていきましょう!
子コンポーネントを増やす
掲載したいプロフィールを増やしたい場合は、下記のように親コンポーネント内で子コンポーネントを増やすことで簡単に実装することができます。
People.jsx// 親コンポーネント const People = () => { return ( <> <h1>みんなのプロフィールが見れるよ!</h1>; <Profile name="山田" age={20} height={170} personality="優しい" /> <Profile name="田中" age={25} height={160} personality="おっとり" /> <Profile name="佐藤" age={21} height={180} personality="しっかり" /> </> ) };ブラウザでは下記みたいになります。
簡単にプロフィールを増やすことができましたね!!
記事中で出てきてここで解説していないこと
import
とexport
React.Fragment
state
とライフサイクルメソッド他の記事で解説するかもしれませんが、今回は割愛しました?♂️
どれも重要な要素なので調べてみると良いかもしれません!!
- 投稿日:2020-08-18T21:23:37+09:00
Static Web Appsでは現状TimerTriggerは使えないっぽい
Azure Functions のタイマー トリガーを使ってStatic Web Appsに紐づいたAPIプログラムもタイマー実行できないかと思いましたがどうやらできないっぽいのでメモ。
ビルドでこんなコケかたします。
Error in processing api build artifacts: the file 'myTimer/function.json' has specified an invalid trigger of type 'timerTrigger' and direction 'in'. Currently, only httpTriggers are supported. Visit https://github.com/Azure/azure-functions-host/wiki/function.json for more information.ドキュメントを見てみると
トリガーとバインドは、HTTP に限定されています。
としっかりと書いてましたね......ちゃんとドキュメントみましょうという教訓をメモにしておきます。
- 投稿日:2020-08-18T20:09:05+09:00
Vue.jsでクリックマップ風を作ってみた
ウェブのアクセス解析にヒットマップ(クリックマップ)のようなものがビジュアル的に見やすくて、直感的なクリエイティブヒントを生み出すヒントとなったりします。
そのため、GAやAAのウェブページ上クリック率などを見られるもの、ウェブサイトを担当していたときに重宝していました。
しかし、それがアプリとなると、いろいろ難しくなります。
当然、解析ツール上にはデータが残っております。
そのデータを活用して、アプリの画面を画像で書き出して、その上に各箇所のクリック率を描画すれば、なんとなくそれらしきものが出来上がるではないかと思い、Vue.jsで実装してみました。事前準備
data.json
htmlと同じ階層にdata.jsonとして置いておきます。
解析ツール上からデータをエクスポート、下記のように整形します。
* 原始的に、解析ツールからファイルを自動配信などして、マクロやGASで必要なフォーマットへ整うなど(GASでの対応)
* セキュリティに許容できるなら、Googleのデータスタジオなどを使うと便利
* そもそも、解析ツールのAPIがもし叩けるなら、なによりも
{"update":"2020/08/18 10:21:46","data":[{"screen_name":"talk","item_name":"element1","ctr":0.2,"x_value":100,"y_value":200},
//中略
{"screen_name":"talk","item_name":"element100","ctr":0.3,"x_value":300,"y_value":400}]}下地の画像ファイル
htmlと同じ階層にimgフォルダを作成し、{appScreen}.jpgという形式でファイルを置いておきます。
html/jsのコード
<!doctype html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="Content-Type" content="text/html"> <title>Click Map</title> </head> <body> <div id="app"> <header> <h1>Click Map</h1> <select v-model="selected"> <option value="default" disabled selected>Please select a view</option> <option v-for="option in options" v-bind:value="option.value">{{ option.name }}</option> </select> </header> <main> <img :src="'img/' + selected + '.jpg'" /> <ul> <li v-for="item in items" v-if="item.screen_name === selected" :key="item.item_name" :class="item.screen_name" :style="{left:item.x_value + 'px', top:item.y_value + 'px', backgroundColor: 'rgb(' + item.ctr * maxRate + ',48,48)'}"> <!--クリック率の最大値を255にして、各Rateをそれに換算してrgbのバックグラウンドカラーに当て込めば、ホットなクリック箇所が良しなに見えてきます。また各クリック率の表示場所はjsonデータ内のx_value,y_valueから位置を指定します。--> {{ item.ctr * 100 | decimalFormat }}%</li> </ul> </main> </div> </body> <script src="https://cdn.jsdelivr.net/npm/vue/dist/vue.js"></script> <script src="https://cdn.jsdelivr.net/npm/axios@0.18.0/dist/axios.min.js"></script> <script> new Vue({ el: '#app', data: { items: '', selected: 'default', options: [{ name: 'appScreen1', //テキトーにスクリーン名を value: 'appScreen1' }, { name: 'appScreen2', //テキトーにスクリーン名を value: 'appScreen2' }, ] }, computed: { maxRate: function() { // クリックマップ風にクリック率とバックグラウンドカラーrgbの係数を生成 var rateList = [] for (let i = 0; i < this.items.length; i++) { if (this.items[i].screen_name == this.selected && this.items[i].ctr < 0.8) { rateList.push(this.items[i].ctr) } } var maxRate = 255 / Math.max.apply(0, rateList) return maxRate } }, filters: { //指定したスクリーンの値を絞り込む decimalFormat: function(num) { return num.toFixed(2) }, numFormat: function(num) { return num.toFixed(0) } }, mounted: function() { //jsonファイルからデータを取得 axios.get("data.json").then(response => (this.items = response.data.data)) } }); </script> <style> /* cssは良しなに */ body{background:#999;padding:0;margin:0} #app{width:960px;min-height:720px;margin:0 auto;background:#fff;position:relative;-moz-box-shadow:4px 4px 4px rgba(0,0,0,.4);-webkit-box-shadow:2px 2px 2px rgba(0,0,0,.4);-o-box-shadow:10px 10px 10px rgba(0,0,0,.4);-ms-box-shadow:10px 10px 10px rgba(0,0,0,.4);border-bottom-left-radius:4px;border-bottom-right-radius:4px} header{padding:16px;border-bottom:solid 1px #efefef;margin-bottom:8px} h1{width:120px;float:left;font-size:18px;text-align:center;margin-right:100px;color:#777} select{height:48px;text-align:center;font-size:20px;padding:8px 16px;border:solid 2px #777;border-radius:4px;color:#777} img{width:100%;margin:0 auto} li{list-style:none;position:absolute;font-size:14px;padding:4px;border-radius:4px;color:#fff;-moz-box-shadow:4px 4px 4px rgba(0,0,0,.4);-webkit-box-shadow:2px 2px 2px rgba(0,0,0,.4);-o-box-shadow:10px 10px 10px rgba(0,0,0,.4);-ms-box-shadow:10px 10px 10px rgba(0,0,0,.4)} </style> </html>
- 投稿日:2020-08-18T19:39:07+09:00
fullcalendar V5 でデーターベースを絡めたサイトを構築
試しにグーグルライクなスケジュールソフトを作ってみました。
週末、調べて作ってみました
ざっくり以下の環境が必要です。
1 fullcalendar V5
2 node.js
3 Express
4 sqlite3ざっくり表のHTMLソースです
以下のようになっています。
これがV5の情報がなくて英語のマニュアルを読んで
作っていました。<!DOCTYPE html> <html> <head> <meta charset='utf-8' /> <link href='../lib/main.css' rel='stylesheet' /> <script src='../lib/main.js'></script> <script src='../lib/locales/ja.js'></script> <script> //alert(data); document.addEventListener('DOMContentLoaded', function() { var calendarEl = document.getElementById('calendar'); var calendar = new FullCalendar.Calendar(calendarEl, { schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source', now: '2020-06-07', locale:'ja', scrollTime: '00:00', // undo default 6am scrollTime editable: true, // enable draggable events selectable: true, aspectRatio: 1.8, headerToolbar: { left: 'today prev,next', center: 'title', right: 'resourceTimelineDay,resourceTimelineThreeDays,timeGridWeek,dayGridMonth,listWeek' }, initialView: 'resourceTimelineDay', views: { resourceTimelineThreeDays: { type: 'resourceTimeline', duration: { days: 3 }, buttonText: '3 days' } }, resourceAreaHeaderContent: '営業先', resources: [ { id: 'a', title: '営業先A' }, { id: 'b', title: '営業先 B', eventColor: 'green' }, { id: 'c', title: '営業先 C', eventColor: 'orange' }, { id: 'd', title: '営業先 D', children: [ { id: 'd1', title: '子 営業先 D1' }, { id: 'd2', title: '子 営業先 D2' } ] }, { id: 'e', title: '営業先 E' }, { id: 'f', title: '営業先 F', eventColor: 'red' }, { id: 'g', title: '営業先 G' }, { id: 'h', title: '営業先 H' ,eventColor: 'orange' }, { id: 'i', title: '営業先 I' }, { id: 'j', title: '営業先 J', eventColor: 'green' }, { id: 'k', title: '営業先 K' }, { id: 'l', title: '営業先 L' }, { id: 'm', title: '営業先 M' }, { id: 'n', title: '営業先 N' }, { id: 'o', title: '営業先 O' }, { id: 'p', title: '営業先 P' }, { id: 'q', title: '営業先 Q' }, { id: 'r', title: '営業先 R' }, { id: 's', title: '営業先 S' }, { id: 't', title: '営業先 T' }, { id: 'u', title: '営業先 U' }, { id: 'v', title: '営業先 V' }, { id: 'w', title: '営業先 W' }, { id: 'x', title: '営業先 X' }, { id: 'y', title: '営業先 Y' }, { id: 'z', title: '営業先 Z' } ], eventResize: function (info) { //alert(info.event.id + " was dropped on " + info.event.start.toISOString()); //if (!confirm("Are you sure about this change?")) { // info.revert(); //} else { //} ////////////////////////////////// var event = calendar.getEventById(info.event.id); var resources = event.getResources(); var resourceIds = resources.map(function (resource) { return resource.id }); //////////////////// var request = new XMLHttpRequest(); var req = 'http://157.7.138.66:3000/api/updata?resourceId=' + resourceIds + '&start=' + info.event.start.toISOString() + '&end=' + info.event.end.toISOString() + '&id=' + info.event.id request.open('GET', req, true); request.responseType = 'json'; request.onload = function () { var data = this.response; console.log(data); //alert(data); calendar.addEventSource(data); calendar.refetchEvents(); calendar.render(); }; request.send(); }, eventDrop: function (info) { //alert(info.event.id + " was dropped on " + info.event.start.toISOString()); //if (!confirm("Are you sure about this change?")) { // info.revert(); //} else { //} ////////////////////////////////// var event = calendar.getEventById(info.event.id); var resources = event.getResources(); var resourceIds = resources.map(function (resource) { return resource.id }); //////////////////// var request = new XMLHttpRequest(); var req = 'http://157.7.138.66:3000/api/updata?resourceId=' + resourceIds + '&start=' + info.event.start.toISOString() + '&end=' + info.event.end.toISOString() + '&id=' + info.event.id request.open('GET', req, true); request.responseType = 'json'; request.onload = function () { var data = this.response; console.log(data); //alert(data); calendar.addEventSource(data); calendar.refetchEvents(); calendar.render(); }; request.send(); }, dateClick: function(info) { //日付をクリックしたときの処理 //addEvent(calendar,info); //あとで使う関数 alert(info); }, select:function(event, jsEvent){ var title = prompt('予定を入力してください:', event.title); if (title && title != "") { alert("[" + title + "] 作成しました"); } alert("開発未定"); //if(title && title!=""){ // event.title = title; // calendar.fullCalendar('updateEvent', event); //イベント(予定)の修正 //}else{ // calendar.fullCalendar("removeEvents", event.id); //イベント(予定)の削除 //} } }); var request = new XMLHttpRequest(); request.open('GET', 'http://157.7.138.66:3000/api/photo/list', true); request.responseType = 'json'; request.onload = function () { var data = this.response; console.log(data); //alert(data); calendar.addEventSource(data); calendar.refetchEvents(); calendar.render(); }; request.send(); calendar.render(); }); </script> <style> body { margin: 0; padding: 0; font-family: Arial, Helvetica Neue, Helvetica, sans-serif; font-size: 14px; } #calendar { max-width: 1100px; margin: 50px auto; } </style> </head> <body> <div id='calendar'></div> </body> </html>だいたいはソースにある通りです。
裏のロジックは後日 執筆します。それよりもソースが欲しい方は以下で連絡してもらえば
メールと一緒に開設します。サンプルサイト
http://157.7.138.66:3000/index2
サンプルサイトです。
ソースプログラムが欲しい方は
以下のメールアドレスからどうぞ連絡をください。qqmdはちsrきゅうk@docomonet.jp
平仮名は数値でお願いします(スパム除け)
*サンプルサイトは期間限定で公開しています。
*仮IDはusername
*仮PWはpasswordです
- 投稿日:2020-08-18T19:39:07+09:00
fullcalendar V5 でデーターベースを絡めたサイトを構築その①(②は未定)
試しにグーグルライクなスケジュールソフトを作ってみました。
週末、調べて作ってみました
ざっくり以下の環境が必要です。
1 fullcalendar V5
2 node.js
3 Express
4 sqlite3ざっくり表のHTMLソースです
以下のようになっています。
これがV5の情報がなくて英語のマニュアルを読んで
作っていました。<!DOCTYPE html> <html> <head> <meta charset='utf-8' /> <link href='../lib/main.css' rel='stylesheet' /> <script src='../lib/main.js'></script> <script src='../lib/locales/ja.js'></script> <script> //alert(data); document.addEventListener('DOMContentLoaded', function() { var calendarEl = document.getElementById('calendar'); var calendar = new FullCalendar.Calendar(calendarEl, { schedulerLicenseKey: 'GPL-My-Project-Is-Open-Source', now: '2020-06-07', locale:'ja', scrollTime: '00:00', // undo default 6am scrollTime editable: true, // enable draggable events selectable: true, aspectRatio: 1.8, headerToolbar: { left: 'today prev,next', center: 'title', right: 'resourceTimelineDay,resourceTimelineThreeDays,timeGridWeek,dayGridMonth,listWeek' }, initialView: 'resourceTimelineDay', views: { resourceTimelineThreeDays: { type: 'resourceTimeline', duration: { days: 3 }, buttonText: '3 days' } }, resourceAreaHeaderContent: '営業先', resources: [ { id: 'a', title: '営業先A' }, { id: 'b', title: '営業先 B', eventColor: 'green' }, { id: 'c', title: '営業先 C', eventColor: 'orange' }, { id: 'd', title: '営業先 D', children: [ { id: 'd1', title: '子 営業先 D1' }, { id: 'd2', title: '子 営業先 D2' } ] }, { id: 'e', title: '営業先 E' }, { id: 'f', title: '営業先 F', eventColor: 'red' }, { id: 'g', title: '営業先 G' }, { id: 'h', title: '営業先 H' ,eventColor: 'orange' }, { id: 'i', title: '営業先 I' }, { id: 'j', title: '営業先 J', eventColor: 'green' }, { id: 'k', title: '営業先 K' }, { id: 'l', title: '営業先 L' }, { id: 'm', title: '営業先 M' }, { id: 'n', title: '営業先 N' }, { id: 'o', title: '営業先 O' }, { id: 'p', title: '営業先 P' }, { id: 'q', title: '営業先 Q' }, { id: 'r', title: '営業先 R' }, { id: 's', title: '営業先 S' }, { id: 't', title: '営業先 T' }, { id: 'u', title: '営業先 U' }, { id: 'v', title: '営業先 V' }, { id: 'w', title: '営業先 W' }, { id: 'x', title: '営業先 X' }, { id: 'y', title: '営業先 Y' }, { id: 'z', title: '営業先 Z' } ], eventResize: function (info) { //alert(info.event.id + " was dropped on " + info.event.start.toISOString()); //if (!confirm("Are you sure about this change?")) { // info.revert(); //} else { //} ////////////////////////////////// var event = calendar.getEventById(info.event.id); var resources = event.getResources(); var resourceIds = resources.map(function (resource) { return resource.id }); //////////////////// var request = new XMLHttpRequest(); var req = 'http://157.7.138.66:3000/api/updata?resourceId=' + resourceIds + '&start=' + info.event.start.toISOString() + '&end=' + info.event.end.toISOString() + '&id=' + info.event.id request.open('GET', req, true); request.responseType = 'json'; request.onload = function () { var data = this.response; console.log(data); //alert(data); calendar.addEventSource(data); calendar.refetchEvents(); calendar.render(); }; request.send(); }, eventDrop: function (info) { //alert(info.event.id + " was dropped on " + info.event.start.toISOString()); //if (!confirm("Are you sure about this change?")) { // info.revert(); //} else { //} ////////////////////////////////// var event = calendar.getEventById(info.event.id); var resources = event.getResources(); var resourceIds = resources.map(function (resource) { return resource.id }); //////////////////// var request = new XMLHttpRequest(); var req = 'http://157.7.138.66:3000/api/updata?resourceId=' + resourceIds + '&start=' + info.event.start.toISOString() + '&end=' + info.event.end.toISOString() + '&id=' + info.event.id request.open('GET', req, true); request.responseType = 'json'; request.onload = function () { var data = this.response; console.log(data); //alert(data); calendar.addEventSource(data); calendar.refetchEvents(); calendar.render(); }; request.send(); }, dateClick: function(info) { //日付をクリックしたときの処理 //addEvent(calendar,info); //あとで使う関数 alert(info); }, select:function(event, jsEvent){ var title = prompt('予定を入力してください:', event.title); if (title && title != "") { alert("[" + title + "] 作成しました"); } alert("開発未定"); //if(title && title!=""){ // event.title = title; // calendar.fullCalendar('updateEvent', event); //イベント(予定)の修正 //}else{ // calendar.fullCalendar("removeEvents", event.id); //イベント(予定)の削除 //} } }); var request = new XMLHttpRequest(); request.open('GET', 'http://157.7.138.66:3000/api/photo/list', true); request.responseType = 'json'; request.onload = function () { var data = this.response; console.log(data); //alert(data); calendar.addEventSource(data); calendar.refetchEvents(); calendar.render(); }; request.send(); calendar.render(); }); </script> <style> body { margin: 0; padding: 0; font-family: Arial, Helvetica Neue, Helvetica, sans-serif; font-size: 14px; } #calendar { max-width: 1100px; margin: 50px auto; } </style> </head> <body> <div id='calendar'></div> </body> </html>だいたいはソースにある通りです。
裏のロジックは後日 執筆します。それよりもソースが欲しい方は以下で連絡してもらえば
メールと一緒に解説します。サンプルサイト
http://157.7.138.66:3000/index2
サンプルサイトです。
ソースプログラムが欲しい方は
以下のメールアドレスからどうぞ連絡をください。qqmdはちsrきゅうk@docomonet.jp
平仮名は数値でお願いします(スパム除け)
*サンプルサイトは期間限定で公開しています。
*仮IDはusername
*仮PWはpasswordです
- 投稿日:2020-08-18T19:20:17+09:00
【JavaScript関数ドリル】 初級編 compact関数の実装
compact関数の課題内容
「課題内容」「解説動画」「解答例」を確認したい場合は、以下リンク先のページを参照。
https://js-drills.com/blog/compact/
compact関数に取り組む前の状態
- 数字以外を取り除くのかな?と思いました。
compact関数に取り組んだ後の状態
- compactメソッドは、false('',0,)を取り除くことができると理解できました。
compact関数の実装コード(答えを見る前)
- compactメソッドの働きを理解しつつ、実装できました。
compact関数の実装コード(答えを見た後)
実装完了しました?
function compact(array) { const compactArray = []; for(let i =0; i < array.length; i++) { if(array[i]){ compactArray.push(array[i]); } } return compactArray; } const compactedValues = compact([0, 345, false, 2, '', 6666, 60, false, '', 5000]); console.log(compactedValues);
- 投稿日:2020-08-18T18:59:29+09:00
【JavaScript】【TypeScript】一時変数を使わないテクニック
ちょっぴりトリッキーなコードの書き方をご紹介します。
// ログイン処理を通し、ログイン不可なら以降の処理は行わない
よくあるやつです。
ためしに書いてみます。// ログイン処理を通し、ログイン不可なら以降の処理は行わない let isLoggedIn = false isLoggedIn = await this.authentication(id, password) if (!isLoggedIn) return
isLoggedIn
は一時変数として let 句を使用しているのですが、ちょっと微妙ですよね? ということで最近はこういう書き方を思いついて書きました。// 即時関数で結果を受け取る
const isLoggedIn = await (async (): Promise<boolean> => { return await this.authentication(id, password) }) if (!isLoggedIn) returnということで、上記のコードは以下のような感じになります。
- 即時関数で実行するよ
- await (後続の処理は待ってね)
- async (非同期処理だよ)
- await this.authentication(後続の処理はまって、認証処理実行してね)
このようにすることで、一時変数を使うことなくコードが書けました。めでたし。めでたし。
// 説明変数すら使いたくないという上級者に向けて
isLoggedIn
は説明変数として使用していますが、これすら残したくない場合には以下のようにするとよいと思います。
ここまでワンライナーで書くと、正直何しているかわからなくてちょっとつらいですが...if (!await (async (): Promise<boolean> => await this.authentication(id, password))) return// 実は即時関数で書くともっとメリットがある
実はそれだけでなく、即時関数化のメリットはスコープの限定化が期待できます。たとえば即時関数を使わないような処理の記述の場合、使用できる変数がどんどん狭まっていく and / or
let
句を用いて一時変数を使いながら、コードを書く必要がある場合があります。ES2015 以降に登場した let によってスコープへの意識はそこまで負担がすくなくなりましたが、より負担を少なくする場合には上記のようなテクニックを使ってみると良いかもしれません。
- 投稿日:2020-08-18T18:52:07+09:00
【JavaScript関数ドリル】 初級編 last関数の実装
last関数の課題内容
「課題内容」「解説動画」「解答例」を確認したい場合は、以下リンク先のページを参照。
https://js-drills.com/blog/last/
last関数に取り組む前の状態
- 最後の数字を出力できると把握していました。
last関数に取り組んだ後の状態
- メソッドの中でも簡単に実装できました。
- lastメソッドは、最後の数字を出力します。
last関数の実装コード(答えを見る前)
- 自力で実装することができました。
- 比較的わかりやすいメソッドでした。
last関数の実装コード(答えを見た後)
実装成功!!
function last(array) { return array[array.length - 1]; } console.log (last([1,2,3,500,10,60000]));
- 投稿日:2020-08-18T18:44:27+09:00
【JavaScript関数ドリル】 初級編 lastindex関数の実装
lastindex関数の課題内容
「課題内容」「解説動画」「解答例」を確認したい場合は、以下リンク先のページを参照。
https://js-drills.com/blog/lastindex/
lastindex関数に取り組む前の状態
- lastinexと聞いてどういった働きをするのか分かりませんでした。
lastindex関数に取り組んだ後の状態
- 第2引数は後ろから指定した番号を探り出力
- 第3引数は指定の番号を検索して出力します。
lastindex関数の実装コード(答えを見る前)
- 自力で実装することができませんでした。
- fromIndexの中の働きが分かりませんでした。
lastindex関数の実装コード(答えを見た後)
function lastIndex(array, value, fromIndex = array.length - 1) { for(let i = fromIndex; 0 <=i; i--) { if(array[i] === value ){ return i; } } return -1; } console.log( lastIndex([1, 2, 1, 2], 2) ); console.log( lastIndex([1, 2, 1, 2], 2, 2) );
- 投稿日:2020-08-18T17:39:48+09:00
【JavaScript関数ドリル】 初級編 nth関数の実装
nth関数の課題内容
「課題内容」「解説動画」「解答例」を確認したい場合は、以下リンク先のページを参照。
https://js-drills.com/blog/nth/
nth関数に取り組む前の状態
- nthと聞いても、どういった働きをするのか想像できませんでした。
nth関数に取り組んだ後の状態
- 配列間にほしい数字を第2引数で指定することで出力できます
- 負の数字は右から。始まりは-1から始まります。
nth関数の実装コード(答えを見る前)
- 自力で実装はできませんでしたが、nthメソッドの働きを理解できました。
nth関数の実装コード(答えを見た後)
- 三項演算子で一行でまとめることもできます!
function nth(array, n=0 ){ if(n>=0) { return array[n]; } else { return array[array.length + n]; } // 三項演算子↓ // return n >= 0 ? array[n] : array[array.length + n]; } const array = ['a','b','c','d','e']; console.log(nth(array, 1)); console.log(nth(array,-2));
- 投稿日:2020-08-18T17:25:25+09:00
javascript を使ったFizzBuzz
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>fizzbuzz</title> </head> <body> <script> for (var i = 1; i < 101; i++) { if (i % 3 === 0 && i % 5 === 0) { document.write("FizzBuzz" + "<br/>"); } else if (i % 3 === 0) { document.write("Fizz" + "<br/>"); } else if (i % 5 === 0) { document.write("Buzz" + "<br/>"); } else { document.write(i + "<br/>"); } } </script> </body> </html>もし3と5で割り切れるかどうかの条件を最後に持ってきた場合、たとえば15なら先に5のみで割り切れると判定されてbuzzが表示されてしまう。(この問題に初めて取り掛かった頃はこれに気づかなかった)
あと、一つづつ改行するやり方も検索しても何故かなかなかヒットせず、困った記憶がある。
そんなことで困りたくないですね。
- 投稿日:2020-08-18T17:18:17+09:00
ポートフォリオ 「SHIZUOKA CAFE」をFLOCSSを使用したCSS設計での実装&レスポンシブ対応の実装
はじめに
フロントエンドエンジニアを目指してプログラミングを学習しています。
長田と申します。
プログラミング学習のアウトプットとして自作の「SHIZUOKA CAFE」を公開します。
この記事では「SHIZUOKA CAFE」の概要ついて説明します。URL
http://satoruosada.sakura.ne.jp/cafe_resposive_portfolio/
ソースコード↓
https://github.com/satoruosada/cafe_resposive_portfolio目的
自分のプログラミング学習のアウトプット
保守性、運用性を高めたるためのFLOCSSを使用した、CSS設計での学習。
レスポンシブデザインの学習。スペック
使用言語 / HTML5/ CSS3 / SCSS/ Javascript
開発環境 / MacOS Catalina 10.15.6, gulp,yarn
バージョン管理 / SourceTree(3.0.15)
主な機能
レスポンシブデザイン対応
javascriptによる動作(スクロールでのヘッダーの色の変化等)概要
将来、自分がカフェをオープンさせると仮定して、独自でマーケティングを行い、コーヒー好きな女性目線を集客出来るように色使いを考え、優しい印象の与えるサイトを作成してみました。
また、保守性と運用性を考えてCSS設計し、モバイルファーストに合わせたレスポンシブデザイン対応にしました。開発手順
1.AdobeXDデザインカンプ作成
2.モバイルファーストに合わせたレスポンシブ対応
3.コーディング
- 投稿日:2020-08-18T17:00:53+09:00
【Nuxt.js】Nuxt文法編:layout
? この記事はWP専用です
https://wp.me/pc9NHC-A4前置き
HeaderやFooter, sidebar(sidemenu)など
どのページでも固定で表示させたいコンポーネントってありますよね❓
そんな時に便利なlayoutを解説していきます??♀️公式サイトを元に順番に説明しています?
https://ja.nuxtjs.org/guides/directory-structure/layouts/layout
基本的な使い方
HeaderとFooterを固定で表示させてみます?
directorylayouts/ --| default.vue pages/ --| index.vue --| sample.vueコード
HeaderやFooterはテキストだけなので省きます?
また、CSSも省略します。ページコンポーネント(pages内のindex.vueなど)の
レンダリングはこちらの3行です?layouts/default.vue<template> <Nuxt /> </template>pagesが表示される位置より上にHeader,
下にFooterを表示させましょう?layouts/default.vue<template> <div class="layout"> <Header /> <nuxt /> <Footer /> </div> </div> </template> <script> import Header from '~/components/Header.vue' import Footer from '~/components/Footer.vue' export default { components: { Header, Footer, } } </script>使用するlayoutの指定は必要ありません。
layouts/default.vueがデフォルトで適応されます??index.vue<template> <div class="page"> <p>sample</p> </div> </template>index.vueとsample.vueを見てみてください?
どちらのページでもHeaderとFooterが固定されているはずです?ページ内コンポーネントのリンクは
router-linkまたはnuxt-linkを使用します??⬇️解説記事はこちら
https://wp.me/pc9NHC-f5index.vueにこちらを貼り付けてみると
Headerなどは固定されたまま
sample.vueに遷移することができます?
リンクカスタムレイアウト
index.vueでは
layouts/default.vue(Header, Footerを使用)にして
blog内のposts.vueでは
layouts/blog.vue(ブログ用ナビゲーションバー)を表示させてみます?? 続きはWPでご覧ください?
https://wp.me/pc9NHC-A4
- 投稿日:2020-08-18T16:39:36+09:00
Laravel(Blade)の配列をJavaScriptのオブジェクトに変換する
- 投稿日:2020-08-18T14:58:26+09:00
【JavaScript関数ドリル】 初級編 slice関数の実装
slice関数の課題内容
「課題内容」「解説動画」「解答例」を確認したい場合は、以下リンク先のページを参照。
https://js-drills.com/blog/slice/
slice関数 に取り組む前の状態
- 指定した間を切り取り出力するのかな?と思いました。
slice関数に取り組んだ後の状態
- いつもは0からfor文を始めますが、for文のループをstartからendで回していきました(最後の要素数手前にするため)
- sliceメソッドは、配列間でほしい数字を第2引数で指定することができると理解できました
slice関数の実装コード(答えを見る前)
- for文をうまく書くことができませんでした。
slice関数の実装コード(答えを見た後)
配列(numbers)から、第2引数の1と4を指定して、234を出力します。
function slice(array, start = 0, end = array.length) { const sliceArray = []; for(let i =start; i < end; i++) { sliceArray.push(array[i]); } return sliceArray; } const numbers = [1,2,3,4,5]; const slicedNumbers = slice(numbers, 1,4); console.log ( slicedNumbers ); console.log(numbers);
- 投稿日:2020-08-18T14:12:08+09:00
【JavaScript関数ドリル】 初級編 tail関数の実装
関数の課題内容
「課題内容」「解説動画」「解答例」を確認したい場合は、以下リンク先のページを参照。
https://js-drills.com/blog/tail/
tail関数に取り組む前の状態
- 先頭の数字をを出力するのかな?と言った感じで思っていました。
tail関数に取り組んだ後の状態
- tailメソッドの引数に先頭の数字が出力されると理解出来ました。
tail関数の実装コード(答えを見る前)
- コード数も少なく、比較的簡単に実装できました。
- 答えを見る前にtail関数の流れを把握できました。
tail関数の実装コード(答えを見た後)
function tail(array){ const tailArray = []; for(let i = 1; i < array.length; i++) { tailArray.push (array[i]); } return tailArray; } console.log(tail([1,2,3,2000,3000]) );
- 投稿日:2020-08-18T12:05:21+09:00
jQueryで複数のinputからそれぞれのvalueを取得する。
はじめに
タイトルの操作で悩んだので、自分で試したり調べたりしたことと、teratailなどで質問して分かったことをまとめます。
結論を簡潔にお伝えすると、複数のinputからそれぞれのvalueを一括に取り出すメソッドはありませんでした。代わりに、複数の要素に対して同じ処理をおこなう
.map()
とvalueを取り出す.value
を組み合わせて解決しました。やりたいこと
同じクラスをもつ複数のinputから、それぞれのvalueを取り出したい。
form.html<form action="" method=""> <input class="input" type="text" name="inputs[]"></input> <input class="input" type="text" name="inputs[]"></input> <input class="input" type="text" name="inputs[]"></input> <button type="button" class="btn">button</button> </form>それぞれのinputには"input1", "input2", "input3"を入力。
解決方法
script.js$(function(){ $('.btn').on('click', function() { const inputs = $('.input').each(function(index, element){ return element.value; // valueを取り出す }).get(); // 標準的な配列に変換 console.log(inputs); // ["input1", "input2", "input3"] }); });
.each()
を使うことで、複数のinputに対して同じ処理(valueの取得)をしています。また、.each()
はjQueryオブジェクトを返すので、.get()
を使って標準スタイルの配列に変換しています。間違えたコード
1つ目
.value
ではなく.val()
を使っていた。
.val()
はjQueryのオブジェクトに使用するメソッド、.value
はHTMLInputElementオブジェクトに使えるメソッドだそうです。$('.input')
はjQueryのオブジェクトで、element
はHTMLInputElement
になっているらしい、、ここらへんの理解が甘いので、勉強します。2つ目
.map()
ではなく.each()
を使っていた(.each()
の使い方を間違えていた)。script.js$(function(){ $('.btn').on('click', function() { const tests = $('.input').each(function(index, element){ return element.value; }); }); });
.each()
のreturn
はbool型にしないといけないらしいです。
一般的なfor文のcontinue
に当たるものがreturn True
、break
に当たるものがreturn False
という認識です。参考
JavaScriptで.valueを使っているのにvalueではなくHTMLタグが返ってくる(teratail)
https://teratail.com/questions/285400?whotofollow=JavaScriptで.valueを使っているのにvalueではなくHTMLタグが返ってくる(スタック・オーバーフロー)
https://ja.stackoverflow.com/questions/69636/javascript%e3%81%a7-value%e3%82%92%e4%bd%bf%e3%81%a3%e3%81%a6%e3%81%84%e3%82%8b%e3%81%ae%e3%81%abvalue%e3%81%a7%e3%81%af%e3%81%aa%e3%81%8fhtml%e3%82%bf%e3%82%b0%e3%81%8c%e8%bf%94%e3%81%a3%e3%81%a6%e3%81%8f%e3%82%8b/69637#69637JavaScript初心者が失敗しがち/間違えがちなこと8選(Workship MAGAZINE)
https://goworkship.com/magazine/javascript-beginner-mistakes/【jQuery入門】2種類のeach()による繰り返し処理まとめ!(Samurai Blog)
https://www.sejuku.net/blog/33609【jQuery入門】map()を活用した繰り返し処理の方法まとめ!(Samurai Blog)
https://www.sejuku.net/blog/66032
- 投稿日:2020-08-18T11:39:02+09:00
C#開発者がはまったJavaScriptのDate型から月日を取得する方法
- 投稿日:2020-08-18T11:27:24+09:00
javascript / jqueryを使用した九九表
まずは基本のfor文を使った九九の作り方(javascriptのみ使用バージョン)。
<!doctype html> <html lang="ja"> <head> <meta charset="utf-8"> <title>loop課題九九</title> </head> <body> <script> document.write("<table>"); for (var i = 1; i <= 9; i++){ document.write("<tr>"); for (var j = 1; j <= 9; j++){ document.write("<td>"+i+"×"+j+"="+(i*j)+"</td>"); } document.write("</tr>"); } document.write("</table>"); </script> </body> </html>for文を上から見ていくと、まずi=1で初期化される。
続いてi<=9の条件を満たしているか評価し、for1つ分の時と同じく順番通りに
本来出力される下にいく。(初めて取り掛かったときはi++まで行ってから下の処理に進むと勘違いしていた)
今回はそこでまずtrが作成される。そしてさらに下にまだ処理していない第二のforがある為、こちらの処理を行う。
同じようにj=1で初期化しj<=9の判定を行い、順番通り下の処理へ。この時td/tdを含んだ九九の式を出力。(i=1,j=1)
続いてjの値が2になり、j<=9の判定を行い再び順番通り出力へ。
この時i=1,j=2。これを第二のforがfalseになるまで続ける。
falseになった後(j=9)、ようやく第一のfor文の加減式にもどり、i=2になる。
i=2をi<=9に当て嵌め、trueのため本来出力される下へ行く。
また第二のforがある為、再びそのforがfalseになるまで加減はされない。
といった流れ。続いてjQueryを用い、隣り合うセルの色が異なる九九表を作成する。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>jQuery九九表</title> <!--<link rel="stylesheet" href="plactice_jquery.css">--> <style> .gray{ background-color :gray; } </style> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> /* 課題処理を追加 */ $(function() { for(var j=1; j<=9; j++){ $('#matrix').append('<tr></tr>'); for(var i=1; i<=9; i++){ $('tr:last-child').append('<td>' + i + '*' + j + '=' + i*j + '</td>'); } } $('td:even').addClass('gray'); }); </script> </head> <body> <table id="matrix" border="1"> <caption>九九表</caption> </table> </body> </html>jqueryはcssのように使うことができる。このなかではlastchildが肝。
そして当然ながらtd:evenのところをtd:oddにすれば色が変わるところを変えることができる。
- 投稿日:2020-08-18T11:27:24+09:00
javascript / jqueryを使用した九九表①
jQueryを用い、隣り合うセルの色が異なる九九表を作成する。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>jQuery九九表</title> <!--<link rel="stylesheet" href="plactice_jquery.css">--> <style> .gray{ background-color :gray; } </style> <script src="https://code.jquery.com/jquery-3.5.1.min.js"></script> <script> /* 課題処理を追加 */ $(function() { for(var j=1; j<=9; j++){ $('#matrix').append('<tr></tr>'); for(var i=1; i<=9; i++){ $('tr:last-child').append('<td>' + i + '*' + j + '=' + i*j + '</td>'); } } $('td:even').addClass('gray'); }); </script> </head> <body> <table id="matrix" border="1"> <caption>九九表</caption> </table> </body> </html>jqueryはcssのように使うことができる。このなかではlastchildが肝。
そして当然ながらtd:evenのところをtd:oddにすれば色が変わるところを変えることができる。
- 投稿日:2020-08-18T10:36:35+09:00
【JavaScript関数ドリル】 初級編 take関数の実装
take関数の課題内容
「課題内容」「解説動画」「解答例」を確認したい場合は、以下リンク先のページを参照。
https://js-drills.com/blog/take/
take関数 に取り組む前の状態
- takeright関数を理解していたので、抵抗がありませんでした。
take関数に取り組んだ後の状態
+ takeright関数の逆バージョンでと理解しました。(takereftみたいな)
+ 第2引数の数字分だけ、左から出力していくことができます。take関数の実装コード(答えを見る前)
- takeright関数を学んでいたので理解しやすく、コードを把握して打つことができました。
take関数の実装コード(答えを見た後)
function take(array, n = 1){ if(n === 0) { return []; } if(n > array.length) { return [...array]; } const takeValues = []; for(let i =0; i < n; i++) { takeValues.push(array[i]) } return takeValues; } console.log( take([1,2,3])); console.log( take([1,2,3],2)); console.log( take([1,2,3],3)); console.log( take([1,2,3],5));
- 投稿日:2020-08-18T10:10:51+09:00
【JavaScript関数ドリル】 初級編 takeRight関数の実装
takeRight関数の課題内容
「課題内容」「解説動画」「解答例」を確認したい場合は、以下リンク先のページを参照。
https://js-drills.com/blog/takeright/
takeRight関数 に取り組む前の状態
- takerightの関数名の通り、右側の値を出力するのかなと思いました。
takeRight関数に取り組んだ後の状態
- takeRight関数の動きを理解することができました。
- 第2引数の数分だけ右から出力することができました。
- ただ、右からの値を出力してしまうと、結果が右から表示することになるので unshiftを使って結果の表示を見やすく小さい数字順に直しました。
takeRight関数の実装コード(答えを見る前)
- takeRight関数の使い方も理解することができたこともあり、コードを速く打つことが来ました。
takeRight関数の実装コード(答えを見た後)
function takeRight(array, n=1) { if(n === 0) { return []; } if(n > array.length) { return [...array]; } const takeArray = []; for(let i=0; i <n; i++){ const indexFromRight = array.length - (1 + i); takeArray.unshift( array[indexFromRight]); } return takeArray; } console.log( takeRight([1,2,3],2)); console.log( takeRight([1,2,3],1));
- 投稿日:2020-08-18T10:02:53+09:00
ランダムな文字列を発生させるサイトをつくる
index.html<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>ランダムな文字列発生させるサイト</title> <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/css/bootstrap.min.css" integrity="sha384-r4NyP46KrjDleawBgD5tp8Y7UzmLA05oM1iAEQ17CSuDqnUK2+k9luXQOfXJCJ4I" crossorigin="anonymous" /> <style> body { margin: 0; background: #f7f7f7; } #wrap { overflow: hidden; } main { max-width: 750px; margin: 0 auto; } </style> </head> <body> <div id="wrap"> <br /> <main> <div class="input-group"> <input type="number" id="num" class="form-control" value="8" /> <div class="input-group-append"> <button id="sumbit" class="btn btn-primary" type="button"> 作成 </button> </div> </div> <br /> <div class="input-group"> <input id="text" class="form-control" readonly="" /> <div class="input-group-append"> <button id="copy" class="btn btn-success" type="button"> コピー </button> </div> </div> </main> </div> <script src="https://stackpath.bootstrapcdn.com/bootstrap/5.0.0-alpha1/js/bootstrap.min.js" integrity="sha384-oesi62hOLfzrys4LxRF63OJCXdXDipiYWBnvTl9Y9/TRlw5xlKIEHpNyvvDShgf/" crossorigin="anonymous" ></script> <script> document.getElementById('sumbit').onclick = () => { const _s = 'abcdefghijklmnopqrstuvwxyz0123456789'; const _ret = [...Array(Number(document.getElementById('num').value))] .map(() => _s[Math.floor(Math.random() * _s.length)]) .join(''); document.getElementById('text').value = _ret; }; document.getElementById('copy').onclick = () => { document.getElementById('text').select(); document.execCommand('copy'); }; </script> </body> </html>