20220225のJavaScriptに関する記事は22件です。

二次元連想配列 foreachで取り出す(Javascript)

Javascriptの二次元連想配列の要素をforeachで1つずつ取得する流れについて自身の理解を整理。 let NBA =[ {region:'oklahoma', team:'Thunder', player:'KD'},//キー値=[0] {region:'sanantonio', team:'Spurs', player:'parker'},//キー値=[1] {region:'washington', team:'Widzards', player:'hachimura'}//キー値=[2] ] for (let count = 0; count < NBA.length; count++) { Object.keys(NBA[count]).forEach(function(key) { console.log(key + '=' + NBA[count][key]); }); } 変数countを二次元連想配列NBAのindex番号(キー値)として使用する。ループする条件としてcountが二次元連想配列NBAのlength(=3)未満とすることでキー値が[0]~[2]までの各配列を扱える。 Object.keys(NBA[count])によって[0]~[2]までの各配列毎にキー値を取り出してforEachで1つずつ受け取り処理をする。 例.count=0の時 Obkect.keys(NBA[0])となる事から、 配列[0]{region:'oklahoma', team:'Thunder', player:'KD'}のキー値であるregion, team, playerが取り出される。 配列[0]においてregionが取り出された時にはコールバック関数の仮引数にregionが渡される為、console.logで以下のように表示される。 region=oklahoma 上記のようにoklahomaとなるのはcount=0によってNBAから連想配列[0]が取得されており、且つその配列のキー値がregionである要素の値に該当する為。 残りのNBA[1], NBA[2]も同じように一つずつ取得された連想配列からそれぞれのキー値に対応する要素の値が出力される。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript]日付、時間の書く順番をその国、言語圏の記法にする

toLocaleDateString() 国や言語圏によって年、月、日を書く順番は違います。 toLocaleDateString.js //現在時刻の取得 let date = new Date(); //アメリカ式表記 console.log(date.toLocaleDateString("en-US")); // 2/25/2022と月・日・年の順に表示される //イギリス式表記 console.log(date.toLocaleDateString("en-GB")); // 25/02/2022と日・月・年の順に表示される //アラビア語圏 console.log(date.toLocaleDateString("ar")); // 25‏/2‏/2022と日・年・月の順に表示される(日と年の間に/がないことと一番最後に/があることに注意) console.log(date.toLocaleDateString("ar-EG")); // ٢٥‏/٢‏/٢٠٢٢とアラビア数字になる //対応していないかもしれない言語を要求した場合 //下は日本語での表記方法を優先し、もしそれが出来なかった場合アメリカ式表記で表示するという意味 console.log(date.toLocaleDateString(["ja", "en-US"])); // 日本語には対応しているので2022/2/25と表示される toLocaleTimeString() toLocaleTimeString.js //現在時刻の取得 let date = new Date(); //アメリカ式表記 console.log(date.toLocaleTimeString("en-US")); // 10:20:40 PMと12時制で表示 //イギリス式表記 console.log(date.toLocaleTimeString("en-GB")); // 22:20:40と24時制で表示 //アラビア語圏 console.log(date.toLocaleTimeString("ar")); // 10:20:40 مと12時制でAM/PMがアラビア語で表示 console.log(date.toLocaleTimeString("ar-EG")); // ١٠:٢٠:٤٠ مとアラビア数字になる //対応していないかもしれない言語を要求した場合 //下は日本語での表記方法を優先し、もしそれが出来なかった場合アメリカ式表記で表示するという意味 date.toLocaleTimeString(["ja","en-US"]) // 日本語には対応しているので22:20:40と表示される 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript]日付、時間の書く順番ををその国、言語圏の記法にする

toLocaleDateString() 国や言語圏によって年、月、日を書く順番は違います。 toLocaleDateString.js //現在時刻の取得 let date = new Date(); //アメリカ式表記 console.log(date.toLocaleDateString("en-US")); // 2/25/2022と月・日・年の順に表示される //イギリス式表記 console.log(date.toLocaleDateString("en-GB")); // 25/02/2022と日・月・年の順に表示される //アラビア語圏 console.log(date.toLocaleDateString("ar")); // 25‏/2‏/2022と日・年・月の順に表示される(日と年の間に/がないことと一番最後に/があることに注意) console.log(date.toLocaleDateString("ar-EG")); // ٢٥‏/٢‏/٢٠٢٢とアラビア数字になる //対応していないかもしれない言語を要求した場合 //下は日本語での表記方法を優先し、もしそれが出来なかった場合アメリカ式表記で表示するという意味 console.log(date.toLocaleDateString(["ja", "en-US"])); // 日本語には対応しているので2022/2/25と表示される toLocaleTimeString() toLocaleTimeString.js //現在時刻の取得 let date = new Date(); //アメリカ式表記 console.log(date.toLocaleTimeString("en-US")); // 10:20:40 PMと12時制で表示 //イギリス式表記 console.log(date.toLocaleTimeString("en-GB")); // 22:20:40と24時制で表示 //アラビア語圏 console.log(date.toLocaleTimeString("ar")); // 10:20:40 مと12時制でAM/PMがアラビア語で表示 console.log(date.toLocaleTimeString("ar-EG")); // ١٠:٢٠:٤٠ مとアラビア数字になる //対応していないかもしれない言語を要求した場合 //下は日本語での表記方法を優先し、もしそれが出来なかった場合アメリカ式表記で表示するという意味 date.toLocaleTimeString(["ja","en-US"]) // 日本語には対応しているので22:20:40と表示される 参考資料
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

[JavaScript]数字をカンマ区切りにすることができる地味に便利な機能

toLocaleString() toLocalString()は数字をカンマ区切りにし、文字列に変換します。 toLocaleString.js let a = 10000; console.log(a.toLocaleString()); //'10,000'とカンマ区切りで表示される let b = 1000.12; console.log(b.toLocaleString()); //'1,000.12'と表示
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

ES6_スプレッド構文

1 スプレッド構文 『...』で配列を展開することができます。 const arr = [1,2]; console.log(arr); // 配列を展開 console.log(...arr); スプレット構文で合計処理をする. ...は順番処理として覚えれば問題ありません。 // sumFun関数を作る const sumFun = (num1, num2) => console.log(num1 + num2); // sumFun(arr[0],arr[1]); // スプレット構文で書く sumFun(...arr); 配列をまとめることもできます。 const arr1 = [1,2,3,4,5]; const [num1, num2, ...arr2] = arr1; console.log(num1); console.log(num2); console.log(...arr2); スプレッド構文で配列をコピー、結合することもできます。 const arr4 = [10,20,30]; // ...でarr4をコピー const arr5 = [...arr4]; console.log(arr5); コピーだけならconst arr5 = arr4;で書くほうが楽と思う人がいると思いますが、 ここで気をつけないと、JSの参照型の罠にはまる可能性があります。 下記の例を確認ください。 参照渡しとはなどのキーワードで検索しても良いと思います。 const arr4 = [10,20,30]; const arr5 = arr4; arr5[0] = 30; console.log(arr4); // arr5の値だけを変更するつもりですが、arr4も変更されました。 しかし、スプレッド構文でコピーすると元の値を影響しないです。配列をコピーしたい時は、スプレッド構文を利用してください。 スプレッド構文で配列を結合することもできます。 const arr4 = [10,20,30]; const arr6 = [40,50]; // ...でarr4とarr6を結合 const arr7 = [...arr4,...arr6]; console.log(arr7); // 結果は10,20,30,40,50
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

フロントエンド環境構築(Typescript)

fe環境構築 以前作成した下記記事に加えて、開発で導入したものを記載します。 dependencies コマンド yarn add dotenv:見せれない情報を.env ファイルで管理することで公開させない yarn add winston:複数のトランスポート(出力先)をサポートするロギングライブラリ devDependencies コマンド yarn add -D @types/node:ts を使う時、node_modules の型定義ファイル yarn add -D cspell:スペルチェック → cspell.json 手書き、cspell.txt 作成 yarn add -D eslint:単純な構文エラーやプロジェクト固有のコーディング規約を定義することができる → 自由にルールを設定できる、rules で上書きすることでルールを緩くできる(.eslintrc.js) yarn add -D eslint-config-prettier:Prettierと競合する可能性のあるルールをすべてオフにする yarn add -D fixpack:package.json内を並び替え yarn add -D prettier:ソースコードを整形してくれるツール(コードフォーマッター)               → eslint では整形できないコードを整形できる yarn add -D ts-node:ts を毎回 tsc コマンドを叩いて js にコンパイルことをしなくてもよくするやつ cspell.json { "version": "0.2", "language": "en", "dictionaries": ["en", "typescript"], "dictionaryDefinitions": [ { "name": "en", "path": "./cspell.txt" } ], "ignorePaths": ["node_modules/**"] } .eslintrc.js const OFF = 0; // eslint-disable-line no-unused-vars const WARN = 1; // eslint-disable-line no-unused-vars const ERROR = 2; // eslint-disable-line no-unused-vars module.exports = { root: true, extends: [ "eslint:recommended", "plugin:@typescript-eslint/recommended", "prettier", ], env: { node: true, es2021: true, }, rules: { camelcase: OFF, "no-console": ERROR, "import/prefer-default-export": OFF, "no-underscore-dangle": OFF, "@typescript-eslint/camelcase": OFF, }, settings: { "import/resolver": { node: { extensions: [".ts", ".js"], }, }, }, }; .eslintignore .eslintrc.js /node_modules .fixpackrc { "sortToTop": [ "name", "version", "author", "private", "main", "scripts", "dependencies", "devDependencies", "browser" ] } prettier.config.js module.exports = { printWidth: 120, semi: false, trailingComma: "all", }; .gitignore gitで管理するためignore入れる .DS_Store .env node_modules dist ロガーのディレクトリ名 > index.ts, type.ts index.ts import { createLogger, format, transports } from "winston" import { CreateCustomLoggerData, CustomInfoLogger, CustomErrorLogger, InitLoggerReturn } from "./type" const displayFormat = format.printf(({ level, message }) => { return `${level}: ${message}` }) const color = format.colorize({ all: true }) /** * @typedef InitLogger * @description ロガーのインスタンス生成 * @param filePath ログ実行時ファイルパス * @returns InitLoggerReturn */ type InitLogger = (filePath: string) => InitLoggerReturn export const initLogger: InitLogger = (filePath) => { const createCustomLoggerData: CreateCustomLoggerData = (logLevel, message, data) => { const logger = createLogger({ level: logLevel, format: format.combine(color, displayFormat), transports: [new transports.Console()], }) return { logger, msg: JSON.stringify({ message, data, filePath, }), } } const infoLogger: CustomInfoLogger = (message, data) => { const { logger, msg } = createCustomLoggerData("info", message, data) logger.info(msg) } const errorLogger: CustomErrorLogger = (message, error) => { const stringError = JSON.stringify(error, Object.getOwnPropertyNames(error)) const { logger, msg } = createCustomLoggerData("error", message, { error: stringError }) logger.error(msg) } return { infoLogger, errorLogger } } type.ts import { Logger } from "winston" /** * @typedef CustomLogger * @description カスタムロガーの型定義 * @param message ログメッセージ * @param data ログ出力データ */ export type CustomInfoLogger = (message: string, data?: Record<string, unknown>) => void export type CustomErrorLogger = (message: string, error: unknown) => void /** * @typedef InitLoggerReturn * @description InitLoggerの戻り値の型 */ export type InitLoggerReturn = { infoLogger: CustomInfoLogger errorLogger: CustomErrorLogger } /** * @typedef CreateCustomLoggerDataReturn * @description CreateCustomLoggerDataの戻り値の型 */ export type CreateCustomLoggerDataReturn = { logger: Logger; msg: string } /** * winston準拠のログレベル */ type LogLevel = "error" | "warn" | "info" | "http" | "verbose" | "debug" | "silly" /** * @typedef CreateCustomLoggerData * @description カスタムロガーと文字列化された出力メッセージ * @param logLevel ログレベル * @param message メインメッセージ * @param data オプショナルデータ */ export type CreateCustomLoggerData = ( logLevel: LogLevel, message: string, data?: Record<string, unknown>, ) => CreateCustomLoggerDataReturn 使う時 下記のようにimportして使うことができる。(console使うとeslintエラー出るのでwinstonを使用) import { initLogger } from "./shared/initLogger" const { infoLogger, errorLogger } = initLogger("src/index.ts") infoLogger("infoLoggerです") errorLogger("errorLoggerです", { error: "失敗" }) 最後に 前回のReactで「Hello World」を表示する記事では長くなってしまうため、今回はローカル開発で導入したもの全てを記載しました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【個人開発】教員向け授業計画作成サービスを作ってみた。

はじめに こんにちは、まーです。 【Twitter】 https://twitter.com/KPz234iNYoOQZF5 【今回作成したサービス】 https://teachingplan.msy-a.com/ 今回は、以前Laravel+Vue.jsで作成した授業計画(指導案)作成サービスをご紹介したいと思います。 要件概要 ○導入 日々多忙な教員にとって毎回の授業内容(指導案)をスピーディーに作成することはとても重要になります。 特に、教育実習生や教員になったばかりの先生は、授業の経験値がなく、毎時間授業の計画を考えることは大きな負担です。自分自身の教育実習に行った経験、教員の友人からのヒアリングから間違いないと思います。 授業計画を効率的に作成することは、授業の質に関わり、それは授業を受ける子どもたちの成長にも関わってきます。 ○そもそも?(問題点) 世の中にタスク管理アプリはたくさんありますが、 「指導案を作成する」ことに特化したサービスはありません。現場の先生方は、効率的に指導案を作成することができていません。 ○なぜなら?(問題点の裏付け) 指導案を作るために、紙やWordの雛形に毎回書き込んでいるのが現場の現状です。 (ひと昔前の教育実習生は、手書きで書いた指導案がボツになり、指導教官に目の前で破り捨てられ、書き直しさせられたなんて話も…) ○だから?(課題) そのため、WEBサービスを利用し、 「効率的に指導案を作成するべき」だと考えます。 ○そこで!(改善策) 効率的に指導案が作成できるサービスがあったらどうだろうか?と考えサービスを作成しました。 サービス概要 1.授業を登録 ユーザー登録後にマイページから授業を登録します。 単元名、日付、時限、本時の目標を登録します。 2.授業の詳細を登録 授業詳細画面で、授業計画を作成していきます。 デフォルトで、「導入」、「展開」、「まとめ」の構成になっています。 追加ボタンを押してタスクを追加していきます。 3.タスクの注意事項を確認 タスクにはそれぞれ、学習活動、時間、留意点が登録できます。 今回は、サクッと授業計画を作ることが目的なので最低限のものだけにしました。 使用技術 PHP 7.4.12 Laravel 5.8.38 JavaScript Vue.js 2.5.17 CSSフレームワーク Bootstrap フロントライブラリ jQuery axios lodash chart.js vue-chartjs vuedraggable テスト PHPUnit mocha テーブル設計 工夫した点 非同期処理でユーザビリティを上げる Vue.jsでaxiosを使用し、タスクの登録・削除を行なっています。 非同期で処理を行うことでサクサク動き、ユーザーにストレスを与えないようにしました。 ドラッグ&ドロップで直感的な操作 タスクをドラッグ&ドロップで移動できるようにしました。 vuedraggableを使って、ドラッグ&ドロップされるたびに、タスクの並び順を非同期通信で書き換えています。 授業の計画を変更したい場合にも、直感的に変更できます。 授業の時間配分をグラフで可視化 「導入」、「展開」、「まとめ」の時間配分を円グラフで可視化しています。 vue-chartjsを使用し、タスクを登録・削除・移動したときにリアルタイムでグラフが描画されます。 おわりに 最後までお読みいただきありがとうございました! ITには社会の問題を解決する力があると思っています。 これからも学んで色んな問題を解決できたらと思います。 それでは!
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Webブラウザの日本語の改行問題 -改行を実現するHTML/CSS-(1)

日本語のWebサイト(ランディングページ等)をつくっていると、読みにくい所で改行されるパターンがありますよね?実際この記事もブラウザの横幅によって単語の途中なのに改行されています。普通に文章を読んでいる時には良いのですが、これがランディングページのヘッドラインで起きるとものすごいカッコ悪いです。 たとえばPhotoshopのページで「写真のレタッチ、合成、カラー変更」というヘッドラインが以下のようになってたらいかがでしょうか?この場合はできれば、「合成、」の後で改行されている方が自然で読みやすくなっています。(ちなみにこれは、Chrome Developer Toolsで編集して、わざと見にくくしたので、実際にはキレイに改行処理しています)。 よーしそれじゃあ<br>入れれば良いんだなと真っ先に思いついた方がいるでしょう。しかし今日はさまざまなデバイスが皆さんのWebサイトに訪れています。デスクトップではキレイに見えた<br>の改行も、モバイルのようなブラウザの横幅が短いものでは<br>の改行は余計な結果を生む事があります。 そもそも何故このような問題が起きるのか?ブラウザの仕様として欧文がスペースを改行ポイント(Word break)とするのに対して、日本語は文字単位(行頭行末禁則文字を省く)で改行を許す(Charactor break)ので、このような事が起きます。 この記事ではWebブラウザでの日本語の改行問題の解決策についてCSSのプロパティやHTMLタグの話を交えて話していきます。(※この記事はモダンブラウザを使う前提で話を進めています。Internet Explorer 11等の話については言及しませんのであらかじめご了承ください) また、予めこの記事で利用するソースコードは、codepenに置いておきます。必要に応じて参照してください。 解決策1. white-space: nowrap + <wbr> はじめに言います。この手法はお勧めしません。先に使える解決策に行きたい方はこのセクションを飛ばしてください。時間がある方はどうかお付き合いください。 white-space: nowrap;1は連続する半角スペース、タブ、改行を一つの半角スペースを詰めて表示し、行の折り返しは行いません。これによって自動での行の折り返しを防ぎ<wbr>2によってブラウザ内で改行してよい位置を教えて上げる事で、改行してほしい場所で改行ができます。 <div> <h3>1. white-space: nowrap; + &lt;wbr&gt;</h3> <p class="nowrap box"> グレートブリテン<wbr>および<wbr>北アイルランド連合王国という<wbr>言葉は<wbr>本当に<wbr>長い言葉<wbr>ですね </p> </div> .box { padding: 10px; border: 1px solid; font-size: 40px; } .nowrap { white-space: nowrap; } white-space: nowrap + <wbr>の問題点 おお、動いているじゃないかと、何が駄目なんでしょうか?理由は、 Firefoxでは一切改行されない 残念ながらFirefoxではwhite-space: nowrap;が指定された中で<wbr>を使っても無視されてしまいます。 こちらで過去に議論されていたみたいですが、今の所解決する手立てがありません。 チャンクが親の要素の横幅より大きい場合、文字が溢れる チャンク(改行できる単位)の横幅が親の要素をからはみ出ている場合は、<wbr>を入れないと文字が溢れてしまいます。この場合は北アイルランド連合王国というがチャンクとなっています。しかし、white-space: nowrap;により改行できなくなっているため、文字が溢れた状態で表示される事になります。 解決策2. word-break: keep-all; + overflow-wrap: break-word; + <wbr> 良い解決策です。最もキレイに日本語改行処理を行いたい場合はこれを使用しましょう。 word-break: keep-all3はCJK(中国語、日本語、韓国語)の改行を禁止します。なお、CJK以外のテキストについては規定の改行規則に従います。これで日本語の改行ができなくなるので、<wbr>によってブラウザ内で改行してよい位置を教えて上げる事で、改行してほしい箇所で改行ができます。 これで解決じゃないかoverflow-wrap: break-word;4は何のために指定するんだ?と疑問に思った人もいるでしょう。overflow-wrap: break-word;は表示範囲を超える長いテキストがある場合、テキストは溢れる事より、改行することを優先します。 word-break: keep-all; + <wbr>の場合 overflow-wrap: break-word;を指定していない状況では、チャンクの横幅が親の要素をからはみ出ている場合は、<wbr>を入れないと文字が溢れてしまいます。この場合は北アイルランド連合王国というがひと塊になっています。 word-break: keep-all; + overflow-wrap: break-word; + <wbr>の場合 対して、overflow-wrap: break-word;を指定している状況では。チャンクの横幅が親の要素をからはみ出ている場合でも次の行に改行されます。すばらしいですね。 <div> <h3>2. word-break: keep-all; + overflow-wrap: break-word; + &lt;wbr&gt;</h3> <p class="keep-all-break-word box"> グレートブリテン<wbr>および<wbr>北アイルランド連合王国という<wbr>言葉は<wbr>本当に<wbr>長い言葉<wbr>ですね </p> </div> .box { padding: 10px; border: 1px solid; font-size: 40px; } .keep-all-break-word { word-break: keep-all; overflow-wrap: break-word; } word-break: keep-all; + overflow-wrap: break-word; + <wbr>の問題点 display: flex;が親の要素に指定されているとoverflow-wrap: break-word;が聞かず、レイアウトが崩れる overflow-wrap: break-word; の説明を注意深く見てみると、以下のようにコンテンツの最小固有寸法(min-content intrinsic size)5を計算する際には考慮されないと書かれています。 行内にその他の分割可能な位置がない場合、通常は分割可能でない単語が任意の場所で分割されますが、コンテンツの最小固有寸法を計算する時に、単語分割によって導入された折り返し可能位置が考慮されません。 ところでflexboxのitemは何もwidthを指定されていない場合は、最小固有寸法によって決定される、つまり北アイルランド連合王国というのチャンクの横幅によってwidthが決定されるため、ブラウザの横幅に収まらない形で以下のように表示されてしまいます。 <div class="flex"> <div> <h3>5. word-break: keep-all; + overflow-wrap: break-word; + &lt;wbr&gt;</h3> <p class="keep-all-break-word box"> グレートブリテン<wbr>および<wbr>北アイルランド連合王国という<wbr>言葉は<wbr>本当に<wbr>長い言葉<wbr>ですね </p> </div> </div> .box { padding: 10px; border: 1px solid; font-size: 40px; } .flex { display: flex; } .keep-all-break-word { word-break: keep-all; overflow-wrap: break-word; } この問題を回避するには、flex-itemに横幅を指定する方法 or overflow-wrap: anywhereを指定する方法がありますが、後者は残念ながら主にSafariでサポート外なので、使用はできません。かなC。また、各チャンクをできるだけ細かく分割し、親の要素の横幅を超えないように調整する事で未然に防ぐ方法もあります。参考までに、display: gridでも同様の問題が起きるので使用する際には注意してください。 解決策3. <span> + display: inline-block; こちらもよく使われている手法で、display: inline-blockのスタイルが適用した<span>を使って改行したくない文字列をくくることで、改行を制御します。非常にシンプルな手法ですが、<span>で囲まれる事によって、HTMLコードが若干複雑になる点は気になります。 <div> <h3>3. display: inline-block;</h3> <p class="box"> <span class="nobr">グレートブリテン</span><span class="nobr">および</span><span class="nobr">北アイルランド連合王国という</span><span class="nobr">言葉は</span><span class="nobr">本当に</span><span class="nobr">長い言葉</span><span class="nobr">ですね</span> </p> </div> .box { padding: 10px; border: 1px solid; font-size: 40px; } .nobr { display: inline-block; } <span> + display: inline-block;の問題点 チャンクが親の要素の横幅より大きい場合、次の文字が回り込まない チャンクの横幅が親の要素をからはみ出ている場合は、文字があふれることはありませんが、解決策2の時のように、次の文字が回り込む事なく、不自然な改行になってしまいます。下の例では、北アイルランド連合王国というが<span>で囲まれている事で、次の言葉はが回り込む事ができません。レイアウトが崩れるわけではありませんが、変な部分で改行されて読みづらくなってしまうという問題があります。これを回避するには各チャンクをできるだけ細かく分割し、親の要素の横幅を超えないように調整しなければなりません。 結局何を使えば良いの? レイアウトが崩れる時に適切な対処を毎回行える場合は、解決策2を使ってください。これが最もきれいに改行できます レイアウトが崩れる事を極力避けたい場合、解決策3を使ってください 自動で日本語改行処理を行うには? ここまで読んだ方は毎回<wbr>や<span>を手動で入れていくのかーと感じているでしょう。文章がHTMLにハードコードされているならともかく、CMSで文章が管理されている場合、リッチテキストエディタとの相性も考えると、これらのタグを手動で入れていくのはかなり骨の折れる作業になります。 BudouXはまさにこの問題を解決するツールになります。詳細な内容は次回の記事に回します。実際の使い方だけでなく、アドビで利用する際に直面した問題や、その解決策等についても説明していきます。 https://developer.mozilla.org/ja/docs/Web/CSS/white-space ↩ https://developer.mozilla.org/ja/docs/Web/HTML/Element/wbr ↩ https://developer.mozilla.org/ja/docs/Web/CSS/word-break ↩ https://developer.mozilla.org/ja/docs/Web/CSS/overflow-wrap ↩ https://ishadeed.com/article/intrinsic-sizing-in-css/ ↩
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】カスタム例外の書き方

JavaScriptにおけるカスタム例外の書き方をメモしておく。 カスタム例外の書き方 カスタム例外を作成することで、例外処理でどのようなエラーが発生したか細かく判別できるようになる。カスタム例外は、組み込みのErrorクラスを継承して、その中にコンストラクタを定義する。サンプルが以下のようになる。 カスタム例外クラス class MyError extends Error { constructor(message) { super(message); this.name = "MyError"; } } ここで、this.name = "MyError";を記述してnameプロパティを正しい名前に上書きしている。これを記述しないとerror.nameはErrorのままになってします。ただし、instanceofでのエラー判別は正常に動作する。これを使って例外処理を書いてみると以下のようになる。 例外処理 try { throw new MyError("カスタム例外が発生しました。"); } catch (error) { if (error instanceof MyError) { console.log(error); // MyError: カスタム例外が発生しました。 console.log(error.message); // カスタム例外が発生しました。 console.log(error.name); // MyError } else { console.log(error); } }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

連想配列(オブジェクト)のキーと値を入れ替える。【javascript】

解決に時間がかかったためメモ。 連想配列のキーを値に、値をキーにしたかった const obj = { a : 'aaa', b : 'bbb', c : 'ccc' } これを const obj = { aaa : 'a', bbb : 'b', ccc : 'c' } こうしたい Object.entries, Object.fromEntries を使う const array = Object.fromEntries( Object.entries(tmp).map(function (value) { return [value[1], value[0]]; }) ); console.log(array); array => { aaa : 'a', bbb : 'b', ccc : 'c' } できた。 Object.fromEntries が ES2019 から追加されたとのこと。 参考 ・https://qiita.com/c-shiraga/items/33812799e4dc17d89b44
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Tailwind CSSでスイッチ(トグル)ボタン作ってみた

最近はTailwind CSSを使用していて、とても快適だなと感じておりましてその流れで今回はよく使用されるであろうON・OFF切り替えできるスイッチボタンを生のCSSを使わずにTaliwind CSSのみで作成してみました! やること CSSを使わずに、Tailwindで用意されている「peer」という兄弟の状態に基づいて要素のスタイルを設定できるものを使用します。なお今回はVue.jsファイルを用いて進めたいと思います。 Tailwind CSS - peer- {modifier}について 準備 Tailwindの環境をご準備下さい。Tailwindのインストール※Tailwindのバージョンは3.0以降を使用。 適当なVueファイルを準備。※Vue.jsのバージョンは3.2を使用。 Index.vue <template> <label> <input type="checkbox" id="checkbox"/> </label> </template> チェックボックスのみが表示されている状態 実際にやってみる 早速スイッチボタンをTailwindを用いて作成していきます。 ON・OFFを判別するための変数を作成 スイッチボタンを押したときに、ON・OFFのフラグを変数で準備します。Vue3では、変数をリアクティブな状態にするためにrefを使用します。 Index.vue <template> <label> + <input class="m-5" v-model="isCheck" type="checkbox"/> + {{ isCheck }} </label> </template> + <script setup lang="ts"> + import { ref } from "vue"; + + const isCheck = ref(false); + </script> ON・OFFで変数の値(true・false)が切り替わっていることを確認。 Tailwindでスイッチボタンのデザインを作成 Tailwindの「peer」を使って、兄弟要素の状態がchecked(チェックされたとき)にデザインが切り替わるようにクラスを指定します。 「sr-only」ではinputを視覚的に非表示にするクラスがTailwindで用意されております。 「after」では、疑似要素を用いて、ボタンの切り替えに使用する球体を作成しています。 「peer」をinput(兄要素)に設定してspan(弟要素)に「peer-checked」を指定することで、チェック(ON)されたときに疑似要素のafterに対して色を変えたり、丸い球体の位置をずらしたりしています。 Index.vue <template> <label> <input v-model="isCheck" type="checkbox" + class="peer sr-only" /> + <span + class="block w-[2em] cursor-pointer bg-gray-500 rounded-full + p-[1px] after:block after:h-[1em] after:w-[1em] after:rounded-full + after:bg-white after:transition peer-checked:bg-blue-500 + peer-checked:after:translate-x-[calc(100%-2px)]" + > + </span> {{ isCheck }} </label> </template> <script setup lang="ts"> import { ref } from "vue"; const isCheck = ref(false); </script> スイッチボタン(トグル)が完成
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Symbolのネームスペースを完全に理解する。

今回はSymbolブロックチェーンのネームスペースについて解説します。 公式ドキュメントはこちらです。 まずはSymbolで定義されるネームスペースの特徴について、初心者と上級者にわけてざっくりと説明します。 初心者の方 アドレスやモザイクトークンに分かりやすい名前を付けることができ、送金ミスを減らすことができます。 ネームスペースはインターネットのドメインと同様に期間でレンタルします。 XYMにはsymbol.xymという分かりやすいネームスペースがあらかじめ定義されています。 上級者の方 ウォレットはネームスペースを名前解決することなくそのままトランザクションに署名できます。 トランザクションが承認されたときのレシートを参照することで、そのときのネームスペース所有者を特定できます。 改ざん不可能なDNSが実現可能(検証が可能)です。 基本操作 ネームスペースの作成、更新、変換まわりを抑えておきましょう。 生成 nsXembookId = new sym.NamespaceId("xembook"); >NamespaceId {fullName: 'xembook', id: Id} > fullName: "xembook" > id: Id {lower: 646738821, higher: 2754876907} 更新 有効期限の確認 nsInfo = await nsRepo.getNamespace(nsXembookId).toPromise(); lastHeight = (await chainRepo.getChainInfo().toPromise()).height; lastBlock = await blockRepo.getBlockByHeight(lastHeight).toPromise(); remainHeight = nsInfo.endHeight.compact() - lastHeight.compact(); new Date(lastBlock.timestamp.compact() + remainHeight * 30000 + epochAdjustment * 1000) //>Tue Mar 29 2022 18:17:06 GMT+0900 (日本標準時) 生成更新トランザクション ルートネームスペース namespaceTx = nem.NamespaceRegistrationTransaction.createRootNamespace( nem.Deadline.create(epochAdjustment), "xembook", nem.UInt64.fromUint(180000), networkType ); サブネームスペース subNamespaceTx = nem.NamespaceRegistrationTransaction.createSubNamespace( nem.Deadline.create(epochAdjustment), "tomato", "xembook", networkType, ); 変換 ネームスペース文字列からIDが一意に決定されます。その値をHexやUInt値に変換することで、署名や参照のためのキー値として利用することもできます。 HEX値 nsXembookId.toHex(); >'A43415EB268C7385' nsId = sym.NamespaceId.createFromEncoded("A43415EB268C7385"); >NamespaceId {id: Id} UInt値 nsXembookId.id > Id {lower: 646738821, higher: 2754876907} nsXembookId.id.toString() >'11832106220717372293' 桁が大きすぎるためJavaScriptでは数値型で扱うことはできません。 ネームスペース関連情報の取得 NamespaceHttpを利用してネームスペースに関連したさまざまな情報をノードから取得します。 注意点として、ネームスペース情報にネームスペース名は含まれない場合があります。 アドレスやモザイクからネームスペース情報を逆引きしてネームスペース名を取得したい場合は、ネームスペース情報からさらにネームスペース名をgetNamespacesNamesで取得する必要があります。 ネームスペースIDからネームスペース名を取得 getNamespacesNames(NamespaceId[]) => NamespaceNames[] ネームスペースIDからネームスペース情報を取得 getNamespace(NamespaceId) => NamespaceInfo アドレス、モザイクなどからネームスペース名一覧を取得 getAccountsNames(Address) => AddressNames(Address,NamespaceName[])[] getMosaicsNames(MosaicId) => MosaicNames(MosaicId,NamespaceName[])[] ネームスペースからアドレスアカウント情報を取得 getLinkedAddress(NamespaceId) => Address getLinkedMosaicId(NamespaceId) => MosaicId ルートネームスペースから全ネームスペース情報の検索 search => NamespaceInfo NamespaceSearchCriteria level0 NamespaceId ルートネームスペースを指定 AliasType Address = 2 Mosaic = 1 one = 0 registrationType RootNamespace = 0 SubNamespace = 1 pageNumber,pageSize,order,ownerAddress 共通 リンク 作成したネームスペースはアカウントアドレスやモザイクトークンに割り当てることができます。 Mosaicにリンク mosaicId = new sym.MosaicId("6BED913FA20223F8"); mosaicAliasTx = sym.AliasTransaction.createForMosaic( sym.Deadline.create(epochAdjustment), sym.AliasAction.Link, namespaceId, mosaicId, networkType ).setMaxFee(200); Addressにリンク address = nem.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ"); addressAliasTx = sym.AliasTransaction.createForAddress( sym.Deadline.create(epochAdjustment), sym.AliasAction.Link, namespaceId, address, networkType ).setMaxFee(200); ネームスペースをトランザクションに使用する 例えば、TransferTransaction.createは以下のように定義されます。 TransferTransaction.create( deadline: Deadline, recipientAddress: UnresolvedAddress, mosaics: new Mosaic(id: UnresolvedMosaicId, amount: UInt64)[], message: Message, networkType: NetworkType, maxFee?: UInt64, signature?: string, signer?: PublicAccount ) このうち、UnresolvedAddress、UnresolvedMosaicIdと定義された箇所はネームスペースを解決せずにそのままトランザクションとして署名することができます。 UnresolvedMosaicIdとして使う UnresolvedMosaicId: (MosaicId | NamespaceId)で定義されたパラメータは MosaicIdの代わりにNamespaceIdのままでトランザクションを署名できます。 nsTomatoId = new sym.NamespaceId("xembook.tomato"); tx = sym.TransferTransaction.create( sym.Deadline.create(epochAdjustment), address, [new sym.Mosaic(nsTomato,sym.UInt64.fromUint(1))], sym.EmptyMessage, networkType ).setMaxFee(200); UnresolvedAddressとして使う UnresolvedAddress: (Address | NamespaceId)で定義されたパラメータは Addressの代わりにNamespaceIdままでトランザクションを署名できます。 nsXembookId = new sym.NamespaceId("xembook"); tx = sym.TransferTransaction.create( sym.Deadline.create(epochAdjustment), nsXembook, [], sym.EmptyMessage, networkType ).setMaxFee(200); メタデータの付与 アカウントやモザイクと同様にネームスペースにもメタデータを付与することができます。 createNamespaceMetaTx = await metaService.createNamespaceMetadataTransaction( nem.Deadline.create(epochAdjustment), networkType, publicAccount.address,//ネームスペースの作成者 namespaceId, key,value, publicAccount.address //メタデータの登録者 ).toPromise(); 企業であればドメイン登録に付随したデータ、あるいは認証アカウント(Issuer)からの署名、 ビットコインやイーサリアムのアドレスを記録しておけば面白いかもしれませんね。 署名時のネームスペースを解決する 現在ネームスペースがリンクしているアドレスやモザイクではなく、トランザクションがブロックチェーンに記録されたときに何とリンクされていたかを調べる方法です。 state = await receiptRepo.searchAddressResolutionStatements({height:179401}).toPromise(); nsBallId = new sym.NamespaceId("xembook.game.ball"); state = await receiptRepo.searchMosaicResolutionStatements({height:181769}).toPromise(); ネームスペースを利用するときに、注意しないといけないことに、 トランザクションを署名した時のネームスペースの所有者と、現在のネームスペースの所有者が異なる可能性がある、という点があります。 サービス提供者にとって、ネームスペースを使えば気軽にアドレスを乗り換えられる利点がありますが、サービスが廃止された後、スキャマーによってネームスペースを取得された場合は要注意が必要です。 マークルパトリシアツリーでネームスペースを検証する 署名を必要とせず、ネームスペースの参照のみ行いたい場合は問い合わせ先ノードが悪意ある第三者に乗っ取られている可能性を考慮する必要があります。そういった場合は、マークルパトリシアツリーを使うことで(ファイナライズブロック情報が他所から取り寄せて)ネームスペースの正当性を検証することができます。 ブロックヘッダーのハッシュ値とマークルパス情報を取得 nsXembookId = new sym.NamespaceId("xembook"); rxjs.zip( stateProofService.namespaceById(nsXembookId), blockRepo.search({order:"desc"}) ).subscribe(x=>{ state = x[0]; // マークルパス merkleRootHash = x[1].data[0].stateHashSubCacheMerkleRoots[1]; console.log(merkleRootHash); }) ネームスペース情報からハッシュ値生成 nsInfo = await nsRepo.search({level0:nsXembookId}).toPromise(); hasher = sha3_256.create(); nsInfoHash = hasher.update(nsInfo.data[0].serialize(nsInfo.data)).hex().toUpperCase(); //>'38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898' ネームスペースIDからパスハッシュ値生成 hasher = sha3_256.create(); hasher.update(sym.Convert.hexToUint8Reverse(nsXembookId.toHex())); nsIdHash = hasher.hex().toUpperCase(); //> '64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647' ステートハッシュとネームスペース情報のシリアライズ値のハッシュを比較 function getLeafHash(encodedPath, leafValue){ hasher = sha3_256.create(); return hasher.update(sym.Convert.hexToUint8(encodedPath + leafValue)).hex().toUpperCase(); } function getBranchHash(encodedPath, links){ const branchLinks = Array(16).fill(sym.Convert.uint8ToHex(new Uint8Array(32))); links.forEach((link) => { branchLinks[parseInt(`0x${link.bit}`, 16)] = link.link; }); hasher = sha3_256.create(); bHash = hasher.update(sym.Convert.hexToUint8(encodedPath + branchLinks.join(''))).hex().toUpperCase(); console.log(encodedPath + branchLinks.join('')); console.log(bHash); return bHash; } merkleBranches = state.merkleTree.branches.reverse(); leafHash = getLeafHash(state.merkleTree.leaf.encodedPath,nsInfoHash ) branchHash = leafHash; bit = ""; for(let i = 0; i < merkleBranches.length; i++){ branch = merkleBranches[i]; merkleTreeBranchLink = branch.links.find(x=>x.link === branchHash) branchHash = getBranchHash(branch.encodedPath,branch.links); bit = merkleTreeBranchLink.bit + bit; } pathHash = bit + state.merkleTree.leaf.path; stateHash = state.merkleTree.leaf.value; rootHash = branchHash;//最後のbranchがroot console.log(rootHash); console.log(merkleRootHash) console.log(pathHash); console.log(nsIdHash); console.log(stateHash); console.log(nsInfoHash); //3FAF3D1B282D7029B76C2FD9251465B1F6BF815CB69861E683F4F9F5E605C495 //3FAF3D1B282D7029B76C2FD9251465B1F6BF815CB69861E683F4F9F5E605C495 //64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647 //64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647 //38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898 //38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898 一部のネームスペースのみ検証することはできません。検証者はルートネームスペースに紐づくすべてのサブネームスペースを入手してstateHashを作成する必要があります。 マークルパトリシアツリーを使った検証についてはこちらもご参考ください。 さいごに Symbolのネームスペースを実現するための技術は意外と多岐にわたり、ネームスペースを理解することができれば、 Symbolブロックチェーンを扱うアプリケーション開発に必要な技術の大部分を理解することができます。 ぜひチャレンジしてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Symbolブロックチェーンのネームスペースを完全に理解する。

今回はSymbolブロックチェーンのネームスペースについて解説します。 公式ドキュメントはこちらです。 まずはSymbolで定義されるネームスペースの特徴について、初心者と上級者にわけてざっくりと説明します。 初心者の方 アドレスやモザイクトークンに分かりやすい名前を付けることができ、送金ミスを減らすことができます。 ネームスペースはインターネットのドメインと同様に期間でレンタルします。 XYMにはsymbol.xymという分かりやすいネームスペースがあらかじめ定義されています。 上級者の方 ウォレットはネームスペースを名前解決することなくそのままトランザクションに署名できます。 トランザクションが承認されたときのレシートを参照することで、そのときのネームスペース所有者を特定できます。 改ざん不可能(検証可能)なDNSが実現可能です。 基本操作 ネームスペースの作成、更新、変換まわりを抑えておきましょう。 生成 nsXembookId = new sym.NamespaceId("xembook"); >NamespaceId {fullName: 'xembook', id: Id} > fullName: "xembook" > id: Id {lower: 646738821, higher: 2754876907} 更新 有効期限の確認 nsInfo = await nsRepo.getNamespace(nsXembookId).toPromise(); lastHeight = (await chainRepo.getChainInfo().toPromise()).height; lastBlock = await blockRepo.getBlockByHeight(lastHeight).toPromise(); remainHeight = nsInfo.endHeight.compact() - lastHeight.compact(); new Date(lastBlock.timestamp.compact() + remainHeight * 30000 + epochAdjustment * 1000) //>Tue Mar 29 2022 18:17:06 GMT+0900 (日本標準時) 生成更新トランザクション ルートネームスペース namespaceTx = nem.NamespaceRegistrationTransaction.createRootNamespace( nem.Deadline.create(epochAdjustment), "xembook", nem.UInt64.fromUint(180000), networkType ); サブネームスペース subNamespaceTx = nem.NamespaceRegistrationTransaction.createSubNamespace( nem.Deadline.create(epochAdjustment), "tomato", "xembook", networkType, ); 変換 ネームスペース文字列からIDが一意に決定されます。その値をHexやUInt値に変換することで、署名や参照のためのキー値として利用することもできます。 HEX値 nsXembookId.toHex(); >'A43415EB268C7385' nsId = sym.NamespaceId.createFromEncoded("A43415EB268C7385"); >NamespaceId {id: Id} UInt値 nsXembookId.id > Id {lower: 646738821, higher: 2754876907} nsXembookId.id.toString() >'11832106220717372293' 桁が大きすぎるためJavaScriptでは数値型で扱うことはできません。 ネームスペース関連情報の取得 NamespaceHttpを利用してネームスペースに関連したさまざまな情報をノードから取得します。 注意点として、ネームスペース情報にネームスペース名は含まれない場合があります。 アドレスやモザイクからネームスペース情報を逆引きしてネームスペース名を取得したい場合は、ネームスペース情報からさらにネームスペース名をgetNamespacesNamesで取得する必要があります。 ネームスペースIDからネームスペース名を取得 getNamespacesNames(NamespaceId[]) => NamespaceNames[] ネームスペースIDからネームスペース情報を取得 getNamespace(NamespaceId) => NamespaceInfo アドレス、モザイクなどからネームスペース名一覧を取得 getAccountsNames(Address) => AddressNames(Address,NamespaceName[])[] getMosaicsNames(MosaicId) => MosaicNames(MosaicId,NamespaceName[])[] ネームスペースからアドレスアカウント情報を取得 getLinkedAddress(NamespaceId) => Address getLinkedMosaicId(NamespaceId) => MosaicId ルートネームスペースから全ネームスペース情報の検索 search => NamespaceInfo NamespaceSearchCriteria level0 NamespaceId ルートネームスペースを指定 AliasType Address = 2 Mosaic = 1 one = 0 registrationType RootNamespace = 0 SubNamespace = 1 pageNumber,pageSize,order,ownerAddress 共通 sdkドキュメントはこちら リンク 作成したネームスペースはアカウントアドレスやモザイクトークンに割り当てることができます。 Mosaicにリンク mosaicId = new sym.MosaicId("6BED913FA20223F8"); mosaicAliasTx = sym.AliasTransaction.createForMosaic( sym.Deadline.create(epochAdjustment), sym.AliasAction.Link, namespaceId, mosaicId, networkType ).setMaxFee(200); Addressにリンク address = nem.Address.createFromRawAddress("TBIL6D6RURP45YQRWV6Q7YVWIIPLQGLZQFHWFEQ"); addressAliasTx = sym.AliasTransaction.createForAddress( sym.Deadline.create(epochAdjustment), sym.AliasAction.Link, namespaceId, address, networkType ).setMaxFee(200); ネームスペースをトランザクションに使用する 例えば、TransferTransaction.createは以下のように定義されます。 TransferTransaction.create( deadline: Deadline, recipientAddress: UnresolvedAddress, mosaics: new Mosaic(id: UnresolvedMosaicId, amount: UInt64)[], message: Message, networkType: NetworkType, maxFee?: UInt64, signature?: string, signer?: PublicAccount ) このうち、UnresolvedAddress、UnresolvedMosaicIdと定義された箇所はネームスペースを解決せずにそのままトランザクションとして署名することができます。 UnresolvedMosaicIdとして使う UnresolvedMosaicId: (MosaicId | NamespaceId)で定義されたパラメータは MosaicIdの代わりにNamespaceIdのままでトランザクションを署名できます。 nsTomatoId = new sym.NamespaceId("xembook.tomato"); tx = sym.TransferTransaction.create( sym.Deadline.create(epochAdjustment), address, [new sym.Mosaic(nsTomato,sym.UInt64.fromUint(1))], sym.EmptyMessage, networkType ).setMaxFee(200); UnresolvedAddressとして使う UnresolvedAddress: (Address | NamespaceId)で定義されたパラメータは Addressの代わりにNamespaceIdままでトランザクションを署名できます。 nsXembookId = new sym.NamespaceId("xembook"); tx = sym.TransferTransaction.create( sym.Deadline.create(epochAdjustment), nsXembook, [], sym.EmptyMessage, networkType ).setMaxFee(200); メタデータの付与 アカウントやモザイクと同様にネームスペースにもメタデータを付与することができます。 createNamespaceMetaTx = await metaService.createNamespaceMetadataTransaction( nem.Deadline.create(epochAdjustment), networkType, publicAccount.address,//ネームスペースの作成者 namespaceId, key,value, publicAccount.address //メタデータの登録者 ).toPromise(); 企業であればドメイン登録に付随したデータ、あるいは認証アカウント(Issuer)からの署名、 ビットコインやイーサリアムのアドレスを記録しておけば面白いかもしれませんね。 署名時のネームスペースを解決する 現在ネームスペースがリンクしているアドレスやモザイクではなく、トランザクションがブロックチェーンに記録されたときに何とリンクされていたかを調べる方法です。 state = await receiptRepo.searchAddressResolutionStatements({height:179401}).toPromise(); nsBallId = new sym.NamespaceId("xembook.game.ball"); state = await receiptRepo.searchMosaicResolutionStatements({height:181769}).toPromise(); ネームスペースを利用するときに、注意しないといけないことに、 トランザクションを署名した時のネームスペースの所有者と、現在のネームスペースの所有者が異なる可能性がある、という点があります。 サービス提供者にとって、ネームスペースを使えば気軽にアドレスを乗り換えられる利点がありますが、サービスが廃止された後、スキャマーによってネームスペースを取得された場合は要注意が必要です。 マークルパトリシアツリーでネームスペースを検証する 署名を必要とせず、ネームスペースの参照のみ行いたい場合は問い合わせ先ノードが悪意ある第三者に乗っ取られている可能性を考慮する必要があります。そういった場合は、マークルパトリシアツリーを使うことで(ファイナライズブロック情報が他所から取り寄せて)ネームスペースの正当性を検証することができます。 ブロックヘッダーのハッシュ値とマークルパス情報を取得 nsXembookId = new sym.NamespaceId("xembook"); rxjs.zip( stateProofService.namespaceById(nsXembookId), blockRepo.search({order:"desc"}) ).subscribe(x=>{ state = x[0]; // マークルパス merkleRootHash = x[1].data[0].stateHashSubCacheMerkleRoots[1]; console.log(merkleRootHash); }) ネームスペース情報からハッシュ値生成 nsInfo = await nsRepo.search({level0:nsXembookId}).toPromise(); hasher = sha3_256.create(); nsInfoHash = hasher.update(nsInfo.data[0].serialize(nsInfo.data)).hex().toUpperCase(); //>'38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898' ネームスペースIDからパスハッシュ値生成 hasher = sha3_256.create(); hasher.update(sym.Convert.hexToUint8Reverse(nsXembookId.toHex())); nsIdHash = hasher.hex().toUpperCase(); //> '64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647' ステートハッシュとネームスペース情報のシリアライズ値のハッシュを比較 function getLeafHash(encodedPath, leafValue){ hasher = sha3_256.create(); return hasher.update(sym.Convert.hexToUint8(encodedPath + leafValue)).hex().toUpperCase(); } function getBranchHash(encodedPath, links){ const branchLinks = Array(16).fill(sym.Convert.uint8ToHex(new Uint8Array(32))); links.forEach((link) => { branchLinks[parseInt(`0x${link.bit}`, 16)] = link.link; }); hasher = sha3_256.create(); bHash = hasher.update(sym.Convert.hexToUint8(encodedPath + branchLinks.join(''))).hex().toUpperCase(); console.log(encodedPath + branchLinks.join('')); console.log(bHash); return bHash; } merkleBranches = state.merkleTree.branches.reverse(); leafHash = getLeafHash(state.merkleTree.leaf.encodedPath,nsInfoHash ) branchHash = leafHash; bit = ""; for(let i = 0; i < merkleBranches.length; i++){ branch = merkleBranches[i]; merkleTreeBranchLink = branch.links.find(x=>x.link === branchHash) branchHash = getBranchHash(branch.encodedPath,branch.links); bit = merkleTreeBranchLink.bit + bit; } pathHash = bit + state.merkleTree.leaf.path; stateHash = state.merkleTree.leaf.value; rootHash = branchHash;//最後のbranchがroot console.log(rootHash); console.log(merkleRootHash) console.log(pathHash); console.log(nsIdHash); console.log(stateHash); console.log(nsInfoHash); //3FAF3D1B282D7029B76C2FD9251465B1F6BF815CB69861E683F4F9F5E605C495 //3FAF3D1B282D7029B76C2FD9251465B1F6BF815CB69861E683F4F9F5E605C495 //64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647 //64AF1132744506286E37EF66E6109279979B03B85BA2184ADB47061ED98FB647 //38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898 //38CC8173E8387ADE8198ED5BC3C8A0E391D38F943062F85F8606798C62F1F898 一部のネームスペースのみ検証することはできません。検証者はルートネームスペースに紐づくすべてのサブネームスペースを入手してstateHashを作成する必要があります。 マークルパトリシアツリーを使った検証についてはこちらもご参考ください。 さいごに Symbolのネームスペースを実現するための技術は意外と多岐にわたり、ネームスペースを理解することができれば、 Symbolブロックチェーンを扱うアプリケーション開発に必要な技術の大部分を理解することができます。 ぜひチャレンジしてみてください。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】複数ファイルをドロップしたときの順番はドラッグしたときに掴んだファイルに依存する

どういうこと? 文章だと説明しにくいので例をあげます。 以下のファイルを選択したとします。 1.txt 2.txt 3.txt 4.txt 上記の状態から「どのファイルを掴んでドラッグを始めたか」によって FileList の順番が変わります。 1.txt を掴んでドラッグを始めた場合 1, 2, 3, 4 2.txt を掴んでドラッグを始めた場合 2, 3, 4, 1 3.txt を掴んでドラッグを始めた場合 3, 4, 1, 2 4.txt を掴んでドラッグを始めた場合 4, 3, 1, 2
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【JavaScript】DataTransfer.files (FileList) の順番はドラッグしたときに掴んだファイルによって変わる

結論 文章だと説明しにくいので例をあげます。 以下のファイルを選択したとします。 1.txt 2.txt 3.txt 4.txt 上記の状態から「どのファイルを掴んでドラッグを始めたか」によって FileList の順番が変わります。 1.txt を掴んでドラッグを始めた場合 1, 2, 3, 4 2.txt を掴んでドラッグを始めた場合 2, 3, 4, 1 3.txt を掴んでドラッグを始めた場合 3, 4, 1, 2 4.txt を掴んでドラッグを始めた場合 4, 1, 2, 3 てっきりファイル名昇順だと思い込んでいたのでハマりました?
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

「Adblockが有効だと特定の要素が消えちゃう!」が起きないサイトを作る方法

Adblockとは、ブラウザの拡張機能で、Webサイトに表示される広告を自動で非表示にしてくれるというものです。 ※ この記事では、「Adblockは悪か」、といった話題には触れず、以下の前提で進めます。 世の中にはAdblockを導入しているユーザーが居る そういったユーザーでもサイトを利用できる状態にしたい = 広告以外の要素が意図せず非表示になるのを防ぎたい この拡張機能が有効なユーザーがサイトを表示したい場合、広告ではない要素まで消えてしまうという事象が起きることがあります。 例えば今だと、某有名レシピサイトさんの右カラムが丸ごと消失してしまうようです。 関係ない要素が消える理由 Adblockには、いくつかの仕組みで広告を検知しているようですが、そのうちの一つが、id名による検知です。 要素のid名に#ad_*のようなものが設定されていると、広告と認識され、非表示にされます。 実際にAdblock導入すると、以下のようなシンプルなCSSが追加されます。 #ad_mobile, #ad_module, #ad_wrapper, ..略.. { display: none !important; } 経験上、Webサイトを作っていて意図せず要素が消失してしまう場合、多くの場合これによるものだと思います。 「ブラウザのシークレットウィンドウだと表示されるけど、通常のウィンドウだと表示されなくなる要素がある」 的な事象のときは、Adblockを疑ってみてください。 対策 対策は、広告じゃない要素にad_*といった命名をしないことです。 特に、メインページ上部に置くスライダーや、サイト内の別コンテンツを紹介するバナーなどに、adと命名してしまうようです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【GAS】送信された内容によって自身、他のフォームの質問の選択肢を増やすことができるフォーム。及び在庫管理システム

このシステムを作るに至った背景(読み飛ばし可) ある日、製品を処理した結果の処理前と処理後の製品の在庫の増減を記録する、GASとgoogleフォーム(以下"フォーム")、googleスプレッドシート(以下"スプレッドシート")を用いたシステムを作りました。データとして製品それぞれの名前、納品先会社名、処理前と処理後の在庫数がスプレッドシートに記入してあり、フォームに入力者の名前、処理した数、NGの数、会社、製品名等を入力して送信するとその内容がデータに反映され、その結果によってスプレッドシートの該当製品の各在庫数が変動するというものです。このシステムにおいて、フォームの選択肢である製品の種類は取引先の企業が増えるたび、製品が増えるたびに増えていきます。その製品と会社名をその都度フォームに手動で追加するのは手間なので、新しい製品をフォームで追加すると自動でフォームの選択肢にその製品が追加されるスクリプトを書きました。 ※なお、現在は他の方法(フォームをその都度フォームの設計図となる情報から作り直す)を採用したのでこの仕組みは採用していません。 ※この記事に関しては相談、了承済み、被監視 本編 サンプル 以下のフォルダからコピーしてdefine.gsの最初の3行に適切なIDを入れることで動作を確認できます IDはシートとフォームのURLの/で挟まれている「10fvROVXs1GsnU0a3lVRtdHKAC0KybKqJVJCQQP-vHU0」くらいの長さの部分です。 https://drive.google.com/drive/folders/1MEUIn5REGHaKHWZeQf6xnE66Kz_rg4yB?usp=sharing このプログラムでできること ある1つのフォームに送信された内容でそのフォームを含めた2つのフォームの選択肢を増やす。 在庫管理を行う。 (説明の簡略化のために2つのフォームを編集することとしますが増やすことが可能です。) サンプル会社の状況 ある会社で製品の処理をする業務を行っていて、その処理を行うと製品の個数が増える。増える個数は製品それぞれについて異なる。複数の企業とその製品を入荷、納品する取引をしている。それぞれの製品の処理前、処理後の在庫を監視したい。新しい取引先や新製品が増えたり、処理したときに増える個数が変化したりする。 システムの構造 新製品登録、情報変更(フォーム):新製品(既存or新しい取引先)の登録、登録済みの製品の情報書き換え 作業報告(フォーム):製品の入荷、処理、出荷を報告するフォーム。報告の際に作業した個数を記入する データベース(スプレッドシート):各種情報、フォームの解答のシートとスクリプトが書き込まれている 製品登録:フォームの解答を受け入れるシート 製品の処理:フォームの解答を受け入れるシート 製品データ:製品それぞれのデータが1行ずつ記入されているシート 会社名リスト:会社が存在するかどうかを判定するために用いられるシート(スクリプトの書き方によっては不要) URLリスト:整理用。不要 フォームとスプレッドシートの要素 フォームの会社選択の質問の選択肢は解凍後にそれぞれその回答に対応するセクションに進むように設定されている。 新製品登録フォーム 機能 新しい会社名、新しい製品名で登録する 既存の会社名、新しい製品名で登録する。既存の会社名は選択可能 既存の会社名、既存の製品名で登録されている内容を編集する(フォーム下部の選択欄を使用) 更新した際の変化 新しい会社名が入力された場合、既存の会社名に新しい会社名を追加 新しい製品名、会社名が入力された場合、製品名を選択する質問に製品名を追加 製品の処理フォーム 機能 製品の入荷数を入力する 製品の処理数を入力する 製品の出荷数を入力する 更新した際の変化 新しい製品名、会社名が入力された場合、製品名を選択する質問に製品名を追加 データベーススプレッドシート フォームの入力を受けるシート 新しい会社名が入力されていくたびに「~~の製品」の列が右に追加されていく 製品データ 製品の情報が一製品につき一行に記載されている。このシートの情報をもとに在庫の計算などを行う。新しい製品が入力された際にはこのシートの最終行に要素が追加される。このシートの左端の列で製品が存在するか否かを判定している。行の順序を変えてもシステムは問題なく稼働する。 会社名リスト 会社名が増えるたびに末尾に会社名が追加されていくシート。このシートの会社名と新しく入力された会社名が合致するかの判定と、既存の会社名を選択する質問の選択肢に持ちいる。製品データの会社名の列を利用すれば不要となるがシンプルにするために設置。 AppsScript内で設定するトリガー フォームが送信されたときに、「送信されたフォームによって異なる関数を実行する関数」が実行されるように設定する。 コード 実行イメージ コードはGASエディタ内で以下の4つのファイルに分けられている。 define.gs →操作するシートとフォームの定義、製品名と会社名のリストを作成する。 whichFtoGo.gs →送信されたフォームによって以下の二つの関数のうちどちらを実行するかを選択し実行する関数。 この関数がこのシステムのトリガーであり、フォームが送信されたときにこの関数が実行されるように設定しておく。 NewProduct.gs →新商品の登録、製品情報の編集を行う。 まず入力された製品名が既存の製品名のリストに含まれているかどうかを判定 ├ 既存の製品名の場合 →入力された内容に製品データを更新、更新の記録を入力。 └ 新しい製品名の場合 →既存の会社名に含まれているかを判定  ├ 既存の会社名の場合 →新しい製品名を質問の選択肢に足し、製品データに新しい行を追加  └ 新しい会社名の場合 →新しい会社名、製品名を質問選択肢に足し、製品データに新しい行を追加 Process.gs →製品と作業の種類によってそれぞれ異なる計算を行って製品データに入力されている製品在庫の増減を記入する。 まず入力されたフォームの作業内容の質問の回答が「入荷、処理、出荷」のいずれであるかを判定 ├ 入荷の場合は製品データの該当製品の処理前在庫を増加 ├ 処理の場合は製品データの該当製品の処理前在庫を減少させ処理後在庫を増加率に応じて増加 └ 出荷の場合は製品データの該当製品の処理後在庫を減少 define.gs フォームとシートのIDからそれぞれを取得して使用可能な形にする。 また製品のリストと会社名のリストを判定などでよく用いるためそれぞれ一次配列として保存する。 define.gs const FormNewProduct = FormApp.openById('新製品登録フォームのID'); //新製品登録フォームのIDを元にフォームを取得 const FormProcess = FormApp.openById('製品処理フォームのID'); //製品の処理フォームのIDを元にフォームを取得 const sheetiD = 'データベースシートのID';//sheetのID  const SheetNewProduct = SpreadsheetApp.openById(sheetiD).getSheetByName('新製品登録、情報変更'); //シートを取得 const SheetProcess = SpreadsheetApp.openById(sheetiD).getSheetByName('作業報告'); //シートを取得 const SheetProductData = SpreadsheetApp.openById(sheetiD).getSheetByName('製品データ'); //シートを取得 const SheetCompanyList = SpreadsheetApp.openById(sheetiD).getSheetByName('会社名リスト'); //シートを取得 const ProductList = SheetProductData.getRange(2,1,SheetProductData.getLastRow(),1).getValues().flat();//製品データの左端の二行目から最終行までを取得。getValuesすると二次配列なのでflatで一次配列にする。 console.log("製品名リスト "+ProductList);//製品名のリスト。製品が存在するかどうかの判定等に用いる。 const CompanyList = SheetCompanyList.getRange(1,1,SheetCompanyList.getLastRow(),1).getValues().flat();//会社名リストの一行目から最終行までを取得。getValuesすると二次配列なのでflatで一次配列にする。 console.log("会社名リスト "+CompanyList);//会社名のリスト。会社が存在するかどうかの判定等に用いる。 whichFtoGo.gs フォームが送信されたときに実行され、送信されたフォームに応じて異なる関数を実行する。eを用いることで更新のあったシートを特定する。 whichFtoGo.gs function whichFtoGo(e) { //更新のあったシートのシート名を取得 var sheetName = e.range.getSheet().getName(); //更新のあったシート名の種類によって実行する関数を変更 if (sheetName === '製品登録') { console.log('製品登録'); NewProduct(); } else if(sheetName=='製品の処理'){ console.log('製品の処理'); Process(); } } NewProduct.gs 新製品情報フォームが送信されたときに実行され、製品の情報の編集もしくは、新しい製品名と会社名の選択肢への追加と製品データへの登録を行う。 NewProduct.gs function NewProduct() { //情報を格納 var infoRange = SheetNewProduct.getRange(SheetNewProduct.getLastRow(),1,1,SheetNewProduct.getLastColumn());//新製品登録シートの最終行の左から右までの範囲 var Info = infoRange.getValues().flat(); //最終列の左端から右端までを配列に格納 console.log(Info);//[タイムスタンプ 入力者 既存の会社名 新しい会社名 新しい製品名 処理時の増加倍率 会社名 シルフカンパニーの製品 デボンコーポレーションの製品 エーテル財団の製品] var WhenEnter = Info[0];//タイムスタンプ var WhoEnter = Info[1];//入力者 var KnownCompanyName = Info[2];//既存の会社名 var NewCompanyName = Info[3];//新しい会社名。という名前だが既存なのを忘れてこちらに記入する可能性もあるので、既存の会社名と統一する if(NewCompanyName == null){ //新しい会社名の欄が空欄の場合は既存の会社名から代入する。 NewCompanyName = KnownCompanyName; } var ProcessedProductName = Info[4];//新しい製品名 var ProcessRate = Info[5];//処理時の増加倍率 var ChosenCompanyName = Info[6];//会社名 var ChosenProduct = Info.slice(7).filter(v => v)[0]; //sliceで最初の6要素を切り落とし、会社名の行よりも右のセル(Info配列における7)の中で記入されているものをフィルタリングして代入 if(ChosenProduct != undefined){ //もし既存の部品が選択されていた場合、ProcessedProductNameに代入する。 console.log("編集先製品"+ChosenProduct); ProcessedProductName = ChosenProduct; } //会社と製品のリストで存在するかどうかを判定 var ProductIfExist = ProductList.indexOf(ProcessedProductName); //-1なら存在しない console.log(ProductIfExist); var ComapyIfExist = CompanyList.indexOf(NewCompanyName); //-1なら存在しない console.log(ComapyIfExist); //それぞれ存在するかで処理を分岐 if(ProductIfExist != -1){ //すでに製品が存在する場合 console.log("すでに存在する製品名が新製品登録に入力されました。") SheetProductData.getRange(ProductIfExist+2,6).setValue(ProcessRate) SheetProductData.getRange(ProductIfExist+2,3,1,3).setValues([[WhenEnter,"情報更新",WhoEnter]]); //更新の反映 }else{ //新しい製品の場合 if(ComapyIfExist != -1){ //すでに会社が存在する場合 ProductAdd(FormNewProduct,3,ProcessedProductName,ComapyIfExist); //◆◆◆◆◆◆◆◆製品登録の処理 ProductAdd(FormProcess,2,ProcessedProductName,ComapyIfExist); //◆◆◆◆◆◆◆◆製品処理の処理 }else{ //新しい会社の場合 SheetCompanyList.appendRow([NewCompanyName]); CompanyList.push(NewCompanyName); CompanyAdd(FormNewProduct,3,ProcessedProductName,NewCompanyName); //◆◆◆◆◆◆◆◆製品登録の処理 CompanyAdd(FormProcess,2,ProcessedProductName,NewCompanyName); //◆◆◆◆◆◆◆◆製品処理の処理 //新製品登録の二番目の既存の会社名の入力の質問に会社名を追加する var companyChoice = FormNewProduct.getItems(FormApp.ItemType.LIST)[1].asListItem(); var choiceA = []; CompanyList.forEach(companyName =>{ choiceA.push(companyChoice.createChoice(companyName)); //選択肢とセクション区切りを合わせた形で配列choiceAに格納 }) companyChoice.setChoices(choiceA); //質問項目の選択肢を更新 } SheetProductData.appendRow([ProcessedProductName,NewCompanyName,WhenEnter,"製品登録",WhoEnter,ProcessRate]); } } //◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆ //会社選択フォームの該当会社に品名の選択肢を追加。の関数 function ProductAdd(FormX,x,NewProductName,ComapyIfExist){ //引数(変更するフォーム、上からいくつめが会社選択のプルダウンか、hantei、品名) var listItem = FormX.getItems(FormApp.ItemType.LIST)[ComapyIfExist+x].asListItem(); var choice = listItem.getChoices();//項目を取得 console.log(listItem.getTitle()) choice.push(listItem.createChoice(NewProductName,FormApp.PageNavigationType.SUBMIT)); listItem.setChoices(choice); //指定した質問項目の選択肢を更新 } //◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆◆ //会社選択フォームにその会社に飛ぶ選択肢を足し、フォームの最後に新しいセクションと質問を足す。の関数 function CompanyAdd(FormX,x,NewProductName,NewCompanyName){ //引数(変更するフォーム、上からいくつ目の会社選択のプルダウンか、品名、会社名) FormX.addPageBreakItem().setTitle(NewCompanyName+'の品名');// 質問を追加してタイトルを設定     var newQ = FormX.addListItem(); //プルダウンの質問を追加。newQには質問が格納されている     newQ.setTitle(NewCompanyName+'の品名を選んでください');//質問のタイトルを追加     newQ.setChoices([newQ.createChoice(NewProductName,FormApp.PageNavigationType.SUBMIT)]);  //新しい会社名での選択肢を追加 //会社名を選ぶ質問に選択肢を加える var Sitem = FormX.getItems(FormApp.ItemType.LIST)[x-1].asListItem(); //作業日報のx目の質問(会社名)を取得[ console.log(Sitem.getTitle()) var choiceA =[]; //選択肢をセクションと合わせて最終的に格納しformに反映する配列 for(var i = 0;i<CompanyList.length;i++){ //会社の数だけループ j = FormX.getItems(FormApp.ItemType.PAGE_BREAK)[i].asPageBreakItem(); //セクションを格納する配列にそのIDで取得したセクション区切りアイテムを取得 choiceA.push(Sitem.createChoice(CompanyList[i],j)); //選択肢とセクション区切りを合わせた形で配列choiceAに格納 } Sitem.setChoices(choiceA); //会社を選ぶ選択肢としてchoiceA配列を適用し選択肢を更新 } Process.gs 作業日報フォームが送信されたときに実行される。製品の入荷、処理、出荷の入力に対してそれらの数値を計算し製品データに反映する。 Process.gs function Process(){ var infoRange = SheetProcess.getRange(SheetProcess.getLastRow(),1,1,SheetProcess.getLastColumn());//製品の処理シートの最終行の左から右までの範囲 var Info = infoRange.getValues().flat(); //最終列の左端から右端までを配列に格納 console.log(Info);//[タイムスタンプ 入力者 作業の種類 入荷or処理or出荷した個数 会社名 シルフカンパニーの製品 デボンコーポレーションの製品 エーテル財団の製品] var WhenEnter = Info[0];//タイムスタンプ var WhoEnter = Info[1];//入力者 var WhatProcess = Info[2];//作業の種類 var ProcessAmount = Info[3];//入荷or処理or出荷した個数 var CompanyName = Info[4];//会社名 var Product = Info.slice(5).filter(v => v)[0]; //sliceで最初の5要素を切り落とし、会社名の行よりも右のセル(Info配列における5)の中で記入されているものをフィルタリングして代入 console.log(Product); //indexOfでProductをProductList内から検索する。含まれない場合は-1,含む場合は1番目を0として出力される。 var whereProduct = ProductList.indexOf(Product); console.log(whereProduct); if(whereProduct == -1){ console.error("製品データに該当製品名が存在しません。"); return; } //製品データ内の該当製品のデータを取得して配列に格納 var ProductData = SheetProductData.getRange(whereProduct+2,6,1,3).getValues().flat(); console.log(ProductData); var ProcessRate = ProductData[0]; //処理時の増加倍率 var StockBefore = ProductData[1]; //処理前の在庫 var StockAfter = ProductData[2]; //処理後の在庫 //作業の内容によって処理を分岐 switch(WhatProcess){ case "製品の入荷": console.log("入荷") StockBefore = StockBefore + ProcessAmount; //処理前の製品の数に入荷数を加算 break; case "製品の処理": console.log("処理") StockBefore = StockBefore - ProcessAmount; //処理前の製品の数から処理数を減算 StockAfter = StockAfter + ProcessAmount * ProcessRate; //処理後の製品の数に処理数*処理時の増加倍率を加算 break; case "製品の出荷": console.log("出荷") StockAfter = StockAfter - ProcessAmount; //処理後の製品の数から出荷数を減算 break; } //結果の製品データへの反映 SheetProductData.getRange(whereProduct+2,3,1,3).setValues([[WhenEnter,WhatProcess,WhoEnter]]); //更新の反映 SheetProductData.getRange(whereProduct+2,7,1,2).setValues([[StockBefore,StockAfter]]); //在庫数の反映 }
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Chromeデベロッパーツール・全て学べる】を動画集一覧にしました

自己紹介 ジーズアカデミー講師 山崎と申します。MicrosoftMVPがChromeを解説するという動画です(笑) ※EdgeもコアはChromiumですから、デベロッパーツールは同じですからね。 今回、私のJavaScriptの授業(初級者向け)でのサポート動画をYoutube配信したものをまとめました。 一つ一つの解説が長いので「ブックマーク」して見ることをおススメいたします ◆動画集一覧 HTML/CSS/JavaScripを使った制作には「Chromeデベロッパーツール」は必須スキルです! デザインの調整、他サイトの構造見たり、エラーの確認、デバッグ等で良く使います。 ※一部新しい機能は、今後追加していきます。 デベロッパーツールは奥が深いので、初学者には「#1,#2」だけをおススメします。 デベロッパーツールは奥が深い~ タイトル 対象 動画? #1「 Elements/Console 」 Webデザイナー/ フロントエンド https://youtu.be/PjZyPfXdJFA #2「 JavaScriptデバッガ― / Sources」 フロントエンド https://youtu.be/TXi5g4J17ds #3「 ブラウザApplicaation機能 」 フロントエンド https://youtu.be/DsHhKs9V03o #4「 Lighthouse 」 Webデザイナー/ フロントエンド https://youtu.be/srayKfhg5Ho #5「 セキュリティ&ネットワーク機能 」 Webデザイナー/ フロントエンド https://youtu.be/Sn1oaEJwt0k #6「 CSSアニメーション検証機能 」 Webデザイナー https://youtu.be/zICHhUf6-Nw 初学者のために「Webデザイナー/ フロントエンド」と領域を記載してますが、両方覚えて損にはなりません。 ※一部、実務で使用してない機能は紹介だけにしてます。 今後も講義動画増えたら、こちらを更新していきます。 ブックマークしていただければ幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【Chromeデベロッパーツール・全て学べる】動画集

自己紹介 ジーズアカデミー講師 山崎と申します。MicrosoftMVPがChromeを解説するという動画です(笑) ※EdgeもコアはChromiumですから、デベロッパーツールは同じですからね。 今回、私のJavaScriptの授業(初級者向け)でのサポート動画をYoutube配信したものをまとめました。 一つ一つが奥が深く解説が長いので「ブックマーク」して見ることをおススメいたします ◆動画集一覧 HTML/CSS/JavaScripを使った制作には「Chromeデベロッパーツール」は必須スキルです! デザインの調整、他サイトの構造見たり、エラーの確認、デバッグ等で良く使います。 ※一部新しい機能は、今後追加していきます。 デベロッパーツールは奥が深いので、初学者には「#1,#2」だけをおススメします。 デベロッパーツールは奥が深い~ タイトル 対象 動画? #1「 Elements/Console 」 Webデザイナー/ フロントエンド https://youtu.be/PjZyPfXdJFA #2「 JavaScriptデバッガ― / Sources」 フロントエンド https://youtu.be/TXi5g4J17ds #3「 ブラウザApplicaation機能 」 フロントエンド https://youtu.be/DsHhKs9V03o #4「 Lighthouse 」 Webデザイナー/ フロントエンド https://youtu.be/srayKfhg5Ho #5「 セキュリティ&ネットワーク機能 」 Webデザイナー/ フロントエンド https://youtu.be/Sn1oaEJwt0k #6「 CSSアニメーション検証機能 」 Webデザイナー https://youtu.be/zICHhUf6-Nw 初学者のために「Webデザイナー/ フロントエンド」と領域を記載してますが、両方覚えて損にはなりません。 ※一部、実務で使用してない機能は紹介だけにしてます。 今後も講義動画増えたら、こちらを更新していきます。 ブックマークしていただければ幸いです。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Gitリポジトリの一部を指定してclone(ダウンロード)したい場合にdegitが便利

degitとは degitはGitリポジトリ(またはその一部)をローカル環境にダウンロードするためのCLIツールです。 git cloneでよくね? --depthを使用すれば最新のコミットだけを落とせますし、git clone --filter=blob:none --no-checkoutとgit sparse-checkoutでリポジトリの一部を指定して落とせます。 git clone --depth=1 git@github.com:user/repo.git git clone --filter=blob:none --no-checkout git@github.com:user/repo.git cd repo git sparse-checkout init git sparse-checkout add subdirectory ただ後述するように、degitではより短いコマンドで簡単にGitリポジトリの一部を指定してダウンロードできます。 インストール npmでインストールします。 npm install -g degit 使う 基本 GitHubだけでなくGitLab、BitBucket、Sourcehutにも対応しています。 GitHub GitHubからリポジトリのmaster(main)ブランチを現在の作業ディレクトリにダウンロードするには以下のようなコマンドを使用します。 degit user/repo また以下のコマンドはすべて同等です。 degit github:user/repo degit git@github.com:user/repo degit https://github.com/user/repo GitLab degit gitlab:user/repo degit git@gitlab.com:user/repo degit https://gitlab.com/user/repo BitBucket degit bitbucket:user/repo degit git@bitbucket.org:user/repo degit https://bitbucket.org/user/repo Sourcehut degit git.sr.ht:user/repo degit git@git.sr.ht:user/repo degit https://git.sr.ht/user/repo リポジトリの一部を指定する 第一引数の末尾にサブディレクトリを追加します。 degit user/repo/subdirectory タグ、ブランチ、コミットを指定する 省略した場合はデフォルトブランチ(基本的にはmasterまたはmain)になります。 degit user/repo#dev # branch degit user/repo#v1.2.3 # release tag degit user/repo#1234abcd # commit hash ダウンロード先を指定する 省略した場合は現在のディレクトリにダウンロードします。 degit user/repo my-new-project 使用例 このリポジトリのmainブランチのclient/android以下が欲しいとします。 以下のコマンドで保存場所をmy-folderに指定して落とすことができます。 degit derrickstolee/sparse-checkout-example/client/android my-folder cd my-folder
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Pyodideを使ってnwdiag(blockdiag)をクライアントサイドで動かす

Pyodide ブラウザ上でPythonを動かすという試みにはPyPy.jsやBrythonといったものが以前からある。Pyodideは後発ではあるもののpipの簡易実装であるmicropipが用意されていて、PyPI上のパッケージを(ものによっては)直接インストールできるところが画期的。 nwdiag nwdiag(やblockdiag)は、PlantUMLやGraphvizなどと同じく、テキストで定義して図を描くためのツール。この手のツールではMermaidの記法が最近GitHubでサポートされて話題になった。nwdiagはそんなテキストで定義して図を描くツールの中でも特にネットワーク構成図に特化したもの。 REPLで試す Pyodideのサイトにはデモ用のREPLが用意されており、WASMに対応したブラウザさえあれば手軽に試すことができる。今回はそれを使ってnwdiagを動かしてみる。 パッケージのインストール パッケージのインストールはpipで指定するのと同じ名前をmicropipに指定するだけ。 Welcome to the Pyodide terminal emulator ? Python 3.9.5 (default, Feb 22 2022 14:12:02) on WebAssembly VM Type "help", "copyright", "credits" or "license" for more information. >>> import micropip >>> await micropip.install('nwdiag') >>> あっけなく完了。 REPLから実行する準備 もともとnwdiagはCLI上から、 $ nwdiag -Tsvg simple.diag と実行するように設計されているため、REPL上で実行するためにはnwdiagコマンドが内部的に呼び出している処理を調べて真似る必要がある。 コマンド周りのソースを見てみると、command.pyのmain関数をCLIで渡す引数と同じものを配列で渡して呼び出せば良さそうだ。 command.pyをimportする。 >>> import nwdiag.command >>> とりあえず、何事もなくimportできた。 REPLから実行 早速、何かdiagファイルを用意して変換を試したいが、REPL環境に取り込むのが手間なので、とりあえずパッケージに含まれているテスト用のファイルで試すことにする。 Pyodideにはemscriptenによって用意された仮想的なファイルシステムが備わっており、そのファイルシステムの/lib/python3.9/site-packages/nwdiag/tests/diagrams以下に、GitHubのこの辺りのファイルが存在している。どれでもいいが、node_belongs_to_multiple_networks.diagで試してみる。 >>> nwdiag.command.main(["-Tsvg","/lib/python3.9/site-packages/nwdiag/tests/diagrams/node_belongs_to_multiple_networks.diag"]) ERROR: unknown format: SVG -1 >>> 動かない。。。 不具合箇所の特定 エラーメッセージを手がかりにnwdiag(と描画を担っているblockdiag)のソースを追ってみると、依存パッケージが足りていないようだ。 インストール済みのパッケージはmicropip.list()で確認できる。 >>> micropip.list() Name | Version | Source ------------- | ------- | ------- webcolors | 1.11.1 | pypi funcparserlib | 1.0.0a0 | pypi blockdiag | 3.0.0 | pypi nwdiag | 3.0.0 | pypi setuptools | 60.3.1 | pyodide pillow | 9.0.0 | pyodide distutils | 1.0 | pyodide micropip | 0.1 | pyodide pyparsing | 3.0.6 | pyodide packaging | 21.3 | pyodide >>> 足りているように見える。。。 さらにソースを読み進んで依存パッケージの有無をどのように判定しているのかまで追ってみる。 この辺りだ。 https://github.com/blockdiag/blockdiag/blob/master/src/blockdiag/imagedraw/__init__.py __init__.py def init_imagedrawers(debug=False): for drawer in pkg_resources.iter_entry_points('blockdiag_imagedrawers'): try: module = drawer.load() if hasattr(module, 'setup'): module.setup(module) except Exception as exc: if debug: warning('Failed to load %s: %r' % (drawer.module_name, exc)) pkg_resourcesが管理しているパッケージ情報がどうなっているのか確認してみる必要がありそうだ。 >>> import pkg_resources >>> pkg_resources.working_set.by_key {'tzdata': tzdata 2021.5 (/lib/python3.9), 'setuptools': setuptools 60.3.1 (/lib/python3.9/site-packages), 'packaging': packaging 21.3 (/lib/python3.9/site-packages), 'unknown': UNKNOWN 9.0.0 (/lib/python3.9/site-packages), 'pyparsing': pyparsing 3.0.6 (/lib/p ython3.9/site-packages), 'blockdiag': blockdiag 3.0.0 (/lib/python3.9/site-packages), 'nwdiag': nwdiag 3.0.0 (/lib/python3.9/site- packages), 'webcolors': webcolors 1.11.1 (/lib/python3.9/site-packages), 'funcparserlib': funcparserlib 1.0.0a0 (/lib/python3.9/si te-packages), 'micropip': micropip 0.1 (/lib/python3.9/site-packages)} >>> 'unknown': UNKNOWN 9.0.0って何だ? micropipの出力と比較してみると9.0.0というバージョン番号からみてpillowで間違いなさそうだ。(画像関係のパッケージだし) 'pillow': pillow 9.0.0のようになるべきところ、何かの不具合でunknownとなってしまっているのだろう。 不具合の回避 パッケージ情報を修正してみる。 >>> for dist in pkg_resources.distributions_from_metadata('/lib/python3.9/site-packages/Pillow-9.0.0.dist-info'): ... pkg_resources.working_set.by_key['pillow'] = dist ... >>> pkg_resources.working_set.by_key {'tzdata': tzdata 2021.5 (/lib/python3.9), 'setuptools': setuptools 60.3.1 (/lib/python3.9/site-packages), 'packaging': packaging 21.3 (/lib/python3.9/site-packages), 'unknown': UNKNOWN 9.0.0 (/lib/python3.9/site-packages), 'pyparsing': pyparsing 3.0.6 (/lib/p ython3.9/site-packages), 'blockdiag': blockdiag 3.0.0 (/lib/python3.9/site-packages), 'nwdiag': nwdiag 3.0.0 (/lib/python3.9/site- packages), 'webcolors': webcolors 1.11.1 (/lib/python3.9/site-packages), 'funcparserlib': funcparserlib 1.0.0a0 (/lib/python3.9/si te-packages), 'micropip': micropip 0.1 (/lib/python3.9/site-packages), 'pillow': Pillow 9.0.0 (/lib/python3.9/site-packages)} >>> 'unknown'というキーの存在はそのままだが、無事末尾に'pillow'が追加されている。 再チャレンジ 再度、node_belongs_to_multiple_networks.diagの変換を試みる。 >>> nwdiag.command.main(["-Tsvg","/lib/python3.9/site-packages/nwdiag/tests/diagrams/node_belongs_to_multiple_networks.diag"]) 0 >>> 無事に変換できたようだ。 変換元のdiagファイルと同じディレクトリ配下に拡張子svgのファイルが生成されているはずなので読み出してみる。 >>> svgfile = open("/lib/python3.9/site-packages/nwdiag/tests/diagrams/node_belongs_to_multiple_networks.svg","r") >>> svg = svgfile.read() >>> print(svg) <?xml version='1.0' encoding='UTF-8'?> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.0//EN" "http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd"> <svg viewBox="0 0 304 444" xmlns="http://www.w3.org/2000/svg" xmlns:inkspace="http://www.inkscape.org/namespaces/inkscape" xmlns:x link="http://www.w3.org/1999/xlink"> <defs id="defs_block"> <filter height="1.504" id="filter_blur" inkspace:collect="always" width="1.1575" x="-0.07875" y="-0.252"> <feGaussianBlur id="feGaussianBlur3780" inkspace:collect="always" stdDeviation="4.2" /> </filter> </defs> <title>blockdiag</title> <desc>{ network { A; } network { A; } network { A; } } </desc> <rect fill="rgb(0,0,0)" height="40" stroke="rgb(0,0,0)" style="filter:url(#filter_blur);opacity:0.7;fill-opacity:1" width="104" x="155" y="162" /> <path d="M 131 103 L 283 103 A2,4 0 0 1 283 111 L 131 111 A2,4 0 0 1 131 103" fill="rgb(0,0,0)" style="filter:url(#filter_blur)" /> <path d="M 131 247 L 283 247 A2,4 0 0 1 283 255 L 131 255 A2,4 0 0 1 131 247" fill="rgb(0,0,0)" style="filter:url(#filter_blur)" /> <path d="M 131 391 L 283 391 A2,4 0 0 1 283 399 L 131 399 A2,4 0 0 1 131 391" fill="rgb(0,0,0)" style="filter:url(#filter_blur)" /> <path d="M 128 100 L 280 100 A2,4 0 0 1 280 108 L 128 108 A2,4 0 0 1 128 100" fill="rgb(185,203,228)" stroke="rgb(0,0,0)" /> <path d="M 280 108 A2,4 0 0 1 280 100" fill="none" stroke="rgb(0,0,0)" /> <path d="M 128 100 L 280 100" fill="none" stroke="none" /> <path d="M 204 35 L 204 100" fill="none" stroke="rgb(0,0,0)" /> <path d="M 128 244 L 280 244 A2,4 0 0 1 280 252 L 128 252 A2,4 0 0 1 128 244" fill="rgb(185,203,228)" stroke="rgb(0,0,0)" /> <path d="M 280 252 A2,4 0 0 1 280 244" fill="none" stroke="rgb(0,0,0)" /> <path d="M 128 244 L 280 244" fill="none" stroke="none" /> <path d="M 128 388 L 280 388 A2,4 0 0 1 280 396 L 128 396 A2,4 0 0 1 128 388" fill="rgb(185,203,228)" stroke="rgb(0,0,0)" /> <path d="M 280 396 A2,4 0 0 1 280 388" fill="none" stroke="rgb(0,0,0)" /> <path d="M 128 388 L 280 388" fill="none" stroke="none" /> <path d="M 204 108 L 204 156" fill="none" stroke="rgb(0,0,0)" /> <path d="M 212 196 L 212 244" fill="none" stroke="rgb(0,0,0)" /> <path d="M 196 196 L 196 240" fill="none" stroke="rgb(0,0,0)" /> <path d="M 196.0 240.0 A8.0,8.0 0 0 1 196.0 256.0" fill="none" stroke="rgb(0,0,0)" /> <path d="M 196 256 L 196 388" fill="none" stroke="rgb(0,0,0)" /> <rect fill="rgb(255,255,255)" height="40" stroke="rgb(0,0,0)" width="104" x="152" y="156" /> <text fill="rgb(0,0,0)" font-family="sans-serif" font-size="11" font-style="normal" font-weight="normal" text-anchor="middle" te xtLength="6" x="204.0" y="182">A</text> </svg> >>>
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

中級編-アニメーションでホームページを動かそう

今回はホームページにCSSやJavaScriptでアニメーションをつける方法について説明していこうと思います。やっぱりメニュー選択時などにアニメーションがあるのとないのとではそのページの面白さというのが全く違ってきますよね。アニメーションはホームページの内容とは関係しない部分ですが、ホームページを訪れた人が飽きないように楽しめるコンテンツとしてあった方がいいと思います。ですので、より特徴的で印象的なホームページを作るためにアニメーションはぜひマスターしておきたいところです( *`ω´) 目次 1.ホバーアニメーション 2.ホバーアニメーション(アンダーライン) 3.固定ヘッダーのサイズ縮小アニメーション 4.ハンバーガーメニューを使おう 5.まとめ 1.ホバーアニメーション 以下の例でHeader部分のメニュー部分(Home・About・Topics)にマウスカーソルを合わせて(以下、ホバーすると言う)みてください。スマートフォン・タブレットの方はタッチしてみてください。 See the Pen Hover1 by Charme (@charme0525) on CodePen. メニュー部分をホバーするとその部分の背景色と文字色が変化しましたね。 クリックできる部分を強調する際にこのホバーアニメーションをよく使います。このアニメーションはCSSのみで実装でき、CSSソースコード内のコメントアウトで示した通り、その箇所のコードにより動作しています。 2.ホバーアニメーション(アンダーライン) 次はサブメニューのアニメーションを追加していきます。今度はホバー時にアンダーラインが表示されるアニメーションです。以下の例のサブメニュー部分を先ほどと同じようにホバーしてみてください。スマホタブレットの方はタッチしてみてください。 See the Pen アンダーラインホバー by Charme (@charme0525) on CodePen. ホバーするとアンダーラインが左から右に現れ、ホバーを外すと右に消えていったかと思います。このアンダーラインホバーアニメーションでは他にも下から表示される場合や右から左に現れるといったようにさまざまな方向からの表示をすることができます。これもCSSのみで実装できるので、CSSのコメントアウトで示した部分を利用してみてください。 3.固定ヘッダーのサイズ縮小アニメーション 今度はヘッダーを固定表示した際のアニメーションです。ヘッダーを固定表示するとページ内のどの部分でもメニューを選択することができて大変便利なのですが、そのままのサイズで固定されると画面の邪魔になってしまいます。そこでページをスクロールした際にヘッダーの高さが小さくなって固定表示するというアニメーションを紹介します。実際に下の結果をスクロールしてみてください。 See the Pen Untitled by Charme (@charme0525) on CodePen. このヘッダーサイズを変更するアニメーションではスクロール量を検知する必要があります。そのため、JavaScriptコードが必要となってくるのですが、jQueryを用いて簡単に実装できます。上の例でもjQueryで記述しています。 ヘッダーサイズを縮小する際にヘッダー内の表示方法を変更しないとメニュー部分がヘッダーからはみ出してしまったり不恰好になってしまうので、スクロールされるとサイズ変更とともにflexboxを使ってページタイトルとメニュー部分を配置し直しています。 jQueryのコードを簡単に解説するとvar scrollPosに現在のスクロール量を入れ、その値が100pxより大きくなるとheaderクラスにheader-animationクラスを追加します。スクロール量が100px以下だとheader-animationクラスを削除します。 さらに、header-animationクラス追加時の動作をCSSで記述しています。 4.ハンバーガーメニューを使おう まずハンバーガーメニューってなに?と思われたかもしれませんが、下の例に見覚えのある3本線のメニューアイコンがあるかと思います。それをクリック・タッチしてみてください。 See the Pen ハンバーガー by Charme (@charme0525) on CodePen. ハンバーガーメニューを押すとサブメニューが表示されましたね。もう一度押すと、元に戻ります。 これはスマホ・タブレットなどのディスプレイが狭いデバイスでよく見るメニュー表示方法で、必要なときにメニューボタンをタップしてメニューを出すようにすることでメイン部分を広く見せることができます。 モバイル向けのサイトを作る際には必ずと言っていいほど使用されるので、ぜひ覚えておきましょう_φ( ̄ー ̄ ) 5.まとめ 今回は簡単なアニメーションを実際のホームページに組み込んだ場合の紹介となりました。アニメーションの実装方法はコピペで大丈夫です。数行程度で書けるのでコピペしてるうちに慣れていくと思います。 CSS・JavaScriptのアニメーションについての詳細な説明は別記事にて作成予定ですので、作成次第リンクを貼ります。 最後まで見ていただきありがとうございました。
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む