CSS selector 覚書

これで飯食っている人なら何を今更、だと思うんですけど。。。
CSS selector と JavaScript の覚書です。

昨日ボヤいた Walmart のサイトデザインが変わって、印刷した inovice のレイアウトが悲惨なことになったという件ですが、一括ダウンロードする際に、CSS を編集して保存していました。
そうすると、サイトの構成が変わったら、また一からダウンロードしなおさないといけないので、このやり方はまずいなと。そこで、ダウンロードは素のままで、それをローカルで開いた時に JavaScript で CSS を後から細工することにしました。
やることは、document.querySelector('#target').style.display = 'none' 的な感じなんですが。

まず最初のお題は、いくつかのアイテムをまとめて注文した場合、返品ボタンとかが複数表示されて、それも印刷されてしまうので まとめて 消したい、です。

先に結論を書きます。

document.querySelectorAll('.product-button-groups').forEach(e => { e.style.display = 'none'; });

ボタンは、product-button-groups というクラスなので、querySelectorAll まとめてひっかけます。

ここまではいいんですが、querySelectorAll で戻ってきた NodeList をどう回していくかというのが問題です。

ありがとう、エラい人。
自分は美しさが気に入りましたよ。
IE なにそれですし。

今までだったら、for で回すところです。
美しくないけど、何やっているかは明らかだし、ちゃんと動くし。

どういえばいいんでしょうか、便秘で溜まったものが一気に出たというか、そういうスッキリ感です。
便秘になったことはないんですけどね。

スッキリしたところ、次のお題です。
先にコードから書いちゃいます。

document.querySelectorAll('[data-automation-id=gc-payment-details] tr:nth-child(n+2)').forEach(e => { e.style.display = 'none'; });

支払い内容の表示なんですけど、table になっていて、普通はカードで払ってそれでおしまいなので、最初の tr に Total が、次の tr にカード番号が入ってきます。

ところがどっこい、最近、ギフトカードがバンドルされたお買い得商品をよく買うもので、買った以上使うわけですが、1 度に 5 枚まで使えるので、そこにさらに 5 件のカード番号が表示されて、もうトイレットペーパー占いで引き出された便所神の如く、だらだらと長いインボイスになってしまうので、2 番目以降には消えてもらおうってわけですよ。

:nth-child(n+2) でまとめてひっかけ、スッキリ。
forEach でまとめて消して、スッキリ。
ダブルスッキリ。

ダブルといえば、JB64 ですがオーディオレスなのでヘッドユニットを買わないといけないのですが、日本だと 2 DIN っていうじゃないですか。アメリカだと、double DIN といいます。
それで、ナビはもっぱらスマホのを使っているので Apple Car Play に対応していて、そのほかの下らない機能、ナビはさておき DVD だとかの光学ドライブといったもう 10 年は使っていないものがなくて、シンプルで安いものを探していたんです。

Amazon.com 売れ筋 No.1 が Sony XAV-AX1000 です。

これ、物理的なボリュームがあるし、前に USB コネクタがあるんですよ。
なもんで、USB メモリを挿すのも楽。
そして何よりも安い。
注文しようと思ったら、日本に直接出荷してくれるセラーさんが品切れしてしまいました。

それと crutchfield で適合を確認したかったんですが、そもそも SUZUKI が存在しなかったです。JB23 の情報でもあれば、付くかどうかくらいはわかると思ったんですが、JB23 を置いておけば、XAV-AX1000 単体を輸入して、動作確認をした上で、配線キットなどは JB23 と JB64 との差分で行けると判断できましたが。
なんにせよ、段取りが悪すぎました。

ステアリングリモコンに対応しているし、純正でも良かったんですけど、それはそれで、面白くないので Alpine の DAF9, フローティングビッグDA を買いました。

しかし、クソ高かったなぁ。。。

その割に、DAF9 もそうだし、純正の DA も、XAV-AX1000 も、Bluetooth 機能はあるんですが、Apple Car Play に関していえば、有線で接続しないとダメなんです。

Mini の場合、乗ると勝手に Bluetooth でペアリングして、勝手に音楽の再生(レジューム)がはじまります。
何もしないでいいんですよ。
センターには Qi の充電機能付きホルダがありますから、XR 以外だったら、そこにセットできます。
自分も 5 号も XR だからセットできませんけど。
なので、それが当たり前だと思っていたのですが、随分と完成度が低い気がしますね。

あと、「ビッグだ」なんて、安直な名前だと思ってたんですけど、

DA = ディスプレイオーディオだったんですね。。。

スッキリと言えば、愛媛の愛すべきクソ CM 企業、たかだ引越センターさんですよ。
スッキリする(?)CM を全国の皆々様にみていただきたいところなんですが、なんと非公開になってしまいました。
ネタバレするので、一応伏せ字にしときますけど。。。

逆に、全くスッキリしないのがコレ。

なんなんですが、このブ男は!
外人さんたちも激おこデス!
なかには、

ひっこシコシコしたかだ

という突っ込みまで入る始末。。。

えっと、なんの話でしたっけ?
CSS selector の話でした。

まあ、ここを見ろで終わってしまう話でもあるんですが。。。

それで、消えてもらう場合ですが、CSS selector でやる場合は、

visibility:hiddenは名前の通り、要素はあるけど見えない状態。
display:noneは、要素も取得されず、完全にその場にない扱い。

の違いがあって、見えなくする(場所は確保されている)のと、全くない場合と 2 パターンあるので、レイアウトの崩れ具合によりどっちがいいってのは変わるわけです。

ところで、querySelector でひっかけた要素に完全に消えてもらう場合、どうするのかという話です。
child.parentNode.removeChild(child); しないといけない。

[document.querySelector].forEach(e => { e.parentNode.removeChild(e); }) みたいな?
これだと e に代入しているから同じだし、まあ All よりも [] で一文字減ったというかなんというか。

逆に、querySelector だと、引っかかる要素がなかったときに、null が戻るもんだから、引っ掛からなかったらそこでエラーが起こって後の処理が中断されてしまうんです。

TypeError: null is not an object (evaluating 'document.querySelector('foo').style')

まあ、それはそれで、Knuth がエラーが起こると塗りつぶして文章を書きなさせるように、不都合を無かったことにしないほうがいいのかなとも思うんですが。

try { /* some stuffs here */ } catch { document.body.style.backgroundColor = 'red' }

この作業自体がとても美しくないことに起因している対症療法であるわけで、適当に済ませておこうと思います。

IchigoJam — おみくじの自動販売機に使えそう

30 年前を思い出させてくれるオモチャですね。
IchigoJam、名前だけは知っていたんですが。

たまたま Facebook で I/O DATA の広告が表示されて、興味深いので見てみました。

自分が子供の頃を思い出すと、当時は MSX というものがあったのですよ。
あと、NEC から、PC-8800 シリーズ が出たりもして、最初に買ってもらったのがコレだったんですけどね、確か、PC-8801 mkII MR だったかと。

それで、MSX もそうですし、PC-88 もそうなんですが、最初に電源を入れて使えるのは BASIC という状態です。
BASIC というのは、その名の通り、ものすごく原始的なプログラム言語です。
思い出を書くとキリがないのでやめておきますけど。

それでまあ、小学校で IchigoJam という超小型のマイコンと BASIC を使ってプログラミングを勉強しましょう、ということなんですが、賛否はさておき、興味をそそられたのは、これでドローンや子供用自動車などを制御するプログラムが書ける的な下りです。

それと、小学生でも始められるってことは、おそらく買ってきて、電源を繋いだら昔の MSX や PC-88 のように、カーソルが表示されて、いきなり BASIC が書ける、ということではないかと思うわけです。
面倒くさくなくていいかなと。
あと、JavaScript 版もあるようです。

事例紹介の中の「3限目 IchigoJamかくちょう」で、暗くなると LED が光ると題して、ブレッドボードを使って明るさのセンサだと思いますが、ANA(2) という命令で数値を読み取る事例がありました。

また、サーボをコントロールする的な文章も散見されます。

まんまの記事もありますね。

おみくじの自動販売機も今は売り切れに対応できていないのですが、そういうこまかなこともプログラムすることで対応できるんじゃないかなと思ったりしました。

そうそう、少し前に注文した薪の自動販売機用の部品ですが、お約束でまだ届いていません。
というよりも、届くんでしょうか。。。

あちこち手を出しすぎるのもどうかと思うんですが、かなり興味深く安いので、ものになるかどうかはさておき、買って遊んでみたいと思います。

Safari + JXA その 2

この間 の続きです。

いや、書いていてですね、ちょっとかったるかったんですよ、doJavaScript。

理由はいくつかあって、要するに文字列にしないといけないので、syntax highlighting が使えないとか。
それ以前の問題として、JavaScript って here document ってないですよね、多分。
複数行にまたがるような長いものは書きづらいと。

なもんで、ゴリゴリやってもいいんだけど、あまりに汚らしいので、ちょっとは綺麗に書こうかしらと。

今回の参考記事。

Marionette = function() {
    this.app = Application('Safari');
    this.world = this.app.windows[0].tabs[0];
};

Marionette.prototype.doJavaScript = function(script) {
    return this.app.doJavaScript('(' + script.toString() + ')()', {in: this.world});
}

Marionette.prototype.say = function(message) {
    var app = Application.currentApplication();
    app.includeStandardAdditions = true;
    app.say(message);
}

m = new Marionette();

result = m.doJavaScript(function(){
    return confirm("Hello, world!");
});

/* NG
var hello = "Hello, world!"
result = m.doJavaScript(function(){
    return confirm(hello);
});
*/

m.say((result ? 'OK' : 'Cancel') + ' returned');

というわけで、半分くらいうまくいったようですが、変数の受け渡しは、当然に NG でした。

これは、JSON.stringify() を使えばなんとかなるんじゃないかと思います。

(function (argv) {
    argv = JSON.parse(argv);
    console.log(argv);
    return 0;
})(JSON.stringify(['foo', 'bar', 'baz']));

say についても、Application.currentApplication() に対して呼べばうまく行きました。
ただ、日本語を喋れません。

Terminal の say コマンドだと -v で Kyoko を指定すればいいんですが、まあ、別に無理して日本語を喋らせる必要はないので、英語のままでいい気もしています。

そういえば、JXA で doShellScript すればいいという話かもしれません。

Safari + JXA

STORES やなんかもそうなんですが、意外とかゆいところに手が届きません。

Yahoo! Auctions なども、API で出品できるようにするといっていたのですが、問い合わせたら、できなくなりました、ということなので、ブラウザの側をスクリプトで制御して、決まった動作を(半)自動化しているのですが、今までは AppleScript で行っていたので、AppleScript と JavaScript、更に shell とのちゃんぽんだったわけです。

別に動いているものを特に書き換えるつもりもないのですが、最近覚えるよりも忘れるほうが早いので、なるべく単一の言語でやりたい、というのがありまして、重い腰を上げて JXA なるものを初めてみました。

というわけで、かなりの人にどうでもいいネタだと思いますが、備忘録として。

Imperia Online という村ゲーをしているのですが、これの特定の村を表示するスクリプトを書いてみました。
いや、ブックマークが 20 件までしか登録できないので、不便なので。

function run(argv) {
    se = Application('System Events');
    if (se.uiElementsEnabled()) {
        app = Application('Safari');
        app.includeStandardAdditions = true;
        
        //app.activate();
        //delay(1); // waiting for competition of ativatation
        
        // search Imperia Online Document
        world = null;
        for (i = 0; ! world && i < app.windows().length; ++i) {
            for (j = 0; j < app.windows[i].tabs().length; ++j) {
                console.log(app.windows[i].tabs[j]);
                if (app.windows[i].tabs[j].url().match(/imperiaonline/)) {
                    world = app.windows[i].tabs[j];
                    break;
                }
            }
        }
        
        if (!world) {
            return;
        }
        
        try {
            app.doJavaScript("xajax_switchVillageToGlobalMap('global-map-window', {x: 1701, y: -614})", {in:world});
        }
        catch (e) {
            console.log(e);
        }
    }
    else {
        with (Application('System Preferences')) {
            pane = panes.byId('com.apple.preference.security');
            pane.anchors['Privacy_Accessibility'].reveal();
            activate();
        }
    }
}

参考にしたのは、この辺り。

動作的には、Safari のすべてのウインドウを取得して、ウインドウの中にぶら下がっているすべてのタブを巡回し、URL をマッチして目的のタブを調べる、という部分、あとは、そこで取得したタブに対して、JavaScript を実行する、という部分で成り立っています。

例えば、Yahoo! Auctions で発送作業をしようと思った時など、発送情報が開かれているタブと、クリックポストの発送入力画面が開かれているタブと、その両方を取得して、それぞれで doJavaScript をすれば、通常できないクロスサイト・スクリプティングが簡単に可能になり、スクリプトメニューにそういうよく使うスクリプトは登録しておくこともでき、また、所詮はスクリプトですから、サイト側のデザイン変更などにも容易、かつ、迅速に対応ができるわけです。

ところで、今のところのハマっている部分。

いちいちエラー画面を出すのがイヤなので、通常は、say を使って喋らせているんですが、うまくいかないんですよね。
多分、Accessibility の権限設定が間違えているんだと思うんだけど。

こちらで参考にしたのは、openspc。
一体何年お世話になりっぱなしなんだろう。

STORES の管理画面がリニューアルされた件

雑貨屋 Hearth & Home 暖炉家(暖炉屋) は STORES という個人のお店用のサービスを使って営業しています。

こういうサイトを一から作るのはそれなりに手間隙がかかるわけで、売れるかどうかもわからない状態で、コストを掛けることはできないし、拙速に行こうということで、サービスが始まった当初に登録して、1 年くらいは開店休業状態だったんですが、2 年ほど前から、本格的に活動を始めたわけです。

ヤフオクも手数料が跳ね上がったので、今までどおりの価格では出品できなくなったのと、簡単取引の仕組みが導入されたたため、すべての出品を一度終了していたのですが、自動出品機能も STORES のほうが変更になったので機能しなくなっていたので、雨で外の仕事ができないので、ごそごそとプログラムを書き直しました。

ほとんど備忘録ですが、JavaScript で変数の汚染の問題があったんですが、とりあえず、無名関数を使って対処するようにしました。

(function(){
    l = [];
    e = document.querySelectorAll('p.quantities_variation_title');
    if (e) {
        for (i = 0; i < e.length; ++i) {
            l.push(e[i].textContent);
        }
    }
    return l.join(';;');
})();

それはそうと、いろいろ調べ物をしていて、今頃知ったんですが、JXA といって、Mac の操作が JavaScript からできるようになっているようです。

Illustrator とかも、JavaScript で自動化できるようですし、Safari 自体も、コンテンツを JavaScript で外部から云々すれば、クロスサイトでの処理も、上の join のようなブサイクなことをしなくても済むので、かなりコーディングが楽になりそうです。

勉強してみようと思いますが、その前に、Raspberry Pi、届いているんですが、micro SD のアダプタがないこと、HDMI で繋がるディスプレイがないこと、この 2 点でテンションが下がって、放置プレイ中です。

忘れるほうが早い年代になってきましたので、忘れないうちにこっちにも時間を割いていこうと思います。

AngularJS 事始め

かれこれ 10 年近く開発の現場からは遠ざかっていたので、その間に世間はすっかり様変わりしているようなんですが、STORES.jp があまりに痒いところに手が届かなさすぎて、痒くて仕方ないので、AngularJS を勉強しようと思った次第です。

まあ、その労力を、ヤフーなり、楽天なり、売れるプラットフォームに引っ越す、ということに費やしたらどうか、と言われそうなのですが、袖振り合うも多生の縁といいますし、もう少し踏ん張ってみうというか、前向きに何かできることをしてみよう、と思ったりしたわけです。

とはいえ、あまりに久しぶりなので、習うより慣れろじゃあないですが、とにかく動かしてみて、理解を深めていこうと思います。

出典 http://www.oreilly.co.jp

出典 http://www.oreilly.co.jp

もう、ハコフグ本 も買ったので、後には引き返せません。

昔の感覚が今でも通用するかはわかりませんが、それでいえば、Oreilly から本が出て、そして、邦訳されている時点で、AngularJS は腐っている、旬は過ぎていると思われますので、今更感が強いんですよね。

そういう意味では、Aurelia.JS あたりで始めたほうがいいようにも思うんですが、STORES.jpAngularJS なんで、それは一旦忘れておこうと思います。

なんせ、税別 2,400 円もしたのですから、多少は元を取りたいと思います。

とりあえず、最初の最初のサンプルをトライしてみたんですが。。。全く動きませんでした。

仕方がないので、次のコードで動きましたけど、いきなり、最初のサンプルで動かない状態なので、先が思いやられます。

<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.2.9/angular.min.js"></script>
<script>
var myApp = angular.module('myApp',[]).
    controller('HelloController', ['$scope',
        function($scope) {
            $scope.greeting = { text :'Hello'};
        }]);
</script>
<body>
<div ng-controller="HelloController">
<input ng-model="greeting.text" />
<p>{{greeting.text}}, world!</p>
</div>
</body>
</html>

とりあえず、ボチボチ頑張って勉強しようと思います。