20190728のJavaScriptに関する記事は28件です。

NuxtのHTML templateはpugを使わないメリットもある

こんにちは、プログラミングスクールのレビューサイト「スクールレポート」を運営しているアカネヤ(@ToshioAkaneya)です。

NuxtのHTML templateはpugを使わないメリットもある

タイトルの通りです。pugは誰でも綺麗にコードがかけるため素晴らしいのですが、エディターの保管が素のHTMLに比べて弱い
という傾向があります。僕のしようしているIntelliJでもその傾向があります。(ちゃんと設定すれば問題ないのかもしれませんが...)

その点HTMLの補完は正確です。この記事が参考になれば幸いです。

終わりに

Ruby on RailsとVueで作成したプログラミングスクールのレビューサイトを運営しています。良ければご覧ください。https://school-report.com/

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

スタートアップで働いて得られた開発tips

スタートアップに入り1年以上が経ちました。日々目まぐるしく環境が変化していく中でやってしまった失敗。
どんな失敗に遭遇し、どう対応したかを簡単にまとめてみました!

ぜひ、参考になれば幸いです!
(もし、需要がありそうな項目があれば深掘りして再投稿してみます)

【フロント・サーバーサイド】

ページのレスポンス速度向上

当初とにかく指摘されたのはサイトのレスポンス速度でした。
そこで、レスポンス速度向上に取り組み、
結果的にPCのトップページの評価が40点台MAX96点にまで向上させることができました。
ちなみにモバイルは70点付近です(笑)
測定ツールはPageSpeed Insights(ページの読み込み時間の測定・改善策の提案をしてくれるサイト)です。

【PCでは96点】
スクリーンショット 2019-07-28 21.22.07.png

行った対策は以下です。

【インフラ系】

ElasticBeanstalkのインスタンスタイプはElasticBeanstalkから変更する

ElasticBeanstalkで環境を作成している場合、EC2からインスタンスタイプの変更しないようにしましょう。
依存関係などを崩してしまうため、うまく行きません。(僕はこれでサーバーを半日落しました(泣))
僕はこの時、環境の再構築をすることでサーバーの復活を果たすことができました。

Elastic Beanstalk > ダッシュボード > アクション > 環境の再構築

エラーページは設定しておく

こちらはサーバーを落としてしまった際、またはメンテナンスの際に503画面が表示されてしまうとユーザーの不信感につながりかねません。404や500はrails側で設定できますが503はサーバーが落ちてしまっているのでインフラの方で設定して置かなければなりません。

CloudFrontのCustom Error Responseを利用して、S3上にあるSorryページを表示する

スロークエリの設定

クエリの重さは、目に見えているページだけでなくサーバーにも大きな負担をかけてしまいます。
その結果、バックグラウンドでも影響が出てしまうことがあります。(メールが大量に届くor届かなくなる、またはjobなど)常にチェックできるように設定しておきましょう!

Amazon RDS for MySQLでスロークエリーログを出力させる手順

ヘルスが変化した際の通知

様々な要因がありますが、ヘルスがSevereになって気づいたら数時間サーバーが止まってしまっていた。なんてことにならないようにヘルス変化は常に通知しておきましょう。Elastic Beanstalkで環境を構築している際は

Elastic Beanstalk > 設定 > 通知 > 通知したい先のアドレス設定 > 適用

で設定できます!

【その他】

ドキュメント管理で効率的に時間を活用する

僕の独自の共有に時間がかかるランキングは

1位 開発フローの把握
2位 システムの環境構築
3位 ユーザーからのお問い合わせ対応

特に上位2つは出入りの激しい(笑)スタートアップの開発リソースを減らしてしまう深刻な問題だと思います。
一番対応の簡単な方法としてドキュメント管理に力を入れています。
もし、こんな質問がきたらこのマニュアルを渡すのようなパターンができていればいいドキュメント管理ができているなと思うようになりました。
(ドキュメントはGithubのwikiにまとめています)

Githubのタグを使い、新加入のエンジニアのキャッチアップ迅速に

ドキュメント管理関連でもう一つ。
途中から参加したエンジニアさんはシステムの把握に時間がかかると思います。
システムの把握に時間をかけすぎてしまうのはもったいないのですが、かけなさすぎると思いがけないバグを生んでしまいます。これがデータ保存系・更新系だと対応が大変です。。

そこで僕がよく使うのはGithubのアサインタグ付けです。
心がけていることは

①自分が担当するissue・pullrequestには必ずself assignする
②ある程度シリーズ化したorしそうなissue・pullrequestにはタグづけをする

この2つを徹底すると以下のことがおきます。
→タグ一つ検索するだけで誰がどのタスクを担当し。どんなコードを書いたかがわかる
→上記がわかるとトラブルやバグ仕様の把握をしたいときに誰とコミュニケーションをとればいいかがすぐわかる

この流れが出来上がると、間に常勤のエンジニアが入ってコミュニケーションの橋渡しをせずに済み、コミュニケーションコストが大幅に下がります!

【イメージ】
rails本家のコードを使わせていただきました。本当はこんな開発者おりません。
スクリーンショット 2019-07-28 22.50.33.png

【タグでソート】
スクリーンショット 2019-07-28 22.55.11.png

【アサイン者でソート】
スクリーンショット 2019-07-28 22.57.07.png

おまけ

副業案件お待ちしております。得意分野はRuby on Railsです!
職務経歴書

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

最近使ったJavaScriptの標準オブジェクトについて

windowオブジェクト

これは使ったというより前よりちょっと理解した程度ですが。
全てのオブジェクトの上層に位置するもの。

locationオブジェクト

以下のように記述する事でlocationオブジェクトからパラメータを取得できます。
あまり良い実装ではないと思いますが、取得したパラメータを使って表示内容を書き換える際に利用しました。

location.search

documentオブジェクト

JSを使ってHTMLや値を取得する時にこのコードが動くか?
それをconsoleで試すと開発速度が3倍ほどになった気がします。(体感)

historyオブジェクト

これを直接使ったわけではないですが、
history.backを参考にRailsの設定を調べることができました。

最後に

他にもscreenオブジェクト、navigatorオブジェクト、self、frames[]
なんかの使ったことのないオブジェクトがあるので学んでいこうと思います。

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

Ruby on Railsのヘルパーメソッドで<script>タグを書く。

ビューヘルパーって慣れない内は難しいよね

結構コロンがいるのかとか、どこに括弧をつけるとかなかなか分かりにくい。
簡単にJSのテストをする時に、viewファイルに以下のタグを書くこともあると思いますが、できればビューヘルパーを使いたいですよね。

sample.html.erb
<script>
</script>

実際のコード

以下のように書くと同じ事になります。

sample.html.erb
<%= javascript_tag do %>
<% end %>

ちなみにクラスはこうやって付けられます、
でも多分(class: "hoge")とかでも大丈夫だしこっちの方がスマート。
記事書いている今試せないのでちょっとあやふやです。

sample.html.erb
<%= javascript_tag(:class => :hoge) do %>
<% end %>

最後に

まあテストだけならビューヘルパー使わなくてもいいじゃないかという話かもしれませんが、
気分的に気になる人はこの書き方を使ってみては?

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

Scrapyでformを送信する際にJavaScriptを回避する

問題

  • Scpapy は JavaScript で生成されたコンテンツにアクセスできないため、例えば、確認のために表示されるアラートウィンドウの「OK」をクリックして先に進む、などができないことがある。

根本的な解決策

  • JavaScriptを制御できるSeleniumなどを併用する。
    • (参考記事)StackOverflow
      • ScrapyとSeleniumを併用するサンプルコードもある

簡単な解決策(条件付き)

  • 場合によっては もっと簡単に解決できる。
  • 以下のform送信ボタンのように、onclick 処理でJavaScriptのイベントが発動している場合には、scrapyの機能で回避できる。
<input type="submit" onclick="return confirm('確定していいですか?')" name="submit" value="確定" class="button">
  • クラスオブジェクト scrapy.FormRequest.from_response のパラメータ dont_clickTrue に指定することで、送信ボタンのクリックを省略して form を送信できる。これを利用すれば、onclick によって発動するイベントを回避して form が送信される。
    def submit_form(self, response):
        yield scrapy.FormRequest.from_response(
            response,
            formcss = 'table#table1 > tr:nth-child(1) > td.value > form',
            formdata = dict( decision = '1' ),
            dont_click = True
        )

〜おしまい〜

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

[vue-router] BeforeRouteLeaveが2度発火する

TL;DR

  • Vue-RouterBeforeRouteLeave等ナビゲーションガードが二回発火する場合がある。
  • 遷移先のコンポーネントに何らかのリダイレクトがあるときに発生する。
  • toを調べることで回避可能。

同じ問題を扱ったissue
=> https://github.com/vuejs/vue-router/issues/2102

本件は仕様として扱われるようなので、今後しばらく対応される見込みはなさそう。

再現環境

  • Chrome バージョン: 75.0.3770.142
  • Nuxt 2.6
  • vue-router 3.0

リポジトリと検証動画

https://github.com/IKKO-Ohta/invoke_brl_twice
output.gif

トップページ - ページ1 - ページ2という構成で、

  • ページ2のバックボタンを押すと、window.confirm()を表示する。「OK」を押すとページ1に遷移する。
  • ページ1では、ページ2からの遷移をキャッチするとトップページに飛ばす

想定としては、例えば商品販売サイトにおいて、ページ2が注文完了画面で、ページ1が決済確認ページ、くらいのとき。
しかしこのとき、ページ2のBeforeRouteLeave が2度発火する。window.confirm()のOKを1度押しても再度ダイアログが表示される。

どうして起こるか?

予想

ex.png

2 => 1, 1 => top のような遷移をしそうな気がする。

実際

rec.png
実際のページ2のbeforeRouteLeaveはこのように挙動する。

beforeRouteLeaveが 2=>1 と 2 => top の2回分呼ばれる。引数のfrom.nameto.nameの中身を調べると確かにそうなっている。1=>top へのリダイレクトがこの2回目の遷移を誘発させる。このとき1=>topの遷移でもない! あくまで from="2", to="top"のbeforeRouteLeaveが走る!

解決策

ページ2beforeRouteLeavetoを検査し、一回目の遷移についてガードしないようにすれば良い。

before

  beforeRouteLeave(to, from, next) {
    window.confirm("このページを離れますか?") ? next() : next(false);
  }

after

  beforeRouteLeave(to, from, next) {
    if (to.name === "first") {
      next();
      return;
    }

    window.confirm("このページを離れますか?") ? next() : next(false);
  }

なお、afterは可読性の低いコードなので(一見「戻る」のときにはガードしない、というコードに見える)公式issueなどを引用して適宜コメントに追記することを推奨する。

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

puppeteerを使ってyoutubeの新着動画を探そう

動機

良い紳士向けMMDが出ているかもしれない。
チェックしなくてはならない。

成果物

youtube_puppet(github)
※slackに関する処理にtoken情報が記載されていたため、そのjsファイルだけはpushしていない状態です。
上記をbat経由で呼び出しています。
トリガーとしてタスクスケジューラーを使用しています。
(将来的にはタスクスケジューラーは止めて別のにする予定です。)

最終的な成果物の出力イメージは以下画像
image.png

概要

簡単に成果物の概要を説明をします。
まず、実現したいこととしては「本日に投稿された紳士向けMMD動画を取得し、それをslack上に特定の時間に流したい」ということです。

・紳士向けMMD動画を取得する方法を考えます。
これはいわゆるスレイピング技術で解決できるでしょう。
クローラーとも呼ばれます。
以下参考
ウェブスレイピング(wiki)

紳士向けMMD動画を取得することに関しては上記で完了しました。

・slackに投稿する方法を考えます
slackに投稿することに関しては以下を参考にすると良いでしょう。
postMessage
slackAPIを使いたいが、どこでtokenを発行するのか分からない人は下記リンクを参照してください。
https://api.slack.com/apps

・特定の時間に起動させる方法を考えます
手段は色々ありますが、
最も簡単な方法だと簡易なbatを書き、それをタスクスケジューラーで呼び出すことだと思います。
以下参考
Windowsでbatファイルを自動実行したい時(タスクスケジューラの設定)

新しいのが好きな方であればfirebaseとか使っちゃえばいいと思います。
流行ってますし。

以下もうちょっと詳しく解説。

URLの取得

image.png

上記画像のように何を検索するか、対象期間はいつにするかを設定し、その際のURLを使えば望みの物が拾えると思います。
今回は「紳士向け MMD」と検索し、フィルターを使い「今日」に絞りました。

puppeteerの出番です。

puppeteerを使い、スレイピングしていきましょう。
そのためにまずF12を押してhtmlを見ていきましょう。
※各自で確認お願いします。

結果だけ言うと欲しいものとしては
document.getElementsByClassName('text-wrapper')
こんな感じで一覧が取れそうでした。
そこからさらに必要な情報を抜き取っていけば望みのものが出来そうです。
具体的にはtext-wrapper配下にある
#meta > #title-wrapper > .title-and-badge > a
のhrefがあれば十分です。
なのでpuppeteerを使ってそれを取得しましょう。

puppeteerの使い方は下記が参考になると思います。
puppeteerでの要素の取得方法

puppeteerの基礎から知りたい方は書籍を買うといいでしょう。
私は下記の書籍のみ購入し、後はネットで調べました。
Puppeteer入門 スクレイピング+Web操作自動処理プログラミング Kindle版

一度書店で立ち読みしてから購入を検討されると良いと思います。

slackAPIを使って投稿

実際のソースを貼るとtokenとか丸見えだったんです。
tokenだけ別ファイルにするとかしてpushしようと思ったんですがめんどくさくなったので
コードを貼り付けます。

const request_promise = require("request-promise");
const SLACK_TOKEN = 'とーくん';
const CHANNEL_ID = 'ちゃんねるID';
const USER_NAME = 'ぼっとのなまえ';

exports.send_message = items => {
  const messages = items.map(item => {
    return item.href;
  });

  messages.forEach(message => {
    (async () => {
      console.log(
        await request_promise({
          uri : 'https://slack.com/api/chat.postMessage',
          method : "POST",
          headers : {
              'content-type' : 'application/x-www-form-urlencoded',
              'charset' : 'utf-8'
          },
          form : {
            token: SLACK_TOKEN,
            channel: CHANNEL_ID ,
            username: USER_NAME ,
            text: message
          },
          json : true
        })
      )
    })()
  });
}

slackのメッセージ投稿については下記が参考になると思います。
NodejsでSlackにメッセージ投稿(2019年1月版)

最終的な成果

一番上の参考画像の通り、slackにURLが投稿されて、動画の一覧が出てきました。

あとがき

今回の成果物ですが、main.jsに記載されている1か所を変えれば恐らく好きな動画をスレイピング出来る物になってると思います。
12行目のawait page.goto('');のURLの部分です。
良ければ変更して使ってください。
使ってくださいと言っている割にcloneしてその部分を変更するだけで使えるものになってないのは申し訳ないなと思います。
cloneもしたくないけど紳士向けMMD動画の新着が見たい方はtwitterでDMください。
何とかするかもしれません。

結構簡単に作れたのでpuppeteer面白いなぁという感想です。
コードは結構拾ってきた部分が多いので記述的にこれで良いのか不安な部分があったりします。
変な部分があれば良かったら指摘してください。

以上です。
最後まで読んでいただいた方ありがとうございました。

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

AMP(Accelerated Mobile Pages)入門

概要

こんにちは、都内でフロントエンドエンジニアをしているかめぽんです。最近、Next.jsのバージョンアップにて対応したことで一層話題になっているAMPですが、僕自身AMPのことをそこまで注目していなくてたまたま調べてみたら割とおもしろうそうだったので、インプットした内容をまとめてみました。サンプルコードなども載せていますので、AMPの基礎的な情報の参考にしていただければと思います。

本題

AMPとは、メリット

AMPとはAccelerated Mobile Pagesの略で、GoogleとTwitterが発足したモバイルページを高速に表示させるためのプロジェクト、またはAMP HTMLというフレームワークのことを指します。

AMP自体は以下の構成要素で成り立っています。

  • AMP HTML
  • Custom Styling(CSS)
  • AMP js
  • AMP Cache(CDN)

静的なHTML, CSS, jsは普通のwebと同じですがAMP HTMLはAMP独自の仕様が定められており、その仕様どおりに沿ってモバイルサイトを作成することで表示速度を高速にすることができます。
加えて、AMPで作られたサイトはクローラに取り込まれた後AMP Cacheと呼ばれるCDNに保存されます。つまり、従来のWebと異なるのは、実際にURL先にリソースを取りに行くのではなくCDN先にリソースを読みに行くため通常アクセスよりも速く表示できることが特徴です。

AMPの基本的なスタンスとしては「読み込みに時間がかかるようなことはさせない」ので高速化を進めていく代わりに、現段階でjsを非同期でしか読み込むことができないという制約があり、インタラクティブで動的なサイトまたはそのようなアプリはまだ不向きなようです。

実践

ここからは実際のソースコードでやっていきたいと思います。

How to use AMP

HTML宣言

最初にHTML宣言ですが以下のように記載します。雷マークがampを意味します。<html amp lang="ja">でも意味は同じです。

<!doctype html>
<html ⚡="" lang="ja">

ampはutf-8のみ対応そしてviewportの設定が必須なので記載します。

<link rel="canonical" href="index.html">
<meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">

application/ld+jsonの設定

ampの設定の中でも大事なのが、LD+jsonです。こちらは、本サイトのamp構造を正しく伝えるための情報になります。Goolgeがschema.orgとLD+jsonで記述することを推奨しています。

<script type="application/ld+json">
{
  "@context": "",
  "@type": "Article",
  "headline": "Welcom to AMP SAMPLE",
  "image": [
    "sample.jpeg"
  ],
  "datePublished": "2019-07-28T08:00:00+08:00"
}
</script>

スタイルの読み込み

ampはインラインスタイルしか読めないので、cssファイルを用意するのではなくamp-boilerplateとして登録しておきます。

<style amp-boilerplate>a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}table{border-collapse:collapse;border-spacing:0}</style>

javascriptの読み込み

ampでは、jsは非同期で読み込むことを推奨しているので、async属性をつけましょう。

<script async src="https://cdn.ampproject.org/v0.js"></script>
<script async src="./script.js"></script>

あとはいつも通りテンプレートを書けば最低限の対応は大丈夫です。LD+jsonに関してはもっと書くべきですがここでは割愛します。以下、ここまでのサンプルを貼っておきます。

<!doctype html>
<html ⚡="" lang="ja">
  <head>
    <meta charset="utf-8">
    <title>AMP SAMPLE</title>
    <link rel="canonical" href="index.html">
    <meta name="viewport" content="width=device-width,minimum-scale=1,initial-scale=1">
    <script type="application/ld+json">
    {
      "@context": "http://schema.org",
      "@type": "Article",
      "headline": "Welcom tao AMP SAMPLE",
      "image": [
        ""
      ],
      "datePublished": "2015-02-05T08:00:00+08:00"
    }
    </script>
    <style amp-boilerplate="">body{-webkit-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-moz-animation:-amp-start 8s steps(1,end) 0s 1 normal both;-ms-animation:-amp-start 8s steps(1,end) 0s 1 normal both;animation:-amp-start 8s steps(1,end) 0s 1 normal both}@-webkit-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-moz-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-ms-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@-o-keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}@keyframes -amp-start{from{visibility:hidden}to{visibility:visible}}</style><noscript><style amp-boilerplate="">body{-webkit-animation:none;-moz-animation:none;-ms-animation:none;animation:none}</style></noscript>
    <style amp-custom="">a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video{margin:0;padding:0;border:0;font-size:100%;font:inherit;vertical-align:baseline}article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section{display:block}body{line-height:1}ol,ul{list-style:none}blockquote,q{quotes:none}blockquote:after,blockquote:before,q:after,q:before{content:'';content:none}table{border-collapse:collapse;border-spacing:0}</style>
    <style>html{font-family:ヒラギノ明朝 ProN W6,HiraMinProN-W6,HG明朝E,MS P明朝,MS PMincho,MS 明朝,serif}.wrap{position:relative;width:auto;text-align:center;margin-top:80px}.title{position:relative;font-size:36px;color:#402c2c;font-family:Cambria,Georgia,serif;font-style:normal;-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variant:normal;font-weight:500;line-height:39.6px;letter-spacing:2px}.title::after{position:relative;content:"";display:block;bottom:0;width:50px;height:3px;top:18px;background:#402c2c;margin-left:auto;margin-right:auto}.sub-title{position:relative;font-size:32px;color:#402c2c;font-family:HG明朝E,MS P明朝,MS PMincho,MS 明朝,serif;font-style:normal;-webkit-font-feature-settings:normal;font-feature-settings:normal;font-variant:normal;font-weight:500;line-height:36.6px;letter-spacing:2.5px;margin-top:80px}.flex-wrap{width:970px;height:auto;margin-left:auto;margin-right:auto;display:flex;flex-wrap:wrap;justify-content:space-between;margin-top:60px}.box{width:300px;height:300px;margin-top:40px;-o-object-fit:cover;object-fit:cover;border:none;background:0 0;overflow:hidden;background-position:50%;background-size:cover;background-repeat:no-repeat;transition:all .2s ease;background-image:url(img/sample.jpg)}.box:hover{filter:drop-shadow(3px 3px 4px #909090f0);transform:translate(-1px,-1px)}@media screen and (max-width:768px){.flex-wrap{width:100%}.box{width:33vw;height:33vw;margin:5px}}</style>
    <script async src="https://cdn.ampproject.org/v0.js"></script>
  </head>
  <body>
    <div id="app">
        <div class="wrap">
            <h1 class="title">Portfolio</h1>
            <p class="sub-title">Creative</p>
        </div>
        <div class="flex-wrap">
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
            <div class="box"></div>
        </div>
    </div>
  </body>
</html>

また、今回作ったページとGithubのリンクも貼っておきます。ぜひ、参考にしていただけたらと思います。

サンプルページ
Github

最後にページが出来上がったらGoogleが提供しているツールでAMPページとして有効かどうかチェックしてくれますので、コードでも良いですしURLを貼り付けて調べることができます。

Google Search Console

まとめ

  • AMPとはモバイルのページを高速に表示させる技術
  • AMPに対応はAMP用のhtmlが必要
  • AMPはAMP独特の制約がある

静的なデータでAMP対応のサイトができました。AMP対応により、サイトがかなり高速に表示されるのはかなりのアドバンテージになると思います。特に、メディアやCMS、ニュースサイトやECサイトなどサイトの速さが命のページにとってはかなり強みになると思います。逆に、javascriptの取り扱いに制約を感じることがあるのでWebアプリケーションとしてAMPの導入はもう少し様子見かなという印象です。Webアプリに関してだとPWAという選択肢もあるので、サービスの特徴によって活用していきたい所存ではあります。また、フロントエンド領域に置いてキャッシュの取り扱いやCDNの知識が必須になってきているので、その部分のキャッチアップもしていきたいと思いました。

参考

https://search.google.com/test/amp?hl=ja
https://amp.dev/ja/

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

NeovimでJSDocを使う

NeovimでJSDocを使う

JSやGASでコードを書いていく時に、コメントはどのように書いているだろうか。
手軽に分かりやすく、コードを共有した人が確認しやすいものはないかと調べた結果、JSDocというものを知った。
jsdoc.vimを参考にインストールを行った。

環境

  • macOS Mojave ver.10.14.5
  • Neovim
  • vim-plug(vimプラグインマネージャー)

インストールの仕方と使い方

~/.config/nvim/init.vim
" jsdoc
Plug 'heavenshell/vim-jsdoc'
nnoremap j :JsDoc <Enter>

上記を~/.config/nvim/init.vimに記入した。

nnoremap j :JsDoc

の部分は、マッピングをさせている。
通常は:JsDoc+<Enter>で実行されるので、これをjに置き換えた。

これでビジュアルモード時にobjectやfunction上でjを押すだけでJSDocが作成される。

以下はJSDocに関しての簡単なまとめである。詳しくは参考文献のリンクを参照にしてほしい。

コメントの構文に関して

コメントは適切であるかを確かめる必要がある。

/** 
 * JSDocコメントはスラッシュと2つのアスタリスクから始めます。 
 * インラインタグは {@code this} のように波括弧で囲みます。 
 * @desc ブロックタグは必ず行の先頭から開始します。 
 */

Google JavaScript スタイルガイド - 日本語訳より

JSDocのインデント

ブロックタグ内での改行が必要な場合はスペース4つ分のインデントが必要。

/** 
 * 長いparam/returnアノテーションの説明文の折り返し方を示します。 
 * @param {string} foo これは1行でおさめるには長すぎるパラメータの 
 *     説明文です。 
 * @return {number} この戻り値の説明文は長すぎて、とても1行の中には 
 *     入りきりません。 
 */
project.MyClass.prototype.method = function(foo) { 
  return 5; 
};

Google JavaScript スタイルガイド - 日本語訳より

ファイルへのコメント

@authorで作成者に関する情報を添付する。

/**
* @fileoverview ファイルの説明を行う。
* 依存関係についての情報
* @author aaa@bbb.com
*/

classへのコメント

/** 
 * Humanクラス 
 * @param {number} height ヒトの身長
 * @param {number} weight ヒトの体重
 * @constructor 
 */
class Human{
  constructor(height, weight){
  this.height = height;
  this.weight = weight;
  }
}

メソッド・関数へのコメント

/** 
 * 日付を処理し、休みの日かどうかでtrueかfaleを返します。 
 * @param {date} date 日付
 * @return {boolean}  
 */
function isSunday(date){
  //...
}

参考文献

Google JavaScript スタイルガイド - 日本語訳
Jsdoc cheatsheet

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

[JavaScript]undifinedをStringオブジェクトの引数に入れたら文字列undifinedに変換されてしまった話

undifinedをStringオブジェクトの引数に入れたら文字列undifinedに変換され、意図しない処理が入ってしまってびっくりしたので、投稿。
JavaScript初心者なので、間違い指摘はコメントください。

結論としては、「undifinedかどうか判断してから文字列変換しよう」です。
 undifinedかどうか判断は、typeof a === "undefined"で比較などで対応しよう。
 (undefinedは、typeof演算子をかけると文字列"undefined"に変換されるため)

実際、どのようにundifinedが文字列に変換されるか見てみましょう。

let a;
console.log(a + ":" + typeof a); // undefined:undefined

let b = String(a);
console.log(b + ":" + typeof b); // 'undefined:string'

let c = a.toString(); // Uncaught TypeError: Cannot read property 'toString' of undefined
// console.log(c + ":" + typeof c); 

let d = a + "";
console.log(d + ":" + typeof d); // 'undefined:string'

let e = a.valueOf(); // Uncaught TypeError: Cannot read property 'valueOf' of undefined
// console.log(e + ":" + typeof e); 

toStringやvalueOfは例外が生じます。
Stringやobj+""は文字列"undefined"に変換されます。

私の場合、Stringオブジェクトで変換していたため例外が生じず、気づくのが遅れてしまいました。

ただし、Stringオブジェクトは、「toStringでプリミティブ値が返されなければvalueOfを呼び、その値を文字列に変換する」というプロセスとなっているため、文字列変換において安定して使えるようです。
toStringやvalueOf、obj+""では、必ず文字列変換されるという保証がないとのことです。
詳しくは、下記のリンク先を読もう。(現在、勉強中・・・)

obj+""≠obj.toString()≠String(obj)っていう話 - Qiita より

toStringはJavaのように文字列の連結に暗黙的に使用されるメソッドではない。
obj+""はまずobj.valueOf()を呼び、obj.toString()を呼ばないことがある(がしかし、実は一つだけ例外がある)。
String(obj)は間違いなく obj.toString() を呼ぶ。

valueOfとtoStringメソッドの水深43cmぐらいの深さの話 - 三等兵

オブジェクトを文字列に変換するならtoStringで、数値ならvalueOfという傾向がある。(valueOfの場合正確には基本型に変換すると表現するのが正しい)
toStringのみでオブジェクトを変換するときはvalueOfに一切触れずに行う。逆にvalueOfのみで変換するときは、文字列のときはtoStringを通じて変換する。ただし、toStringが使えないようならそのままvalueOfで返す。

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

Vue.jsについての基礎(インストール〜基本構文)

はじめに

おはようございます。こんにちは。こんばんは。
ワタタクです。
今回はVue.jsについて見けいけたらいいなと書いています。
Vue.jsに関しては少し触ったことがある程度なので、今後の為にと勉強させていただきますので
もし間違いとかありましたらご教授、アドバイスなんかいただけたら幸いでございます。
では早速参ります。

Vue.jsとは?

公式サイト

Vue.js(ヴュージェイエス)、またはVueは、Webアプリケーションにおけるユーザーインターフェイスを構築するための、オープンソースのJavaScriptフレームワークである。他のJavaScriptライブラリを使用するプロジェクトへの導入において、容易になるように設計されている。一方で高機能なシングルページアプリケーション(SPA)を構築することも可能である。

chromeの拡張機能のvuejs-devtoolsを入れて置くと便利。

環境構築

今回はCDNを使わずvue-cliを使います。

nodeがインストールされているか確認する

$node -v
v11.2.0

もし、コマンドを叩いてもversionが表示されなかったらnode.jsをインストールしてください。
以下も確認して置いてください。

$npm -v
6.9.0

vue.jsのインストール

上記のことを確認したら、いよいよvue.jsをインストールしていきます。

$ npm install -g vue-cli

成功すれば以下を確認してください。

$vue -V
3.1.1

確認できれば次のコマンドを実行してください。

$ vue init webpack test-vue

※test-vueの部分はプロジェクト名&ディレクトリ名になります。
いろいろ聞かれますが、全部EnterでもOKです。
実行が終わったら下記を実行してください。

$ cd test-vue
$ npm run dev

localhost:8080にブラウザからアクセスしてみてください。Vueアプリケーションの土台ができており、
下記の画面が表示されるはずです。

test-vue.png

これで環境構築は終了です。
お疲れ様でした。

基本構文

いろんなファイルが出来ててキョどりますが大丈夫です。
基本的に難しい設定をしなければ、だいたいsrcディレクトリの中のVueファイルだけをいじるだけです。

v-showディレクティブ

まず最初に要素を表示するかどうかを決める条件付きレンダリングです。

HelloWold.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
<!--////////////////追加/////////////////-->
    <h2 v-show="showText">表示項目</h2>
<!--/////////////////////////////////-->
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
//----------------------追加----------------------
      showText: false
//-----------------------------------------------
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

v-showディレクティブはタグの中にいれて書きます。上のコード内ではイコールでshowTextという変数をみてます。この変数の名前は任意ですが、これがfalseなら非表示、trueなら表示となります。

イベントハンドリング

ボタンをクリックしたら表示、非表示が切り替わる仕組みを作ります。
早速コードを見てみましょう。

HelloWold.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
<!--////////////////追加/////////////////-->
    <button v-on:click="toggle">toggle</button>
<!--/////////////////////////////////-->
    <h2 v-show="showText">表示項目</h2>
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      showText: false
    }
  }//追加
  ,methods: { 
    toggle: function () {
      this.showText = !this.showText
    }
  }
   //-----------------------------------------------
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

v-onディレクティブ

buttonタグにv-on:clickというものがついてます。v-onでイベント発火時の JavaScript の実行が可能になります。clickしたら呼ばれる関数名をtoggleにしてます。これはmethodsオブジェクトの中で定義されています。
v-on:は@とも書ける

methodsオブジェクト

methodsオブジェクトの中に実行したい関数をガシガシ定義していきます。これをv-on:clickで読んだり、他の関数から叩いたりします。

thisについて

data内で定義した値はthis.showTextのようにthisを使って参照していきます。

v-modelディレクティブ

v-modelはVue.jsを使ってフォームを構築する際によく使う機能です。
v-modelはv-onとv-bindをまとめて一行で書くためのシュガーシンタックスです。
例としてフォームに入力した文字数をカウントする仕組みを作ります。

HelloWould.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
<!--////////////////追加/////////////////-->
    <input type="text" name="text" v-model="count">
    <h3>現在の文字数:{{ charaCount }}文字</h3>
<!--/////////////////////////////////-->

  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      //追加
      count: ''
    }
  }
  ,//追加
  computed: {
    charaCount: function() {
        return this.count.length;
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

ここで重要なのは、「computed」です。
computedという機能を使えば、dataで保持している値に変更がある度に、それを利用した値をリアルタイムに生成できます。

ついでにもう一つ。
computedに似ているのですが、「watch」という機能を紹介します。
watchは、dataで保有している値に1対1で対応して、その値が変更されたときに動作するメソッドのようなものです。
Vue公式ガイドではwatchよりもcomputedを推してます。

以上が双方向データバインディング(v-model)の基本的な使い方で、webアプリを作成するときには非常によく使う手法ですので、必ずマスターしてください。

v-forディレクティブ

v-forディレクティブを利用して、配列の要素を繰り返し表示させてみます。

HelloWold.vue
<template>
  <div class="hello">
    <h1>{{ msg }}</h1>
<!--////////////////追加/////////////////-->
    <table>
          <thead>
              <tr>
                  <th>タイトル</th>
                  <th>著者</th>
              </tr>
          </thead>
          <tbody>
              <tr v-for="book in books" v-bind:key="book.id">
                  <td>{{ book.title }}</td>
                  <td>{{ book.author }}</td>
              </tr>
          </tbody>
      </table>
<!--/////////////////////////////////-->
  </div>
</template>

<script>
export default {
  name: 'HelloWorld',
  data () {
    return {
      msg: 'Welcome to Your Vue.js App',
      //追加
      books: [
          { id: 1, title: '坊っちゃん', author: '夏目漱石' },
          { id: 2, title: '人間失格', author: '太宰治' },
          { id: 3, title: 'ノルウェイの森', author: '村上春樹' }
      ]
    }
  }
}
</script>

<!-- Add "scoped" attribute to limit CSS to this component only -->
<style scoped>

</style>

booksプロパティとしての配列からbookという変数名に置き換えたオブジェクトを取得しています。v-forディレクティブは一般的に下記のような記述をします。

v-for="変数名 in 配列やオブジェクト"

また、v-forディレクティブを利用する場合、v-bindディレクティブで一意の(ユニークな)keyプロパティを設定する必要があります。
最低、今までのことが理解できればVue.jsの基本構文は大丈夫だと思います。
あとの描き方なんかは公式サイト参照でお願いします。

コンポーネント

初めて触る人に対して、一番理解に苦しむと思いますが、(作者は苦労しました。)
Vue.jsに限らず、Reactなどのjavascriptフレームワークでは必須の考え方なので理解しておきましょう。
マップアプリを作りながら解説していきます。
その前に、以下のコマンドを入力してください。

npm install --save materialize-css@1.0.0-rc.1

次に「main.js」に以下を記述し、vue-routerの設定をコメントアウトで潰してください。

js;main.js
import Vue from 'vue'
import App from './App'
//import router from './router'
<!--//////////////追加///////////////////////-->
import 'materialize-css'
import 'materialize-css/dist/css/materialize.min.css'
<!--/////////////////////////////////////-->
Vue.config.productionTip = false

/* eslint-disable no-new */
new Vue({
  el: '#app',
  //router,
  components: { App },
  template: '<App/>'
})

その次に、「index.html」に以下を記述しGoogleMapApiを読み込んでください。

これで準備が整いました。
では、「components/Map.vue」をつくります。
作れたら、App.vue(親コンポーネント)に以下のコードを記述してください。

App.vue
<template>
  <div id="app">
    <nav class="blue navbar">
      <div class="nav-wrapper">
        <a href="#" class="brand-logo center"><i class="material-icons left" >ヘッダーだよん!!</i></a>
      </div>
    </nav>

    <!-- Mapをレンダリング -->
    <Map v-bind:center="center"></Map>
  </div>
</template>

<script>
// Mapを読込
import Map from './components/Map.vue';

export default {
  name: 'app',
  components: {
    Map
  },

  data () {
    return {
     // 地図のセンター位置(東京駅)
     //{ lat: 緯度, lng: 経度 }
     center: { lat: 35.681298, lng: 139.7640529 }
    }
  },
}
</script>

<style>

</style>

ここで重要なのは

import Map from './components/Map.vue';

components: {
    Map
}

です。以下で子コンポーネント(Map)を読み込んでいます。
そして<Map v-bind:center="center"></Map>でレンダリングしています。

※v-bind, :value
親 -> 子にデータを受け渡す際、親側で記述します。親からデータを渡すための窓口のイメージです。

v-bind:プロパティ名もしくは:プロパティ名という書き方をします。プロパティ名は、後述する子要素のpropsの変数名のことです。

次に「components/Map.vue」です。(子コンポネート)

Map.vue
<template>
  <div class="main-area">
    <div class="main-area-inner">
      <div id="map" ref="map"></div>
    </div>
  </div>
</template>

<script>
  export default {

    // 親のコンポーネントからpropsでdataを受け取る
    props: ['center'],

    data () {
      return {
        // Map Objectを保存する
        map: null,
        // Markerオブジェクトを配列で保存する
        markers: [],
      }
    },

    mounted () {
      // Mapの初期処理を実行する
      const map = this.$refs.map;
      this.map = new window.google.maps.Map(map, {
        center: this.center,
        zoom: 17
      });

      // マーカーを画面上に置く
      this.markers = [];
      const marker = new window.google.maps.Marker({
        position: {
          lat: this.center.lat,
          lng: this.center.lng
        },
        map: this.map,
        animation: google.maps.Animation.DROP
      });
     }

  }

</script>

<style>

</style>

ここで重要なのはprops
propsは、親 -> 子にデータを受け渡す際、子側で記述します。子がデータを受け取るための窓口のイメージです。

非同期通信と$emit

まず、今回非同期通信はaxiosを使います。

インストール

$npm install --save axios

main.jsにてaxiosを取り込みます。これですべてのコンポーネントにおいて「this.$axios」でaxiosが利用できるようになります。

main.js
import Vue from 'vue'
import App from './App'
//import router from './router'

import 'materialize-css';
import 'materialize-css/dist/css/materialize.min.css'

import axios from 'axios' //追加

Vue.config.productionTip = false

Vue.prototype.$axios = axios //追加

/* eslint-disable no-new */
new Vue({
  el: '#app',
  //router,
  components: { App },
  template: '<App/>'
})

※任意のコンポーネントの中にてaxiosをインポートすることも可能です。その場合は「this.$axios」ではなく「axios」となります。

さて先ほど作ったマップアプリを使いながらaxios$emitの解説をしていきましょう。
※今回、楽天トラベルAPIを使います。

前回、行ったように「components/Card.vue」を作成し、コンポーネントを読みこんでください。

vue;Card.vue
<template>
  <div class="card">
      <div class="row">
        <div class="col s3 image">
          <img v-bind:src="this.rankings.hotelImageUrl" class="responsive-img" />
        </div>
        <div class="col s9 content">
            <i class="left">ランキング:<font color="red">{{ this.rankings.rank }}</font></i>
            <i class="left">ホテル名:{{ this.rankings.hotelName }}</i>
            <i class="left">都道府県:{{ this.rankings.middleClassName }}</i>
        </div>
      </div>
  </div>
</template>

<script>

  export default {
    data () {
      return {
        rankings: ""
      }
    },
    mounted () {
      // 楽天APIからデータを取得する
      this.searchPlans();
    },
    methods: {
      // 楽天APIに接続して、データを取得する
      searchPlans () {
        this.$axios.get(" 楽天APIからのURL ",{
        }).then((resp) => {
          this.rankings = resp.data.Rankings[0].Ranking.hotels[0].hotel

          this.$emit("ranking", resp.data.Rankings[0].Ranking);
        }).catch(err => {
          console.log(err);
        });
      }
    }
  }
</script>
 
<style scoped>

</style>
App.vue
<template>
  <Card v-on:ranking="getRanking"></Card>
</template>

<script>
data () {
    return {

      rankings: [],

    }
  },
  methods: {
    getRanking(ranking) {
      this.rankings = ranking;
    },
  }
}
</script>

<style>

</style>

ここで重要となってくるのがaxios(非同期通信)の書き方this.$emitの使い方です。
まずaxiosの書き方は

//GET
axios.get('URL', {
  params: {
    キー: 値
  }
})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});

//※これでも可
axios.get('/URL?キー: 値')
  .then(function (response) {
console.log(response);
})
.catch(function (error) {
  console.log(error);
});

//POST
axios.post('URL', {
  キー: 値,
  キー: 値
})
.then(function (response) {
  console.log(response);
})
.catch(function (error) {
  console.log(error);
});

です。

続いてthis.$emitの説明をします。
簡単にいうと、子 -> 親にデータを受け渡しするときに使います。
例えば、子コンポネートの変数hogeの値を親コンポーネントに渡したい時は

子コンポート
this.$emit("イベント名", hoge);

として、親コンポートで

親コンポーネント
<Template>
  <子コンポーネント v-on:イベント名:"関数"></子コンポーネント>
</Template>
<script>
data () {
  return {
    hoge: ""
  }
},
methods: {
  関数名 (hoge) {
    this.hoge = hoge;
  },

}
</script>

このように使うと子コンポーネントの値が親で使えます。

まとめ

最後にややこしいけどめっちゃ重要なことをまとめておきます。
Vueの親子コンポーネント間でデータをやりとりするときの鉄則は Events Up, Props Down です。
すなわち、

  • 親 -> 子へデータを受け渡す際にはv-bindpropsを使う(props down)
  • 子 -> 親へデータを受け渡す際には$emitv-onを使う(events up)

最後に

長くなりましたが今回はこの辺で、最後まで読んでいただきありがとうございました。
もし、間違い等、アドバイス、ご指摘等有れば教えていただけたら幸いです。

次回はVue-routerの使い方についてです。
今後はVuex,Nuxt.jsについて触れていきたいと思います。

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

日付をX軸としたamChartsの折れ線グラフで初期表示(ズーム後)を直近の1週間表示に変更する

概要

amChartsDate Based Dataを使用しグラフを作成します。
こちらのグラフは起動時に全体を表示し、”最終日からある割合”の日付をズームして表示します。(かっこいい)
Demo source
Image from Gyazo
この”最終日からある割合””直近の1週間”に変更していきたいとおもいます。(上のDemo sourceのgifでは直近の8日にプロットされた値が表示されてますね)

作成する前提

amChartsがCDNで読み込めている 
参考文献amcharts 4 Demos を使ってグラフを作成(amChartsの導入方法が記載されています)
使用するグラフの種類:Date Based Data
使用するモデル:Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。において作成した体重管理アプリをモデルに作成します。コントローラーファイルなどの記述はこちらを参考にしてください。

編集するファイル

・ビューファイル

ビューファイル(Demo source)

まずは、Demo sourceがどういう構造になっているか見てみましょう。

demo.erb
<!-- Styles -->
<style>
#chartdiv {
  width: 100%;
  height: 500px;
}

</style>

<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

// Add data
chart.data = [{
  "date": "2012-07-27",
  "value": 13
}, {
  "date": "2012-07-28",
  "value": 11
}, {
  "date": "2012-07-29",
  "value": 15
}, {
//~省略~
  "date": "2013-01-30",
  "value": 81
}];

// Set input format for the dates
chart.dateFormatter.inputDateFormat = "yyyy-MM-dd";

// Create axes
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());

// Create series
var series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "value";
series.dataFields.dateX = "date";
series.tooltipText = "{value}"
series.strokeWidth = 2;
series.minBulletDistance = 15;

// Drop-shaped tooltips
series.tooltip.background.cornerRadius = 20;
series.tooltip.background.strokeOpacity = 0;
series.tooltip.pointerOrientation = "vertical";
series.tooltip.label.minWidth = 40;
series.tooltip.label.minHeight = 40;
series.tooltip.label.textAlign = "middle";
series.tooltip.label.textValign = "middle";

// Make bullets grow on hover
var bullet = series.bullets.push(new am4charts.CircleBullet());
bullet.circle.strokeWidth = 2;
bullet.circle.radius = 4;
bullet.circle.fill = am4core.color("#fff");

var bullethover = bullet.states.create("hover");
bullethover.properties.scale = 1.3;

// Make a panning cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.behavior = "panXY";
chart.cursor.xAxis = dateAxis;
chart.cursor.snapToSeries = series;

// Create vertical scrollbar and place it before the value axis
chart.scrollbarY = new am4core.Scrollbar();
chart.scrollbarY.parent = chart.leftAxesContainer;
chart.scrollbarY.toBack();

// Create a horizontal scrollbar with previe and place it underneath the date axis
chart.scrollbarX = new am4charts.XYChartScrollbar();
chart.scrollbarX.series.push(series);
chart.scrollbarX.parent = chart.bottomAxesContainer;

chart.events.on("ready", function () {
  dateAxis.zoom({start:0.79, end:1});
});

}); // end am4core.ready()
</script>

<!-- HTML -->
<div id="chartdiv"></div>

なんだか難しそうで、なにを書いているかパッとみたただけではわかりませんね。
ただ、データが記載されているところはわかるのではないでしょうか?

demo.erb
<script>
// Add data
chart.data = [{
  "date": "2012-07-27",
  "value": 13
}, {
  "date": "2012-07-28",
  "value": 11
}, {
  "date": "2012-07-29",
  "value": 15
}, {
//~省略~
  "date": "2013-01-30",
  "value": 81
}];
</script>

↑ここです。
この部分を体重管理アプリのモデルに合わせていきます。

ビューファイル(本アプリ)

まずは、コントローラーから変数をもってきて算出までします。

index.erb
<script>
//JSON形式で値を渡す
const weights = <%== JSON.dump(@weights) %>;
const dates = <%== JSON.dump(@dates) %>;
//表示期間を計算
var firstDate = new Date(dates[0]) //一番最初
var lastDate = new Date(dates.slice(-1)[0]) //一番最後
var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1 //表示する期間
</script>

細かいところはRailsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。を参照してください。(本記事をそのままコピペしても動きません)
さて、Demo sourceのchart.dataの値を下記のように変えてください。

index.erb
<script>
// Add data
  chart.data = [];
  for (var j =0; j< weights.length; j++){
    for (var i = 0; i < termDate; i++) {
      var newDate = new Date(firstDate)
      newDate.setDate(newDate.getDate() + i); //初日からi日分たす
      if ((new Date(dates[j])) - (newDate)==0){
        weight =weights[j]
        chart.data.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }
</script>

valueはRailsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。のときと同様にweightに変更してます。weightの変更箇所はその他にもあるのですべて置換してあげてください。

初期表示(ズーム後)を直近の1週間表示にする

本記事の本題です。
まずはDemo sourceからズームっぽい記述を探してみましょう。後方から9行目付近をみてください。

demo.erb
<script>
chart.events.on("ready", function () {
  dateAxis.zoom({start:0.79, end:1});
});
</script>

{start:0.79, end:1}の数値を試しに{start:0, end:1}かえてみてください。
変えるときは、【小ネタ】amChartsのDemo sourceをVScodeなどのエディタにコピペせず、ブラウザ上でプロパティを変更し表示を確認する方法を使うと便利です。
Image from Gyazo
zoomされず、全体が表示されました。
ということは、startの数値を変更すれば直近の1週間が表示できそうです。

全体の日数はtermDateという変数で取得できてるので、
1週間は全体の日数のどれくらいの割合か出してみます。
例:termDateが70日の場合
式: 7/termDate=0.1
0.1という数値をstartに入れては駄目です。なぜならstartは0に近いほど、小さい値(日付)を取得します。
なので0.1を入れると0.1≦x≦1の範囲になり(endが1の場合)、一番古い日付から1週間後から最新の日付を範囲としてしまいます。
では、どうするかというと1から引いてあげれば良いです。

{start:1-7/termDate, end:1}としてあげます。

こうすることで0.9≦x≦1となり、直近の1週間の割合がだせます。
最後にビューファイルの全体を記載します。

index.erb
<!-- Styles -->
<style>
#chartdiv {
  width: 100%;
  height: 500px;
}

</style>

<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/kelly.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
am4core.useTheme(am4themes_kelly);
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);
const weights = <%== JSON.dump(@weights) %>;
const dates = <%== JSON.dump(@dates) %>;

var firstDate = new Date(dates[0])
var lastDate = new Date(dates.slice(-1)[0])
var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1

// Add data
  chart.data = [];
  for (var j =0; j< weights.length; j++){
    for (var i = 0; i < termDate; i++) {
      var newDate = new Date(firstDate)
      newDate.setDate(newDate.getDate() + i); //初日からi日分たす
      if ((new Date(dates[j])) - (newDate)==0){
        weight =weights[j]
        chart.data.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }

// Set input format for the dates
chart.dateFormatter.inputDateFormat = "yyyy-MM-dd";

// Create axes
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());

// Create series
var series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "weight";
series.dataFields.dateX = "date";
series.tooltipText = "{weight}"
series.strokeWidth = 2;
series.minBulletDistance = 15;

// Drop-shaped tooltips
series.tooltip.background.cornerRadius = 20;
series.tooltip.background.strokeOpacity = 0;
series.tooltip.pointerOrientation = "vertical";
series.tooltip.label.minWidth = 40;
series.tooltip.label.minHeight = 40;
series.tooltip.label.textAlign = "middle";
series.tooltip.label.textValign = "middle";

// Make bullets grow on hover
var bullet = series.bullets.push(new am4charts.CircleBullet());
bullet.circle.strokeWidth = 2;
bullet.circle.radius = 4;
bullet.circle.fill = am4core.color("#fff");

var bullethover = bullet.states.create("hover");
bullethover.properties.scale = 1.3;

// Make a panning cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.behavior = "panXY";
chart.cursor.xAxis = dateAxis;
chart.cursor.snapToSeries = series;

// Create vertical scrollbar and place it before the value axis
chart.scrollbarY = new am4core.Scrollbar();
chart.scrollbarY.parent = chart.leftAxesContainer;
chart.scrollbarY.toBack();

// Create a horizontal scrollbar with previe and place it underneath the date axis
chart.scrollbarX = new am4charts.XYChartScrollbar();
chart.scrollbarX.series.push(series);
chart.scrollbarX.parent = chart.bottomAxesContainer;

chart.events.on("ready", function () {
  dateAxis.zoom({start:1-7/termDate, end:1});
});

}); // end am4core.ready()
</script>

<!-- HTML -->
<div id="chartdiv"></div>

完成!!

Image from Gyazo
Jul22~Jul28の直近の1週間が表示されてますね。

参考文献

Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。

最後に

この記事を書いた目的

・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)

筆者について

TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思います:muscle:

ひとこと

最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!

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

Rails + Cropper.jsで画像トリミング機能を実装する

概要

Cropper.jsを用いてRailsでトリミング機能付き画像アップロード機能を実装してみます。
cropper_demo.gif

ソースコードは以下に置いてます。
https://github.com/Tak-Iwamoto/jquery-cropper-rails

環境

Rails 5.2.2
Bootstrap 4.3.1
active storage
mini_magick 4.9.4

active storage

active_storageを利用するために以下のコマンドを実行してください。
rails active_storage:install
rails db:migrate

Cropper.jsの読み込み

https://github.com/fengyuanchen/cropperjs
上のgithubからcropper.min.jsとcropper.min.cssを取得し、Railsの中に配置しましょう。サードパーティライブラリはvendorディレクトリの中に入れることが推奨されており、vendorの中にassets/javascriptsとassets/stylesheetsディレクトリを作成し、それぞれcropper.min.jsとcropper.min.cssを配置します。
https://github.com/fengyuanchen/jquery-cropper
また、JQueryを用いるために上のgithubからjquery-cropper.min.jsを取得し、同様にRailsの中に配置します。最後にapplication.jsとapplication.scssに以下のように編集します。
application.js
//= require rails-ujs
//= require jquery
//= require jquery_ujs
//= require cropper.min.js
//= require jquery-cropper.min.js
//= require activestorage
//= require turbolinks
//= require_tree .
//= require popper
//= require bootstrap-sprockets

application.scss
*= require_tree .
*= require cropper.min.css
*= require_self
*/

これでCropper.jsが使えるようになりました。

modelとcontrollerを作成

デモのためのuserモデル、コントローラーを作成します。

user.rb
class User < ApplicationRecord
  attr_accessor :x, :y, :width, :height
  has_one_attached :image
end
users_controller.rb
class UsersController < ApplicationController
  def new
    @user = User.new
  end

  def create
    @user = User.new(user_params)
    @user.save
    session[:crop_x] = user_params[:x]
    session[:crop_y] = user_params[:y]
    session[:crop_width] = user_params[:width]
    session[:crop_height] = user_params[:height]

    redirect_to user_path @user
  end

  def show
    @user = User.find(params[:id])
  end

  private
    def user_params
      params.require(:user).permit(:image, :x, :y, :width, :height)
    end
end

Cropper.jsでトリミングした結果のパラメータをsessionで保持し、のちに画像を表示するときに用いていますが、このやり方は筋が悪い気がしています... active_storageで加工した画像を保存する方法が分からなかったため、この方法で行っていますが、おそらく他にいい方法があると思います。

画像アップロードフォーム

トリミング画像をアップロードするためのフォームを作成します。

new.html.erb
<div class="container">
  <div class="row">
    <div class="col-md-6 col-md-offset-3">
      <div class="preview" style='overflow:hidden'>
          <div id="beforeUpload">
            <%= icon("fas fa-4x fw","file-image")%>
          </div>
          <img src="" id="croppedImage">
      </div>
      <%= form_with model: @user, url: users_path do |f| %>
        <%= f.file_field :image%>
        <%= f.hidden_field :x, id:"dataX"%>
        <%= f.hidden_field :y, id:"dataY"%>
        <%= f.hidden_field :width, id:"dataWidth"%>
        <%= f.hidden_field :height, id:"dataHeight"%>

        <%= f.submit "button", id:"btnUpload", class: 'btn btn-success'%>
      <% end %>
      <div class="modal fade" id="cropModal" role="dialog">
        <div class="modal-dialog">
          <div class="modal-content">
            <div class="modal-body">
              <div class="img-container">
                <img src="" id="imageModal" alt="picture">
              </div>
            </div>
            <div class="modal-footer">
              <button type="button" class="btn btn-primary" id="btn-save" data-dismiss="modal">Save</button>
            </div>
          </div>
        </div>
      </div>
    </div>
  </div>
</div>

<div class="preview" style='overflow:hidden'>でトリミング後の画像を表示するフィールドを作成しています。。この時、style='overflow:hidden'を指定しないと上手く表示されない場合があります。
トリミング後の画像は<img src="" id="croppedImage">で表示します。

form_with でフォームを作成し、トリミングした結果のパラメータはf.hidden_fieldでcontrollerに渡して、以下のshow.html.erbで画像を表示する際に用います。

<div class="modal fade" id="cropModal" role="dialog">以下ではトリミング操作を行うためのmodalをbootstrapによって表示させています。

トリミングした画像を表示するために以下のhtmlを作成します。
show.html.erb
<%= image_tag @user.image.variant(crop: "#{session[:crop_width]}x#{session[:crop_height]}+#{session[:crop_x]}+#{session[:crop_y]}") %>

では、トリミング処理を行うために以下のjavascriptファイルを作成します。

crop_image.js
$(function () {
  let $image = $('#imageModal'),
    $img_field = $('#user_image'),
    $croppedImage = $('#croppedImage'),
    $cropModal = $('#cropModal'),
    $beforeUpload = $('#beforeUpload'),
    $button = $('#btn-save'),
    $dataX = $('#dataX'),
    $dataY = $('#dataY'),
    $dataWidth = $('#dataWidth'),
    $dataHeight = $('#dataHeight');

  let options = {
    dragmode: 'crop',
    aspectRatio: 1/1,
    restore: false,
    guides: false,
    center: false,
    highlight: true,
    cropBoxMovable: true,
    cropBoxResizable: true,
    modal: true,
    crop: (e) => {
      $dataX.val(Math.round(e.detail.x));
      $dataY.val(Math.round(e.detail.y));
      $dataWidth.val(Math.round(e.detail.width));
      $dataHeight.val(Math.round(e.detail.height));
    }
  };

  // when file upload
  $img_field.change((e) => {
    $image.cropper('destroy').removeAttr('src');
    file = e.target.files[0];
    reader = new FileReader();
    if (file.type.indexOf('image') < 0) {
      window.alert("画像を選択してください");
      return ;
    }
    reader.onload = ((e) => {
      $image.attr('src',"");
      $image.attr('src', e.target.result);
      $cropModal.modal('show');
      $cropModal.on('shown.bs.modal', () => {
        $image.cropper(options);
      });
    });
    reader.readAsDataURL(file);
  })
  // onclick save button
  $button.click(() => {
    imgCropping();
  });

  // modalを閉じたとき、cropper要素を初期化
  $cropModal.on('hidden.bs.modal',function() {
    $image.cropper('destroy').removeAttr('src');
    let $cropperContainer = $('.cropper-container');
    $cropperContainer.remove();
  });

  function imgCropping() {
    if (!croppable) {
      alert('トリミングする画像が用意されていません')
      return false;
    }
    $beforeUpload.hide();
    let croppedData = $image.cropper('getCroppedCanvas').toDataURL();
    $croppedImage.attr('src', croppedData);
    $cropModal.modal('hide');
  }
});

$img_fieldでidがuser_imageのDOMを指定していますが、html側にはそのようなidはありません。Railsはform_withでfile_fieldを作成すると、自動的に<img id="モデル名_フィールド名">のタグが作成されるため、注意が必要です。自分もこれが原因でハマってしまいました...

optionsではどのように画像をトリミングするか設定しています。
$img_field.change以下で画像がアップロードされた時にトリミングが開始されるように記述しています。
modalが表示された際、$image.cropper(options)で先ほど設定したoptionsでcropperインスタンスを作成し、トリミングを行います。また、最初に$image.cropper('destroy')でcropperインスタンスを初期化していることに注意してください。この処理を行わないと、前にトリミングした画像が残ったままとなり画像が複数表示されたりといった不具合が起こります。

また、cropperインスタンスが作成された時点で、<div class="cropper-container cropper-bg"></div>のDOMが作成されます。modalを閉じた時点でこのDOMを削除しておかないと前回トリミングした画像が残ったままになっていることがあります。そのため$cropModal.on('hidden.bs.modal'以下で削除する操作を行っています。

以上です。

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

TypeError: Class constructor cannot be invoked without 'new'

const class

エラーの出たJavaScript
const A = class {
  constructor(_a) {
    this.a = _a;
  }
}

const B = class extends A {
  constructor(_b) {
    super(_b);
  }
}

(() => {
  var b = new B("test");
  console.log(b);
})();

とあるJavaScriptを試したところ「TypeError: Class constructor cannot be invoked without 'new'」が出た。

エラーの原因は何? :thinking:

普通のclass

エラーの出ないJavaScript
class A {
  constructor(_a) {
    this.a = _a;
  }
}

class B extends A {
  constructor(_b) {
    super(_b);
  }
}

(() => {
  var b = new B("test");
  console.log(b);
})();

普通によく見るclassならエラーが出ない。

判明した原因とは

原因

const B = class extends A {}()

後ろの無名関数の即時関数とくっついてnew A()し損ねたと判定された。

constの後ろに「;」をつけてやれば解決。


提案

const classを使わないようにしよう。
知らないだけかもだがあまり見ないと思う :unamused:

「;」を省略しないようにしよう。
ブックマークレットが動かなくなるし今回のようなこともある。

自戒と反省を込めて :relaxed:

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

【小ネタ】amChartsのDemo sourceをVScodeなどのエディタにコピペせず、ブラウザ上でプロパティを変更し表示を確認する方法

概要

amChartsでグラフを作成した際に、初期値やzoomの比率などのプロパティを変更して
自分のイメージにあうか検討した際に便利な機能を見つけたので共有します。

Open in...

Image from Gyazo

の・・・・
これ!!
Image from Gyazo

Image from Gyazo
Image from Gyazo

最後に

この記事を書いた目的

・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)

筆者について

TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思います:muscle:

ひとこと

最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!

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

Railsにて不連続な間隔(日付など)で投稿された値をamChartsを使って折れ線グラフを作成する。

概要

TECH::EXPERTのカリキュラムでオリジナルのミニアプリを作成する機会があり、
同期で学習中の方がamChartsを使っていてかっこいいなぁ〜と思い、自分なりに調べてみました。
いくつか工夫したところがあったので共有します。
体重管理アプリのようなものを想定してます。
体重を計る日とそうでない日がマチマチにある場合のグラフを作成します。
例:
7月1日 70kg
7月2日 71kg
7月4日 73kg
7月7日 68kg
7月8日 69kg
7月9日 67kg
=>7月3日と7月6日の記録がない

作成する前提

amChartsがCDNで読み込めている 
参考文献amcharts 4 Demos を使ってグラフを作成(amChartsの導入方法が記載されています)
使用するグラフの種類:Line Chart with Scroll and Zoom
Image from Gyazo
diaryモデルにweightカラムがある
diaries_controler.rbにindexアクションが用意されてある

編集するファイル

・コントローラーファイル
・ビューファイル

コントローラーファイル

diaries_controller.rb
class DiariesController < ApplicationController
  def index
    @diaries=Diary.all.order('created_at ASC')
    @weights=@diaries.map(&:weight)
    @dates=@diaries.map{|diary| diary.created_at.strftime('%Y/%m/%d') }
  end
end

今回は、weigthカラムの値ビューファイルに渡します。
配列で渡したいので上記のようにmapメソッドを使いました。
eachでは下記のように記載します。

diaries_controller.rb
##eachの場合
@weights=[]
@diaries.each do |diary|
 @weigth << diary.weight
end

"&:"の記法は処理が1つの場合しか使えないので、@datesはデフォルトの記法です。

ビューファイル

index.erb
<!-- Styles -->
<style>
#chartdiv {
  width: 100%;
  height: 500px;
}
</style>

<!-- Resources -->
<script src="https://www.amcharts.com/lib/4/core.js"></script>
<script src="https://www.amcharts.com/lib/4/charts.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/kelly.js"></script>
<script src="https://www.amcharts.com/lib/4/themes/animated.js"></script>

<!-- Chart code -->
<script>
am4core.ready(function() {

// Themes begin
am4core.useTheme(am4themes_kelly);
am4core.useTheme(am4themes_animated);
// Themes end

// Create chart instance
var chart = am4core.create("chartdiv", am4charts.XYChart);

//JSON形式で値を渡す
const weights = <%== JSON.dump(@weights) %>;
const dates = <%== JSON.dump(@dates) %>;
//表示期間を計算
var firstDate = new Date(dates[0])
var lastDate = new Date(dates.slice(-1)[0])
var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1

// Add data
chart.data = generateChartData();

// Create axes
var dateAxis = chart.xAxes.push(new am4charts.DateAxis());
dateAxis.renderer.minGridDistance = 50;

var valueAxis = chart.yAxes.push(new am4charts.ValueAxis());

// Create series
var series = chart.series.push(new am4charts.LineSeries());
series.dataFields.valueY = "weight";
series.dataFields.dateX = "date";
series.strokeWidth = 2;
series.minBulletDistance = 10;
series.tooltipText = "{valueY}";
series.tooltip.pointerOrientation = "vertical";
series.tooltip.background.cornerRadius = 20;
series.tooltip.background.fillOpacity = 0.5;
series.tooltip.label.padding(12,12,12,12)

// Add scrollbar
chart.scrollbarX = new am4charts.XYChartScrollbar();
chart.scrollbarX.series.push(series);

// Add cursor
chart.cursor = new am4charts.XYCursor();
chart.cursor.xAxis = dateAxis;
chart.cursor.snapToSeries = series;

//不連続な間隔(日付)で投稿された値を表示する
function generateChartData() {
  var chartData = [];
  for (var j =0; j< weights.length; j++){
    for (var i = 0; i < termDate; i++) {
      var newDate = new Date(firstDate)
      newDate.setDate(newDate.getDate() + i); //初日からi日分たす
      if ((new Date(dates[j])) - (newDate)==0){
        weight =weights[j]
        chartData.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }
  return chartData;
}

}); // end am4core.ready()
</script>
<div id="chartdiv"></div>

基本はDemo sourceをコピペしてください。
Image from Gyazo
styleがビューファイルにありますが、本記事の趣旨とズレるのでそのままビューファイルに記載してます。
また、Demo sourceの"visits"は"weight"としてます。(単数形です..Demo sourceはなぜ複数形にしてるんだろう...?)
下記コピペ部分とは違うところの説明です。

1.コントローラーの変数をJavascriptで使えるようにする

index.erb
<script>
//JSON形式で値を渡す
const weights = <%== JSON.dump(@weights) %>;
const dates = <%== JSON.dump(@dates) %>;
</script>

JSONでコントローラーの値をわたします。
こちらはrailsのcontrollerからjavascriptに対して変数を渡すを参考にさせていただきました。

2.表示する期間を算出する

index.erb
<script>
//表示期間を計算
var firstDate = new Date(dates[0]) //一番最初
var lastDate = new Date(dates.slice(-1)[0]) //一番最後
var termDate= (lastDate - firstDate)/ 1000 / 60 / 60 / 24 + 1 //表示する期間
</script>

コントローラー側で配列にしたとき、

diaries_controller.rb
@diaries=Diary.all.order('created_at ASC')

としてるので、昇順(日付が古い順)になってます。なので、容易に一番最初を最後を取得できますね。
termDateは期間を計算しました。日付の場合そのまま引き算をできないので、/ 1000 / 60 / 60 / 24としてます。
+1は、たとえば 3日から5日までの期間を出すために"5-3=2"では1日たりないので+1としてます。

3.不連続な間隔(日付)で投稿された値を表示する
Demo sourceだと下記のように表現されているところです。

Demo.erb
<script>
function generateChartData() {
    var chartData = [];
    var firstDate = new Date();
    firstDate.setDate(firstDate.getDate() - 1000);
    var visits = 1200;
    for (var i = 0; i < 500; i++) {
        // we create date objects here. In your data, you can have date strings
        // and then set format of your dates using chart.dataDateFormat property,
        // however when possible, use date objects, as this will speed up chart rendering.
        var newDate = new Date(firstDate);
        newDate.setDate(newDate.getDate() + i);

        visits += Math.round((Math.random()<0.5?1:-1)*Math.random()*10);

        chartData.push({
            date: newDate,
            visits: visits
        });
    }
    return chartData;
}
</script>

簡易的に説明するならば、初期値1200であとは、ランダムで数値をあれやこれやとかえて、
i=1から500まで表現してます。
これはこれで学習するところがあっておもしろかったです。
本記事ではコントローラーから取得した値を下記のようにしてます。

index.erb
<script>
//不連続な間隔(日付)で投稿された値を表示する
function generateChartData() {
  var chartData = [];
  for (var j =0; j< weights.length; j++){
    for (var i = 0; i < termDate; i++) {
      var newDate = new Date(firstDate)
      newDate.setDate(newDate.getDate() + i); //初日からi日分たす
      if ((new Date(dates[j])) - (newDate)==0){
        weight =weights[j]
        chartData.push({
            date: newDate,
            weight: weight
        });
      }
    }
  }
  return chartData;
}
</script>

[1]変数をinteger型の変数を2つ用意してます(i,j)
[2]jはweights.lengthの数まで繰り返します。つまり体重のレコードの数までですね。
[3]iをjにネストしてます。こちらはtermDateの数だけ繰り返します。つまり、”2.表示する期間を算出する”で計算してあげた期間分繰り返します。
[4]表示するのは、あくまで、体重のレコードが存在する日付のみなので、jの日付(体重記録がある日付)とiの日付(newDate)の差が0のときのみ、日付と体重をハッシュの形にし、chartDataに入れてあげます。

完成!!

Image from Gyazo

gifでは"Jul 11"と"Jul 12"に記録がありません。

参考文献

amcharts 4 Demos を使ってグラフを作成
railsのcontrollerからjavascriptに対して変数を渡す

最後に

この記事を書いた目的

・自分なりに工夫した点をアウトプットして、理解を深める。
・あわよくば有識者にフィードバックをもらいたい。
・私と同じ初学者からも奇譚のない意見をもらいたい。(自分だったらどうこうする的な)

筆者について

TECH::EXPERTにて4月27日より52期夜間・休日コースでruby/railsを学習している未経験エンジニアです。
ご不備等ありましたら、ご指摘ください。ちなみに本記事が初投稿になります。
言わずもがなかもしれませんが、趣味はボディメイク・筋トレでございます。
余談ですが、120kg⇨66kgまで減量して大会出場した経験があり
ダイエットについての質問はなんでも答えられるかと思います:muscle:

ひとこと

最後までご覧いただきまして、ありがとうございました。
もし気に入っていただけたら、イイね・ストック・フォローご自由に!

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

JSの参照渡し、値渡し。PHPの参照渡し、値渡し。

JSの配列やobjectは参照渡し。
stirngやintegerは値渡し。

PHPは通常、値渡し。
&をつけて&$hogeとすると参照渡し。

JSが参照渡しになることを知らなくて、なんでだー、Vueのせいかとかいろいろやった挙げ句、バグの原因がこれだったので、あーとなった。

参考
JavaScript の配列やオブジェクトは参照渡しになる…バグを生む落とし穴
http://neos21.hatenablog.com/entry/2018/05/20/080000

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

JSで文字列(数値、アルファベット混在を)並び替えたい

JSで文字列(数値、アルファベット混在を)並び替える方法のメモです。

配列ならソートは簡単にできそうですが、文字列だと一発でできなくて、こうやるとできます。
もっとスマートな方法がありそうですが、見つかりませんでした。

let string = "1ab234";

string.split('').sort(function (a, b) {
    if (a > b) {
        return 1;
    }

    if (a < b) {
        return -1;
    }

    return 0;
}).join(',').replace(/,/g, '');

console.log(string) => "1234ab"

参考サイト
goma
JS:配列の正しいソート方法

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

v-forで回している選択肢に、動的に色をつけたいときに簡単に書く

v-forで回している選択肢に、動的に色をつけたいときに簡単に書く。

v-for="(hoge, key) in hoges"
:class="{'hoge-color': key === current_key}"

<script>

    data() {
        return {
            current_key: 0, // 初期表示のとき
        }
    }

'hoge-color': key === current_keyここのロジックをメソッド化してもいいけど、めんどいのでこう書いた。

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

can not set reatcive property on undefined,Vueで複数選択可のチェックボックスを実装するときのエラー

Vueで次のエラーを解消するための一つの方法。
ノートのメモなので動かしてないので、違ってたらすみません。

Vueで複数選択可のチェックボックスを実装するときのエラーです。

結論を言うと、dataにあらかじめ定義してないよというエラーみたい。
dataにあらかじめ定義してなくてもよい場合と、定義してないといけない場合があるみたい。

can not set reatcive property on undefined, null, or primitive.value
<label
    v-for="(hoge, key) in hoges"
    :key = "key"
    :for = "key"
>
<input
    type="checkbox"
    v-model="hoges.hoge_id"
    :value="hoge.id"
    id="index"
>
{{ hoge.names }}

<script>

data: ~ 中略
// 定義しておく
    hoges: {
        hoge_id: []
    }


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

【Programming News】Qiitaまとめ記事 Weekly July 4rd week, 2019 Vol.2

筆者が昨日2019/7/21(日)~7/27(土)に気になったQiitaの記事のまとめのまとめを作成しました。日々のまとめ記事のWeekly版です。

皆様が興味のある言語や思いもよらぬハック方法をこの中から見つけられたら幸いです。

Java

Python

Ruby

Rails

C

Android

Swift

Kotlin

Flutter

Fulx

JavaScript

React

Node.js

Vue.js

Vuex

Nuxt.js

Nest.js

Angular

jQuery

TypeScript

ReactNative

Laravel

C

PHP

CakePHP

Rust

Go言語

R言語

Scala

Unity

A-Frame

PowerApp

Line

HTML

CSS

Sass

SQL

MySQL

PostgreSQL

Oracle

MongoDB

SQL Server

ビッグデータ

Visual Studio Code

IntelliJ IDEA

AI

IoC

Git

AWS

Azure

Oracle Cloud

IBM Clod

Active Directory

インフラ

ブロックチェーン

Ethereum

セキュリティ

機械学習

Network

RPA

CI

Docker

Heroku

VirtualBox

kubernetes

OpenID

OAuth2.0

Elasticsearch

Linux

Cent OS

Windows

Google API

Google Apps Script

Google Cloud Platform

Google Colaboratory

Google Drive

Firebase

Server Side

CSS

BootStrap

WordPress

Develop

PowerShell

Vim

awk

LaTex

Redmine

UML

Raspberry

RPA

IoT

Alexa

Line

SharePoint

VBA

ShellScript

Nim

Emacs

WPF

UI

Ansible

Arduino

Julia

Coral

ionic

QRCode

OCR

資格

転職

更新情報

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

【web開発 CSS】Inputのtextデザイン

はじめに

最近のwebサービスの開発では正しく機能しているかを重視してきたが、そろそろCSSの基本的なデザインは理解しとくべきだし、必ず役に立つ(と信じている)ため、今回はInputのtype="text"についてGoogleなどの大手サイトを参考にしながら作成してみた。また、基本的なアニメーションの勉強も兼ねた。今回はCSSデザインの勉強ということで、下に紹介する検索textボックスなどの検索ボタンや削除ボタンなどは機能は持たせていない。

1. 少し見栄えの良いtextボックス

ここでは3つの簡単なデザインを紹介。

1.1 枠線のみ変更

#text1{
    width: 100%; /*親要素いっぱい広げる*/
    padding: 10px 15px; /*ボックスを大きくする*/
    font-size: 16px;
    border-radius: 3px; /*ボックス角の丸み*/
    border: 2px solid #ddd; /*枠線*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

上のCSSではデフォルトの枠線をpaddingによって広げ色を変えただけのシンプルなもの。これだけでもデフォルトのtextボックスより見栄えは良い。
text1-1
上のgif画像で見られるようにfocus時にはデフォルトの青い枠線が表示される。これをもうちょっと変えてみたのが下の二つ。

box-sizing: border-box;

これを設定することで要素がpaddingとborderも含めてwidthと考えるようになる(デフォルトではwidthは要素本体のみ)ため親要素をはみ出ない。

1.2 focus時の枠線の色変更(border)

#text1:focus {
    border: 2px solid #ff9900; 
    z-index: 10;
    outline: 0;

text1-2
こちらはborderのデフォルトの色を変えただけ。

1.3 focus時の枠線の色変更(shadow)

#text1:focus {
    box-shadow: 0 0 5px 0 rgba(255,153,0,1);
    border: 2px solid #FFF !important;
    outline: 0;
}

text1-3
こちらはshadowだけで色を表現。borderは背景と同じ白に設定しているため非表示になっている。

2. 検索テキストボックス

こちらはGoogleの設定画面に設置されているものを参考に作成。

<div class="group">
   <label for="text2">text2</label>
   <div class="search_bar">
      <i class="fas fa-search search_icon"></i>
      <input id="text2" type="text" placeholder="キーワードを入力">
      <i class="fas fa-times search_icon"></i>
   </div>
</div>
.search_bar{
    display: flex; /*アイコン、テキストボックスを調整する*/
    align-items: center; /*アイコン、テキストボックスを縦方向の中心に*/
    justify-content: center; /*アイコン、テキストボックスを横方向の中心に*/
    height: 50px;
    width: 100%;
    background-color: #ddd;
}

.search_icon{ /*アイコンに一定のスペースを設ける*/
    height: 15px;
    width: 15px;
    padding: 5px 5px;
}

#text2{
    font-size: 16px;
    width: 100%; /*flexの中で100%広げる*/
    background-color: #ddd;
    border: none; /*枠線非表示*/
    outline: none; /*フォーカス時の枠線非表示*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

text2

flex

ここでのポイントは、虫眼鏡と×のアイコンとtextボックスを横並びにする際にinline-blockではなくflexを使っていること。flexを使うことで#text2のwidth: 100%;としても.search_bar要素をはみ出すことなく自動で調整してくれる。また縦のラインを合わせる際にinline-blockよりもflexのほうが簡単。

3. ちょっとしたアニメーションを付けたtextボックス

こちらはYoutubeのコメント投稿欄を参考に作成。擬似要素を使用した二つのデザインを紹介

3.1 中央からのアニメーション

<div class="group">
   <label for="text3" id="l_text3">text3</label>
   <input id="text3" type="text" placeholder="コメントを入力する">
   <div class="text_underline"></div>
</div>
#text3{
    font-size: 16px;
    width: 100%;
    border: none;
    outline: none;
    padding-bottom: 8px;
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

.text_underline{
    position: relative; /*.text_underline::beforeの親要素*/
    border-top: 1px solid #c2c2c2; /*text3の下線*/
}

/*共通のstyle*/
.text_underline::before,
.text_underline::after{
    position: absolute; 
    bottom: 0px; /*中央配置*/
    width: 0px; /*アニメーションで0pxから50%に*/
    height: 1px; /*高さ*/
    content: '';
    background-color: #3be5ae; /*アニメーションの色*/
    transition: all 0.5s;
    -webkit-transition: all 0.5s;
}

/*中央から右へのアニメーション*/
.text_underline::before{
    left: 50%; /*中央配置*/
}

/*中央から左へのアニメーション*/
.text_underline::after{ 
    right: 50%; /*中央配置*/
}

#text3:focus + .text_underline::before,
#text3:focus + .text_underline::after{
    width: 50%;
}

text3-1

擬似要素

今回::beforeや::afterというような擬似要素を使用することで無駄に要素を追加しないでアニメーションを適応してみた。上の例では::beforeと::afterをtextボックスの下線の中央に配置することで左右に伸びるアニメーションを実現した。

セレクタ

今回使用したセレクタは隣接セレクタ(+)。これは該当要素に隣接する兄弟関係の要素でありかつ該当要素直下しか適応されない。これが理由で#text3の直前に存在するlabelにはアニメーションをCSSで加えることはできない(もしあばご教授願います)。よって今回は一番簡単だと考えられるjQueryでの実装にした。

$('#text3').focus(function(){
    $('#l_text3').animate({'color': '#3be5ae'}, 500);
}).blur(function(){
    $('#l_text3').animate({'color': 'black'}, 500);
});

こちらのanimateメソッドでは色指定にHTMLカラーコード(#から始まる色を表す英数字文字列)、色の名前およびRGB値のどれでも記述可能。またjQueryにはデフォルトではanimateメソッドで色を変える機能は付いていないぽいので以下のスクリプトが必要。

<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery-color/2.1.2/jquery.color.js"></script>

アニメーション

transition: 対象 ミリ秒;でアニメーションを与えることができる。非常にシンプル。

左右からのアニメーション

/*左から右へのアニメーション*/
.text_underline::before{
    position: absolute; 
    left: 0px; /*左配置 ここright: 0px;にすると右から左のアニメーション*/
    bottom: 0px; /*左配置*/
    width: 0px; /*アニメーションで0pxから100%に*/
    height: 1px; /*高さ*/
    content: '';
    background-color: #3be5ae; /*アニメーションの色*/
    transition: all 0.5s;
    -webkit-transition: all 0.5s;
}

#text3:focus + .text_underline::before{
    width: 100%;
}

text3-2
3.1のものとほとんど同じなので説明は簡単に。こちらでは左から右にアニメーションするだけなので伸びる方向が1つ。よって擬似要素も::beforeの1つで実現。ただ::beforeを左端に配置して右に伸ばしてるだけ。

4. かっこいいアニメーションtextボックス

こちらもGoogleのログイン画面に設置されているものを参考に作成。

<div class="group">
   <label for="text4">text4</label>
   <div class="password_box">
      <div class="password_inner">
         <input id="text4" type="password">
         <div class="password_string">パスワードを入力</div>
      </div>
      <i class="fas fa-eye-slash"></i>
   </div>
</div>
.password_box{
    display: flex; /*アイコン、テキストボックスを調整する*/
    align-items: center; /*アイコン、テキストボックスを縦方向の中心に*/
    justify-content: center; /*アイコン、テキストボックスを横方向の中心に*/
    width: 100%;
    height: 50px;
    border-radius: 5px;
    border: 1px solid lightgray;
}

.password_inner{
    width: 100%;
    height: 100%;
    background-color: transparent; /*.password_boxの枠線お角一部被るため透明に*/
    position: relative;
}

#text4{
    position: absolute;
    z-index: 1; /*.password_stringよりも上に配置*/
    height: 100%;
    width: 100%;
    top: 0; left: 0; bottom: 0; right: 0;
    border: none; /*枠線非表示*/
    outline: none; /*フォーカス時の枠線非表示*/
    padding: 0 10px;
    font-size: 16px;
    background-color: transparent; /*後ろの.password_stringを見せるため*/
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
}

.password_string{
    position: absolute;
    height: 100%;
    width: 140px; /*文字列分の長さ*/
    top: 0; left: 0; bottom: 0; right: 0;
    padding-left: 10px; /*position: absolute;でのmarginは親要素はみ出す*/
    font-size: 16px;
    line-height: 50px; /*文字列を縦方向にmiddleに見せるため*/
    background-color: transparent;
    color: #80868b;
    box-sizing: border-box; /*横幅の解釈をpadding, borderまでとする*/
    transition: all 0.2s;
    -webkit-transition: all 0.2s;
}

.fa-eye-slash{ /*アイコンに一定のスペースを設ける*/
    height: 20px;
    width: 20px;
    padding: 5px 5px;
}

/*アニメーション*/
#text4:focus + .password_string{
    color: #3be5ae;
    font-size: 10px;
    line-height: 10px;
    width: 85px;
    height: 10px;
    padding: 0 2px;
    background-color: white;
    transform:translate3d(5px, -6px, 0);
}
$('#text4').focus(function(){
    $('.password_box').animate({borderTopColor: '#3be5ae', borderLeftColor: '#3be5ae', borderRightColor: '#3be5ae', borderBottomColor: '#3be5ae'}, 200);
}).blur(function(){
    $('.password_box').animate({borderTopColor: '#d3d3d3', borderLeftColor: '#d3d3d3', borderRightColor: '#d3d3d3', borderBottomColor: '#d3d3d3'}, 200);
});

text4
こちらでも.password_boxの枠線をCSSで変更する方がめんどくさそうだったのでjQueryで記述。またこの時、borderの色を変えるにはborderTopColor, borderLeftColor, borderRightColor, borderBottomColorの全てを指定する必要があり、色の名前(blackなど)は使えない。使えるのはHTMLカラーコードとRGB値のみ。

position: absolute;でのpadding

少し躓いた。position: absolute;ではmarginを使うと親要素をはみ出してしまうため、paddingを使う。position: absolute;の設定下ではpadding分要素が小さくなり、親要素の中に収まる仕組みになっている。

translate3d

アニメーションで要素を動かす際translate()を使うと描画にCPUを使用し、translate3d()を使うとGPUを使用するようになる。当然GPUを使った方がスムーズだしこれを使うべき。

最後に

今回こうしていくつかデザインを作った目的は、これからのwebサービス作りの際の辞書代わりに使うため、CSSデザインの勉強だった。色々触ってるとアニメーションにもいくつもやり方があったりとかなり勉強になった。擬似要素を使うとかなり複雑なデザインも実現できるため習得できると強みになると思った。また、インターネット上の様々なwebサイトをchromeのdeveloperツールを使ってどのような構造になっているのかを見るのはとても勉強になる。

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

Chrome 77のDevToolsの新機能をざっくり確認してみた

こちらにChrome 77のdev toolの新機能について紹介があったため、私が勉強のために試してみたと言う投稿になります。

2019/07/28現在のchromeのバージョンは75.0.3770.142なので、
Chrome Canaryバージョン78.0.3866.0を使用して確認しています。

要素のスタイルをコピー Copy element's styles

ElementsパネルのDOMを右クリックしてcssをクリップボードにコピーできるようになった!

nN6JlZU7oo.gif

レイアウトシフトを可視化 (※こちらはうまく動かせませんでした) Visualize layout shifts

参考ページにレイアウトシフトの説明があったので一部引用です。

It usually happens when images and ads finish loading. The page hasn't reserved any space for the images and ads, so the browser has to shift all the other content down to make room for them.
↓ google翻訳 ↓
通常、画像や広告の読み込みが完了したときに発生します。 ページは画像や広告のためのスペースを予約していないので、ブラウザはそれらのためのスペースを空けるために他のすべてのコンテンツを下にシフトしなければなりません。

画像などの読み込み後に画面ががたついてしまったりする問題のことをレイアウトシフトと言うようです。

devtoolの新機能ではレイアウトのずれを検出することができるようなのですが、私の環境ではうまく動きませんでした。

手順通りにやってみたのですが、画面が真っ黒になってしまい、うまくいきません。

  1. devtoolを開く
  2. Shift + command + P でCommand Menuを開く
  3. Rendering と入力
  4. Show Rendering を選択する
  5. Renderingパネルの「Layout Shift Regions」にチェックをつける

画面が黒くなってしまう

gYpMKtOkiU.gif

また、コマンドからChrome Canaryを起動してみるとエラーが出ていました、

$ /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary

m1LfsWhpyN.gif

エラーログ
$ /Applications/Google\ Chrome\ Canary.app/Contents/MacOS/Google\ Chrome\ Canary
tracing_service_impl.cc: Configured tracing, #sources:3, duration:0 ms, #buffers:1, total buffer size:25600 KB, total sessions:1
[3700:775:0726/005556.605442:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target.
[3700:775:0726/005556.608870:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0
[3700:775:0726/005556.623967:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0
[3700:775:0726/005558.518672:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0
[3700:775:0726/005558.530997:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target.
[3700:775:0726/005558.555357:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target.
[3700:775:0726/005558.571220:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target.
[3700:775:0726/005558.823379:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target.
[3700:775:0726/005558.823437:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0
[3700:775:0726/005559.314042:ERROR:gles2_cmd_decoder.cc(10736)] [.DisplayCompositor]RENDER WARNING: there is no texture bound to the unit 0
[3700:775:0726/005559.321744:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target.
[3700:775:0726/005601.189083:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target.
[3700:775:0726/005601.204262:ERROR:gles2_cmd_decoder.cc(6509)] [.DisplayCompositor]GL ERROR :GL_INVALID_OPERATION : glBindTexture: texture bound to more than 1 target.

...

うまく動かす方法を見つけたら追記します :bow:

AuditsパネルにLighthouse 5.1 Lighthouse 5.1 in the Audits panel

※「実際にはChrome76に同梱されていた」と注意書きあり

  • Provides a valid apple-touch-icon. Checks that a PWA can be added to an iOS homescreen.
  • Keep request counts and file sizes low. Reports the total number of network requests and file sizes for various categories, such as documents, scripts, stylesheets, images, and so on.
  • Maximum Potential First Input Delay. Measures the maximum potential time between a user's first page interaction and the browser's response to that interaction. Note that this metric replaces the Estimated Input Latency metric. Maximum Potential First Input Delay does not factor into your Performance category score.

参考ページによると↑のように色々な機能が追加されるようなのですが、私がAuditsパネル、Lighthouse関して知識がなく、(^ ^;)
有効なapple-touch-iconをチェックしてくれるようなった機能 だけみてみました:bow:


このようにapple-touch-iconが有効かどうかの項目が増えたので、

Screen Shot 2019-07-28 at 4.png

192pxの正方形のpngをhtmlで読み込んでみると

<link rel="apple-touch-icon" sizes="192x192" href="icon.png">

有効と判定してくれました!

Screen Shot 2019-07-28 at 4.38.01.png

OSテーマと同期 OS theme syncing

OSをダークテーマにすると、設定から変更しなくてもdevtoolが自動でダークテーマになる

<-左 Chrome (バージョン 75) : Chrome Canary (バージョン 77) 右->

Screen Shot 2019-07-28 at 4.43.46.png

条件付きブレークポイントにショートカットキーが割り当てられた Keyboard shortcut for opening the Breakpoint Editor

ソースパネルのエディタにフォーカスがあるときにControl + Alt + B(Windows)またはCommand + Option + B(Mac)を押すと、Conditional Breakpointsを設定できるエディタを開ける(今まではショートカットはなく、ソースパネルのエディタの行番号を右クリックのメニューから設定できる)

Conditional Breakpointsとは
Conditional Breakpoints(条件付きブレークポイント)を使用すると、定義された式がtrueと評価されたときにコードブロック内でブレークできます。

参考:https://blittle.github.io/chrome-dev-tools/sources/conditional-breakpoints.html

SAHGqsaD9R.gif

Networkパネルでprefechしたリソースがわかるように Prefetch cache in Network panel

prefetchで取得したリソースについては、Networkパネルで(prefetch cache)と表示してくれるようになった

<link rel="prefetch" href="image.png">

Screen Shot 2019-07-28 at 6.png

Prefetchとは
こちらの記事がprefetchを含むResource Hintsについて、とてもわかりやすくて私は毎回参考にしています。m(_ _)m

参考:Resource Hintsでリソースを事前取得しよう! - Qiita

オブジェクトプレビューにクラスのプライベートプロパティ表示 Private properties when viewing objects

コンソールのオブジェクトプレビューにクラスのプライベートプロパティが表示されるようになりました

const human = new class {
    #name = "Jhon";
    age = 20;
}
console.log(human)

Screen Shot 2019-07-28 at 6.20.53.png

Applicationパネルで通知とpushメッセージを確認できるように Notifications and push messages in the Application panel

Applicationパネルで通知とpushメッセージを確認できるようになる

プッシュメッセージは、サーバーがサービスワーカーに情報を送信したときに発生
通知は、サービス担当者またはページのスクリプトがユーザーに情報を表示したときに発生

試してみるとこんな感じで表示されるようでした。

RN76yU3Sv2.gif


最後まで見ていただいてありがとうございましたm(_ _)m

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

【Programming News】Qiitaまとめ記事 July 27, 2019 Vol.13

筆者が2019/7/27(土)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら

2019/7/15(月)~2019/7/20(土)のWeeklyのまとめのまとめ記事もこちらで公開しております。

Java

Python

Ruby

Rails

JavaScript

Node.js

Vue.js

Android

Swift

PHP

A-Frame

Line

MySQL

Azure

AWS

Firebase

TypeScript

Google Apps Script

Go言語

Rust

Julia

ShellScript

Unity

Docker

Develop

Raspberry

Heroku

OAuth2.0

Visual Studio Code

IntelliJ IDEA

更新情報

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

カメラモジュールOV7725の使い方(ESP32-WROVER-B)

概要

以前OV2640を使って画像を取得したので、今回はOV7725を使って画像を取得してみました。OV7725はjpegではなく、bmp形式で画像が出力されるので、少し制御方法が異なります。

今回はブラウザ上でOV7725のレジスタを変更できるようにしました!結論から申し上げると、30万画素ではあまりきれいな写真は撮れませんでした。初期の携帯に搭載されていたカメラと同性能らしいです…
きれいな写真を撮りたい場合はラズベリーパイを使って、OV5640で撮影したりした方がいいかと思います。

te.jpg

DSC_0418.JPG

OV2640ではESP32-WROOM-32を使用しましたが、OV7725の場合はbmpで、メモリ使用量が増えるため、PSRAM搭載のESP32-WROVER-Bを使用しました。

公式
https://github.com/espressif/esp32-camera

たぶん公式に採用される前
https://github.com/igrr/esp32-cam-demo

ピン配置
https://www.instructables.com/id/The-Incredible-ESP32-Wrover-From-Espressif/

OV7725 データシート
https://cdn.sparkfun.com/datasheets/Sensors/LightImaging/OV7725.pdf

設定

esp-idfのフォルダ内でsubmoduleをクローンします。

git submodule add https://github.com/espressif/esp32-camera.git components/esp32-camera
mv esp-idf/components/esp32-camera/driver/private_include/sccb.h esp-idf/components/esp32-camera/driver/include/sccb.h
mv esp-idf/components/esp32-camera/driver/private_include/sensor.h esp-idf/components/esp32-camera/driver/include/sensor.h

注意点

  • driver公式のページ通りにbuildすると、camera_init()が重複していてエラーになったので、中身をapp_main関数に移動しました。
  • ピン配置は公式のものではなく、https://github.com/igrr/esp32-cam-demo を参考にしました。
  • XCLK:20MHz→6MHzへ変更

コード

esp-idfのexampleにあるsimple serverを改良し、ブラウザで「http://<ローカルアドレス>/」へアクセスすると撮影画像が表示されます。ajaxで画像データ(base64で変換したデータ)を取得し、ブラウザに表示しています。

全コードは下記に載せています。
https://github.com/koki-iwaizumi/esp32-ov7725

サイズはVGA、出力形式はYUV422にしています。wifi周りの設定は変更してください。

app_main.c
#include <string.h>
#include <esp_wifi.h>
#include <esp_event_loop.h>
#include <esp_log.h>
#include <esp_system.h>
#include <nvs_flash.h>
#include <sys/param.h>
#include <esp_http_server.h>
#include <esp_camera.h>

#include "mbedtls/base64.h"
#include "cJSON.h"

#include "sensor.h"
#include "sccb.h"

/********* io ***********/
#define CAM_PIN_PWDN    2
#define CAM_PIN_RESET   15
#define CAM_PIN_XCLK    27
#define CAM_PIN_SIOD    25
#define CAM_PIN_SIOC    23
#define CAM_PIN_VSYNC   22
#define CAM_PIN_HREF    26
#define CAM_PIN_PCLK    21
#define CAM_PIN_D9      19
#define CAM_PIN_D8      36
#define CAM_PIN_D7      18
#define CAM_PIN_D6      39
#define CAM_PIN_D5      5
#define CAM_PIN_D4      34
#define CAM_PIN_D3      32
#define CAM_PIN_D2      35

/********* wifi ***********/
#define EXAMPLE_WIFI_SSID "*"
#define EXAMPLE_WIFI_PASS "*"

static const char *TAG = "APP";
sensor_t* sensor = NULL;

static camera_config_t camera_config = {
    .pin_pwdn  = CAM_PIN_PWDN,
    .pin_reset = CAM_PIN_RESET,
    .pin_xclk = CAM_PIN_XCLK,
    .pin_sscb_sda = CAM_PIN_SIOD,
    .pin_sscb_scl = CAM_PIN_SIOC,

    .pin_d7 = CAM_PIN_D9,
    .pin_d6 = CAM_PIN_D8,
    .pin_d5 = CAM_PIN_D7,
    .pin_d4 = CAM_PIN_D6,
    .pin_d3 = CAM_PIN_D5,
    .pin_d2 = CAM_PIN_D4,
    .pin_d1 = CAM_PIN_D3,
    .pin_d0 = CAM_PIN_D2,
    .pin_vsync = CAM_PIN_VSYNC,
    .pin_href = CAM_PIN_HREF,
    .pin_pclk = CAM_PIN_PCLK,

    .xclk_freq_hz = 6000000,
    .ledc_timer = LEDC_TIMER_0,
    .ledc_channel = LEDC_CHANNEL_0,

    .pixel_format = PIXFORMAT_YUV422,
    .frame_size = FRAMESIZE_VGA,

    .jpeg_quality = 12,
    .fb_count = 1
};

esp_err_t index_get_handler(httpd_req_t *req)
{
    extern const unsigned char index_start[] asm("_binary_index_html_start");
    extern const unsigned char index_end[]   asm("_binary_index_html_end");
    const size_t index_size = (index_end - index_start);

    httpd_resp_send_chunk(req, (const char *)index_start, index_size);
    httpd_resp_sendstr_chunk(req, NULL);

    return ESP_OK;
}

esp_err_t image_post_handler(httpd_req_t *req)
{
    int content_buf_size = 10 * 1000;
    char *content_buf = calloc(content_buf_size, sizeof(char));
    if(content_buf == NULL){
        ESP_LOGE(TAG, "Failed to allocate frame buffer - content_buf");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    if(httpd_req_recv(req, content_buf, req->content_len) <= 0){
        ESP_LOGE(TAG, "httpd_req_recv failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    ESP_LOGE(TAG, "httpd_req_recv content=%s", content_buf);

    cJSON *content_json = cJSON_Parse(content_buf);

    for(int i = 0; i <= 172; i++){
        char key[4];
        itoa(i,key,10);
        int value = strtol(cJSON_GetObjectItem(content_json, key)->valuestring, NULL, 16);

        SCCB_Write(sensor->slv_addr, i, value);
    }

    cJSON_Delete(content_json);
    free(content_buf);

    camera_fb_t *fb = NULL;
    esp_err_t res = ESP_OK;
    int64_t fr_start = esp_timer_get_time();

    fb = esp_camera_fb_get();
    if( ! fb){
        ESP_LOGE(TAG, "Camera capture failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    uint8_t *buf = NULL;
    size_t buf_len = 0;
    bool converted = frame2bmp(fb, &buf, &buf_len);
    esp_camera_fb_return(fb);
    if( ! converted){
        ESP_LOGE(TAG, "BMP conversion failed");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    ESP_LOGI(TAG, "esp_get_free_heap_size (%d bytes)", esp_get_free_heap_size());
    ESP_LOGI(TAG, "esp_get_minimum_free_heap_size (%d bytes)", esp_get_minimum_free_heap_size());
    ESP_LOGI(TAG, "heap_caps_get_free_size(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_free_size(MALLOC_CAP_8BIT));
    ESP_LOGI(TAG, "heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT));
    ESP_LOGI(TAG, "heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));

    int image_buf_size = 1300 * 1000;
    uint8_t *image_buf = calloc(image_buf_size, sizeof(char));
    if(image_buf == NULL){
        free(buf);
        ESP_LOGE(TAG, "Failed to allocate frame buffer - image_buf");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    size_t olen = 0;
    int base64_err = mbedtls_base64_encode(image_buf, image_buf_size, &olen, buf, buf_len);
    free(buf);
    if (base64_err != 0) {
        ESP_LOGE(TAG, "error base64 encoding, error %d, buff size: %d", base64_err, olen);
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    ESP_LOGI(TAG, "esp_get_free_heap_size (%d bytes)", esp_get_free_heap_size());
    ESP_LOGI(TAG, "esp_get_minimum_free_heap_size (%d bytes)", esp_get_minimum_free_heap_size());
    ESP_LOGI(TAG, "heap_caps_get_free_size(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_free_size(MALLOC_CAP_8BIT));
    ESP_LOGI(TAG, "heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_minimum_free_size(MALLOC_CAP_8BIT));
    ESP_LOGI(TAG, "heap_caps_get_largest_free_block(MALLOC_CAP_8BIT) (%d bytes)", heap_caps_get_largest_free_block(MALLOC_CAP_8BIT));

    uint8_t *image_json = calloc(image_buf_size, sizeof(char));
    if(image_json == NULL){
        free(image_buf);
        ESP_LOGE(TAG, "Failed to allocate frame buffer - image_json");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    sprintf((char *)image_json, "{\"image\":\"%s\"}", (const char *)image_buf);
    free(image_buf);
    ESP_LOGI(TAG, "image_json length=%d", strlen((const char*)image_json));

    res = httpd_resp_set_type(req, "application/json") || httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*") || httpd_resp_send(req, (const char *)image_json, strlen((const char*)image_json));
    free(image_json);

    int64_t fr_end = esp_timer_get_time();
    ESP_LOGI(TAG, "BMP: %uKB %ums", (uint32_t)(buf_len/1024), (uint32_t)((fr_end - fr_start)/1000));
    return res;
}

esp_err_t config_get_handler(httpd_req_t *req)
{
    int data_json_size = 10 * 1000;
    char *data_json = calloc(data_json_size, sizeof(char));
    if(data_json == NULL){
        ESP_LOGE(TAG, "Failed to allocate frame buffer - data_json");
        httpd_resp_send_500(req);
        return ESP_FAIL;
    }

    strcat(data_json, "{");
    for(int i = 0; i <= 172; i++){
        char data_json_buf[255] = {'\0'};
        sprintf(data_json_buf, "\"%d\":\"%d\"", i, SCCB_Read(sensor->slv_addr, i));
        strcat(data_json, data_json_buf);
        if(i != 172) strcat(data_json, ",");
    }
    strcat(data_json, "}");

    ESP_LOGI(TAG, "data_json=%s", data_json);
    ESP_LOGI(TAG, "data_json length=%d", strlen((const char*)data_json));

    esp_err_t res = httpd_resp_set_type(req, "application/json") || httpd_resp_set_hdr(req, "Access-Control-Allow-Origin", "*") || httpd_resp_send(req, (const char *)data_json, strlen((const char*)data_json));
    free(data_json);

    return res;
}

httpd_uri_t index_uri = {
    .uri = "/",
    .method = HTTP_GET,
    .handler = index_get_handler,
};

httpd_uri_t image_uri = {
    .uri = "/image",
    .method = HTTP_POST,
    .handler = image_post_handler,
};

httpd_uri_t config_uri = {
    .uri = "/config",
    .method = HTTP_GET,
    .handler = config_get_handler,
};

httpd_handle_t start_webserver(void)
{
    httpd_handle_t server = NULL;
    httpd_config_t config = HTTPD_DEFAULT_CONFIG();

    ESP_LOGI(TAG, "Starting server on port: '%d'", config.server_port);
    if(httpd_start(&server, &config) == ESP_OK){
        ESP_LOGI(TAG, "Registering URI handlers");
        httpd_register_uri_handler(server, &index_uri);
        httpd_register_uri_handler(server, &image_uri);
        httpd_register_uri_handler(server, &config_uri);
        return server;
    }

    ESP_LOGI(TAG, "Error starting server!");
    return NULL;
}

void stop_webserver(httpd_handle_t server)
{
    httpd_stop(server);
}

static esp_err_t event_handler(void *ctx, system_event_t *event)
{
    httpd_handle_t *server = (httpd_handle_t *) ctx;

    switch(event->event_id){
        case SYSTEM_EVENT_STA_START:
            ESP_LOGI(TAG, "SYSTEM_EVENT_STA_START");
            ESP_ERROR_CHECK(esp_wifi_connect());
            break;
        case SYSTEM_EVENT_STA_GOT_IP:
            ESP_LOGI(TAG, "SYSTEM_EVENT_STA_GOT_IP");
            ESP_LOGI(TAG, "Got IP: '%s'", ip4addr_ntoa(&event->event_info.got_ip.ip_info.ip));

            if(*server == NULL){
                *server = start_webserver();
            }
            break;
        case SYSTEM_EVENT_STA_DISCONNECTED:
            ESP_LOGI(TAG, "SYSTEM_EVENT_STA_DISCONNECTED");
            ESP_ERROR_CHECK(esp_wifi_connect());

            if(*server){
                stop_webserver(*server);
                *server = NULL;
            }
            break;
        default:
            break;
    }
    return ESP_OK;
}

static void initialise_wifi(void *arg)
{
    tcpip_adapter_init();
    ESP_ERROR_CHECK(esp_event_loop_init(event_handler, arg));
    wifi_init_config_t cfg = WIFI_INIT_CONFIG_DEFAULT();
    ESP_ERROR_CHECK(esp_wifi_init(&cfg));
    ESP_ERROR_CHECK(esp_wifi_set_storage(WIFI_STORAGE_RAM));
    wifi_config_t wifi_config = {
        .sta = {
            .ssid = EXAMPLE_WIFI_SSID,
            .password = EXAMPLE_WIFI_PASS,
        },
    };
    ESP_LOGI(TAG, "Setting WiFi configuration SSID %s...", wifi_config.sta.ssid);
    ESP_ERROR_CHECK(esp_wifi_set_mode(WIFI_MODE_STA));
    ESP_ERROR_CHECK(esp_wifi_set_config(ESP_IF_WIFI_STA, &wifi_config));
    ESP_ERROR_CHECK(esp_wifi_start());
}

void app_main()
{
    static httpd_handle_t server = NULL;
    ESP_ERROR_CHECK(nvs_flash_init());

    esp_err_t err = esp_camera_init(&camera_config);
    if(err != ESP_OK){
        ESP_LOGE(TAG, "Camera Init Failed");
        return;
    }

    sensor = esp_camera_sensor_get();

    initialise_wifi(&server);
}

HTML周り

index.html
<!doctype html>
<html lang="ja">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
        <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.6.4/css/all.css">
        <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/css/bootstrap.min.css" integrity="sha384-ggOyR0iXCbMQv3Xipma34MD+dH/1fQ784/j6cY/iJTQUOhcWr7x9JvoRxT2MZw1T" crossorigin="anonymous">
        <title>OV7725</title>
        <style>
            .alert{
                display:none;
            }
            #overlay{   
                position: fixed;
                top: 0;
                z-index: 100;
                width: 100%;
                height:100%;
                display: none;
                background: rgba(0,0,0,0.6);
            }
            .cv-spinner{
                height: 100%;
                display: flex;
                justify-content: center;
                align-items: center;  
            }
            .spinner{
                width: 40px;
                height: 40px;
                border: 4px #ddd solid;
                border-top: 4px #2e93e6 solid;
                border-radius: 50%;
                animation: sp-anime 0.8s infinite linear;
            }
            @keyframes sp-anime{
                0% { 
                    transform: rotate(0deg); 
                }
                100% { 
                    transform: rotate(359deg); 
                }
            }
            .is-hide{
                display:none;
            }
            .ov_a{
                height:600px;
                overflow-y:auto;
            }
        </style>
    </head>
    <body>

        <div id="overlay">
            <div class="cv-spinner">
                <span class="spinner"></span>
            </div>
        </div>

        <div class="text-center mt-3 mb-3">
            <h1>OV7725</h1>
        </div>

        <div class="container-fluid">
            <div class="row">
                <div class="col-lg-6 text-center mb-4">
                    <div class="row">
                        <div class="col-md-12">
                            <div class="alert alert-success" role="alert">success</div>
                            <div class="alert alert-danger" role="alert">failed</div>
                        </div>
                        <div class="col-md-12">
                            <img id="img" class="mw-100">
                        </div>
                        <div class="col-md-12 mt-4">
                            <button id="image_button" type="button" class="btn btn-success w-100">Take a Picture</button>
                        </div>
                    </div>
                </div>

                <div class="col-lg-6">
                    <div class="ov_a">
                        <form id="form">
                            <table id="table_regs" class="table table-striped">
                                <tr>
                                    <th>Address(Hex)</th>
                                    <th>Value(Hex)</th>
                                </tr>
                            </table>
                        </form>
                    </div>
                </div>
            </div>
        </div>

        <script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
        <script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script>

        <script type="text/javascript">
            $(function(){

                var parseJson = function(data) {
                    var returnJson = {};
                    for(idx = 0; idx < data.length; idx++){
                    returnJson[data[idx].name] = data[idx].value
                    }
                    return returnJson;
                };

                $(document).ready(function(){
                    read_config();
                    get_image();
                });

                $("#image_button").on("click", get_image);

                function get_image(){
                    $.ajax({
                        url: "./image",
                        type: "POST",
                        data: JSON.stringify(parseJson($("#form").serializeArray())),
                        dataType: "json",
                        beforeSend: function() {
                            $(".alert").css("display", "none");
                            $("#overlay").fadeIn(300);
                        }
                    })
                    .done((data) => {
                        $(".alert").css("display", "none");
                        $(".alert-success").css("display", "block");
                        $("#img").attr("src", "data:image/jpeg;base64," + data["image"]);
                    })
                    .fail((data) => {
                        $(".alert").css("display", "none");
                        $(".alert-danger").css("display", "block");
                        console.log(data);
                    })
                    .always((data) => {
                        setTimeout(function(){
                            $("#overlay").fadeOut(300);
                        },500);
                    });
                }

                function read_config(){
                    $.ajax({
                        url: "./config",
                        type: "GET",
                        dataType: "json",
                        async: false,
                    })
                    .done((data) => {
                        $.each(data, function(i, item){
                            $("#table_regs").append('<tr><td>0x' + Number(i).toString(16) + '</td><td><div class="input-group"><div class="input-group-prepend"><span class="input-group-text" id="text1a">0x</span></div><input name="' + i + '" value="' + Number(item).toString(16) + '" type="text" class="form-control"></div></td></tr>');
                        });
                    })
                }
            });
        </script>
    </body>
</html>

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

【Programming News】Qiitaまとめ記事 July 26, 2019 Vol.12

遅くなりましたが筆者が2019/7/26(金)に気になったQiitaの記事をまとめました。昨日のまとめ記事はこちら

2019/7/15(月)~2019/7/20(土)のWeeklyのまとめのまとめ記事もこちらで公開しております。

Python

JavaScript

Ruby

Android

Swift

Kotlin

Rails

React

Nuxt.js

Laravel

Sass

PHP

MySQL

Oracle

AWS

Docker

Visual Studio

IBM Cloud

Unity

TypeScript

Raspberry

Julia

Git

Vim

VirtualBox

更新情報

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

v-slotのサンプルコード(Vue.js初心者)

Vue.js公式サイトのスコープ付きスロットに、v-slotの解説とコード断片が載っていますが、 最初読んだ時、データの関係が私にはよく理解できず、試しにコード書いても変数のReferenceErrorで進まず。「サンプルコード全体(HTML+JavaScript)は一体どのように書けば動作するのか?」と躓いてしまいました。が、試行錯誤してやっとわかったのでここに書きます。

  • 対象Version: Vue.js 2.6.10+

躓いた箇所

この解説文。

親コンポーネント内でスロットコンテンツとして user を使えるようにするためには、 要素の属性として user をバインドします:

HTML
<span>
  <slot v-bind:user="user">
    {{ user.lastName }}
  </slot>
</span>

v-bindの両辺にuserが出てくるので、いざコードを書いていくと「自分が今ここに書いたuserは上の解説文でいうどれのこと?」と、データの関係があやふやな状態になり頭が混乱していきます。

わかった!

v-slotのコーディングを実演しているこの動画を観て、コツがわかりました!

Vue 2.6 First Look And V-Slot Tutorial!

完成したサンプルコード

See the Pen [Vue.js basics] v-slot example 1 by Kazuhiro Hashimoto (@kaz_hashimoto) on CodePen.

理解を助けるためのポイント

先にJavaScript側から。

JavaScript
Vue.component('current-user', {
  data: function() {
    return {
      xxuser: {
        firstName: 'Kazuhiro',
        lastName: 'Hashimoto'
      }
    };
  },
  template:`
<span>
  <slot v-bind:content="xxuser">
    {{ xxuser.lastName }}
  </slot>
</span>
`
});

new Vue({
  el: '#app'
});
  • v-bind:○○○="▲▲▲" の左辺と右辺で別々の名前を付けて、見た目で区別できるようにしておく。上記サンプルでは、左辺○○○をcontent、右辺▲▲▲をあえてxxuserとした。
  • dataプロパティはVue.componentの引数に渡すオブジェクト内で定義し、その値は関数リテラル。で、その関数の戻り値は、xxuserを要素に持つオブジェクト。
  • templateに記述するslotのコンテンツ{{...}}に入れるのは、v-bind:○○○="▲▲▲"の▲▲▲、つまりxxuser経由でアクセスする式。
HTML
<div id="app">
  <current-user>
    <template v-slot:default="slotProps">
      {{ slotProps.content.firstName }}
    </template>
  </current-user>
  <hr>
  <current-user></current-user>
</div>
  • v-slotの右辺の変数slotPropsでスロットプロパティを受け取る(名前は何でもよい)
  • Vue.componentで指定したdata: function() {...}の戻り値のオブジェクトが、slotPropsに相当するイメージ。
  • slotPropsからxxuserのメンバーにアクセスするには、v-bind:○○○="▲▲▲" に指定した○○○部分、つまりcontent経由でアクセスする。(ココが最初わからなくてハマった)

v-slotを使ったコーディングに慣れるまでは、自分にわかりやすいv-bind:content="xx..."形式でサンプルコードを書いて練習しようと思います。

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

「2019年にReact Nativeを使い始める:初めてのアプリケーションを構築する」をやってみた

今までReact Nativeをやってみたいと思っていたのですが、なかなか機会がなく、今回↓のチュートリアルがとてもわかりやすそうでしたのでやってみました。

Getting Started with React Native in 2019: Build Your First App

この投稿で書いてある内容は、素人の私が参考サイトの通りやってみただけの内容です。
参考サイトの方では詳しい解説などもあるため、私の投稿は読まずに参考サイトを見れば大丈夫です。
ですが一応やってみたので投稿させていただきました。

私が試してみたコード

バージョン

  • macOS Mojave 10.14.5
  • react 16.8.6
  • react-native 0.60.4
  • Xcode 10.2
  • Android Studio 3.3.2

React Native CLI のインストール

$ yarn add -D react-native-cli
$ yarn react-native --version
yarn run v1.17.3
warning package.json: No license field
$ /Users/kengookumura/dev/tmp/rn/node_modules/.bin/react-native --version
react-native-cli: 2.0.1 # ←react-native-cliのバージョンを確認
react-native: n/a - not inside a React Native project directory
✨  Done in 0.19s.

React Native Appを実行

yarn react-native init EmojiDictRN
cd EmojiDictRN/

Screen Shot 2019-07-27 at 19.44.32.png

アプリを起動

yarn start

iOSで起動

yarn startで実行しているのとは別のターミナルのタブで↓を実行する

yarn react-native run-ios

iOSシミュレータを起動することができました

Screen Shot 2019-07-27 at 21.17.42.png


※最初Xcode 11.0 beta 3がインストールされている状態で行ったのですが、以下のようにエラーが出ました。
Xcode 10.2をインストールして動かせました

error Could not find "iPhone X" simulator.

↓からXcode 10.2をダウンロードしました

Androidで起動

yarn startで実行しているのとは別のターミナルのタブで↓を実行する

bash
yarn react-native run-android #※私の環境ではエラーになりました

エラーになってしまったので、自分がやってみことです

まずこちらを参考にし、ANDROID_HOMEのパスを設定しました

export ANDROID_HOME=/Users/kengookumura/Library/Android/sdk

私の環境では↑だけではうまくいかず、

↑こちらのリンクで、「アプリケーションを実行するには、Androidシミュレータを起動するか、デバイスを接続する必要がある」とあっため、↓を参考にエミュレータを起動した上でyarn react-native run-androidを実行するとうまくいきました

Screen Shot 2019-07-27 at 23.17.54.png

App.jsを変更してみる

参考サイトの通りにApp.jsを変更して、iOSシミュレータの場合はcommand + Rでリロードすると、

App.js
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';

export default class App extends Component {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.instructions}>Hello World!</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF'
    },
    instructions: {
        textAlign: 'center',
        color: '#333333',
        marginBottom: 5
    }
});

表示を変更することができました

Screen Shot 2019-07-27 at 23.32.55.png

Hot Reloadする

iOSの場合ですと、iOSエミュレータのメニューで[Hardware] > [Shake Gesture]とするとメニューが表示されるので、
「Enable Hot Reloading」を選択するとHot Reloadしてくれました

Screen Shot 2019-07-27 at 23.49.59.png

lj4uTkCLT2.gif

簡単にコンポーネントを作成する

src/components/EmojiDict.jsファイルを作成する

src/components/EmojiDict.js
import React, { Component } from 'react';
import { View, Text, StyleSheet } from 'react-native';

class EmojiDict extends Component {
    state = {
        '?': '? Smiley',
        '?': '? Rocket',
        '⚛️': '⚛️ Atom Symbol'
    };

    render() {
        return (
            <View style={styles.container}>
                <Text>{this.state['?']}</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center'
    }
});

export default EmojiDict;
App.js
import React, { Component } from 'react';
import { Platform, StyleSheet, Text, View } from 'react-native';
import EmojiDict from './src/components/EmojiDict';

export default class App extends Component {
  render() {
    return <EmojiDict />;
  }
}

const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#F5FCFF'
  },
  instructions: {
    textAlign: 'center',
    color: '#333333',
    marginBottom: 5
  }
});

Screen Shot 2019-07-28 at 0.16.46.png


emojiのリストをFlatListで表示するする

src/components/EmojiDict.js
import React, { Component } from 'react';
import { Text, StyleSheet, FlatList } from 'react-native';

class EmojiDict extends Component {
    state = {
        '?': '? Smiley',
        '?': '? Rocket',
        '⚛️': '⚛️ Atom Symbol'
    };

    render() {
        return (
            <FlatList
                contentContainerStyle={styles.container}
                data={[
                    { key: '?', value: '? Smiley' },
                    { key: '?', value: '? Rocket' },
                    { key: '⚛️', value: '⚛️ Atom Symbol' }
                ]}
                renderItem={({ item }) => <Text>{item.value}</Text>}
            />
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center'
    }
});

export default EmojiDict;

Screen Shot 2019-07-28 at 0.40.40.png


React Nativeについてはわからないことだらけですが、とりあえず動かすことができたとてもありがたかったです。

読んでいただいてありがとうございました。m(_ _)m

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