テックアカデミー第17回メンタリング:ファイル選択ボタンの不具合を解消した流れ

2018年4月30日

昨日はテックアカデミー第17回目のメンタリングでした。

今回は結構な難所となりましてメンターの方にソースを見てもらい修正してもらいようやく解決した所だったのです。

ファイル選択ボタンの不具合と解決策の確認

前回、RailsでAWS S3へ画像のアップロードが出来た♪という記事を投稿しました。

ようやく画像アップロード部分の実装が完了して「やれやれ‥」と思っていた矢先、herokuにデプロイしてブラウザで見るとおかしな表示が‥

[Browse] No file selected.

何で英語表記になってんの?

特にこれは僕のスマホ(ASUS)での事象のようです(iphoneでは発生しなかった)

この部分を相談させてもらいました。

まず参考にしたサイトがこちら。
最速・最高のファイルアップロードに近づくための1歩

これの序盤のUI編のところです。

デモサイトがこちら。デモサイト

何をやってるかというと、ファイルインプットの部分をCSSで見えなくして、自作したファイルアップロードボタンを作って、それがクリックされたらjavascriptで見えなくしたボタンをクリックさせる手法です。

ファイルアップロード絡みはブラウザによって表示が変わったりするので、しばしばこの手法が取られるんですよ。

とのこと。

実際にやってみたのですが、これが結構難解です。わざわざ初心者にわかりにくく書いてあるのだろうか。。

1個1個詳しく紐解いていきます。
デモサイト

まずこの部分ですが

<button type="button">
  upload
</button>
<input type="file" name="file" id="file">

input typeのところがファイル選択の部分です。で、id=”file”を指定しています。CSSで以下のように記述することで高さを0にして表示を消してる訳ですね。

#file {
  height: 0px;
  visibility: hidden;
  position: absolute;
}

それからこれ↓。

$("button").on("click", function() {
  $("#file").click();
});

1行目のbuttonと指定しているところ、これはhtml内に配置されたどこかのbuttonがクリックされたらこのfunctionが動くようになっています。だから他にもbuttonタグがあれば、そっちでも反応しちゃうのです。

で、クリックされたら#file(これはid=fileという意味)で、idにfileが指定された部分をクリックせよ、と読めます。

それが何かと言うとさっきの

<input type="file" name="file" id="file">

この部分です。これ表示はCSSで消してるけど、機能を消してる訳じゃないので動きます。だから画面上のどこかのbuttonタグがクリックされたら、この消したファイル選択部分がクリックされたのと同じ挙動になります。

これが全容です。で、fileとかbuttonとかどこにでもあるような単語が使われてるので理解しにくくなってるんだと思います(少なくとも自分には理解できなかった(´・ω・`)

で、これをRailsの方に持ってきて実装しようと思いました。

Railsでファイルアップロードボタンを自作する

ユーザー画像のアップロード部分抜粋。

こんなような記述をしています。

  <%= form_for(@user) do |f| %>
  <%= f.file_field :image %>
  <%= f.submit 'プロフィール編集を完了する' %>

これを↓のようにしてbuttonタグを追記しました。

  <%= form_for(@user) do |f| %>
  <button type="button">選択</button>
  <%= f.file_field :image %>
  <%= f.submit 'プロフィール編集を完了する' %>
#file_button {
  height: 0px;
  visibility: hidden;
  position: absolute;
}

これだとf.file_fieldにidが付与されないのでidを付与するために1時間調べまくりました(けど見つからなかった‥リファレンスも意味不明だし、サンプルもないし泣きそうでした)結果↓このようにしてidを付与できました。

<%= f.file_field :image, id: 'file_button' %>

これによってファイル選択ボタンが非表示になりました。で、その上に[選択]という自作のボタンが出現しました。ここから自分では進められなくてテックアカデミーのメンターに確認。

javascriptってどこに置けばよいのですか?

ボタンの近くとか、jsファイルの中にいれておくでもよいですよ。

じゃぁhead内じゃなくて、bodyタグの中でもよいのですね。

てことで、javascriptをedit.html.erb内に記述。
javascriptを以下のようにしました。しかしこれでは動きませんでした。

<script type="text/javascript">
$("button").on("click", function() {
  $("#file_button").click();
});
</script>

動かないんですけど(´Д⊂グスン

じゃぁクリックイベントが発生しているかを確認しましょう。javascriptを以下のように書き換えて、ボタンクリックした時にアラートが出るか確認してみてください。

<script type="text/javascript">
$("button").on("click", function() {
  alert('hi')
  // $("#file_button").click();
});
</script>
はい。クリックしてもアラートは出ません。

じゃぁ次はscriptのなかにはきているのかを確認しましょう。以下のように書き換えて画面をリロードした瞬間にアラートhihiが出るか確認してみてください。

<script type="text/javascript">
alert('hihi')
$("button").on("click", function() {
  $("#file_button").click();
});
</script>
はい。これは出ました!

てことはscriptには来てるけど、ボタンクリックのイベントが拾えてない事になりますね。

chromeの画面上で右クリック>要素と進んで、出てきた画面のタブからconsoleをクリックして、そこに以下を記述してもらえますか?

$(“button”)

はい。consoleに記述してエンターを押すと。。

jQuery.fn.init(2) [button.navbar-toggle.collapsed, button, prevObject: jQuery.fn.init(1), context: document, selector: “button”]

こんなメッセージが出ました。

これ他にボタンを使ってるから出ましたね。buttonにidを付けて指定しましょう。buttonとスクリプトを以下のようにしてください。

<script type="text/javascript">
$("#select_file_button").on("click", function() {
  $("#file_button").click();
});
</script>

<button type="button" id='select_file_button'>ファイルを選択</button>
あ~id付けてスッキリしました。

でも、動きません。。

じゃぁ私もCloud9に入ってファイル修正してみましょう。

てことで、メンターの方に開発環境に遠隔から入ってもらって調査してもらいました。

結果、以下のようにしたら動きました。

<script type="text/javascript">
$(document).on('click', '#select_file_button', function() { 
  $("#file_button").click();
 })
</script>

javascriptの書き方って複数あって、このようにdoumentという書き方にしたら動きました。
なんでさっきの記述で動かないのかは謎ですけど。

これで自作のファイルアップロードボタンを作ることができました^^

まとめると‥

画面の表示部分は

  <%= form_for(@user) do |f| %>
  <button type="button" id='select_file_button'>選択</button>
  <%= f.file_field :image, id: 'file_button' %>
  <%= f.submit 'プロフィール編集を完了する' %>

cssは

#file_button {
  height: 0px;
  visibility: hidden;
  position: absolute;
}

javascriptは

<script type="text/javascript">
$(document).on('click', '#select_file_button', function() { 
  $("#file_button").click();
 })
</script>

こうなりました。

しかしこれだけでは駄目でした。ファイル選択後にファイル名が表示されないのです。

今回のテックアカデミーの講師とのメンタリングは30分で終了です。ここから1人作業が始まりました。

input type=”file”でファイル名を表示する方法

ファイル名表示をさせます。

画面の表示部分は以下のようになっているので、

  <%= form_for(@user) do |f| %>
  <button type="button" id='select_file_button'>選択</button>
  <%= f.file_field :image, id: 'file_button' %>
  <%= f.submit 'プロフィール編集を完了する' %>

↓このように修正しました。

<%= form_for(@user) do |f| %>
ファイルを選択
<input id="filename" readonly type="text" value="" ><button type="button" id='select_file_button'>選択</button>
<%= f.file_field :image, id: 'file_button' %>
<%= f.submit 'プロフィール編集を完了する' %>

さらにjavascriptを1個追加。

<script type="text/javascript">
$(document).on('change', '#file_button', function() { 
$('#filename').val($(this).val());
 })
</script>

何をしているのかと言うと、まずinput type=”text”タグでテキストボックスを作っています。それをreadonlyでユーザーによる書き換え不可にしています。これにid=”filename”を付与しました。初期値はvalue=””で空です。

で、javascriptですが、#file_buttonが変更されたタイミングで#filenameに値を入れる、という事をやっています。なのでファイルを選択したタイミングで、このテキストボックスに値(ファイル名)が入るようになります。

ふ~やれやれ、これでやっと画像アップロードが出来るぜ・・・と思ったらまた問題が‥

chromeで見ると、選択したファイル名のところに「C:\fakepath\\…」と記述がある。なんじゃこれ?

input type=”file”でC:\fakepath\が入る問題の解決策

調べてみたらファイルパスを表示させないセキュリティが勝手に働き(chromeブラウザのみ)勝手にパスを書き換えてこのような表示にさせているんだそうです。(#^ω^)

ここからさらに調べて、このファイルパスを削除してファイル名だけ表示させるようにjavascriptを書き換えました。replace関数を使って正規表現でパスの先頭から\の間を消せ、という指示です。

<script type="text/javascript">
$(document).on('change', '#file_button', function() { 
  $('#filename').val($(this).val().replace(/^.*\\/, ""));
 })
</script>

今度こそ完成しただろ。

ファイルアップロードボタンの自作の全てのコードをまとめました
input type=”file”ファイル選択アップロードボタンの設置まとめ

テックアカデミーはこちら