archive

こんにちは。
ユニトラストの宮本です。

昨今、Webアプリケーション開発においてJavascript抜きでは語れない時代になりました。
最近はReactVue.jsなどモダンなJavascriptフレームワークが流行っていますが、jQueryもまだまだ現役です。

今回はjQueryの.append()を使用するときに気をつけるポイントを書きたいと思います。

まず、jQueryの.append()のおさらい

簡単に説明すると、jQueryの.append()は各要素内の一番後ろに指定したHTMLやエレメントを挿入します。

ex)
次のようなHTMLがあったとして

<div>
    <div class="hello">
        こんにちは
    </div>
</div>

次のようなjsが実行されると

// class="hello"に該当するセレクタの一番後ろに'<p>テスト</p>'を追加
$('.hello').append('<p>テスト</p>');

次のようなHTMLになります

<div>
    <div class="hello">
        こんにちは
        <p>テスト</p>
    </div>
</div>

jQueryオブジェクトを.append()する時は気をつけよう

複数の要素に対して同一のjQueryオブジェクトを.append()する時

ex)
ボタンをクリックしたら.test_1と.test_2にhello!を追加する処理

次のようなHTML・jsがあるとして

<button>add word</button>

<div>
    <p class="test_1">
        test1
    </p>
    <p class="test_2">
        test2
    </p>
</div>
var button = $("button")

// クリックしたら.test_1と.test_2に<span class="hi">hello!</span>を追加
button.on("click", function(){
    // <span class="hi">hello!</span>のDOM要素(jQueryオブジェクト)を生成・変数にする
    var $ele = $('<span />', {class: 'hi'}).append('hello!');
    // $eleを.test_1と.test_2に追加
    $('.test_1').append($ele);
    $('.test_2').append($ele);
});

見本その1

ボタンをクリックした時、

想定では

<button>add word</button>

<div>
    <p class="test_1">
        test1
        <span class="hi">hello!</span>
    </p>
    <p class="test_2">
        test2
        <span class="hi">hello!</span>
    </p>
</div>

見本その2

になる!…と思われがちですが、実際には

<button>add word</button>

<div>
    <p class="test_1">
        test1
    </p>
    <p class="test_2">
        test2
        <span class="hi">hello!</span>
    </p>
</div>

見本その3

の様になってしまいます。

一体どういうことなのでしょうか?

jQueryオブジェクトの.append()は追加 or 移動

困った時はまず、公式マニュアルを読みましょう!

jQuery公式マニュアル .append()

原因が書いてありました。

If an element selected this way is inserted into a single location elsewhere in the DOM, it will be moved into the target (not cloned)
Important: If there is more than one target element, however, cloned copies of the inserted element will be created for each target except for the last one.

google翻訳 ↓

この方法で選択された要素がDOMの他の場所の単一の場所に挿入された場合、それはターゲットに移動されます(クローンされません)
重要:ただし、ターゲット要素が複数ある場合は、最後の要素を除く各ターゲットに対して、挿入された要素のクローンコピーが作成されます。

つまり、処理の流れを説明すると

ボタンをクリック

DOM要素を生成・変数にする

var $ele = $('<span />', {class: 'hi'}).append('hello!');


作成した変数($ele)をclass=”test_1″に該当するセレクタの一番後ろに追加する

$('.test_1').append($ele);

この段階ではこうなっている

<button>add word</button>

<div>
    <p class="test_1">
        test1
        <span class="hi">hello!</span>
    </p>
    <p class="test_2">
        test2
    </p>
</div>


作成した変数($ele)をclass=”test_2″に該当するセレクタの一番後ろに追加するのではなく、作成した変数($ele)をclass=”test_2″に該当するセレクタの一番後ろに移動させる

$('.test_2').append($ele);

この段階でこうなってしまう!

<button>add word</button>

<div>
    <p class="test_1">
        test1
    </p>
    <p class="test_2">
        test2
        <span class="hi">hello!</span>
    </p>
</div>


結果、最後にappendした要素にのみ適用されてしまったのです。

では、どうする?

【解決法】.append()する時に、変数を複製(.clone())する!

    $('.test_1').append($ele);
    $('.test_2').append($ele);

    // $eleを複製する($eleと$ele_clone_1は内容は同じだけど、別の変数になる)
    var $ele_clone_1 = $ele.clone();
    $('.test_1').append($ele_clone_1);
    var $ele_clone_2 = $ele.clone();
    $('.test_2').append($ele_clone_2);

もしくは

    // こちらでもOK
    $('.test_1').append($ele.clone());
    $('.test_2').append($ele.clone());

複製された変数を.append()することで、要素を追加する。
とてもわかりやすいですね。

【非推奨】この方法でも解決できることは出来る

変数に代入する値をjQueryオブジェクトではなく文字列にしてしまえばいい!

var $ele = $('<span />', {class: 'hi'}).append('hello!');

var $ele = $('<span />', {class: 'hi'}).append('hello!')[0].outerHTML;

.append()の引数がjQueryオブジェクトでなければ単純に文字列が追加される。
こちらでもできますが、読みづらいのでおすすめしません。

ちなみに

次のような場合はどうなるでしょうか?

<button>add word</button>

<div>
    <p class="test">
        test1
    </p>
    <p class="test">
        test2
    </p>
</div>
var button = $("button")

// クリックしたら全ての.testに<span class="hi">hello!</span>を追加
button.on("click", function(){
    // <span class="hi">hello!</span>のDOM要素(jQueryオブジェクト)を生成・変数にする
    var $ele = $('<span />', {class: 'hi'}).append('hello!');
    // $eleを全ての.testに追加
    $('.test').append($ele);
});

上記の場合には、要素が全てに追加(実際は要素が複製された上で追加)されます。

<button>add word</button>

<div>
    <p class="test">
        test1
        <span class="hi">hello!</span> //要素が複製された上で追加
    </p>
    <p class="test">
        test2
        <span class="hi">hello!</span> //最後のターゲットだけ要素がそのまま追加
    </p>
</div>

この方法で選択された要素がDOMの他の場所の単一の場所に挿入された場合、それはターゲットに移動されます(クローンされません)
重要:ただし、ターゲット要素が複数ある場合は、最後の要素を除く各ターゲットに対して、挿入された要素のクローンコピーが作成されます。

ターゲット要素(.test)が2つあるので、最後のターゲット以外は複製された$eleが挿入され、
全てのターゲット要素(.test)に対して、$eleの内容が追加されます。

まとめ

様々なJavascriptフレームワークが登場し始め、いよいよお役御免と思われがちなjQueryですが、
2019年2月現在、世界上位50万サイトの90%近くがまだjQueryを使用しています。

まだまだ使う機会が多いjQuery、一度学びなおしてみてはいかがでしょうか?

jQuery公式サイト[https://jquery.com/]

こんにちは、システム開発グループの高橋です。
2019年度の新入社員向け事前研修を2/22(金)に開催しましたので、その様子をご紹介いたします。

研修第一部

弊社がいつもお世話になっている社外アドバイザーの駒谷さんにお越しいただいて、
社会人として、より良く成功するための振る舞い方、考え方をレクチャーいただきました。

 

聞くだけではなく、考えや意見の交換も積極的に行うのが特徴です。
聞きたいことも遠慮なく聞けるので、少しでも不安な点が解消できたのではないでしょうか。

研修第二部

恒例?のマシュマロチャレンジを行いました。
各チームとも創意工夫やコミュニケーションを取りながら楽しんで貰えたようです。
楽しむことも大切ですね!

マシュマロチャレンジとは

“パスタ、テープ、紐を使って塔を立て、マシュマロを塔の頂点に乗せて高さを競うゲームです。
チーム戦で行う事と、何回か繰り返すことでチームワークやPDCAサイクルの重要性を学ぶことができます。”

 

チームメンバーで試行錯誤しながら塔を立てていきます。
まさにチームワーク!

 

結果は、新人チームが71cmという記録で一位になりました。
若手の社員チームも飛び入り参加しましたが僅差で負けてしまいました。
今年の新入社員の方たちには期待が高まります!

研修第三部

最後に少しだけ、今回の研修のアンケートと事務手続きなどの説明があり終了です。
半日お疲れ様でした!

懇親会

研修終了後、懇親会を行いました。
久々に集まった同期のメンバーたち。今年はなんと10名!なのでとても賑やかです!
今回、残念ながら参加できなかった2名もいるので4月には総勢12名が揃います!

弊社の社員も混じって様々な話題に花を咲かせました。

いよいよ、入社まであと1ヶ月です!
フレッシュなメンバーが入社してくるのを社員一同心待ちにしております!

こんにちは。
ユニトラストでエンジニアをしている我妻です。

以前にe2eテストをSeleniumでやろうとした時に環境構築で大分時間が取られ挫けそうになった経験があり、
フロントエンドのテストを行える手軽に使えるテストツールはないかといくつか試している中で公式サイトでも「1分でセットアップ」と謳っており、
実際に触って便利だなと思ったTestCafeの紹介です。

なお、今回動かしているコードサンプルはGitHubに置いてあります
testcafe-e2e-sample

TestCafeとは

TestCafeとはNode.js製のe2e自動テストツールで実行ホストに関わらず動作させることができ、主要ブラウザに対してならwebdriver等の導入も不要でテストを行う事ができます。同名のwebサービスも展開されていますが、今回取り扱うのはMITライセンスで公開されている開発者向けテストツールになります。

動作環境

今回動作させた環境は以下の通りです。

実際に動かす

今回のコード構成は下記の通りです

/
├─report
├─screenshot
└─tests
    ├─features
    │  └─testcafe
    └─pages
        └─testcafe

Page Object Model

TestCafeはPage Object Modelに対応しているため、それに準拠した形でページ構成・挙動を管理するPageと実テストをつかさどるFeatureに分けています。
場合によってはPageの操作部分を更にOperationとして分離させるケースもあるようですが、今回は簡便化のためにそこまでは行いません。

Demo

解説

今回は2通りのテストパターンを盛り込んでいます。
1つ目がGoogle検索にてワード検索を行い、検索結果から期待したサイトに遷移できるか、
2つ目がサイト内の移動と要素の表示状態の検査となります。

Google検索

export default class TopPage {
  constructor () {
    this.url = 'https://www.google.co.jp/';
    this.inputSearchBox = Selector('.gLFyf.gsfi');
    this.buttonSearch = Selector('input[name="btnK"]');
    this.buttonFeelingLucky = Selector('input[name="btnI"]');
  }

ページ構成要素宣言部です。
HTMLでのセレクタと同じように記述することができるので、javascript等でなれている人は特に違和感なく記述することができるのではないでしょうか。

  /**
   * 検索実行
   */
  async search(text) {
    await t.typeText(this.inputSearchBox, text)
      .click(this.buttonSearch);
  }

画面の振る舞いをメソッドとして定義していきます。
連続した操作の場合はメソッドチェーンとして記述していくことができます

/**
 * Google検索→検索結果表示->詳細クリック
 */
test('Google検索', async (t) => {
  const searchWord = 'Example Domain';
  await topPage.search(searchWord);
  await listPage.showDetail(searchWord);
  const actualUrl = ClientFunction(() => window.location.href);
  const expectedUrl = 'https://example.com/';
  await t.takeScreenshot();
  await t.expect(actualUrl()).eql(expectedUrl);
});

実際のテストメソッドの記述はこのようになります。
先程定義したPage Objectの振る舞いを順に呼び出していきます。
仮に画面の構成要素が移動したり名称が変わったりしてもテストコード自体には影響が出にくいのがPage Object Modelの良いところですね。

フォーム入力・サイト内移動

続いてTestCafeで用意されているサンプルページでの動作テストです。
Page Object側については特に変わらないので割愛します。

  // ページタイトル
  await t.expect(Selector('title').innerText).eql(examplePage.pageTitle);
  // コメント入力欄が非活性状態
  await t.expect(examplePage.textAreaComment.hasAttribute('disabled')).ok();
  // Submit ボタンが非活性状態
  await t.expect(examplePage.buttonSubmit.hasAttribute('disabled')).ok();

こちらのテストコードについては初期表示時に非活性になっている項目が存在しているため、状態のチェックを挟んでいます。

出力レポートについてはJenkins等のCIツールと連携しやすいようにxUnit形式のレポートファイルを出力するようにしています。
また公式でDockerイメージを配布しているため、これを利用することにより更に開発環境に左右されないe2eテストを実施することができるようになります。

こういったテストをプロジェクトで導入しようとすると事前準備の面倒さで躓く事が多いのでNode.jsさえあれば簡単に導入できるのは強みですね。