דיון אלמאר ופמלה פוקס, Google
יוני 2007
הערת העורך: Google Gears API כבר לא זמין.
- מבוא
- הסבר על האפליקציה
- שימוש בפידים של Google Base data API
- הוספת Google Gears לאפליקציה
- ניפוי באגים באפליקציה אופליין
- סיכום
מבוא
בסרטון הזה אנחנו משלבים בין Google Base לבין Google Gears כדי להדגים איך ליצור אפליקציה שאפשר להשתמש בה במצב אופליין. אחרי שתקראו את המאמר הזה, תכירו טוב יותר את Google Base API ותבינו איך להשתמש ב-Google Gears כדי לאחסן העדפות ונתונים של משתמשים ולגשת אליהם.
הסבר על האפליקציה
כדי להבין את האפליקציה הזו, כדאי קודם להכיר את Google Base, שהוא בעצם מסד נתונים גדול של פריטים שכולל קטגוריות שונות כמו מוצרים, ביקורות, מתכונים, אירועים ועוד.
כל פריט כולל הערה עם שם, תיאור, קישור למקור המקורי של הנתונים (אם קיים) ומאפיינים נוספים שמשתנים בהתאם לסוג הקטגוריה. Google Base מנצל את העובדה שלפריטים באותה קטגוריה יש קבוצה משותפת של מאפיינים – למשל, לכל המתכונים יש רכיבים. פריטים מ-Google Base אפילו יופיעו מדי פעם בתוצאות החיפוש מחיפוש האינטרנט של Google או מחיפוש המוצרים של Google.
באפליקציית ההדגמה שלנו, Base with Gears, אפשר לאחסן ולהציג חיפושים נפוצים שאולי תבצעו ב-Google Base, כמו חיפוש מתכונים עם המילה 'שוקולד' (יאמי) או מודעות אישיות עם המילים 'טיולים על החוף' (רומנטי!). אפשר לחשוב על זה כעל 'קורא Google Base' שמאפשר להירשם לחיפושים ולראות את התוצאות המעודכנות כשחוזרים לאפליקציה, או כשהאפליקציה מחפשת פידים מעודכנים כל 15 דקות.
מפתחים שרוצים להרחיב את האפליקציה יכולים להוסיף עוד תכונות, כמו התראה חזותית למשתמש כשתוצאות החיפוש כוללות תוצאות חדשות, אפשרות למשתמש להוסיף פריטים מועדפים לסימנייה (כוכב) (אופליין + אונליין), ואפשרות למשתמש לבצע חיפושים של מאפיינים ספציפיים לקטגוריה כמו Google Base.
שימוש בפידים של Google Base Data API
אפשר לשלוח שאילתות ל-Google Base באופן פרוגרמטי באמצעות Google Base data API, שתואם למסגרת Google Data API. פרוטוקול Google Data API הוא פרוטוקול פשוט לקריאה וכתיבה באינטרנט, והוא נמצא בשימוש במוצרים רבים של Google: Picasa, Spreadsheets, Blogger, Calendar, Notebook ועוד.
הפורמט של Google Data API מבוסס על XML ועל פרוטוקול Atom Publishing, ולכן רוב האינטראקציות של קריאה/כתיבה הן ב-XML.
דוגמה לפיד של Google Base שמבוסס על Google Data API:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera
סוג הפיד snippets
מאפשר לנו לגשת לפיד הפריטים שזמין לציבור. המסנן -/products
מאפשר לנו להגביל את הפיד לקטגוריית המוצרים. הפרמטר bq=
מאפשר לנו לצמצם עוד יותר את הפיד, כך שיוצגו בו רק תוצאות שמכילות את מילת המפתח 'מצלמה דיגיטלית'. אם תציגו את הפיד הזה בדפדפן, תראו XML שמכיל צמתי <entry>
עם תוצאות תואמות. כל רשומה מכילה את האלמנטים האופייניים של מחבר, שם, תוכן וקישור, אבל היא כוללת גם מאפיינים נוספים שספציפיים לקטגוריה (כמו 'מחיר' לפריטים בקטגוריית המוצרים).
בגלל ההגבלה על XMLHttpRequest בדפדפן, אנחנו לא יכולים לקרוא ישירות פיד XML מ-www.google.com בקוד JavaScript שלנו. אפשר להגדיר שרת proxy בצד השרת כדי לקרוא את ה-XML ולהחזיר אותו במיקום באותו דומיין של האפליקציה שלנו, אבל אנחנו רוצים להימנע לחלוטין מתכנות בצד השרת. למזלנו, יש חלופה.
בדומה לממשקי Google Data API אחרים, ל-Google Base data API יש אפשרות פלט בפורמט JSON, בנוסף לפורמט XML רגיל. הפלט של הפיד שראינו קודם בפורמט JSON יהיה בכתובת ה-URL הזו:
http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera&alt=json
JSON הוא פורמט קל להחלפת נתונים שמאפשר היררכיה של קינון וגם סוגים שונים של נתונים. אבל מה שיותר חשוב הוא שפלט JSON הוא קוד JavaScript מקורי, ולכן אפשר לטעון אותו לדף אינטרנט רק על ידי הפניה אליו בתג סקריפט, וכך לעקוף את ההגבלה על גישה בין דומיינים.
ממשקי Google Data API מאפשרים גם לציין פלט מסוג json-in-script עם פונקציית קריאה חוזרת שתופעל אחרי שה-JSON ייטען. כך קל עוד יותר לעבוד עם פלט ה-JSON, כי אפשר להוסיף באופן דינמי תגי סקריפט לדף ולציין פונקציות שונות של קריאה חוזרת לכל אחד מהם.
לכן, כדי לטעון באופן דינמי פיד JSON של Base API לדף, אפשר להשתמש בפונקציה הבאה שיוצרת תג script עם כתובת הפיד (בצירוף ערכי alt
callback
) ומצרפת אותו לדף.
function getJSON() { var script = document.createElement('script'); var url = "http://www.google.com/base/feeds/snippets/-/products?bq=digital+camera"; script.setAttribute('src', url + "&alt=json-in-script&callback=listResults"); script.setAttribute('type', 'text/JavaScript'); document.documentElement.firstChild.appendChild(script); }
לכן, פונקציית הקריאה החוזרת listResults
יכולה עכשיו לבצע איטרציה ב-JSON שהועבר כפרמטר היחיד, ולהציג מידע על כל רשומה שנמצאה ברשימה עם תבליטים.
function listTasks(root) { var feed = root.feed; var html = ['']; html.push('<ul>'); for (var i = 0; i < feed.entry.length; ++i) { var entry = feed.entry[i]; var title = entry.title.$t; var content = entry.content.$t; html.push('<li>', title, ' (', content, ')</li>'); } html.push('</ul>'); document.getElementById("agenda").innerHTML = html.join(""); }
הוספת Google Gears
עכשיו יש לנו אפליקציה שיכולה לתקשר עם Google Base באמצעות Google Data API, ואנחנו רוצים לאפשר לאפליקציה הזו לפעול במצב אופליין. כאן נכנס לתמונה Google Gears.
יש אפשרויות שונות לארכיטקטורה כשכותבים אפליקציה שיכולה לפעול במצב אופליין. תשאלו את עצמכם שאלות לגבי אופן הפעולה של האפליקציה באינטרנט לעומת אופן הפעולה שלה במצב אופליין (למשל: האם היא פועלת בדיוק באותו אופן? האם חלק מהתכונות מושבתות, כמו חיפוש? איך תטפל בסנכרון?)
במקרה שלנו, רצינו לוודא שמשתמשים בדפדפנים ללא Gears עדיין יוכלו להשתמש באפליקציה, וגם לאפשר למשתמשים שיש להם את התוסף ליהנות משימוש אופליין ומממשק משתמש עם תגובה מהירה יותר.
הארכיטקטורה שלנו נראית כך:
- יש לנו אובייקט JavaScript שאחראי לאחסון של שאילתות החיפוש שלכם ולהחזרת תוצאות מהשאילתות האלה.
- אם התקנתם את Google Gears, תקבלו גרסת Gears שמאחסנת את כל הנתונים במסד הנתונים המקומי.
- אם Google Gears לא מותקן, תקבלו גרסה ששומרת את השאילתות בקובץ Cookie ולא שומרת את התוצאות המלאות בכלל (ולכן התגובה קצת יותר איטית), כי התוצאות גדולות מדי כדי לשמור אותן בקובץ Cookie.
if (online) {}
בכל מקום. במקום זאת, האפליקציה מבצעת בדיקה אחת של Gears, ואז נעשה שימוש במתאם הנכון.
שימוש במסד נתונים מקומי של Gears
אחד מהרכיבים של Gears הוא מסד נתונים מקומי של SQLite שמוטמע ומוכן לשימוש. קיים API פשוט למסד נתונים, שייראה לכם מוכר אם השתמשתם בעבר בממשקי API למסדי נתונים בצד השרת כמו MySQL או Oracle.
השלבים לשימוש במסד נתונים מקומי הם פשוטים למדי:
- אתחול האובייקטים של Google Gears
- אחזור אובייקט של מפעל מסד נתונים ופתיחת מסד נתונים
- התחלת הרצת בקשות SQL
נעבור עליהם במהירות.
הפעלת האובייקטים של Google Gears
האפליקציה שלכם צריכה לקרוא את התוכן של /gears/samples/gears_init.js
ישירות, או על ידי הדבקת הקוד בקובץ JavaScript משלכם. אחרי שמפעילים את <script src="..../gears_init.js" type="text/JavaScript"></script>
, מקבלים גישה למרחב השמות google.gears.
קבלת אובייקט של Database Factory ופתיחת מסד נתונים
var db = google.gears.factory.create('beta.database', '1.0'); db.open('testdb');
קריאה אחת תיתן לכם אובייקט של מסד נתונים שיאפשר לכם לפתוח סכימת מסד נתונים. כשפותחים מסדי נתונים, הם מוגדרים באמצעות אותם כללים של מדיניות מקור זהה, כך שהמסד testdb שלכם לא יתנגש עם המסד testdb שלי.
התחלת ביצוע של בקשות SQL
עכשיו אפשר לשלוח בקשות SQL למסד הנתונים. כששולחים בקשות מסוג select, מקבלים בחזרה קבוצת תוצאות שאפשר לעבור עליהן כדי למצוא את הנתונים הרצויים:
var rs = db.execute('select * from foo where name = ?', [ name ]);
אפשר להשתמש בשיטות הבאות כדי לבצע פעולות על קבוצת התוצאות שהוחזרה:
boolean | isValidRow() |
void | next() |
void | close() |
int | fieldCount() |
string | fieldName(int fieldIndex) |
variant | field(int fieldIndex) |
variant | fieldByName(string fieldname) |
מידע נוסף מופיע במסמכי העזרה בנושא Database Module API. (הערת העורך: Google Gears API כבר לא זמין).
שימוש ב-GearsDB כדי להצפין את ה-API ברמה הנמוכה
רצינו לאגד כמה מהמשימות הנפוצות במסד הנתונים ולהפוך אותן לנוחות יותר. לדוגמה,
- רצינו דרך נוחה לרשום ביומן את ה-SQL שנוצר כשניסינו לנפות באגים באפליקציה.
- רצינו לטפל בחריגים במקום אחד, במקום להשתמש ב-
try{}catch(){}
בכל מקום. - רצינו לעבוד עם אובייקטים של JavaScript במקום עם קבוצות תוצאות כשקוראים או כותבים נתונים.
כדי לטפל בבעיות האלה באופן כללי, יצרנו את GearsDB, ספרייה בקוד פתוח שעוטפת את אובייקט מסד הנתונים. עכשיו נראה איך להשתמש ב-GearsDB.
הגדרה ראשונית
בקוד window.onload, אנחנו צריכים לוודא שטבלאות מסד הנתונים שאנחנו מסתמכים עליהן נמצאות במקום. אם Gears מותקן אצל המשתמש כשמריצים את הקוד הבא, נוצר אובייקט GearsBaseContent
:
content = hasGears() ? new GearsBaseContent() : new CookieBaseContent();
לאחר מכן, פותחים את מסד הנתונים ויוצרים טבלאות אם הן עדיין לא קיימות:
db = new GearsDB('gears-base'); // db is defined as a global for reuse later! if (db) { db.run('create table if not exists BaseQueries' + ' (Phrase varchar(255), Itemtype varchar(100))'); db.run('create table if not exists BaseFeeds' + ' (id varchar(255), JSON text)'); }
בשלב הזה, אנחנו בטוחים שיש לנו טבלה לאחסון השאילתות והפידים. הקוד new GearsDB(name)
יכלול את הפתיחה של מסד נתונים עם השם שצוין. השיטה run
עוטפת את השיטה ברמה הנמוכה יותר execute
, אבל היא גם מטפלת בפלט של ניפוי הבאגים במסוף ובחריגות.
הוספת מונח חיפוש
כשמפעילים את האפליקציה בפעם הראשונה, לא מופיעים בה חיפושים. אם תנסו לחפש Nintendo Wii במוצרים, מונח החיפוש הזה יישמר בטבלה BaseQueries.
גרסת ה-Gears של השיטה addQuery
עושה זאת על ידי קבלת הקלט ושמירתו באמצעות insertRow
:
var searchterm = { Phrase: phrase, Itemtype: itemtype }; db.insertRow('BaseQueries', searchterm);
insertRow
מקבל אובייקט JavaScript (searchterm
) ומטפל בהוספה שלו לטבלה. הוא גם מאפשר לכם להגדיר אילוצים (לדוגמה, אילוץ ייחודיות שמונע הוספה של יותר ממופע אחד של "בוב"). עם זאת, ברוב המקרים תטפלו במגבלות האלה במסד הנתונים עצמו.
קבלת כל מונחי החיפוש
כדי לאכלס את רשימת החיפושים הקודמים, אנחנו משתמשים ב-wrapper נחמד לבחירה בשם selectAll
:
GearsBaseContent.prototype.getQueries = function() { return this.db.selectAll('select * from BaseQueries'); }
הפונקציה תחזיר מערך של אובייקטים של JavaScript שתואמים לשורות במסד הנתונים (למשל [ { Phrase: 'Nintendo Wii', Itemtype: 'product' }, { ... }, ...]
).
במקרה כזה, אפשר להחזיר את הרשימה המלאה. אבל אם יש לכם הרבה נתונים, כדאי להשתמש בפונקציית קריאה חוזרת בפונקציית הבחירה כדי שתוכלו לבצע פעולות על כל שורה שמוחזרת כשהיא מגיעה:
db.selectAll('select * from BaseQueries where Itemtype = ?', ['product'], function(row) { ... do something with this row ... });
אלה עוד כמה שיטות שימושיות לבחירה ב-GearsDB:
selectOne(sql, args) | החזרת אובייקט JavaScript ראשון או יחיד שתואם |
selectRow(table, where, args, select) | בדרך כלל משתמשים בה במקרים פשוטים כדי להתעלם מ-SQL |
selectRows(table, where, args, callback, select) | פונקציה זהה ל-selectRow, אבל לתוצאות מרובות. |
טעינת פיד
כשמקבלים את פיד התוצאות מ-Google Base, צריך לשמור אותו במסד הנתונים:
content.setFeed({ id: id, JSON: json.toJSONString() }); ... which calls ... GearsBaseContent.prototype.setFeed = function(feed) { this.db.forceRow('BaseFeeds', feed); }
קודם לוקחים את פיד ה-JSON ומחזירים אותו כמחרוזת באמצעות ה-method toJSONString
. לאחר מכן יוצרים את האובייקט feed
ומעבירים אותו לשיטה forceRow
. forceRow
יוסיף רשומה אם היא לא קיימת, או יעדכן רשומה קיימת.
הצגת תוצאות חיפוש
התוצאות של חיפוש מסוים מוצגות באפליקציה שלנו בחלונית השמאלית של הדף. כך אנחנו מאחזרים את הפיד שמשויך למונח החיפוש:
GearsBaseContent.prototype.getFeed = function(url) { var row = this.db.selectRow('BaseFeeds', 'id = ?', [ url ]); return row.JSON; }
עכשיו, כשיש לנו את ה-JSON של שורה, אנחנו יכולים eval()
אותו כדי לקבל את האובייקטים בחזרה:
eval("var json = " + jsonString + ";");
אנחנו מוכנים להתחיל להוסיף תוכן מקובץ JSON לדף באמצעות innerHTML.
שימוש בחנות משאבים לגישה אופליין
מאחר שאנחנו מקבלים תוכן ממסד נתונים מקומי, האפליקציה הזו אמורה לפעול גם במצב אופליין, נכון?
ובכן, לא. הבעיה היא שכדי להפעיל את האפליקציה הזו, צריך לטעון את משאבי האינטרנט שלה, כמו JavaScript, CSS, HTML ותמונות. במצב הנוכחי, אם המשתמש יבצע את הפעולות הבאות, יכול להיות שהאפליקציה עדיין תפעל: התחברות לאינטרנט, ביצוע חיפושים, לא לסגור את הדפדפן, מעבר למצב אופליין. יכול להיות שהפעולה הזו תעבוד כי הפריטים עדיין יישמרו במטמון של הדפדפן. אבל מה קורה אם זה לא המצב? אנחנו רוצים שהמשתמשים שלנו יוכלו לגשת לאפליקציה מההתחלה, אחרי הפעלה מחדש וכו'.
לשם כך, אנחנו משתמשים ברכיב LocalServer ומקליטים את המשאבים שלנו. כשמבצעים לכידה של משאב (כמו HTML ו-JavaScript שנדרשים להפעלת האפליקציה), Gears שומרת את הפריטים האלה וגם מיירטת בקשות מהדפדפן להחזרתם. השרת המקומי יפעל כפקח תנועה ויחזיר את התוכן השמור מהחנות.
אנחנו גם משתמשים ברכיב ResourceStore, שבו צריך לציין באופן ידני למערכת אילו קבצים רוצים ללכוד. במקרים רבים, כדאי ליצור גרסאות של האפליקציה ולאפשר שדרוגים באופן טרנזקציונלי. קבוצת משאבים מגדירה גרסה, וכשמפרסמים קבוצת משאבים חדשה, רוצים שהמשתמשים יוכלו לשדרג את הקבצים בצורה חלקה. אם זה המודל שלכם, תשתמשו ב-ManagedResourceStore API.
כדי ללכוד את המשאבים שלנו, האובייקט GearsBaseContent יבצע את הפעולות הבאות:
- הגדרת מערך של קבצים שצריך לצלם
- יצירת LocalServer
- פותחים ResourceStore קיים או יוצרים חדש.
- הפניה ללכידת הדפים בחנות
// Step 1 this.storeName = 'gears-base'; this.pageFiles = [ location.pathname, 'gears_base.js', '../scripts/gears_db.js', '../scripts/firebug/firebug.js', '../scripts/firebug/firebug.html', '../scripts/firebug/firebug.css', '../scripts/json_util.js', 'style.css', 'capture.gif' ]; // Step 2 try { this.localServer = google.gears.factory.create('beta.localserver', '1.0'); } catch (e) { alert('Could not create local server: ' + e.message); return; } // Step 3 this.store = this.localServer.openStore(this.storeName) || this.localServer.createStore(this.storeName); // Step 4 this.capturePageFiles(); ... which calls ... GearsBaseContent.prototype.capturePageFiles = function() { this.store.capture(this.pageFiles, function(url, success, captureId) { console.log(url + ' capture ' + (success ? 'succeeded' : 'failed')); }); }
חשוב לציין שאפשר ללכוד משאבים רק בדומיין שלכם. נתקלנו במגבלה הזו כשניסינו לגשת לקובץ JavaScript של GearsDB ישירות מהקובץ המקורי 'gears_db.js' ב-SVN trunk שלו. הפתרון פשוט: צריך להוריד את כל המשאבים החיצוניים ולמקם אותם בדומיין שלכם. שימו לב שהפניות 302 או 301 לא יפעלו, כי LocalServer מקבל רק קודי שרת 200 (הצלחה) או 304 (לא שונה).
יש לכך השלכות. אם תמקמו את התמונות בכתובת images.yourdomain.com, לא תוכלו לצלם אותן. אי אפשר לראות את www1 ואת www2. אפשר להגדיר שרתי proxy בצד השרת, אבל זה יבטל את המטרה של פיצול האפליקציה לכמה דומיינים.
ניפוי באגים באפליקציה במצב אופליין
ניפוי באגים באפליקציה אופליין הוא קצת יותר מורכב. יש עכשיו יותר תרחישים לבדיקה:
- אני מחובר/ת לאינטרנט והאפליקציה פועלת במלואה במטמון
- אני מחובר לאינטרנט אבל לא ניגשתי לאפליקציה, ואין כלום במטמון
- אני במצב אופליין אבל יש לי גישה לאפליקציה
- אני במצב אופליין ומעולם לא הייתה לי גישה לאפליקציה (לא מצב טוב!)
כדי להקל על החיים, השתמשנו בדפוס הבא:
- אנחנו משביתים את המטמון ב-Firefox (או בדפדפן שבחרתם) כשאנחנו צריכים לוודא שהדפדפן לא פשוט שולף משהו מהמטמון
- אנחנו מנפים באגים באמצעות Firebug (ו-Firebug Lite לבדיקה בדפדפנים אחרים); אנחנו משתמשים ב-
console.log()
בכל מקום, ומזהים את המסוף למקרה הצורך - אנחנו מוסיפים קוד JavaScript מסייע ל:
- לאפשר לנו לנקות את מסד הנתונים ולהתחיל מחדש
- להסיר את הקבצים שצולמו, כך שכשתטען מחדש, המערכת תפנה לאינטרנט כדי לקבל אותם שוב (שימושי כשמבצעים איטרציה בפיתוח ;)
ווידג'ט הניפוי באגים מופיע בצד ימין של הדף רק אם Gears מותקן. יש בו הערות לניקוי הקוד:
GearsBaseContent.prototype.clearServer = function() { if (this.localServer.openStore(this.storeName)) { this.localServer.removeStore(this.storeName); this.store = null; } } GearsBaseContent.prototype.clearTables = function() { if (this.db) { this.db.run('delete from BaseQueries'); this.db.run('delete from BaseFeeds'); } displayQueries(); }
סיכום
אפשר לראות שהעבודה עם Google Gears היא די פשוטה. השתמשנו ב-GearsDB כדי לפשט עוד יותר את רכיב מסד הנתונים, והשתמשנו ב-ResourceStore הידני, שעבד מצוין בדוגמה שלנו.
האזור שבו אתם מבלים הכי הרבה זמן מגדיר את האסטרטגיה של מתי להוריד נתונים באינטרנט ואיך לאחסן אותם אופליין. חשוב להקדיש זמן להגדרת סכימת מסד הנתונים. אם תצטרכו לשנות את הסכימה בעתיד, תצטרכו לנהל את השינוי הזה, כי למשתמשים הנוכחיים כבר תהיה גרסה של מסד הנתונים. המשמעות היא שתצטרכו לשלוח קוד סקריפט עם כל שדרוג של מסד נתונים. כמובן שכדאי לצמצם את התופעה הזו, ואפשר לנסות את GearShift, ספרייה קטנה שיכולה לעזור לכם לנהל את השינויים.
יכולנו גם להשתמש ב-ManagedResourceStore כדי לעקוב אחרי הקבצים שלנו, עם ההשלכות הבאות:
- אנחנו רוצים להיות אזרחים טובים ולשמור על גרסאות של הקבצים שלנו כדי לאפשר שדרוגים נקיים בעתיד
- יש תכונה ב-ManagedResourceStore שמאפשרת ליצור כינוי לכתובת URL כדי להפנות אותה לחלק אחר של תוכן. אפשרות ארכיטקטורה תקינה היא ש-gears_base.js יהיה גרסה שאינה Gears, וליצור כינוי כך שמערכת Gears עצמה תוריד את gears_base_withgears.js, שתכלול את כל התמיכה באופליין.
אנחנו מקווים שהיה לכם קל ומהנה להשתמש באפליקציות של Gearing up. אם יש לכם שאלות או אפליקציה שאתם רוצים לשתף, אתם מוזמנים להצטרף לפורום של Google Gears.