1. 入力内容をテンプレとして保存する

タイトル:
種別: 出退勤 非出退勤
乗り物: JR 地下鉄
プロジェクト:
メンバー:
備考:
// content_script.js

$(function () {
    ChEx.matchUrl('/expense/', function() {
        ChEx.templateStorage({
            storageKey: 'example01.templates',          //Storageのキー
            init: $opener => $opener.appendTo('form'),  //「テンプレートから選択」ボタンの配置
            title: 'テンプレートから選択',              //表示するボタンのタイトル
            inputs: [                                   //保存するinputを指すselector
                '[name="a"]',
                '[name="b"]',
                '[name="c"]',
                '[name="d"]',
                '[name="e"]',
                '[name="f"]',
            ],
        });
    });
});

2. 別ページで算出した結果をもとに転記支援

1月2月3月
商品A02030
商品B10300
商品C0-1080


別画面の入力欄
商品A:
商品B:
商品C:
// content_script.js

$(function() {
    ChEx.matchUrl('/stocks/', function() {
        //合計列を追加し、同時にstorageに保存する
        ChEx.waitDom('#stock table', '合計するテーブル', 10, function($table) {
            let sums = {};
            $table.find('tr').each((i, tr) => {
                let $tr = $(tr);
                let key = $tr.data('name');
                if (key) {
                    let sum = 0;
                    $tr.find('td').each((i, td) => sum += parseInt($(td).text()));
                    sums[key] = sum;
                    $tr.append(`<th style="background-color: lightyellow;">${sum}</th>`);
                } else {
                    $tr.append(`<th style="background-color: lightyellow;">合計</th>`);
                }
            });
            ChEx.storage.saveLocalDirectly('example02.sums', sums);
        });
    });

    ChEx.matchUrl('/inputs/', function() {
        //入力画面が表示されたらstorageからデータを読み出し転記しやすいリンクを出す
        ChEx.storage.loadLocal('example02.sums', {}, sums => {
            for (let key of Object.keys(sums)) {
                let $input = $(`input[name="${key}"]`);
                $input.after(
                    $('<a href="javascript:void(0)"></a>').text(`<< ${sums[key]}`).click(() => {
                        $input.val(sums[key]);
                    })
                );
            }
        });
    });
});

3. 絞り込み

↓クリックで選択可能
0【前夜祭】大人のビルコン 〜撤退技術スペシャル〜(1)lestrrat
1【前夜祭】大人のビルコン 〜撤退技術スペシャル〜(2)lestrrat
2【前夜祭】大人のビルコン 〜撤退技術スペシャル〜(3)lestrrat
3【前夜祭】大人のビルコン 〜撤退技術スペシャル〜(4)lestrrat
4Openinglestrrat
5Desktop Apps with JavaScriptfelixrieseberg
6オンプレ、クラウドを組み合わせて作るビックデータ基盤nii_yan
7DeepLearningによるアイドル顔識別を支える技術sugyan
8初めてのMySQLチューニング -5.7 対応版-mamy1326
9PHPで支える大規模アーキテクチャytake
10OSS開発を仕事にする技術mumoshu
11ランチセッション Alestrrat
12ランチセッションBlestrrat
13真のコンポーネント粒度を求めてTakazudo
14横山三国志に「うむ」は何コマある?heruheru3
15Androidアプリ開発アンチパターンfkmhrk
16ブラウザ拡張のクロスブラウザ対応についてどう向き合っているかpastak
17マイクロチームでの高速な新規開発を支える開発・分析基盤timakin
18複雑なJavaScriptアプリケーションに立ち向かう...Shinpeim
19Anatomy of DDoSsuzannealdrich
20Haskellを使おうhiratara
21フレームなき道を拓くPHPzonuexe
22Goで実装する軽量マークアップ言語パーサーaereal
23RDBアンチパターン リファクタリングsoudai
24Building high performance...cubicdaiya
25Kubernetesのクラスタ1つに開発と本番運用に必...Kenichi Naoe
26ディープラーニングを加速するVolta GPUプラットフォームMana Murakami
27Lightning Talkslestrrat
28懇親会lestrrat
293Dプリンタで作る1次元セル・オートマトン、階差機関、...mackee
30ここまで出来るmrubypyama86
31知られざる世界 〜WEB以外のPHP〜uzulla
32Chrome拡張を使って様々なWebサービスをハックするWataru Terada
33静的解析とUIの自動生成を駆使してモバイルアプリの運用...tenntenn
34Googleが開発したニューラルネット専用LSI「Te...kazunori279
35小さく始めて育てるコンパイラrhysd
36今日から使えるCSS Gridgeckotang
37サーバサイドKotlinのすすめNoriyuki Ishida
38Ionic...rdlabo
39ランチセッション Clestrrat
40ランチセッション Dlestrrat
41AWS CodeBuild...ssig33
42polyglot になろう !!januswel
43Make you a React: How to...jbucaran
44OSSで始めるセキュリティログ収集bungoume
45OSS の引き継ぎ方hsbt
46Factory Classobra
47HADOが試したポジショントラッキングの話izugch4423
48フロントエンドエンジニアが主役のBaaSを作った話Takezaki Shinichiro
49OSS貢献超入門shigemk2
50WEB+DB PRESS 100号記念 特別企画(仮)lestrrat
51Writing PHP at Slack HQcarlyhasredhair
52汎用CMSから新規開発の自社サービスへ移行した事例のご紹介motchang
53型を意識した PHP アプリケーション開発shin1x1
54ここが辛いよサーバーレス。だが私は乗り越えたyamitzky
55Serverless Server Side SwiftnoppoMan
56Closinglestrrat
57MP4 ParserをSwiftで書いてみたSatoshi Shmd
58分割QRコードの読み取り方Satoshi Shmd
59SketchでVueコンポーネントを設計してみるYosuke Doke
60電池一本で5年間動くデバイスを作るには。Takashi Komori

// content_script.js

$(function () {
    ChEx.matchUrl('/sessions/', function() {
        ChEx.keyDownSearch({
            init: $input => {
                ChEx.findOne('.main', '絞り込みの枠').prepend(
                    $('<div style="padding: 5px; background-color: darkblue; color: white;">文字をタイプすることで絞り込みが可能です: </div>').append(
                        $input.css({ marginLeft: '10px', fontWeight: 'bold', fontSize: 'medium' })
                    )
                );
            },
            $foundRows: ChEx.findOneThen('#aaa', 'table > tbody > tr', '絞り込み単位となる行')
        });
    });
});

4. ユーザ登録画面のテストの際にバリデーション通る入力内容を簡単に作り出す

名前(*)
メアド(*)
パスワード(*)
電話番号(*)
都道府県(*)
住所1(*)
住所2(*)
(*) = 必須
// content_script.js

$(function () {
    ChEx.matchUrl('/users/add', function() {
        let i = 0;
        ChEx.findOne('#user-add-form').append(
            $('<input type="button" value="クリックのたびに違うユーザに">').click(() => {
                i++;
                $example.find('[name="a"]').val(`名前${i}`);
                $example.find('[name="b"]').val(`example${i}@example.com`);
                $example.find('[name="c"]').val(`example${i}@example.com`);
                $example.find('[name="d"]').val(`000-0000-000${i}`);
                $example.find('[name="e"]').val(`東京都`);
                $example.find('[name="f"]').val(`○○区${i}丁目`);
                $example.find('[name="g"]').val(`${i}-${i}`);
            })
        );
    });
});

5. redmine の担当者を選択しやすくする

コメント欄:
uzulla: 初回投稿
Wataru Terada: 回答その1

担当者:
// content_script.js

$(function () {
    ChEx.matchUrl('/tickets/*/edit', function() {
        let users = $("a[href^=\"/users/\"]").get().map(a => $(a).text().trim());
        users = ChEx.uniq(users);
        users = users.sort((a, b) => (a.toUpperCase() > b.toUpperCase()));
        $('.user-select').after(
            $('<div></div>').append(
                users.map(name => ChEx.clickableLink(`[${name}]`).css({
                    marginLeft: '8px',
                    fontSize: 'small',
                }).click(() => $(`.user-select option:contains(${name})`).prop('selected', true)))
            )
        );
    });
});

6. 日時を日本時間に書き換え、現在時との差分を色で示す

本日: 2017-07-05
1abcdefSun Jul 03 2017 09:01:06 UTC
2ghijklSun Jul 08 2017 09:01:06 UTC
3mnopqrSun Jul 04 2017 09:01:06 UTC
4stuvwxSun Jul 09 2017 09:01:06 UTC
5yzabcdSun Jul 01 2017 09:01:06 UTC
6efghijSun Jul 10 2017 09:01:06 UTC
7klmnopSun Jul 02 2017 09:01:06 UTC
8qrstuvSun Jul 05 2017 09:01:06 UTC
9wxyxabSun Jul 06 2017 09:01:06 UTC
0cdefghSun Jul 07 2017 09:01:06 UTC

// content_script.js

$(function () {
    ChEx.matchUrl('/tickets/', function() {
        ChEx.dateColor({
            selector: '.exp-dt',
            format: 'Y-M-D H:I:S',
            color: true,
        });

        //ソート
        $('.data-table').after(
            ChEx.clickableLink('Sort').click(function () {
                ChEx.sortDom({
                    selector: '#aaa tr',
                    getValue: $tr => new Date($tr.find('.exp-dt').text()).getTime(),
                });
            })
        );
    });
});

7. 特定の領域を書き換えられるように


// content_script.js

$(function () {
    ChEx.matchUrl('/expense/', function() {
        ChEx.rewritableTexts({
            storageKey: 'example07.comments',
            $targets: $('#aaa .chex-text'),
            getId: $target => $target.prev('.chex-id').text(),
            onChange: ($target, changed) => $target.css('color', changed ? 'red' : 'black'),
        });
    });
});

8. 利用者に通知を送る

// content_script.js

$(function () {
    ChEx.info('●●の改造', 'wataru.terada@accenture.com', '寺田 渉');
    ChEx.notify('example08.notify', [
        { ymd: '2017-06-17 (v1.22)', htmls: ['通知する機能を追加しました。今後、新機能を追加した際にはこの機能でお知らせします。',
            '【重要】Timeレポートのプルダウンで絞り込みできるようにしました。キー入力することでリストが絞り込まれます。BSキーで絞り込み文字を削除できます。',
            '【重要】Taxi のテンプレート保存機能は作ったものの、いまいち便利さが判らないわりにメンテが大変なので削除することにしました。もし困るという人がいましたらすぐに連絡ください。']},
        { ymd: '2017-06-26 (v1.23)', htmls: ['【バグFIX】新規作成ページの場合に時間転記のリンクが出なかったので修正しました。']},
    ]);
});

9. Storage の内容をメンテしやすく

<!-- option.html -->

<meta charset="UTF-8">
<h1>Option</h1>
<form>
    <div style="display: flex; height: 500px;">
        <div class="ope-storage-array"  data-col-rate="1" data-storage="example01.templates" data-title="example01.templates"></div>
        <div class="ope-storage-object" data-col-rate="2" data-storage="example02.sums"      data-title="example02.sums"></div>
    </div>
    <div style="display: flex; height: 300px;">
        <div class="ope-storage-object" data-col-rate="1" data-storage="example07.comments" data-title="example07.comments"></div>
        <div class="ope-storage-object" data-col-rate="1" data-storage="example08.notify"   data-title="example08.notify"></div>
    </div>
</form>
<script src="jquery-2.2.0.min.js"></script>
<script src="ChEx.js"></script>
<script src="ope-storage.js"></script>

10. gmail の宛名をドメインごとにまとめる

説明しやすいように抜粋してます
To
例2-1 (test2-1@ex.jp)
例2-2 (test2-2@exe.jp)
例2-3 (test2-3@ex.jp)
例2-4 (test2-4@ex.jp)

送信

// content_script.js

$(function () {
    ChEx.onChangeDom($('body'), function () {
        //既存のボタン検索
        $(`div[id][role='button']:contains('送信')`).each(function () {
            let $sendButton = $(this);
            let $parent = $sendButton.parent();
            if ($parent.children('.chex-gmail-btn-confirm').length) return; //確認ボタンがまだ無い場合のみ
            //送信ボタン非表示
            $sendButton.hide();
            //確認ボタン作成
            let $confirmButton = $(`<div class="T-I J-J5-Ji aoO T-I-KE L3 chex-gmail-btn-confirm" role="button" tabindex="1" style="-webkit-user-select: none;">確認</div>`);
            $confirmButton.on("click", function () {
                //メール書き込みエリアを探す
                let $dialogMail = $confirmButton.closest(`div[role='dialog'][aria-labelledby]`); //とりあえずダイアログ形式のみ
                //確認ダイアログ表示
                let dialog = ChEx.dialog({
                    title: '宛先を確認してください。',
                    css: { width: '600px' },
                    makeBody: function() {
                        let sender_map = {};
                        for (let key of ['to', 'cc', 'bcc']) {
                            //情報収集
                            let addrAry = $dialogMail.find(`input[name="${key}"]`).get().map(input => $(input).val());
                            //アドレスをドメインごとに振り分ける
                            for (let addr of addrAry) {
                                let domain = addr;
                                domain = domain.replace( /\s/g , "" ); //空欄は撤去
                                domain = domain.replace( /("|").*?("|")/g , "" ); //"~" は撤去
                                domain = domain.replace( /^.*(?:<|<)(.*?)(?:>|>).*$/ , '$1' );   //<~> ならその中身がメアド
                                if (!domain) return false;
                                domain = domain.replace( /^.*?@/ , "@" );
                                domain = domain.toLowerCase();
                                //マップに登録
                                if ( ! sender_map[domain] ) {
                                    sender_map[domain] = [];
                                }
                                sender_map[domain].push( ChEx.h(addr) );
                            }
                        }
                        return $(`
                            <div>
                                ${Object.keys(sender_map).sort().map(domain => `
                                    <div class="chex-gmail-checkable" style="padding: 2px; margin: 20px 0; background-color: #DDDDDD; font-family: monospace;">
                                    <div style="margin: 0; padding: 2px; font-weight: bold; font-size: large;">${domain}</div>
                                    <div style="margin: 0; padding: 2px; background-color: white; line-height: 1.2;">${sender_map[domain].sort().join("<br/>\n")}</div>
                                    </div>
                                `).join('')}
                            </div>
                        `);
                    },
                });
                dialog.open();
                dialog.addButton('送信', () => {
                    dialog.close();
                    $sendButton.click();
                });
            });
            $sendButton.before($confirmButton);
        });
    });
});
ちなみに 「Gmail送信前チェッカー」完全版はこちらで公開しています。

11. 単体テスト

ChEx.padding()

// test.html
<script src="../src/jquery-2.2.0.min.js"></script>
<script src="../src/ChEx.js"></script>
<script src="test-helper.js"></script>
<script>
$(function () {
    unitTest($('#test-result'), test => {
        test('ChEx.padding(1234567)           ', assert => assert(ChEx.padding(1234567)           , '1,234,567'));
        test('ChEx.padding(1, 3)              ', assert => assert(ChEx.padding(1, 3)              , '  1'));
        test('ChEx.padding(1, -3)             ', assert => assert(ChEx.padding(1, -3)             , '1  '));
        test('ChEx.padding("a", 3)            ', assert => assert(ChEx.padding("a", 3)            , '  a'));
        test('ChEx.padding("a", -3)           ', assert => assert(ChEx.padding("a", -3)           , 'a  '));
        test('ChEx.padding(1, 3, "0")         ', assert => assert(ChEx.padding(1, 3, "0")         , '001'));
        test('ChEx.padding(0.12345, 6, "0", 2)', assert => assert(ChEx.padding(0.12345, 6, '0', 2), '000.12'));
    });
});
</script>
その他のテスト

12. 結合テスト

<script src="../src/jquery-2.2.0.min.js"></script>
<script src="../src/ChEx.js"></script>
<script src="test-helper.js"></script>

<div id="example01">
    <h2>1. 入力内容をテンプレとして保存する</h2>
    <form>
        <div><span>タイトル: </span><input type="text" name="a"></div>
        <div><span>種別: </span><input type="radio" name="b" value="1">出退勤 <input type="radio" name="b" value="2">非出退勤</div>
        <div><span>乗り物: </span><input type="checkbox" name="c" value="1">JR <input type="checkbox" name="c" value="2">地下鉄</div>
        <div><span>プロジェクト: </span><select name="d"><option><option value="1">○○PROJ<option value="2">△△PROJ</select></div>
        <div><span>メンバー: </span><select name="e" multiple size="4"><option value="100001">Aさん<option value="100002">Bさん<option value="100003">Cさん<option value="100004">Dさん</select></div>
        <div><span>備考: </span><textarea name="f" rows="3"></textarea></div>
    </form>
    <script>
        example('example01', function () {
            //あらかじめ保存されているのを再現
            ChEx.storage.saveLocal('example01.templates', [], templates => {
                if (templates.length) return;
                templates.push("家→会社1\t交通費(家→会社1)\t1\t2\t1\t100001,100003\t改行→\n←改行");
                templates.push("会社1→会社2\t交通費(会社1→会社2)\t2\t1,2\t2\t100002,100004\t改行を\n含むメモ");
            });
            //テンプレート保存機能
            ChEx.templateStorage({
                storageKey: 'example01.templates',
                init: $opener => $opener.appendTo('#example01 form'),
                inputs: [
                    '#example01 [name="a"]',
                    '#example01 [name="b"]',
                    '#example01 [name="c"]',
                    '#example01 [name="d"]',
                    '#example01 [name="e"]',
                    '#example01 [name="f"]',
                ],
                title: 'テンプレートから選択',
            });
        });
    </script>
</div>