htmlテーブルをレスポンシブに
目次
画面幅に合わせて配置が変わるテーブル
表を、画面サイズに合わせて狭めたり広げてみせたり、ということを試してみました。 画面幅に合わせて、3列、2列、1列と変化します。
これ、IEでは正常に動きませんでした。 Chromeでは動きました。あしからず。
ソースはこんな感じです。
<!doctype html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>ファイルアップロード</title> <style type="text/css"> table{ max-width: 1139px; width:100%; } table,th,td{ border-collapse: collapse; border:1px solid #333; vertical-align: baseline; margin:0; padding:0; } table tr{ display: inline; float: left; } table th{ background-color: #CCCCCC; } .department{ width:80px; } .num{ width:80px; } .name{ width:80px; } .position{ width:120px; } .detail.department{ background-color: #CCCCCC; } </style> <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> <script> $(document).ready(function(){ // 初期表示処理 tableResize(); //リサイズされたときの処理 $(window).resize(function() { // ダミー行を消し、隠し列を表示に戻す $('#datatable tr[data-department="dummy"]').remove(); $('#datatable td.department').css('display','table-cell'); tableResize(); }); // リサイズ時イベント function tableResize(){ // テーブル幅をtr幅で割る switch (Math.floor($("#datatable").width() / 370)) { case 0: case 1: console.log('1行に1人表示'); break; case 2: console.log('1行に2人表示'); addDummyTr(2); break; case 3: default: console.log('1行に3人表示'); addDummyTr(3); break; } } function addDummyTr(humanCnt){ // ヘッダ部調整用の仮行 var DUMMY_DETAILHEADER_TMPL ='<tr data-department="dummy"><th class="header department">所属</th><th class="header num">社員番号</th><th class="header name">氏名</th><th class="header position">役職</th></tr>'; var dummy_detailheader = ""; // ヘッダに調整用の行を追加する。 for (var i = 1; i < humanCnt; i++) { dummy_detailheader = dummy_detailheader + DUMMY_DETAILHEADER_TMPL; } $('#datatable thead tr:last').after(dummy_detailheader); // ヘッダ2番目以降の所属列を消す $('#datatable thead th.department:gt(0)').css('display','none'); // 明細部調整 // まず、所属を全て取得し一意にする。 var departmentList = []; $('#datatable tbody tr').each(function(index, element){ departmentList.push($(element).data('department')); }); departmentList = departmentList.filter(function (x, i, self) { return self.indexOf(x) === i; }); console.log('departmentList:' + departmentList); var DUMMY_DETAILROW_TMPL ='<tr data-department="dummy"><td class="department"></td><td class="num" style="color: transparent;">dummy</td><td class="name"></td><td class="position"></td></tr>'; // 例として、1行に2人の場合は、所属1人だとスキマができるため、スキマ埋めのためのdummy行を追加する。 for(let v of departmentList) { // スキマ埋めすべきdummy行数を求める。 let addDummyRowCnt = 0; let departmentLength = $('#datatable tbody tr[data-department="' + v + '"]').length if(departmentLength <= humanCnt) { // 例:所属2人で、1行に3人表示の場合は、1人分のスキマ埋めが必要。 addDummyRowCnt = humanCnt-departmentLength } else { // 例:所属4人で、1行に3人表示の場合は、2行になるため、この所属の必要な領域は3×2=6となる。2人分のスキマ埋めが必要。 addDummyRowCnt = (Math.ceil(departmentLength / humanCnt) * humanCnt) % departmentLength; } // スキマ分、ダミー行を追加する。 for(let i = 0; i < addDummyRowCnt; i++) { $('#datatable tbody tr[data-department="' + v + '"]:last').after(DUMMY_DETAILROW_TMPL); } } // 1行に2人以上の場合、表示上の左から1番目の所属列は残し、後の所属列は消す。 if(humanCnt > 1){ // 1行あたりの表示人数の倍数ではない行(見た目上、右側に移った行)は所属列を消す $('#datatable tbody tr').filter(function(index) { return index % humanCnt >= 1; }).find('td.department').css('display','none'); } } }); </script> </head> <body> <form action="" method="post"> <table id="datatable"> <thead> <tr> <th class="header department">所属</th> <th class="header num">社員番号</th> <th class="header name">氏名</th> <th class="header position">役職</th> </tr> </thead> <tbody> <tr data-department="tokyo"> <td class="detail department">東京</td> <td class="detail num">3</td> <td class="detail name">田中</td> <td class="detail position">課長</td> </tr> <tr data-department="tokyo"> <td class="detail department">東京</td> <td class="detail num">11</td> <td class="detail name">鈴木</td> <td class="detail position">主任</td> </tr> <tr data-department="tokyo"> <td class="detail department">東京</td> <td class="detail num">16</td> <td class="detail name">内海</td> <td class="detail position">パート</td> </tr> <tr data-department="tokyo"> <td class="detail department">東京</td> <td class="detail num">5</td> <td class="detail name">田口</td> <td class="detail position">パート</td> </tr> <tr data-department="nagoya"> <td class="detail department">名古屋</td> <td class="detail num">14</td> <td class="detail name">富田</td> <td class="detail position">主任</td> </tr> <tr data-department="osaka"> <td class="detail department">大阪</td> <td class="detail num">7</td> <td class="detail name">松永</td> <td class="detail position">主任</td> </tr> <tr data-department="osaka"> <td class="detail department">大阪</td> <td class="detail num">14</td> <td class="detail name">井ノ瀬</td> <td class="detail position">パート</td> </tr> <tr data-department="fukuoka"> <td class="detail department">福岡</td> <td class="detail num">21</td> <td class="detail name">牧田</td> <td class="detail position">主任</td> </tr> <tr data-department="fukuoka"> <td class="detail department">福岡</td> <td class="detail num">6</td> <td class="detail name">水島</td> <td class="detail position">リーダー</td> </tr> <tr data-department="fukuoka"> <td class="detail department">福岡</td> <td class="detail num">16</td> <td class="detail name">堀川</td> <td class="detail position">パート</td> </tr> <tr data-department="kagosshima"> <td class="detail department">鹿児島</td> <td class="detail num">27</td> <td class="detail name">大迫</td> <td class="detail position">主任</td> </tr> </tbody> </table> </form> </body> </html>
考え方
- テーブルのtrタグをinlineにし、行を横並びに出来るようにする。
- 所属ごとに人数が違うので、javascriptに動的に空白行を追加したりして、ちょうどよい見た目にする。
やり方
ポイントは、cssでtrタグをdisplay: inline;にしていること。
table tr{ display: inline; float: left; }
これでtrが横に並びます。 画面横幅に合わせて勝手に折り返してくれます。
とはいえ、所属によって人数が異なるので、名古屋所属の人が東京の行に表示されたりしておかしくなります。 そこで空白行を埋めることで、調整します。
テーブルの幅を検知して、何列表示になるのか判断します。
// リサイズ時イベント function tableResize(){ // テーブル幅をtr幅で割る switch (Math.floor($("#datatable").width() / 370)) { case 0: case 1: console.log('1行に1人表示'); break; case 2: console.log('1行に2人表示'); addDummyTr(2); break; case 3: default: console.log('1行に3人表示'); addDummyTr(3); break; } }
trにはデータ属性departmentを定義しており、どこの所属かわかるようにしています。 javascriptのaddDummyTr関数内で、所属ごとの人数から、必要な空行を追加します。
横幅に合わせて、ちょうどよくなるように、空白行が追加されました。
次に、ヘッダ行も表示します。 ヘッダのtrタグの後ろに、ヘッダをさらに追加してしまいます。 1行に2人表示する画面幅なら、ヘッダを1行追加し、 3人表示するなら、ヘッダを2行追加します。
// ヘッダ部調整用の仮行 var DUMMY_DETAILHEADER_TMPL ='<tr data-department="dummy"><th class="header department">所属</th><th class="header num">社員番号</th><th class="header name">氏名</th><th class="header position">役職</th></tr>'; var dummy_detailheader = ""; // ヘッダに調整用の行を追加する。 for (var i = 1; i < humanCnt; i++) { dummy_detailheader = dummy_detailheader + DUMMY_DETAILHEADER_TMPL; } $('#datatable thead tr:last').after(dummy_detailheader);
これでヘッダも付きました。
所属列は要らないので消します。
// ヘッダ2番目以降の所属列を消す $('#datatable thead th.department:gt(0)').css('display','none');
departmentはヘッダの所属列についたクラスです。 gt(0)で、2番目以降が選択対象になります。 それをdisplay:noneにして見えなくします。
これで完成です。
あとがき
同じことを実現するにも、様々なやり方があるでしょうし、外部のモジュールを取り入れたら、あっさり実現できたと思います。
今回の書き方が、きれいな正当なやり方だとは思っていませんが、お勉強兼ねてやってみました。
【javascript】2つの連想配列を比較し、重複するものを返す
目次
javascriptの連想配列を比較し、重複するものがないか、調べたい
どうも、高島です。
javascriptで2つの配列があります。 両方とも存在するデータを取り出すには、どうすればいいでしょうか?
普通の配列を比較するサンプルは、ネット上に結構あるのですが、連想配列の例があまり無かったので、記事を書きました。
早速ですが、filterの中でfilterを呼べば、可能です。
const ary1 = [ {"keyno":"A1","room":"101"}, {"keyno":"A1","room":"102"}, {"keyno":"B1","room":"201"}, {"keyno":"C1","room":"301"}, {"keyno":"D1","room":"401"}, ]; const ary2 = [ {"keyno":"A1","room":"101"}, {"keyno":"C3","room":"301"}, {"keyno":"D1","room":"401"}, {"keyno":"E1","room":"501"}, ]; // 重複有り var reList = ary1.filter(ary1row=>ary2.filter( ary2row=> ary2row.keyno === ary1row.keyno && ary2row.room === ary1row.room).length > 0); console.log("両方とも存在するデータ"); console.log(reList);
結果はこうです。
両方とも存在するデータ [ { keyno: 'A1', room: '101' }, { keyno: 'D1', room: '401' } ]
filter内はどんな動きなのか?
実際はどんな処理がされているのでしょうか? console.logを埋め込んで確認してみます。
// 内部の動作を確認してみる ary1.filter(ary1row=>{ console.log("ary1row.room=" + ary1row.room); ary2.filter( ary2row=>{ console.log(" ary2row.room=" + ary2row.room); }) });
結果はこうなりました。
ary1row.room=101 ary2row.room=101 ary2row.room=301 ary2row.room=401 ary2row.room=501 ary1row.room=102 ary2row.room=101 ary2row.room=301 ary2row.room=401 ary2row.room=501 ary1row.room=201 ary2row.room=101 ary2row.room=301 ary2row.room=401 ary2row.room=501 ary1row.room=301 ary2row.room=101 ary2row.room=301 ary2row.room=401 ary2row.room=501 ary1row.room=401 ary2row.room=101 ary2row.room=301 ary2row.room=401 ary2row.room=501
ary1をループして、さらにその中でary2をループしていることがわかります。 filterを使えば、ループを書かなくて済む、というわけです。
冒頭の重複を取り出す処理に話を戻します。
ary2.filter関数が、ary1rowとary2rowを比較し、一致する行を返してくれます。 この部分ですね。
ary2.filter( ary2row=> ary2row.keyno === ary1row.keyno && ary2row.room === ary1row.room)
ary2.filterは、比較条件がtrueならば、データを返します。 もしfalseなら何も返しません。
データを返ってきたか否かは、lengthで判断します。
もうちょっとわかりやすく、returnを省略せずに、lengthでのtrue/false判定を分離して書いてみます。
var reList = ary1.filter(ary1row=>ary2.filter(
ary2row=>
ary2row.keyno === ary1row.keyno &&
ary2row.room === ary1row.room).length > 0);
↓↓↓↓↓↓
var reList = ary1.filter(ary1row=>{ var reAry2row = ary2.filter( ary2row=>{ return (ary2row.keyno === ary1row.keyno && ary2row.room === ary1row.room) }); return reAry2row.length > 0 ; });
ary1rowとマッチしたreAry2row が存在すれば、length > 0 、ですのでtrueで返します。 ary1.filter()はtrueとなったary1rowをreListに返します。
こうして重複したデータのみが reListにセットされます。
2つの配列を比較し、重複しないものを返す
では、ary1に存在するがary2に存在しないデータは?
これは簡単です。結果が0件のものですね。 length === 0 に変えます。
// 重複しない reList = ary1.filter(ary1row=>ary2.filter( ary2row=> ary2row.keyno === ary1row.keyno && ary2row.room === ary1row.room).length === 0); console.log("ary1に存在するがary2に存在しないデータ"); console.log(reList);
ary1に存在するがary2に存在しないデータ [ { keyno: 'A1', room: '102' }, { keyno: 'B1', room: '201' }, { keyno: 'C1', room: '301' } ]
(おまけ)自分自身の配列内で重複を取り出す
これまでは、異なる配列同士を比較しましたが、 今度は自分自身と比較すればいいです。
const ary1 = [ {"keyno":"A1","room":"101"}, {"keyno":"A1","room":"102"}, {"keyno":"B1","room":"201"}, {"keyno":"C1","room":"301"}, {"keyno":"A1","room":"101"}, {"keyno":"C1","room":"301"}, {"keyno":"D1","room":"401"}, ]; // 重複有り var reList = ary1.filter((ary1row,ary1idx,selfary)=>selfary.filter( ary2row=> ary2row.keyno === ary1row.keyno && ary2row.room === ary1row.room).length > 1); console.log("重複するデータ"); console.log(reList);
重複するデータ [ { keyno: 'A1', room: '101' }, { keyno: 'C1', room: '301' }, { keyno: 'A1', room: '101' }, { keyno: 'C1', room: '301' } ]
filter() はcallback関数を引数にとります。 このcallback関数の第3引数は自分自身の配列です。 それと比較すればいいです。
まとめ
filter内でfilterを呼び、そこで比較すれば、二重ループを書く必要はなくなります。
最初にfilter見たときは訳わかりませんでしたが、理解するとスッキリ書けます。
欠点といえば、読むには慣れが必要なことでしょうか。
人が書いたものは、正直ちょっとわかりにくいことが多いです。
var reList = ary1.filter((x,y,z)=>z.filter(
w=>
w.keyno === x.keyno &&
w.room === x.room).length > 1);
アロー関数は、こんな感じで略した変数名で書くことが多いので、変数名からは内容が推測できませんし、returnを省略して書かれると、初心者はコードが読めずに苦しんだりします。
もう慣れるしかないんでしょうけどね。
ファイルのmime type(content type)を確認するためのjavascript
目次
前書き
どうも、高島です。
ファイルの形式やファイルサイズを、ローカルでも簡単に確認できるWebサイトを作ってみました。
- どうやって?
File APIを使ってみる
1.ソースコードです。 これをコピーして、デスクトップにmimetype_check.htmlという名前で保存してください。
<!doctype html> <head> <meta charset="utf-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <title>ファイルアップロード</title> <script src="https://code.jquery.com/jquery-1.12.4.min.js" integrity="sha256-ZosEbRLbNQzLpnKIkEdrPv7lOy9C27hHQ+Xp8a4MxAQ=" crossorigin="anonymous"></script> <script> $(document).ready(function(){ $("#btn_file_up").click(function () { var self = $(this).parents("form"); //ファイル var file = self.find('#file_up').val(); if (file !== "") { //ファイルタイプ filetype = self.find('#file_up').prop('files')[0].type; //ファイルネーム filename = self.find('#file_up').prop('files')[0].name; //ファイルサイズ filesize = self.find('#file_up').prop('files')[0].size; filesize = filesize / 1000; // 単位をkbにする $("#presult").html("mimetypeは、" + filetype + "です。<br>ファイルサイズは、" + filesize + "KBです。"); } }); }); </script> </head> <body> <form action="" method="post"> <p><input type="file" id="file_up" name="file_up" value=""></p> <p><input type="button" id="btn_file_up" name="btn_file_up" value="アップロード"></p> <p>ファイルを選んでアップロードボタンを押してください。<br> そのファイルのmimetypeとファイルサイズが表示されます。<br> </p> <p id="presult"> </p> </form> </body> </html>
2. 早速、保存したファイルをブラウザで開きます。
3. 「ファイルの選択」から、ファイルを選んでアップロードしましょう。
4.mime typeとファイルサイズが表示されました。
mime typeはどこに設定されているの?
1.ちなみに、xlsではなく、xlsx形式だと、「application/vnd.openxmlformats-officedocument.spreadsheetml.sheet」と表示されます。これは、どこに設定されているのでしょうか?
2.windowsのレジストリエディターを起動してみましょう。レジストリをいじるとOSが正常に起動しなくなります。ここから先は自信がある人だけ。windows検索窓で「regedit」と打って、レジストリエディターを起動します。
3. HKEY_CLASSES_ROOTの配下にたくさん拡張子が並んでいます。
4. xlsやxlsxを見てみましょう。そこの「Content type」がそうです。
5.つまり、ここの「Content type」に登録されていないと、HTML5のFile APIではファイル形式の判断が出来ません。 Officeなど、それを使用するソフトが正常にインストールされていないと、登録されていないことがあるようです。
あとがき
File APIは便利です。わざわざサーバに飛ばさなくても、クライアント側の処理で、チェックすることが出来ます。 例えばアップロード前のファイルサイズチェックとか。
ただ、ファイル形式チェックは、拡張子が無難かも知れません。 例えばgoogleスプレッドシートを利用していて、OfficeをインストールしていないPCで、アップロードすることもあるかも知れません。そのようなPCに、「Content type」が登録されているかは正直わかりません。
まさか、「Content type」がレジストリに登録されていたとはね。
C#でAngleSharpを使ってHTMLをパースする
こんにちは。中原です。
今回はC#でAngleSharpというのを利用してHTMLをパースしてみました。
HTMLパースすることなんてあまりないかもしれませんが、もしパースする機会があればAngleSharpを使ってみてください。
AngleSharpはこちら。
github.com
AngleSharpをインストールする
まずは、NuGetからAngleSharpを検索・選択して「インストール」。
参照設定に追加されました。
パースするHTMLを用意する
パースするHTMLファイルを用意します。
今回はこんなHTMLを作成しました。
<!DOCTYPE html> <html lang="ja"> <head> <meta charset="UTF-8"> <title>HTML Parser Sample</title> </head> <body> <p id="id-p">ID=id-pの文字</p> <p class="class-p">class=class-pの文字1</p> <p class="class-p">class=class-pの文字2</p> <p class="class-p">class=class-pの文字3</p> </body> </html>
Program.csに以下のusigを追加して、AngleSharpを使えるようにします。
using AngleSharp.Html.Parser;
それでは簡単なパースをしてみます。
idを指定しタグの値を取得する
// htmlファイルを読み込む var html = File.ReadAllText("html_parser_sample.html", System.Text.Encoding.UTF8); // HTMLParserのインスタンス生成 var parser = new HtmlParser(); // htmlをパースする var doc = parser.ParseDocument(html); // idを指定してElement取得 var idP = doc.GetElementById("id-p"); Console.WriteLine("GetElementByIdを使って[id-p]の値取得 : {0}", idP.TextContent);
実行結果
タグの値が取得できました。
classを指定してタグの値を取得する
// htmlファイルを読み込む var html = File.ReadAllText("html_parser_sample.html", System.Text.Encoding.UTF8); // HTMLParserのインスタンス生成 var parser = new HtmlParser(); // htmlをパースする var doc = parser.ParseDocument(html); // classを指定してElementを取得 var classpList = doc.GetElementsByClassName("class-p"); foreach (var c in classpList) { Console.WriteLine("GetElementsByClassNameを使って[class-p]の値取得 : {0}", c.TextContent); }
実行結果
指定したクラスの値が全て取得できました。
QuerySelectAllを使ってclassを指定してタグの値を取得する
// htmlファイルを読み込む var html = File.ReadAllText("html_parser_sample.html", System.Text.Encoding.UTF8); // HTMLParserのインスタンス生成 var parser = new HtmlParser(); // htmlをパースする var doc = parser.ParseDocument(html); // QuerySelectorAllでclassを指定してElementを取得 var elements = doc.Body.QuerySelectorAll("p.class-p"); foreach (var e in elements) { Console.WriteLine("QuerySelectorAllを使って[class-p]の値取得 : {0}", e.TextContent); }
実行結果
一つ前の実行結果と同じ結果が得られました。
上記以外にも沢山機能はありますが、今回は上記3パターンを実装してみました。
また機会あればご紹介したいと思います。
全体のソースコード一式は以下に置きました。
github.com
Gmail自動削除の方法
目次
前書き
どうも、高島です。
gmailって、何もしないとどんどん貯まっていきます。
容量を食うので定期的に削除しようと思います。
googleカレンダーのメールやソーシャルサイトからのメールは、単なる通知メールですので、数日で消してしまっていいです。
これらを自動で消していこうと思います。
- どうやって?
Google Apps Scriptを使います。 これでgmailのサービスにアクセスして、条件に合ったメールを 定期的にゴミ箱に移動させます。
- 方法は?Gmail編。
gmailのフィルタ機能を使って、メール受信時に自動でラベルを付けます。
- 方法は?Google Apps Script編。
ラベルが付いたメールを毎日0時にゴミ箱に移動します。
Gmailにラベルを付けよう
1.まずは、消したいメールの法則性を掴みます。 googleカレンダーからの通知メールを消したいので、送信元のメアドで判断しましょう。
2. gmailの「設定」を開きます。
3. 「フィルタとブロック中のアドレス」を選択します。
4.フィルタ条件を作成します。
5. 「ラベルを付ける」から、新しいラベルを作ります。
6.ラベルが作成されました。今後受信したメールには、このラベルが自動的に付くようになります。
7. ただし、今まで受信したメールにはラベルが付いていません。ラベルを付けてあげましょう。 受信トレイの検索窓に、フィルタ条件と同じものを打ち込みます。そして、「この検索条件に一致するすべてのスレッドを選択する」をクリックします。
8. 条件に一致したメールにラベルを付けてあげましょう。
スクリプト作成と、タイマートリガー設定
1. Google Apps Scriptを作成します。 今回はgoogleスプレッドシート上に乗っけます。 (こうすることで、削除したメールの一覧などをスプレッドシート上に出力することも可能です)
2.Googleスプレッドシートを開いたら、「ツール」→「スクリプトエディタ」を開きます。
3.スクリプトファイル名は〇〇.gsとなります。今回は「コード.gs」とします。
4. コードは以下のとおりです。
function myFunction() { //3日より前のGoogleカレンダー通知メールを削除する deleteMail(GmailApp.search('label:calendargoogle older_than:3d')); } function deleteMail(deleteThreads) { for (var i = 0; i < deleteThreads.length; i++) { deleteThreads[i].moveToTrash(); } }
解説
myFunctionは最初に呼び出される関数です(詳しくは後で)。 GmailApp.search('label:calendargoogle older_than:3d') は、ラベルが「calendargoogle」と付いた、3日前のメールを抽出しています。
deleteMail関数で、それらを受け取って、 moveToTrash()でゴミ箱に移動します。簡単ですね。
参考:https://developers.google.com/apps-script/reference/gmail/gmail-app
5. 虫さんのマークからデバッグも出来ますよ。 初回実行時は、プロジェクト名とか決める必要があるので、入力しましょう。
6. gmailにアクセスする許可が必要です。聞かれたら、画面の指示に従います。
7. 実行中。しばらく待ちましょう。
8. gmailのゴミ箱見てみましょう。 おお!見事にゴミ箱に移動しています! ここに入ったメールは30日経てば、完全に削除されます。
9. 動作がうまくいくことを確認したら、バッチ化しましょう。 時計マークをクリックします。
10. トリガーを追加します。
11. トリガーを追加します。 実行する関数を選択→これはmyfunctionで良いです。 イベントのソースを選択→時間主導型を選択します。
時間ベースのトリガーのタイプを選択→日付ベースを選びます。 時刻は午前0時~1時を選びます。
12. トリガーが作成されました。
13. 翌日に実行されていることを確認しましょう。
あとがき
これでメールの自動削除が出来ました。
ほっておいても、ゴミ箱に勝手に移動してくれます。
なお、GmailApp.searchは、いろいろな条件を指定できます。 gmailの検索窓で入力できる方法をそのまま流用できます。
例えば、gmailはカテゴリというものがあり、自動的にソーシャルとかプロモーションとか分類されますが、
GmailApp.search('category:promotions older_than:15d')
とすると、15日前のプロモーションのメールが対象になります。
Google Apps Scriptを初めて使いましたが、応用次第では色々できそうです。