慣れないjavascriptで非常に時間がかかってしまったので流れの復習の意味も込めて残しておきます。
はじめに
文字数カウンター javascript とかで検索するといっぱい出てきますのでそちらを参考にしたほうがわかりやすいし、簡単かもしれないです笑
やりたいことを簡単にまとめると
*textareaに入力した文字をkeyupで取得
*textareaに入力しなければならない最低文字数をdata属性で取得
*javascriptを呼び出すためにclassを用意する
こんな感じでしょうか。ということで早速viewから、いじっていきます。
view
<th>
<!-- 質問項目 -->
<span><%= @question.hoge %></span>
</th>
<!-- 解答欄 -->
<td class="js-text" data-counter="<%= hogehoge.num %>">
<%= f.text_area :answer, class: "js-counter"%>
<!-- バリデーションにひっかかったときのための対処 -->
<%if question.answer.nil? %>
<span> 0 文字</span>
<% else %>
<span><%= question.answer.length %> 文字</span>
<% end %>
</td>
今回はformタグを引き継いだ感じになっており、そのままtextareaを指定されていましたのでそのままそれを使用しました。
先ほどの「まとめ」のところで書いたのをそのまま書き現わすという感じですかね。
data属性をformタグにぶちこめなかったのでこんな感じで書いています。
javascript
(function($) {
$.fn.charCount = function(options){
// 汎用性を持たせるために用意
var defaults = {
cE: $(this).next(), //ここでdomをそのまま扱う。
cW: 'font-red',
cS: 'font-green'
};
//オプションがあるならここでデフォルトに上書き
var options = $.extend(defaults, options);
$(this).keyup(function(){
//改行を文字としてカウント
var count = $(this).val().replace(/(\r\n|\r|\n)/g, "\r\n").length;
//データ属性としてもたせた「最低文字数」を取得
var Num = $(this).parent().data("counter");
var hoge = Num - count;
if(hoge >= 0){
$(options.cE).removeClass(options.cS).addClass(options.W);
} else {
$(options.cE).removeClass(options.cW).addClass(options.cS);
}
$(this).parent().find(options.cE).html(count + ' 文字');
});
}
})(jQuery);
$(function(){
$(".js-counter").charCount();
});
長かった・・・とまあこんな感じで実装しました。
悩んだところはやはりjavascriptの部分
特に途中までparent()の存在を知らなくて大変でした笑
parent()を使用することに気づく前はこんな感じの実装。
$(function(){
(function($) {
$.fn.charCount = function(options){
function calculate(obj){
var count = $(obj).val().replace(/(\r\n|\r|\n)/g, "\r\n").length;
var hoge = options.allowed - count;
if(hoge > 0){
$(obj).next().removeClass('font-green');
$(obj).next().addClass('font-red');
} else {
$(obj).next().removeClass('font-red');
$(obj).next().addClass('font-green');
}
$(obj).next().html(count + ' 文字');
};
this.each(function() {
calculate(this);
$(this).keyup(function(){calculate(this)});
$(this).change(function(){calculate(this)});
});
};
})(jQuery);
//parent()を知らなかったのでeachでひとつひとつの解答欄に対しての挙動にしている
$(".js-text").each(function(){
var Num = $(this).data('counter');
$(this).find("textarea").charCount({
allowed:Num
})
});
});
ここでプルリクを出し跳ね返されて試行錯誤して二日が経ち・・・・最終的にこうやりました。。。。
<th>
<!-- 質問項目 -->
<span><%= @question.hoge %></span>
</th>
<!-- 解答欄 -->
<%= f.text_area :answer, :size => "10x2",:class =>"js-text", :data => {num: "#{hogehoge.num}"} %>
<!-- バリデーションにひっかかったときのための対処 -->
<span class="js-view"><%= question.answer.length if !question.answer.nil?%></span>文字
</td>
(function($) {
// 汎用性を持たせるために用意
$.fn.charCount = function(options){
//この関数内のあちこちでthisを使用するので変数にしとく
var _self=this;
var defaults = {
cssWarning: 'font-red',
cssSuccess: 'font-green',
counterMinNum: $(_self).data("num"),
counterViewSelector: $(_self).next('.js-view')
};
//デフォルトに上書きできるようにしとく
var options = $.extend(defaults, options);
charCountar();
$(_self).keyup(function(){
charCountar();
});
//ここで先ほどデファルトやオプションにしたものを使用することで汎用性を担保する
function charCountar(){
var counter = $(_self).val().replace(/(\r\n|\r|\n)/g, "\r\n").length;
$(options.counterViewSelector).html(counter);
if(counter >= options.counterMinNum){
$(options.counterViewSelector).addClass(options.cssSuccess).removeClass(options.cssWarning);
}else{
$(options.counterViewSelector).addClass(options.cssWarning).removeClass(options.cssSuccess);
}
}
};
})(jQuery);
$(function(){
$.each($(".js-text"), function(idx,target){
$(target).charCount();
});
});
最初のを今見返すと汎用性もなければ、なんか結構気持ち悪い感じになっていますね笑
ただ今回のことを通してthisの使い方だったり、そもそもjavascriptの全体の流れみたいのはつかめたので、次回からは時間かけずにクリアしたいです。
ちなみにjavascriptを実装するときは結局どこのdomをいじりたいのかでしかないので、
どういう構図なのか書き出してみると楽なのかもと思いました。
イメージは下記の感じなのかなーって思います。間違っているかもしれないのでググりながらやっていきましょう!笑