20200321のHTMLに関する記事は6件です。

[Ruby on Rails]enumを用いたselectフォームの作成

selectを作る際に、選択肢が3つ程度のものであればoptionで記載しても良いかとは思いますが、多くなればコードが冗長になってしまいます。
enumを用いているカラムに対してであれば、スッキリ綺麗なコードで実装できます。

実装した機能

gyazoリンク
https://gyazo.com/67a692c100281014ff59426fa6eb1a53

enumとは

列挙型、列挙クラスといわれる。
本記事はenumについてではないため、ザックリとした説明になりますが、
自分の認識では、
わざわざ、tableを作成し、外部キーとして呼ばなくとも中身を持ったidとして管理できるもの

どんなものに使うのか?

enumを使う代表的なものといえば、都道府県でしょうか
optionで書くのも面倒ですし、テンプレを使ったとしても冗長で読みやすいコードとは言い難いですね。

product.rb
 enum delivery_prefecture:{
   "北海道":1,"青森県":2,"岩手県":3,"宮城県":4,"秋田県":5,"山形県":6,"福島県":7,
   "茨城県":8,"栃木県":9,"群馬県":10,"埼玉県":11,"千葉県":12,"東京都":13,"神奈川県":14,
   "新潟県":15,"富山県":16,"石川県":17,"福井県":18,"山梨県":19,"長野県":20,
   "岐阜県":21,"静岡県":22,"愛知県":23,"三重県":24,
   "滋賀県":25,"京都府":26,"大阪府":27,"兵庫県":28,"奈良県":29,"和歌山県":30,
   "鳥取県":31,"島根県":32,"岡山県":33,"広島県":34,"山口県":35,
   "徳島県":36,"香川県":37,"愛媛県":38,"高知県":39,
   "福岡県":40,"佐賀県":41,"長崎県":42,"熊本県":43,"大分県":44,"宮崎県":45,"鹿児島県":46, 
   "沖縄県":47
 }

このように記載することで、今回でいうdelivery_prefectureがidで管理されるようになります
migrationファイルはintegerにしておきましょう

selectフォームの作成

products/new.html.haml
 .form__group
   = f.label :delivery_prefecture do
     発送元の地域
     %span.form-description.form-require 必須
   = f.select :delivery_prefecture, Product.delivery_prefectures.keys,{include_blank: '選択してください'},{class: "exhibition__select"}

今回であれば

Product.delivery_prefectures.keys
モデル.カラム名(複数形).keys

この記述で選択肢に先ほどのenumの記述部分が適用されます。
注意としては、複数形にすることです。

ちなみに

{include_blank: '選択してください'}

によって何も選択されていない時に 選択してください と表示されるようにしています

おまけ

products/new.html.haml
 .form__group
   = f.label :delivery_days do
     発送までの日数
     %span.form-description.form-require 必須
   = f.select :delivery_days, Product.delivery_days.keys,{include_blank: '選択してください'},{class: "exhibition__select"}
product.rb
  enum delivery_days:{
    "1~2日で発送": 1,
    "2~3日で発送": 2,
    "3~7日で発送": 3,
  }

このようにカラム名に複数形が使われている場合(あまり望ましくないが、dayだと変なので今回は止むを得ず、、)は、そのままカラム名を記載するだけでうまく行きます。

最後に

本記事がQiita初投稿になります:innocent:
アドバイス等いただけたら幸いです!

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

HTML6의 새로운 기능

d130e3502b21.jpg

이 기사에서는 HTML6에서 가장 많이 바뀔 사항과 HTML6의 새로운 점을 살펴 보겠습니다.
주요 내용을 살펴볼 때 벨트를 고정하십시오.

목차

익스프레스 태그
기본 모달 지원
이미지 크기를 조정할 자유
HTML 6 전용 라이브러리
이미지 및 비디오에 대한 주석
인증 향상
HTML6의 사용자 정의 메뉴
HTML6 통합 카메라
좋은 마이크로 포맷
JavaScript가없는 단일 페이지 앱

참조: https://morioh.com/p/6d422fc49bd2

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

VSCodeの自動補完機能が便利すぎる

VSCodeの自動補完機能の使い方

HTMLファイルを作成する時、!DOCTYPE・・・とか、div class=・・・とか一つ一つ記述するのは結構めんどくさいですよね:joy:

そんなときは、VSCodeの自動補完機能を使ってラクしちゃいましょう!!
例えば、!を入力した後にtabを押すと、下記の記述が自動で生成されます。

<!DOCTYPE html>
<html lang="en">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>Document</title>
</head>
<body>
</body>
</html>

div.contentを記述してtabを押すと

<div class="content">
</div>

div#containerを記述してtabを押すと

<div id="container">
</div>

button.btnを記述してtabを押すと

<button class="btn">
</button>

と言ったように、色々な記述に使えるのでとっても便利です:thumbsup:

cssを読み込むlinkを記述する際は、linkと書くと候補の中にlink:cssがあるので、
そこでtabを押すか選択すると

<link rel="stylesheet" href="style.css">

が自動生成されます。

タイプミスを防ぐためにも、ぜひ一度試してみてください!
他にもここで紹介していない自動補完でオススメのものがあれば、教えていただけると幸いです。
以上、ここまで読んでいただきありがとうございました!

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

複数画像投稿で盛大に自爆した時の確認事項[備忘録]

はじめに

某フリマアプリの模倣アプリを開発中、出品機能実装で複数画像の登録に死ぬほど手を焼いたので、備忘録として掲載します。

誤った記述などあればご指摘いただけると幸いです。

開発環境・前提

Ruby 2.5.1p57
Ruby on rails 5.2.3
jquery-rails 4.3.5
haml-rails 2.0.1
sass-rails 5.1.0
CarrierWave 2.1.0

完成コード

先に完成コードを載せておく。

image.rb
  belongs_to :item, optional: true
  validates_presence_of :item
  validates :content, presence: true
  mount_uploader :content, ImageUploader
item.rb
  belongs_to :brand, optional: true
  belongs_to :user, optional: true
  belongs_to :category,  optional: true
  has_many :images, dependent: :destroy
  accepts_nested_attributes_for :images, allow_destroy: true
items_controller.rb
def new
    @item = Item.new
    @brands = Brand.all
    @category_parent_array = ["指定なし"]
    Category.where(ancestry: nil).each do |parent|
      @category_parent_array << parent.name
    end
    @item.images.build
  end

  def create
    @item = Item.new(item_params)

    if @item.save!
      @image = @item.images.create
      redirect_to :root
    else
      render :new
    end
  end

private
  def item_params
    params.require(:item).permit(
      :name, :description, :condition, :price, 
      :fee, :brand_id, :area, :shipping_days, 
      images_attributes: [:content, :id, :_destroy]
      ).merge(user_id: current_user.id, category_id: params[:category_id], brand_id: params[:item][:brand_id])
  end
new.html.haml
#画像投稿フォームの記述部分
.main-items
    = form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content"
              .js-remove
                %span.js-remove__text
                  削除

モデルへのmout_uploaderの記述

before

image.rb
mount_uploaders :content, ImageUploader

after

image.rb
mount_uploader :content, ImageUploader

mount_uploaderとするかmount_uploadersか。

error.message
NoMethodError (undefined method `map' for #<ActionDispatch::Http::UploadedFile......> #省略
Did you mean?  tap):

app/controllers/items_controller.rb:68:in `create'

mount_uploadersにすると、デフォルトでmapメソッドが使われてしまう。

つまり、1つのfile_fieldに複数の画像データが入っている配列である必要があるのだ。

そういう時は、file_fieldにmultiple: trueを記載する必要がある。

multiple: true

multiple: true を記述すると、一つのfile_fieldに複数画像をアップロードしようとする。

修正前の記述

new.html.haml
= form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除

inputタグのtype[file]部分のHTML(検証)

multiple有り
<input multiple="multiple" class="image-wrapper__image-box__js__label__file js-file" 
id="item_images_attributes_0_content" type="file" 
name="item[images_attributes][0][content][]">

multiple無し
<input class="image-wrapper__image-box__js__label__file js-file"
 id="item_images_attributes_0_content" type="file"
 name="item[images_attributes][0][content]">

デフォルトで設定されるname属性が変わる

私の場合は、各file_fieldに一つずつ保存させるようなコードを書いていたのにもかかわらず、multipleの記述をしてしまっていて、エラーが起きた。

labelタグのfor属性

labelタグのfor属性は、連動させたい子要素のidの値を記述する必要がある。

修正前のlabel部分の記述
html.haml
            %label.image-wrapper__image-box__js__label
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除
修正後
html.haml
            = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required" 
              .js-remove
                %span.js-remove__text
                  削除

labelタグの性質をよく理解せずに使っていた。
修正前の記述だと、検証でみてみるとわかるが、labelタグにfor属性が付与されておらず、inputタグのid属性(ここでは、item_images_attributes_0_content)に対応しておらず、不具合がおきた。

後からわかったことだが、、hidden_fieldの記述を消してやれば、%labelのままでも支障はないことがわかった。

hidden_fieldの記述

修正前の記述

new.html.haml
= form_with model: @item, local: true do |f|
      .wrapper.image-wrapper
        #image-box.image-wrapper__image-box
          = f.fields_for :images do |i|
            .image-wrapper__image-box__js.js-file_group{data:{index: "#{i.index}"}}
              = i.label :content, class: "image-wrapper__image-box__js__label" do
                .image-wrapper__image-box__js__label__image.img_field{id: "img_field--#{i.index}", onClick: "$('#file').click()"}
                  - if @item.images[i.index][:content].present?
                    = image_tag(f.image.content)
                  - else
                    = image_tag 'icon_camera.png', class: "image-wrapper__image-box__js__label__image__url" 
                = i.file_field :content, multiple: true, class: "image-wrapper__image-box__js__label__file js-file", id: "item_images_attributes_#{i.index}_content", required: "required"
                = i.hidden_field :item_id, value: @item.id
              .js-remove
                %span.js-remove__text
                  削除

imagesのitem_idをparamsに送るための記述をしていたが、idや外部キーはデフォルトで送られるようになっているため、必要なかった。逆に、これがあることによって、:contentが入っていない空のfile_fieldがhidden_fieldと共にparamsに送られてしまうため、validationに引っかかってしまう。

最後に

チーム開発で商品出品機能を担当したことにより、HTML&CSS, jQuery, Rubyについての知識がかなり深まった。欲張りな性格なので、いろんな記事のいいとこ取りをしようとした結果、こんなにもの何重もの罠を自分で仕掛けて自分でハマるということになってしまった。次からは是非とも一つ一つの用法や性質を理解した上で、実装していきたい。

でも、何かしらの初学者ってこういう風に泥臭く成長していくのかなぁとも思った。

諦めたら、そこで試合終了だよ。

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

初心者によるプログラミング学習ログ 268日目

100日チャレンジの268日目

twitterの100日チャレンジ#タグ、#100DaysOfCode実施中です。
すでに100日超えましたが、継続。
100日チャレンジは、ぱぺまぺの中ではプログラミングに限らず継続学習のために使っています。
268日目は、

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

Electronでカレンダーを作る④

前回まで

前回は月の切り替えボタンを作った。
月を変えても、表示内容の変更だけで同じ画面を使い続けるので微妙。

月を変えたら画面自体を新しく作り直したい

というわけでindex.jsをいじって、読み込みごとにカレンダーを作るように修正。

index.js
'use strict';

const moment = require("moment");
const ipcRenderer = require("electron").ipcRenderer;

window.onload = function() {

    const month = parseURLParam(location.search).month;

    const callendar = new Callendar(month);
    callendar.show();

    //先月ボタン押下時
    document.getElementById('preMonth').onclick = function() {
        ipcRenderer.sendSync('month',this.value);
    }

    //来月ボタン押下時
    document.getElementById('nextMonth').onclick = function() {
        ipcRenderer.sendSync('month',this.value);
    }
}

/**
 * URLパラメータを分解してkey:valueの形式で返す。
 * @param URL 
 */
function parseURLParam(URL) {
    // URLパラメータを"&"で分離する
    const params = URL.substr(1).split('&');

    var paramsArray = [];
    var keyAndValue = null;

    for(var i = 0 ; i < params.length ; i++) {
        // "&"で分離したパラメータを"="で再分離
        keyAndValue = params[i].split("=");

        // パラメータを連想配列でセット
        paramsArray[keyAndValue[0]] = keyAndValue[1];
    }

    // 連想配列パラメータを返す
    return paramsArray;
}

/**
 * カレンダークラス
 * @param month(YYYY-MM) 
 */
const Callendar = function(month) {

    //コンストラクタ
    this._moment = moment(month);

    /**
     * カレンダーを表示する。
     */
    this.show = function() {
        const __moment = this._moment;

        //captionを表示
        document.getElementById('caption').innerText = __moment.format("YYYY年MM月");

        //当月の日数を取得
        const daysOfMonth = __moment.daysInMonth();

        //月初の曜日を取得(index.htmlと合わせるために+1する)
        const firstDayOfManth = __moment.startOf('month').day() + 1;

        //カレンダーの各セルに日付を表示させる
        let cellIndex = 0;
        for(let i = 1; i < daysOfMonth + 1; i++) {
            if(i === 1) {
                cellIndex += firstDayOfManth;
            } else {
                cellIndex++;
            }
            document.getElementById("cell" + cellIndex).innerText = i;
        }

        //6行目の第1セルが空白なら6行目自体を非表示にする。
        if(document.getElementById("cell36").innerText === "") {
            document.getElementById('row6').style.visibility = "hidden";
        }

        //先月
        document.getElementById('preMonth').value = __moment.add(-1,'month').format("YYYY-MM");
        //来月(先月のmomentオブジェクトとなっているので+2ヶ月)
        document.getElementById('nextMonth').value = __moment.add(2,'month').format("YYYY-MM");

    }
}

新しくCallendarクラスを作成し、状態としてmomentオブジェクトを持つようにした。

window.onload = function() {

    const month = parseURLParam(location.search).month;

    const callendar = new Callendar(month);
    callendar.show();

window.onload内で画面読み込み毎にURLパラメータから表示月を読み込んでカレンダーを作成する。

main.jsにも少し手を加える。

main.js
'use strict';

// Electronのモジュール
const electron = require("electron");
// Momentのモジュール
const moment = require("moment");

// アプリケーションをコントロールするモジュール
const app = electron.app;

// ウィンドウを作成するモジュール
const BrowserWindow = electron.BrowserWindow;

// メインウィンドウはGCされないようにグローバル宣言
let mainWindow;

// 全てのウィンドウが閉じたら終了
app.on('window-all-closed', function() {
  if (process.platform != 'darwin') {
    app.quit();
  }
});

// Electronの初期化完了後に実行
app.on('ready', function() {
  // 当月をYYYY-MM形式で取得
  const month = moment().format('YYYY-MM');

  // メイン画面の表示。ウィンドウの幅、高さを指定できる
  mainWindow = new BrowserWindow({width: 800, height: 650});
  // 初期表示は当月をパラメータで渡す
  mainWindow.loadURL('file://' + __dirname + '/index.html?month=' + month);

  // ウィンドウが閉じられたらアプリも終了
  mainWindow.on('closed', function() {
    mainWindow = null;
  });

  // ipc通信受信時の処理
  const ipcMain = electron.ipcMain;
  ipcMain.on('month', (event, arg) => {
    // 受信した値をパラメータに渡して画面再読み込み
    mainWindow.loadURL('file://' + __dirname + '/index.html?month=' + arg);
  })
});

ipc通信でメッセージのやり取りを作る

Electronはipc通信とやらでプロセス間のメッセージの送受信機能を実現しているらしい。

使うの自体は簡単。

送信側(index.js)
    //ipcモジュール
    const ipcRenderer = require("electron").ipcRenderer;

    //先月ボタン押下時
    document.getElementById('preMonth').onclick = function() {
        ipcRenderer.sendSync('month',this.value);
    }

    //来月ボタン押下時
    document.getElementById('nextMonth').onclick = function() {
        ipcRenderer.sendSync('month',this.value);
    }

ボタンが押されたらipcRenderer.sendSync('month',this.value)で「month」というキーの値を同期送信している。

受信側(main.js)
  // ipc通信受信時の処理
  const ipcMain = electron.ipcMain;
  ipcMain.on('month', (event, arg) => {
    // 受信した値をパラメータに渡して画面再読み込み
    mainWindow.loadURL('file://' + __dirname + '/index.html?month=' + arg);
  })

ipcMain.on('month', コールバック関数)で「month」キーのメッセージ受信時の処理を記述。
受け取った値はコールバック関数の第二引数(ここではargs)で取得可能。

受信した値(YYYY-MM)をパラメータに設定して画面を読み込みなおす。

$ electron .

image.png
image.png

問題なく月の変更が出来ていそう。

TODO

・月を変える度に画面更新が行われて数秒真っ白画面になっちゃうのが微妙。もっとフレキシブルな切り替えにしたい。
・見た目をどうにかしたい。

あとがき

結局再読み込みになるのでCallendarクラスのメリットがほぼ無い気がしてる。
画面を裏で複数作っておいて、それを切り替えていくとかが良いのかな。

明日、お仕事の方で本番リリースなので寝ます。

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