Tutorial: Aplikasi Pencarian Video satu halaman

Peringatan

Bing Search API berpindah dari Cognitive Services ke Bing Search Services. Mulai 30 Oktober, 2020, setiap instans baru Pencarian Bing perlu disediakan mengikuti proses yang didokumentasikan di sini. API Pencarian Bing yang disediakan menggunakan Cognitive Services akan didukung selama tiga tahun ke depan atau hingga akhir Perjanjian Enterprise Anda, mana saja yang lebih dulu. Untuk mengetahui petunjuk migrasi, lihat Bing Search Services. Bing Video Search API memungkinkan Anda mencari di Web dan mendapatkan hasil video yang relevan dengan kueri pencarian. Dalam tutorial ini, kita membangun aplikasi Web satu halaman yang menggunakan Bing Search API untuk menampilkan hasil pencarian di halaman. Aplikasi ini mencakup komponen HTML, CSS, dan JavaScript.

Catatan

Judul JSON dan HTTP di bagian bawah halaman saat diklik memperlihatkan respons JSON dan informasi permintaan HTTP. Detail ini dapat berguna saat menjelajahi layanan.

JSON, hasil mentah HTTP

Aplikasi tutorial ini menunjukkan cara:

  • Melakukan panggilan Bing Video Search API di JavaScript
  • Meneruskan opsi pencarian ke Bing Search API
  • Menampilkan hasil pencarian video atau secara opsional menyertakan halaman Web, berita, atau gambar
  • Mencari kerangka waktu 24 jam, seminggu, sebulan terakhir, atau semua waktu yang tersedia
  • Membalik halaman hasil pencarian dengan cepat
  • Menangani ID klien Bing dan kunci langganan API
  • Menangani kesalahan yang mungkin terjadi

Halaman tutorial sepenuhnya mandiri; tidak menggunakan kerangka kerja, lembar gaya, atau file gambar eksternal. Halaman tutorial ini hanya menggunakan fitur bahasa JavaScript yang didukung secara luas dan berfungsi dengan versi saat ini dari semua browser Web utama.

Dalam tutorial ini, kita membahas bagian yang dipilih dari kode sumber. Kode sumber lengkap tersedia. Untuk menjalankan contoh, salin dan tempel kode sumber ke editor teks dan simpan sebagai bing.html.

Komponen aplikasi

Seperti aplikasi Web satu halaman lainnya, aplikasi tutorial ini mencakup tiga bagian:

  • HTML - Menentukan struktur dan konten halaman
  • CSS - Menentukan tampilan halaman
  • JavaScript - Menentukan aktivitas halaman

Sebagian besar HTML dan CSS ini adalah yang umum digunakan, sehingga tutorial ini tidak membahasnya. HTML berisi formulir pencarian di mana pengguna memasukkan kueri dan memilih opsi pencarian. Formulir tersambung ke JavaScript yang melakukan pencarian menggunakan atribut onsubmit dari tag <form>:

<form name="bing" onsubmit="return bingWebSearch(this)">

Penanganan onsubmit mengembalikan false, yang menjaga agar formulir tidak dikirim ke server. Kode JavaScript melakukan pekerjaan mengumpulkan informasi yang diperlukan dari formulir dan melakukan pencarian.

HTML juga berisi pembagian (tag <div> HTML) tempat hasil pencarian muncul.

Mengelola kunci langganan

Agar tidak perlu menyertakan kunci langganan Bing Search API dalam kode, kami menggunakan penyimpanan persisten browser untuk menyimpan kunci. Sebelum kunci disimpan, kami meminta kunci kepada pengguna. Jika kunci kemudian ditolak oleh API, kami membatalkan kunci yang disimpan sehingga pengguna akan dimintai kunci lagi.

Kami menentukan fungsi storeValue dan retrieveValue yang menggunakan objek localStorage (tidak semua browser mendukungnya) atau cookie. Fungsi getSubscriptionKey() menggunakan fungsi-fungsi ini untuk menyimpan dan mengambil kunci pengguna.

// Cookie names for data we store
API_KEY_COOKIE   = "bing-search-api-key";
CLIENT_ID_COOKIE = "bing-search-client-id";

// ... omitted definitions of store value and retrieve value
// Browsers differ in their support for persistent storage by 
// local HTML files. See the source code for browser-specific
// options.

// Get stored API subscription key, or prompt if it's not found.
function getSubscriptionKey() {
    var key = retrieveValue(API_KEY_COOKIE);
    while (key.length !== 32) {
        key = prompt("Enter Bing Search API subscription key:", "").trim();
    }
    // always set the cookie in order to update the expiration date
    storeValue(API_KEY_COOKIE, key);
    return key;
}

Tag <form> HTML onsubmit memanggil fungsi bingWebSearch untuk mengembalikan hasil pencarian. bingWebSearch menggunakan getSubscriptionKey() untuk mengautentikasi setiap kueri. Seperti yang ditunjukkan pada definisi sebelumnya, getSubscriptionKey meminta kunci kepada pengguna jika kunci belum dimasukkan. Kunci kemudian disimpan untuk selanjutnya digunakan oleh aplikasi.

<form name="bing" onsubmit="this.offset.value = 0; return bingWebSearch(this.query.value, 
    bingSearchOptions(this), getSubscriptionKey())">

Memilih opsi pencarian

Gambar berikut ini memperlihatkan kotak teks kueri dan opsi yang menentukan pencarian.

Opsi Bing News Search

Formulir HTML menyertakan elemen dengan nama berikut:

Elemen Deskripsi
where Menu drop-down untuk memilih pasar (lokasi dan bahasa) yang digunakan untuk pencarian.
query Bidang teks untuk memasukkan istilah pencarian.
modules Kotak centang untuk mempromosikan modul hasil, semua hasil, atau video terkait tertentu.
when Menu drop-down untuk membatasi pencarian secara opsional ke hari, minggu, atau bulan terbaru.
safe Kotak centang yang menunjukkan apakah akan menggunakan fitur Bing SafeSearch untuk memfilter hasil "dewasa".
count Bidang tersembunyi. Jumlah hasil pencarian yang akan dikembalikan pada setiap permintaan. Ubah untuk menampilkan lebih sedikit atau lebih banyak hasil per halaman.
offset Bidang tersembunyi. Offset hasil pencarian pertama dalam permintaan; digunakan untuk penomoran. Ini diatur ulang ke 0 pada permintaan baru.

Catatan

Bing Web Search menawarkan parameter kueri lainnya. Kita hanya menggunakan beberapa darinya.

// build query options from the HTML form
// build query options from the HTML form
function bingSearchOptions(form) {

    var options = [];
    options.push("mkt=" + form.where.value);
    options.push("SafeSearch=" + (form.safe.checked ? "strict" : "moderate"));

    if (form.when.value.length) options.push("freshness=" + form.when.value);
    var what = [];
    for (var i = 0; i < form.what.length; i++) 
        if (form.what[i].checked) what.push(form.what[i].value);
    if (what.length) {
        options.push("modules=" + what.join(","));
        options.push("answerCount=9");
    }
    options.push("count=" + form.count.value);
    options.push("offset=" + form.offset.value);
    options.push("textDecorations=true");
    options.push("textFormat=HTML");
    return options.join("&");
}

Misalnya, parameter SafeSearch dalam panggilan API aktual dapat berupa strict, atau moderate, dengan moderate yang menjadi defaultnya.

Melakukan permintaan

Mengingat kueri, string opsi, dan kunci API, fungsi BingWebSearch menggunakan objek XMLHttpRequest untuk membuat permintaan ke titik akhir Pencarian Bing. Anda dapat menggunakan titik akhir global di bawah, atau titik akhir subdomain kustom yang ditampilkan di portal Azure untuk sumber daya Anda.

// Search on the query, using search options, authenticated by the key.
function bingWebSearch(query, options, key) {

    // scroll to top of window
    window.scrollTo(0, 0);
    if (!query.trim().length) return false;     // empty query, do nothing

    showDiv("noresults", "Working. Please wait.");
    hideDivs("pole", "mainline", "sidebar", "_json", "_headers", "paging1", "paging2", "error");

    var endpoint = "https://api.cognitive.microsoft.com/bing/v7.0/videos/search";
    var request = new XMLHttpRequest();
    var queryurl = endpoint + "?q=" + encodeURIComponent(query) + "&" + options;

    try {
        request.open("GET", queryurl);
    } 
    catch (e) {
        renderErrorMessage("Bad request (invalid URL)\n" + queryurl);
        return false;
    }

    // add request headers
    request.setRequestHeader("Ocp-Apim-Subscription-Key", key);
    request.setRequestHeader("Accept", "application/json");
    var clientid = retrieveValue(CLIENT_ID_COOKIE);
    if (clientid) request.setRequestHeader("X-MSEdge-ClientID", clientid);

    // event handler for successful response
    request.addEventListener("load", handleOnLoad);

    // event handler for erorrs
    request.addEventListener("error", function() {
        renderErrorMessage("Error completing request");
    });

    // event handler for aborted request
    request.addEventListener("abort", function() {
        renderErrorMessage("Request aborted");
    });

    // send the request
    request.send();
    return false;
}

Setelah berhasil menyelesaikan permintaan HTTP, JavaScript memanggil penanganan aktivitas load, handleOnLoad(), untuk menangani permintaan GET HTTP ke API yang berhasil.

// handle Bing search request results
function handleOnLoad() {
    hideDivs("noresults");

    var json = this.responseText.trim();
    var jsobj = {};

    // try to parse JSON results
    try {
        if (json.length) jsobj = JSON.parse(json);
    } catch(e) {
        renderErrorMessage("Invalid JSON response");
    }

    // show raw JSON and headers
    showDiv("json", preFormat(JSON.stringify(jsobj, null, 2)));
    showDiv("http", preFormat("GET " + this.responseURL + "\n\nStatus: " + this.status + " " + 
        this.statusText + "\n" + this.getAllResponseHeaders()));

    // if HTTP response is 200 OK, try to render search results
    if (this.status === 200) {
        var clientid = this.getResponseHeader("X-MSEdge-ClientID");
        if (clientid) retrieveValue(CLIENT_ID_COOKIE, clientid);
        if (json.length) {
            if (jsobj._type === "Videos") {//"SearchResponse" && "rankingResponse" in jsobj) {
                renderSearchResults(jsobj);
            } else {
                renderErrorMessage("No search results in JSON response");
            }
        } else {
            renderErrorMessage("Empty response (are you sending too many requests too quickly?)");
        }
    }

    // Any other HTTP response is an error
    else {
        // 401 is unauthorized; force re-prompt for API key for next request
        if (this.status === 401) invalidateSubscriptionKey();

        // some error responses don't have a top-level errors object, so gin one up
        var errors = jsobj.errors || [jsobj];
        var errmsg = [];

        // display HTTP status code
        errmsg.push("HTTP Status " + this.status + " " + this.statusText + "\n");

        // add all fields from all error responses
        for (var i = 0; i < errors.length; i++) {
            if (i) errmsg.push("\n");
            for (var k in errors[i]) errmsg.push(k + ": " + errors[i][k]);
        }

        // also display Bing Trace ID if it isn't blocked by CORS
        var traceid = this.getResponseHeader("BingAPIs-TraceId");
        if (traceid) errmsg.push("\nTrace ID " + traceid);

        // and display the error message
        renderErrorMessage(errmsg.join("\n"));
    }
}

Penting

Jika terjadi kesalahan dalam operasi pencarian, Bing News Search API mengembalikan kode status HTTP non-200 dan menyertakan informasi kesalahan dalam respons JSON. Selain itu, jika permintaan dibatasi tarif, API akan mengembalikan respons kosong. Permintaan HTTP yang berhasil tidak selalu berarti bahwa pencarian itu sendiri berhasil.

Sebagian besar kode dalam kedua fungsi sebelumnya dikhususkan untuk penanganan kesalahan. Kesalahan dapat terjadi pada tahap berikut:

Tahap Potensi kesalahan Ditangani oleh
Membangun objek permintaan JavaScript URL tidak valid Blok try/catch
Membuat permintaan Kesalahan jaringan, koneksi yang dibatalkan Penanganan aktivitas error dan abort
Melakukan pencarian Permintaan tidak valid, JSON tidak valid, batas tarif pengujian dalam penanganan aktivitas load

Kesalahan ditangani dengan memanggil renderErrorMessage() dengan detail apa pun yang diketahui tentang kesalahan tersebut. Jika respons meneruskan gauntlet lengkap dari pengujian kesalahan, kami memanggil renderSearchResults() untuk menampilkan hasil pencarian di halaman.

Menampilkan hasil pencarian

Fungsi utama untuk menampilkan hasil pencarian adalah renderSearchResults(). Fungsi ini mengambil JSON yang dikembalikan oleh layanan Bing News Search dan merender hasil berita dan pencarian terkait, jika ada.

// render the search results given the parsed JSON response
function renderSearchResults(results) {

    // add Prev / Next links with result count
    var pagingLinks = renderPagingLinks(results);
    showDiv("paging1", pagingLinks);
    showDiv("paging2", pagingLinks);

    // Render the results to the mainline section
    for (section in { mainline: 0 }) {
         showDiv(section, renderResultsItems(section, results));
    }
}

Hasil pencarian dikembalikan sebagai objek value tingkat atas dalam respons JSON. Kami meneruskannya ke renderResultsItems() fungsi kami, yang melakukan perulangan melaluinya dan memanggil fungsi untuk merender setiap item ke HTML. HTML yang dihasilkan dikembalikan ke renderSearchResults(), di mana HTML dimasukkan ke dalam divisi results di halaman.

// render search results
    function renderResultsItems(section, results) {   

        var items = results.value;
        var html = [];
        for (var i = 0; i < items.length; i++) { 
            var item = items[i];
            // collection name has lowercase first letter
            var type = "videos";
            var render = searchItemRenderers[type];
            html.push(render(item, section));  
        }
    return html.join("\n\n");
}

Bing News Search API mengembalikan hingga empat jenis hasil terkait yang berbeda, masing-masing dalam objek tingkat atasnya sendiri. Keempat jenis hasil tersebut adalah:

Relasi Deskripsi
pivotSuggestions Kueri yang menggantikan kata pivot dalam pencarian asli dengan kata lain. Misalnya, jika Anda mencari "bunga merah", kata pivotnya mungkin adalah "merah", dan saran pivotnya mungkin adalah "bunga kuning."
queryExpansions Kueri yang mempersempit pencarian asli dengan menambahkan lebih banyak istilah. Misalnya, jika Anda mencari "Microsoft Surface", ekspansi kuerinya mungkin adalah "Microsoft Surface Pro."
relatedSearches Kueri yang juga telah dimasukkan oleh pengguna lain yang memasukkan pencarian asli. Misalnya, jika Anda mencari "Mount Rainier", pencarian terkaitnya mungkin adalah "Mt. Saint Helens."
similarTerms Kueri yang artinya mirip dengan pencarian asli. Misalnya, jika Anda mencari "sekolah", istilah serupanya mungkin adalah "pendidikan."

Seperti yang dapat dilihat sebelumnya di renderSearchResults(), kami hanya merender saran relatedItems dan menempatkan tautan yang dihasilkan di bilah samping halaman.

Merender item hasil

Dalam kode JavaScript objek, searchItemRenderers, dapat berisi fungsi renderer: yang menghasilkan HTML untuk setiap jenis hasil pencarian. Halaman pencarian video hanya menggunakan videos. Lihat tutorial lain untuk melihat berbagai jenis perender.

searchItemRenderers = {
    news: function(item) { ... },
    webPages: function (item) { ... }, 
    images: function(item, index, count) { ... },
    videos: function (item, section, index, count) { ... },
    relatedSearches: function(item) { ... }
}

Fungsi perender dapat menerima parameter berikut:

Parameter Deskripsi
item Objek JavaScript yang berisi properti item, seperti URL dan deskripsinya.
index Indeks item hasil dalam koleksinya.
count Jumlah item dalam koleksi item hasil pencarian.

Parameter index dan count dapat digunakan untuk menomori hasil, untuk menghasilkan HTML khusus untuk awal atau akhir koleksi, untuk menyisipkan hentian baris setelah item dalam jumlah tertentu, dan sebagainya. Jika perender tidak memerlukan fungsionalitas ini, perender tidak perlu menerima kedua parameter ini.

Perender video ditampilkan dalam kutipan javascript berikut ini. Menggunakan titik akhir Video, semua hasil adalah jenis Videos. searchItemRenderers diperlihatkan dalam segmen kode berikut.

// render functions for various types of search results
    searchItemRenderers = {

    videos: function (item, section, index, count) {
        var height = 60;
        var width = Math.round(height * item.thumbnail.width / item.thumbnail.height);
        var html = [];

        html.push("<p class='images'>");
        html.push("<a href='" + item.hostPageUrl + "'>");
        var title = escapeQuotes(item.name) + "\n" + getHost(item.hostPageDisplayUrl);
        html.push("<img src='" + item.thumbnailUrl + "&h=" + height + "&w=" + width +
            "' height=" + height + " width=" + width + " title='" + title + "' alt='" + title + "'>");
        html.push("</a>");
        html.push("<br>");
        html.push("<nobr><a href='" + item.contentUrl + "'>Video page source</a> - ");
        html.push(title.replace("\n", " (").replace(/([a-z0-9])\.([a-z0-9])/g, "$1.<wbr>$2") + ")</p>");
        return html.join("");
    }
}

Fungsi perender:

  • Membuat tag paragraf, menetapkannya ke kelas images, dan mendorongnya ke array html.
  • Menghitung ukuran gambar mini gambar (lebar ditetapkan pada 60 piksel, tinggi dihitung secara proporsional).
  • Membangun tag <img> HTML untuk menampilkan gambar mini gambar.
  • Membangun tag <a> HTML yang ditautkan ke gambar dan halaman tempat gambar berada.
  • Membuat deskripsi yang menampilkan informasi tentang gambar dan situs tempatnya berada.

Ukuran gambar mini digunakan dalam tag <img> dan bidang h dan w dalam URL gambar mini. Bing akan mengembalikan gambar mini tepat dengan ukuran tersebut.

ID klien tetap

Respons dari Bing Search API dapat mencakup header X-MSEdge-ClientID yang harus dikirim kembali ke API dengan permintaan berturut-turut. Jika beberapa Bing Search API sedang digunakan, ID klien yang sama harus digunakan dengan semuanya, jika memungkinkan.

Menyediakan header X-MSEdge-ClientID memungkinkan Bing API untuk mengaitkan semua pencarian pengguna, yang memiliki dua manfaat penting.

Pertama, herader ini memungkinkan mesin pencari Bing untuk menerapkan konteks masa lalu ke pencarian untuk menemukan hasil yang lebih memuaskan pengguna. Jika pengguna sebelumnya telah mencari istilah yang terkait dengan berlayar, misalnya, pencarian berikutnya untuk "simpul" akan lebih disukai jika mengembalikan informasi tentang simpul yang digunakan dalam berlayar.

Kedua, Bing dapat secara acak memilih pengguna untuk mencoba fitur baru sebelum fitur tersebut tersedia secara luas. Memberikan ID klien yang sama dengan setiap permintaan memastikan bahwa pengguna yang melihat fitur tersebut akan selalu melihatnya. Tanpa ID klien, pengguna mungkin melihat fitur muncul dan menghilang, tampaknya secara acak, dalam hasil pencarian mereka.

Kebijakan keamanan browser (CORS) dapat mencegah header X-MSEdge-ClientID tersedia di JavaScript. Pembatasan ini terjadi ketika respons pencarian memiliki asal yang berbeda dari halaman yang memintanya. Dalam lingkungan produksi, Anda harus menangani kebijakan ini dengan menghosting skrip sisi server yang melakukan panggilan API pada domain yang sama dengan halaman Web. Karena skrip memiliki asal yang sama dengan halaman Web, header X-MSEdge-ClientID kemudian tersedia untuk JavaScript.

Catatan

Dalam aplikasi Web produksi, Anda harus melakukan permintaan sisi server. Jika tidak, kunci Bing Search API Anda harus disertakan di halaman Web, yang tersedia bagi siapa saja yang melihat sumber. Anda ditagih untuk semua penggunaan pada kunci langganan API, bahkan permintaan yang dibuat oleh pihak yang tidak berwenang, jadi penting untuk tidak menunjukkan kunci Anda.

Untuk tujuan pengembangan, Anda dapat membuat permintaan Bing Web Search API melalui proksi CORS. Respons dari proksi semacam itu memiliki header Access-Control-Expose-Headers yang mengizinkan header respons dan menyediakannya untuk JavaScript.

Sangat mudah untuk memasang proksi CORS untuk mengizinkan aplikasi tutorial kami mengakses header ID klien. Pertama, jika Anda belum memilikinya, pasang Node.js. Kemudian, keluarkan perintah berikut di jendela perintah:

npm install -g cors-proxy-server

Selanjutnya, ubah titik akhir Bing Web Search dalam file HTML menjadi:
http://localhost:9090/https://api.cognitive.microsoft.com/bing/v7.0/search

Terakhir, mulai proksi CORS dengan perintah berikut:

cors-proxy-server

Biarkan jendela perintah terbuka saat Anda menggunakan aplikasi tutorial; menutup jendela akan menghentikan proksi. Di bagian Header HTTP yang dapat diluaskan di bawah hasil pencarian, Anda sekarang dapat melihat header X-MSEdge-ClientID (di antara yang lain) dan memverifikasi bahwa header tersebut sama untuk setiap permintaan.

Langkah berikutnya