() translation by (you can also view the original English article)
Ini adalah bagian kedua dari seri tentang Audero Feed Reader. Dalam artikel ini, kami akan mempelajari logika bisnis aplikasi kami dan memberikan latar belakang tambahan pada plugin dan API yang digunakan untuk proyek kami.
Ikhtisar Plugin & API
Plugin Notifikasi
Di beberapa titik dalam aplikasi Pembaca Umpan Audero Feed Reader kami akan menggunakan metode alert()
pada Notification Plugin. Bagaimana peringatan akan ditampilkan benar-benar tergantung pada platform yang akan dijalankan aplikasi. Bahkan, sebagian besar sistem operasi yang didukung menggunakan kotak dialog asli, tetapi yang lain, seperti Bada 2.x, menggunakan fungsi alert()
klasik browser, yang kurang dapat disesuaikan. Metode ini menerima hingga empat parameter:
-
message
: String yang berisi pesan yang akan ditampilkan. -
alertCallback
: callback untuk meminta ketika dialog siaga dipecat. -
title
: judul dialog (nilai default adalah "alert"). -
buttonName
: teks tombol yang disertakan dalam dialog (nilai default adalah "OK")
Perlu diingat bahwa Windows Phone 7 dan 8 tidak memiliki tanda built-in browser. Jadi, jika Anda ingin menggunakan alert('message');
Anda harus menetapkan window.alert = navigator.notification.alert
.
InAppBrowser Plugin
Dalam bagian pertama dari seri ini, saya sebutkan bahwa titik yang menarik dari halaman kredit adalah atribut target = "_blank"
diterapkan untuk link. Bagian ini akan menjelaskan bagaimana metode openLinksInApp()
kelas aplication
bekerja.
InAppBrowser adalah sebuah browser web yang akan ditampilkan di app ketika Anda menggunakan panggilan
Seperti saya katakan dalam bagian pertama, mulai dari versi 2.3.0, memiliki dua metode baru: window.open
.executeScript()
dan insertCSS()
. Saat ini, plugin ini menyediakan lima metode berikut dalam Total:
-
addEventListener()
: memungkinkan pengguna untuk mendengarkan peristiwa tiga (loadstart
,loadstop
, danexit
), dan untuk melampirkan fungsi yang berjalan segera setelah peristiwa-peristiwa dipecat. -
removeEventListener()
: digunakan untuk menghilangkan pendengar terpasang sebelumnya. -
Close()
: digunakan untuk menutup jendela InAppBrowser. -
executeScript()
: memungkinkan injeksi kode JavaScript ke jendelaInAppBrowser
. -
executeScript()
: memungkinkan injeksi kode CSS ke jendelaInAppBrowser
.
Jika Anda tidak menggunakan Cordova selama beberapa bulan, atau jika Anda tetap untuk versi 2.0.0, Anda akan ingat bahwa secara default itu membuka pranala di Cordova WebView sama yang menjalankan aplikasi. Oleh karena itu, setelah halaman eksternal dikunjungi Halaman ditampilkan terakhir ditunjukkan persis seperti itu sebelum pengguna meninggalkan. Dari versi itu pada, hal ini tidak lagi perilaku standar. Pada kenyataannya, link eksternal sekarang dibuka menggunakan Cordova WebView jika URL dalam aplikasi Anda daftar putih. URL yang tidak pada daftar putih Anda dibuka menggunakan InAppBrowser Plugin (lebih pada ini dalam dokumentasi). Tapi apa ini berarti praktis? Itu berarti bahwa jika Anda tidak mengelola link dengan benar dan jika aplikasi Anda pengguna mengklik link dan kemudian kembali ke aplikasi, Semua Mobile jQuery atau perangkat tambahan lain seperti hilang. Hal ini terjadi karena semua CSS dan JavaScript file yang dimuat hanya di halaman utama, dan URL berikutnya dimuat menggunakan AJAX (sistem default yang diadopsi oleh jQuery Mobile).
Perbaikan untuk masalah ini dilaksanakan dalam metode openLinksInApp()
. Pada kenyataannya, solusinya adalah untuk menangkap klik semua link eksternal dengan menetapkan target = "_blank"
atribut, mencegah perilaku default yang tidak diinginkan dan membuka link menggunakan metode window.open()
. Untuk bekerja dengan baik, solusi ini akan mengharuskan Anda menetapkan daftar putih di file konfigurasi.
Google Feed API
Sebelum berbicara tentang kelas di Audero Feed Reader, kita perlu menyelidiki Google Feed API dan antarmuka Google Feed JSON karena kita akan menggunakannya dalam fitur inti dari aplikasi kita. Seperti yang saya tunjukkan di bagian pertama dari seri ini, antarmuka mem-parsing umpan RSS atau ATOM dan mengembalikan objek JSON yang bersatu dan mudah diuraikan. Tentu saja, kita bisa mengelola objek JSON ini menggunakan JavaScript.
Antarmuka ini mendukung dua jenis queri: Temukan Umpan dan Umpan akan di Muat. Pencarian pertama untuk feed, berdasarkan kata kunci yang diberikan dilewatkan sebagai argumen, sedangkan pencarian kedua untuk feed berdasarkan URL feed yang disediakan. Dalam aplikasi kami, kami hanya akan menggunakan fitur Load Feed.
Setiap permintaan ke Google API ini harus mengirim setidaknya dua parameter: v
dan q
, mereka punya nama yang sangat samar! Parameter pertama, v
, menentukan nomor versi protokol. Pada saat penulisan ini, satu-satunya nilai yang valid adalah '1.0'. Pada parameter kedua, q
, kita meneruskan URL untuk mengurai. Selain ini, aplikasi kami juga akan menggunakan parameter num
Dokumentasi menentukan jumlah entri untuk memuat dari umpan yang ditentukan oleh q. Nilai -1 menunjukkan jumlah entri maksimum yang didukung, saat ini 100.
Secara default, load feed menghasilkan empat hasil. Jadi, penting untuk menerapkan fitur kami memuat 10 entri secara default dan kemudian naik dengan 10 lainnya setiap kali pengguna diminta untuk menampilkan lebih banyak
Sekarang Anda menyadari bagaimana kami akan meminta layanan Google, penting untuk mengklarifikasi hasil yang akan dihasilkannya. Jika URL yang kami berikan benar, kami akan menemukan entri Umpan di dalam properti responseData.feed.entries
. Setiap entri memiliki banyak informasi, tetapi kami akan menggunakan hanya beberapa dari mereka. Secara khusus, kami akan mencetak properti berikut:
-
title
: Judul entri -
link
: URL untuk versi HTML dari entri. -
author
: Penulis entri. -
contentSnippet
: Potongan kurang dari 120 karakter dari atribut konten. Cuplikan tidak mengandung tag HTML apa pun.
Detail yang di berikan di atas cukup untuk tujuan aplikasi kami, tetapi jika Anda ingin mempelajari lebih lanjut, lihat Dokumentasi Google Feed.
Membangun Kelas Feed
Bagian ini akan menjelaskan kelas Feed
dan metodenya, semua termasuk dalam file feed.js
. Seperti yang kami tunjukkan di bagian sebelumnya, kami hanya akan menyimpan dua bidang untuk setiap umpan: judul dan URL. Jadi, kelas ini menerima dua titik data ini sebagai parameter Di dalamnya, kami membuat dua properti pribadi: _db
dan _tableName
. Ingat bahwa JavaScript sebenarnya tidak memiliki pengubah visibilitas properti dan metode, jadi kita sebenarnya meniru data pribadi.
Yang pertama adalah shortcut untuk properti localStorage
dari objek window
. Ini digunakan untuk mengakses metode yang diekspos oleh Plugin Penyimpanan, di mana aplikasi kami berbasis, dan yang akan kami gunakan untuk menyimpan Feed. Yang kedua adalah string yang berisi nama kunci tempat kami akan menyimpan data. Bahkan, mengingat spesifikasi Storage, menyimpan data menggunakan format nilai-kunci. Oleh karena itu, untuk menyimpan array feed kami, kami perlu JSON-ify it. Itulah tepatnya yang akan dilakukan oleh metode save ()
. Dengan cara yang sama, untuk mengambil data kita harus mem-parse string JSON untuk mengubahnya menjadi objek. Tugas ini dicapai dengan metode load ()
. Metode-metode tersebut adalah satu-satunya yang perlu berada di dalam definisi kelas karena mereka menggunakan properti pribadi.
Bagian relatif dari file feed.js
tercantum di bawah ini:
1 |
|
2 |
function Feed(name, url) { |
3 |
var _db = window.localStorage; |
4 |
var _tableName = 'feed'; |
5 |
|
6 |
this.name = name; |
7 |
this.url = url; |
8 |
|
9 |
this.save = function (feeds) { |
10 |
_db.setItem(_tableName, JSON.stringify(feeds)); |
11 |
};
|
12 |
|
13 |
this.load = function () { |
14 |
return JSON.parse(_db.getItem(_tableName)); |
15 |
};
|
16 |
}
|
Sekitar dua metode sederhana ini kami akan membuat yang banyak umum lainnya. Secara khusus, kami akan membuat beberapa metode instance seperti add ()
, untuk menambahkan Feed baru, delete ()
, untuk menghapus feed, dan compareTo ()
, untuk membandingkan instance Feed dengan Feed yang lain. Selain ini, kami juga akan mengembangkan beberapa metode statis seperti getFeeds()
untuk mengambil semua umpan dari penyimpanan, getFeed()
untuk mengambil hanya satu, dan compare()
untuk membandingkan dua objek.
Metode perbandingan layak dibahas sedikit untuk memahami bagaimana kami akan membandingkannya. Kami akan melewatkan deskripsi compareTo()
karena tidak melakukan apa pun kecuali memanggil pasangan statisnya, compare()
, yang benar-benar melakukan pekerjaan. Di dalamnya, pertama-tama kita akan menguji apakah salah satu dari nilai yang diberikan adalah null-like. Jika tidak ada yang seperti nol, kami akan membandingkan secara leksikografis nama mereka dan, jika mereka sama, bandingkan URL mereka. Namun, seperti yang akan Anda temukan nanti, kami akan memaksa pengguna untuk tidak pernah memiliki dua feed dengan nama atau URL yang sama.
Metode compare()
penting karena mendefinisikan cara kita membandingkan dua feed, dan ini sangat penting untuk menetapkan bagaimana Feed akan diurutkan dalam halaman list-feeds.html
. Bahkan, kami akan menggunakan metode array sort()
yang menerima parameter opsional fungsi, yang menentukan urutan urutan array berdasarkan nilai kembalinya.
Kode yang mengimplementasikan apa yang saya jelaskan adalah sebagai berikut:
1 |
|
2 |
Feed.prototype.compareTo = function (other) { |
3 |
return Feed.compare(this, other); |
4 |
};
|
5 |
|
6 |
Feed.compare = function (feed, other) { |
7 |
if (other == null) { |
8 |
return 1; |
9 |
}
|
10 |
if (feed == null) { |
11 |
return -1; |
12 |
}
|
13 |
var test = feed.name.localeCompare(other.name); |
14 |
return (test === 0) ? feed.url.localeCompare(other.url) : test; |
15 |
};
|
Selain metode yang terlihat sejauh ini, kita akan membuat dua metode pencarian yang akan kita gunakan untuk menemukan dan menghapus Feed yang diberikan: searchByName()
dan searchByUrl()
. Metode terakhir yang ingin kami soroti adalah getIndex()
, dan itu adalah yang digunakan untuk mengambil indeks file tertentu.
Sekarang setelah kami menemukan semua detail kelas ini, kami dapat mencantumkan seluruh kode sumber file:
1 |
|
2 |
function Feed(name, url) { |
3 |
var _db = window.localStorage; |
4 |
var _tableName = 'feed'; |
5 |
|
6 |
this.name = name; |
7 |
this.url = url; |
8 |
|
9 |
this.save = function (feeds) { |
10 |
_db.setItem(_tableName, JSON.stringify(feeds)); |
11 |
};
|
12 |
|
13 |
this.load = function () { |
14 |
return JSON.parse(_db.getItem(_tableName)); |
15 |
};
|
16 |
}
|
17 |
|
18 |
Feed.prototype.add = function () { |
19 |
var index = Feed.getIndex(this); |
20 |
var feeds = Feed.getFeeds(); |
21 |
|
22 |
if (index === false) { |
23 |
feeds.push(this); |
24 |
} else { |
25 |
feeds[index] = this; |
26 |
}
|
27 |
|
28 |
this.save(feeds); |
29 |
};
|
30 |
|
31 |
Feed.prototype.delete = function () { |
32 |
var index = Feed.getIndex(this); |
33 |
var feeds = Feed.getFeeds(); |
34 |
|
35 |
|
36 |
if (index !== false) { |
37 |
feeds.splice(index, 1); |
38 |
this.save(feeds); |
39 |
}
|
40 |
|
41 |
return feeds; |
42 |
};
|
43 |
|
44 |
Feed.prototype.compareTo = function (other) { |
45 |
return Feed.compare(this, other); |
46 |
};
|
47 |
|
48 |
Feed.compare = function (feed, other) { |
49 |
if (other == null) { |
50 |
return 1; |
51 |
}
|
52 |
if (feed == null) { |
53 |
return -1; |
54 |
}
|
55 |
var test = feed.name.localeCompare(other.name); |
56 |
return (test === 0) ? feed.url.localeCompare(other.url) : test; |
57 |
};
|
58 |
|
59 |
Feed.getFeeds = function () { |
60 |
var feeds = new Feed().load(); |
61 |
return (feeds === null) ? [] : feeds; |
62 |
};
|
63 |
|
64 |
Feed.getFeed = function (feed) { |
65 |
var index = Feed.getIndex(feed); |
66 |
if (index === false) { |
67 |
return null; |
68 |
}
|
69 |
var feed = Feed.getFeeds()[index]; |
70 |
return new Feed(feed.name, feed.url); |
71 |
};
|
72 |
|
73 |
Feed.getIndex = function (feed) { |
74 |
var feeds = Feed.getFeeds(); |
75 |
for (var i = 0; i < feeds.length; i++) { |
76 |
if (feed.compareTo(feeds[i]) === 0) { |
77 |
return i; |
78 |
}
|
79 |
}
|
80 |
|
81 |
return false; |
82 |
};
|
83 |
|
84 |
Feed.deleteFeeds = function () { |
85 |
new Feed().save([]); |
86 |
};
|
87 |
|
88 |
Feed.searchByName = function (name) { |
89 |
var feeds = Feed.getFeeds(); |
90 |
for (var i = 0; i < feeds.length; i++) { |
91 |
if (feeds[i].name === name) { |
92 |
return new Feed(feeds[i].name, feeds[i].url); |
93 |
}
|
94 |
}
|
95 |
|
96 |
return false; |
97 |
};
|
98 |
|
99 |
Feed.searchByUrl = function (url) { |
100 |
var feeds = Feed.getFeeds(); |
101 |
for (var i = 0; i < feeds.length; i++) { |
102 |
if (feeds[i].url === url) { |
103 |
return new Feed(feeds[i].name, feeds[i].url); |
104 |
}
|
105 |
}
|
106 |
|
107 |
return false; |
108 |
};
|
Membangun Kelas Aplikasi
Bagian ini membahas kelas kedua dan terakhir dari proyek Application
, yang terdapat di dalam file application.js
. Tujuannya adalah menginisialisasi tata letak halaman, melampirkan acara ke elemen halaman aplikasi, dan menggunakan kelas Feed
untuk menyimpan, memuat, dan mengambil feed.
Kelas ini diatur untuk memiliki titik masuk dalam metode initApplication()
. Ini disebut segera setelah Cordova telah diinisialisasi dan API-nya siap untuk bertindak. Dalam metode ini, kami melampirkan khusus untuk setiap inisialisasi halaman sehingga kami dapat mengelola peristiwa yang dipicu oleh widget mereka. Di dalamnya, kami juga akan memanggil Application.openLinksInApp()
untuk alasan yang dibahas sebelumnya. Selain itu, untuk meningkatkan pengalaman pengguna, kami akan menangkap setiap penekanan tombol fisik (yang memang ada) untuk mengalihkan pengguna ke halaman beranda aplikasi kami.
Fungsi inti dari aplikasi kami adalah initShowFeedPage()
karena menggunakan antarmuka JSON Google Feed. Sebelum menjalankan permintaan ke layanan, kami menghitung jumlah entri yang sudah dimuat (currentEntries
variable) dan menghitung berapa banyak entri layanan harus mengambil (entriesToShow
variabel). Kemudian kita akan menjalankan permintaan AJAX, menggunakan metode jQuery ajax()
, dan pada saat yang sama kita menampilkan widget pemuatan halaman ke pengguna. Ketika callback sukses dijalankan, pertama-tama kita menguji apakah jumlah entri yang dikembalikan sama dengan jumlah yang sudah ditampilkan, dalam hal ini kita menunjukkan pesan 'Tidak ada lagi entri untuk dimuat'. Jika tidak, kami menambahkannya ke daftar dan menyegarkan widget akordion ($list.collapsibleset ('refresh')
). Seiring dengan setiap entri, kami juga melampirkan handler ke tombol yang dibuat jadi jika koneksi dimatikan pesan diminta bukan mengakses halaman.
Akhirnya, metode updateIcons()
akan dibahas di bagian berikutnya dan terakhir dari seri.
Kode yang mengimplementasikan kelas yang didiskusikan tercantum di bawah ini:
1 |
|
2 |
var Application = { |
3 |
initApplication: function () { |
4 |
$(document) |
5 |
.on('pageinit', '#add-feed-page', function () { |
6 |
Application.initAddFeedPage(); |
7 |
})
|
8 |
.on('pageinit', '#list-feeds-page', function () { |
9 |
Application.initListFeedPage(); |
10 |
})
|
11 |
.on('pageinit', '#show-feed-page', function () { |
12 |
var url = this.getAttribute('data-url').replace(/(.*?)url=/g, ''); |
13 |
Application.initShowFeedPage(url); |
14 |
})
|
15 |
.on('pageinit', '#aurelio-page', function () { |
16 |
Application.initAurelioPage(); |
17 |
})
|
18 |
.on('backbutton', function () { |
19 |
$.mobile.changePage('index.html'); |
20 |
});
|
21 |
Application.openLinksInApp(); |
22 |
},
|
23 |
initAddFeedPage: function () { |
24 |
$('#add-feed-form').submit(function (event) { |
25 |
event.preventDefault(); |
26 |
var feedName = $('#feed-name').val().trim(); |
27 |
var feedUrl = $('#feed-url').val().trim(); |
28 |
if (feedName === '') { |
29 |
navigator.notification.alert('Name field is required and cannot be empty', function () { |
30 |
}, 'Error'); |
31 |
return false; |
32 |
}
|
33 |
if (feedUrl === '') { |
34 |
navigator.notification.alert('URL field is required and cannot be empty', function () { |
35 |
}, 'Error'); |
36 |
return false; |
37 |
}
|
38 |
|
39 |
if (Feed.searchByName(feedName) === false && Feed.searchByUrl(feedUrl) === false) { |
40 |
var feed = new Feed(feedName, feedUrl); |
41 |
feed.add(); |
42 |
navigator.notification.alert('Feed saved correctly', function () { |
43 |
$.mobile.changePage('index.html'); |
44 |
}, 'Success'); |
45 |
} else { |
46 |
navigator.notification.alert('Feed not saved! Either the Name or the Url specified is already in use', function () { |
47 |
}, 'Error'); |
48 |
}
|
49 |
return false; |
50 |
});
|
51 |
},
|
52 |
initListFeedPage: function () { |
53 |
var $feedsList = $('#feeds-list'); |
54 |
var items = Feed.getFeeds(); |
55 |
var htmlItems = ''; |
56 |
|
57 |
$feedsList.empty(); |
58 |
items = items.sort(Feed.compare); |
59 |
for (var i = 0; i < items.length; i++) { |
60 |
htmlItems += '<li><a href="show-feed.html?url=' + items[i].url + '">' + items[i].name + '</a></li>'; |
61 |
}
|
62 |
$feedsList.append(htmlItems).listview('refresh'); |
63 |
},
|
64 |
initShowFeedPage: function (url) { |
65 |
var step = 10; |
66 |
var loadFeed = function () { |
67 |
var currentEntries = $('#feed-entries').find('div[data-role=collapsible]').length; |
68 |
var entriesToShow = currentEntries + step; |
69 |
$.ajax({ |
70 |
url: 'https://ajax.googleapis.com/ajax/services/feed/load?v=1.0&num=' + entriesToShow + '&q=' + encodeURI(url), |
71 |
dataType: 'json', |
72 |
beforeSend: function () { |
73 |
$.mobile.loading('show', { |
74 |
text: 'Please wait while retrieving data...', |
75 |
textVisible: true |
76 |
});
|
77 |
},
|
78 |
success: function (data) { |
79 |
var $list = $('#feed-entries'); |
80 |
if (data.responseData === null) { |
81 |
navigator.notification.alert('Unable to retrieve the Feed. Invalid URL', function () { |
82 |
}, 'Error'); |
83 |
return; |
84 |
}
|
85 |
var items = data.responseData.feed.entries; |
86 |
|
87 |
|
88 |
var $post; |
89 |
if (currentEntries === items.length) { |
90 |
navigator.notification.alert('No more entries to load', function () { |
91 |
}, 'Info'); |
92 |
return; |
93 |
}
|
94 |
for (var i = currentEntries; i < items.length; i++) { |
95 |
$post = $('<div data-role="collapsible" data-expanded-icon="arrow-d" data-collapsed-icon="arrow-r" data-iconpos="right">'); |
96 |
$post
|
97 |
.append($('<h2>').text(items[i].title)) |
98 |
.append($('<h3>').html('<a href="' + items[i].link + '" target="_blank">' + items[i].title + '</a>')) // Add title |
99 |
.append($('<p>').html(items[i].contentSnippet)) // Add description |
100 |
.append($('<p>').text('Author: ' + items[i].author)) |
101 |
.append( |
102 |
$('<a href="' + items[i].link + '" target="_blank" data-role="button">') |
103 |
.text('Go to the Article') |
104 |
.button() |
105 |
.click(function (event) { |
106 |
if (Application.checkRequirements() === false) { |
107 |
event.preventDefault(); |
108 |
navigator.notification.alert('The connection is off, please turn it on', function () { |
109 |
}, 'Error'); |
110 |
return false; |
111 |
}
|
112 |
$(this).removeClass('ui-btn-active'); |
113 |
})
|
114 |
);
|
115 |
$list.append($post); |
116 |
}
|
117 |
$list.collapsibleset('refresh'); |
118 |
},
|
119 |
error: function () { |
120 |
navigator.notification.alert('Unable to retrieve the Feed. Try later', function () { |
121 |
}, 'Error'); |
122 |
},
|
123 |
complete: function () { |
124 |
$.mobile.loading('hide'); |
125 |
}
|
126 |
});
|
127 |
};
|
128 |
$('#show-more-entries').click(function () { |
129 |
loadFeed(); |
130 |
$(this).removeClass('ui-btn-active'); |
131 |
});
|
132 |
$('#delete-feed').click(function () { |
133 |
Feed.searchByUrl(url).delete(); |
134 |
navigator.notification.alert('Feed deleted', function () { |
135 |
$.mobile.changePage('list-feeds.html'); |
136 |
}, 'Success'); |
137 |
});
|
138 |
if (Application.checkRequirements() === true) { |
139 |
loadFeed(); |
140 |
} else { |
141 |
navigator.notification.alert('To use this app you must enable your internet connection', function () { |
142 |
}, 'Warning'); |
143 |
}
|
144 |
},
|
145 |
initAurelioPage: function () { |
146 |
$('a[target=_blank]').click(function () { |
147 |
$(this).closest('li').removeClass('ui-btn-active'); |
148 |
});
|
149 |
},
|
150 |
checkRequirements: function () { |
151 |
if (navigator.connection.type === Connection.NONE) { |
152 |
return false; |
153 |
}
|
154 |
|
155 |
return true; |
156 |
},
|
157 |
updateIcons: function () { |
158 |
var $buttons = $('a[data-icon], button[data-icon]'); |
159 |
var isMobileWidth = ($(window).width() <= 480); |
160 |
isMobileWidth ? $buttons.attr('data-iconpos', 'notext') : $buttons.removeAttr('data-iconpos'); |
161 |
},
|
162 |
openLinksInApp: function () { |
163 |
$(document).on('click', 'a[target=_blank]', function (event) { |
164 |
event.preventDefault(); |
165 |
window.open($(this).attr('href'), '_blank'); |
166 |
});
|
167 |
}
|
168 |
};
|
Kesimpulan
Pada seri ketiga dan terakhir dari seri ini, kita akan melihat bagaimana membangun dan menguji installer menggunakan CLI dan Adobe PhoneGap Build.