jQuery Validation Plugin で複数のセレクトボックスから項目を選択する場合に、重複チェックするバリデータを作成した。 用途としては、例えば
- 秘密の質問 Q1〜Q3で同じ質問を選択してないか
- 好きな○○で重複した項目を選択していないか
- 経験のある言語で重複した項目を選択していないか
などに利用できるかと思う。
無駄な空行が多いコードから、一発で無駄な部分を消したくなった。
Pythonのflake8をはじめ、他言語のコーディング規約をみても恐らく3行以上の空白を意図的に入れる必要性が感じられないので、4つ以上 <LF>
が続いた場合には消す処理で良さそうだ。
command! DeleteUselessBlankLines :%s/\n\{4,}/\r\r\r/
これで :DeleteUselessBlankLines
すると3行以上の無駄な空行を削除できるようになる。
もし保存時に自動実行したい場合には、
autocmd BufWritePre * :%s/\n\{4,}/\r\r\r/
とするといいだろう。
pip freezeを使う。virtualenv と組み合わせて使うとよい。
$ pip freeze beautifulsoup4==4.4.1 flake8==2.4.1 mecab-python3==0.7 pep8==1.5.7 pyflakes==0.8.1 PyYAML==3.11
$ pip freeze > requirements.txt # 現在インストールしているパッケージ一覧をrequirements.txtに書き出し $ pip install -r requirements.txt # requirements.txt内のパッケージをインストール
HTML5 Form Validationのブラウザごとの実装状況が異なることから、 jQuery Validation Plugin | Form validation with jQuery をまだ現役で利用しているケースも多いはず。
先日 jQuery Validation Plugin のエラーを Bootstrap3 の popover(tooltip) に組み込んでみたのでその方法を記録。
実装イメージ。
こんな感じの構成とする
<form> <div class="form-group"> <label for="exampleInputEmail1">Email address</label> <input type="email" class="form-control" name="exampleInputEmail1" placeholder="Email"> </div> <div class="form-group"> <label for="exampleInputPassword1">Password</label> <input type="password" class="form-control" name="exampleInputPassword1" placeholder="Password"> </div> <div class="form-group"> <label for="exampleInputFile">File input</label> <input type="file" id="exampleInputFile"> <p class="help-block">Example block-level help text here.</p> </div> <div class="checkbox"> <label> <input type="checkbox"> Check me out </label> </div> <button type="submit" class="btn btn-default">Submit</button> </form>
showErrors()というメソッドがある。
これはエラー時の表示処理を制御するメソッドで、defaultでは以下のようなdefaultShowErrors()メソッドが実行される。
defaultShowErrors: function() { var i, elements, error; for ( i = 0; this.errorList[ i ]; i++ ) { error = this.errorList[ i ]; if ( this.settings.highlight ) { this.settings.highlight.call( this, error.element, this.settings.errorClass, this.settings.validClass ); } this.showLabel( error.element, error.message ); } if ( this.errorList.length ) { this.toShow = this.toShow.add( this.containers ); } if ( this.settings.success ) { for ( i = 0; this.successList[ i ]; i++ ) { this.showLabel( this.successList[ i ] ); } } if ( this.settings.unhighlight ) { for ( i = 0, elements = this.validElements(); elements[ i ]; i++ ) { this.settings.unhighlight.call( this, elements[ i ], this.settings.errorClass, this.settings.validClass ); } } this.toHide = this.toHide.not( this.toShow ); this.hideErrors(); this.addWrapper( this.toShow ).show(); },
端的に言うと、エラー箇所の次の位置にエラーメッセージを含むlabel要素をappendしている。
このshowErrorsメソッドは、以下のような記述でオーバーライド可能。 Validator.showErrors() | jQuery Validation Plugin
$("form").validate({ rules: { exampleInputEmail1: { required: true }, exampleInputPassword1: { required: true } }, showErrors: function(errorMap, errorList) { // validationに引っかからなかった要素はpopover非表示にする $.each(this.successList, function(index, value) { $(value).popover('hide'); }); // validationに引っかかった要素はpopover表示する $.each(errorList, function(index, value) { var _popover = $(value.element).popover({ trigger: 'manual', placement: 'bottom', content: value.message, template: "<div class='popover' role='tooltip'><div class='arrow'></div><h3 class='popover-title'></h3><div class='popover-content'></div></div>" }); _popover.data('bs.popover').options.content = value.message; // popover要素のテキストを更新する $(value.element).popover('show'); }); } });
上記を実装するとこんな感じになる。
.popover-validation
などとclass名を追加して、cssに
.popover.popover-validation { background-color: #f9f2f4; color: #c7254e; } .popover.popover-validation > .arrow:after { border-bottom-color: #f9f2f4; }
とするとさらにいい感じになった。
setDefaults()メソッドを利用しましょう。 jQuery.validator.setDefaults() | jQuery Validation Plugin
$.validator.setDefaults({ // エラーメッセージのBootstrap3: popover表示 showErrors: function(errorMap, errorList) { ... }); } });
messages: {}
も纏めて jquery.validate.settings.js などとして置いとくと、サイト内で共通のエラーメッセージを使い回せます。
最終的な今回の成果物として、デモ用のリポジトリを用意したのでご参考にどうぞ。
参考: How to use Twitter Bootstrap popovers for jQuery validation notifications? - Stack Overflow
Pythonで以下のような破壊的ループをしようとすると、indexのズレが発生してすべての要素に対して処理が行われないケースがある。
(この例の処理ではリスト内包表記で充分代替可能だったりするが、あくまで例として単一処理にしている。)
li = [ { "id": "D028xxxxx", "is_im": True, "user": "USLACKBOT", "created": 1397471294, "is_user_deleted": False }, { "id": "D028xxxxx", "is_im": True, "user": "U028xxxxx", "created": 1397471294, "is_user_deleted": False }, { "id": "D028xxxxx", "is_im": False, "user": "U028xxxxx", "created": 1397471294, "is_user_deleted": False } ] for user in li: if user['is_im'] is True: li.remove(user) print(li) # [{'id': 'D028QH1PT', 'created': 1397471294, 'user': 'U028NTG5T', 'is_im': True, 'is_user_deleted': False}, {'id': 'D028QH1PR', 'created': 1397471294, 'user': 'U028P546G', 'is_im': False, 'is_user_deleted': False}] # 'is_im': Trueの要素が残っている
正しく破壊的ループをする場合には、リスト全体をコピーして実行すると良い。
ループ処理前に temp_li = li
を行うやり方もWebでは散見されたが、
li[:]
によるスライスコピーが一番綺麗に記述できる。
for user in li[:]: if user['is_im'] is True: li.remove(user) print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]
ちなみにfilter()で書くとこう。
li = list(filter(lambda user: user['is_im'] is False, li)) print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]
Python3からはfilter()はリストでなくイテレータを返却するようになったので、list()で囲う必要がある。
結局はリスト内包表記がいいのかも知れない。
li = [user for user in li if user['is_im'] is False] print(li) # [{'created': 1397471294, 'id': 'D028QH1PR', 'is_im': False, 'is_user_deleted': False, 'user': 'U028P546G'}]
上記の内容はPythonチュートリアルに記載されていた。数年ぶりに読み返したが、まだ発見があったのでまだまだだなと痛感。
一応速度も計測してみたが、今回のリストだと速度に顕著な差異が見られないので割愛した。 コードはPython3.xに準拠。
先日ハマったのでメモ。
Pythonの引数のデフォルト値は一度しか評価されない。
def func(url, l=[]): …
としたい場合には、代わりに
def func(url, l=None): if l is None: l = [] …
とする。
GitHub APIを叩いて全てのbranch_listを取得しようと、 以下のような関数を書いた。
def fetch_all(url, all_list=[]): res = urllib.request.urlopen(url) res_link, res_body = res.getheader('Link'), res.read().decode('utf-8') all_list += json.loads(res_body) if 'rel="next"' in res_link: next_url = res_link.split('; rel="next"')[0].strip('<>') fetch_all(next_url, all_list) return all_list
が、2度目の fetch_all(url)
実行時には all_listが []
でなく、1度めに実行した際の配列が格納されていることに気付いた。
重要な警告: デフォルト値は一度しか評価されない。デフォルト値が可変オブジェクト、すなわちリスト、ディクショナリ、およびほとんどのクラスのインスタンスであると、このことが影響する。
def f(a, L=[]): L.append(a) return L print f(1) # [1] print f(2) # [1, 2] print f(3) # [1, 2, 3]
と実行される。 デフォルト値を共有されたくないのであれば、以下のように書く必要がある。
def f(a, L=None): if L is None: L = [] L.append(a) return L print f(1) # [1] print f(2) # [2] print f(3) # [3]
冒頭の例だと以下のように書き換えると問題なく動作する
def fetch_all(url, all_list=None): # init if all_list is None: all_list = [] res = urllib.request.urlopen(url) res_link, res_body = res.getheader('Link'), res.read().decode('utf-8') all_list += json.loads(res_body) if 'rel="next"' in res_link: next_url = res_link.split('; rel="next"')[0].strip('<>') fetch_all(next_url, all_list) return all_list
によると、 def func(item, l = something.defaultList())
のように、関数の実行結果をデフォルト引数とした際に都度実行されることを防ぐ為の目的もあるようだ。
久しぶりに tmux を再起動したらこんな警告が出るようになった。
/Users/user_name/.tmux.conf:16: unknown option: mode-mouse /Users/user_name/.tmux.conf:17: unknown option: mouse-resize-pane /Users/user_name/.tmux.conf:18: unknown option: mouse-select-pane /Users/user_name/.tmux.conf:19: unknown option: mouse-select-window
tmux 2.1からmouse系のオプションが1つに纏められた模様。
Incompatible Changes ==================== * Mouse-mode has been rewritten. There's now no longer options for: - mouse-resize-pane - mouse-select-pane - mouse-select-window - mode-mouse Instead there is just one option: 'mouse' which turns on mouse support entirely.
https://raw.githubusercontent.com/tmux/tmux/master/CHANGES
上記4つのオプションを消して、
set -g mouse on
を追加したら問題なく動作するようになった。
スクロール時に自動でコピーモードにならなくなっていた。 GitHubでもissueがあがっていて、そこに解決方法が載っていた。
bind -n WheelUpPane if-shell -F -t = "#{mouse_any_flag}" "send-keys -M" "if -Ft= '#{pane_in_mode}' 'send-keys -M' 'select-pane -t=; copy-mode -e; send-keys -M'" bind -n WheelDownPane select-pane -t= \; send-keys -M
Mouse scrolling in tmux 2.1 on OSX no longer auto-starts · Issue #145 · tmux/tmux · GitHub
上記2行で、
が動作するようになる。
ちょっと手元の環境ではtmuxを再起動しても反映されず、tmuxのプロセスを直接killしてから立ち上げてやっと有効になった。