הקדמה:
כעת אני קורא את השיעור הקודם
(למי שמעוניין יש בו את הקוד המלא של איפה שאנחנו אוחזים, מדובר באפליקצייה של דף בודד ותו לא),
ואני רואה שלא ממש גמרנו, צריך גם להראות את הרשימה.
לשם כך אני משנה את המקטע של if (req.url == '/') שזה מתייחס ל"דף הבית" שלנו, ככה:
if (req.url == '/') {
res.write('Hello To List Page!<br>' + list.join('<br>') + '<br><a href="/add-item">Add Item To List</a>') ;
}
זה משרשר כמה טקסטים, בתוכם יש קריאה לפונקציה join של מערך (הליסט שלנו), כשהמפריד הוא הbr שזה אלמנט קפיצת שורה בHTML. בסוף על הדרך שמתי לינק (אלמנט a בHTML) לדף הadd-item להוספת פריט חדש.
כעת אפשר להריץ ולראות בהתחלה בדף הבית לינק להוספת פריט.
כעוברים לדף add-item וכשמזינים את הטופס ושלחים מקבלים דף ריק, אם נחזור ידנית לדף הבית נראה את הפריט שהתווסף בהצלחה.
שיעור 7 - שוב על POST וגם על REDIRECT.
הסברתי בעבר שבשביל לשלוח את הטופס לשרת אני משתמש בPOST כי זה בדיוק התפקיד שלו.
למה? זה גורם לפעולה בצד השרת, זה בקשת אינטרנט שיש לה השפעה ולא רק קבלת מידע.
ומה הרווחנו שאנו פועלים לפי הכלל הזה? למשל את התנהגות הדפדפן שבעת ריענון של הדף אחרי השליחה זה יזהיר אותנו מפני פעולה כפולה. בGET זה יעשה את הפעולה שוב בלי אומר ודברים.
בצד שרת אנחנו יכולים לזהות האם הבקשה היא POST וGET בדיוק כפי שאנו יכולים לבדוק מה הURL. אם כן אנו יכולים להעמיס על URL שכבר בשימוש לGET, כמו כאן שכניסה לadd-item מביאה את הטופס, פעולה נוספת במקרה של POST.
השאלה היא מה קורה אחרי הPOST. אם נחזיר חזרה את הטופס בדיוק כפי שהיה, אז ללקוח יהיה תחושה שלא קרה כלום.
מה שמקובל (כל עוד אנו לא בAJAX - לא להיבהל, יילמד בהמשך) זה או להחזיר את הלקוח לרשימה ואז הוא מבין (וגם רואה) שבקשתו בוצעה.
בפשוטת יכולנו לעשות את זה פשוט - להחזיר בres.write את הרשימה, משהו כזה:
else if (req.url == '/add-item') {
if (req.method == "POST") {
parsePostData(req, function (result) {
list.push(result["the-name"]);
//בשלב זה אנחנו יכולים לכתוב תשובה ללקוח בדיוק כמו שעשינו בדף הבית
res.write('Hello To List Page!<br>' + list.join('<br>') + '<br><a href="/add-item">Add Item To List</a>') ;
});
}
זה לא טוב. למה? כי למשתמש כעת יהיה בשורת הכתובת כתוב add-item והדף בעצם כעת מכיל רשימה. זה גם מאוד "לא נכון" וגם מבלבל, הוא יכול להעתיק את הלינק הזה או לשמור אותו במועדפים מאוחר יותר בלי להתייחס למה כתוב בו, ולהניח שזה לינק לרשימה, שהרי זה מה שהוא רואה.
אז אנחנו צריכים "לשנות את הכתובת בדפדפן" לדף הבית, ובכן זה בלתי אפשרי. מה שעושים זה גורמים לדפדפן לעבור לכתובת של דף הבית כאילו המשתמש לחץ על היפר קישור. עושים את זה ע"י תשובת Redirect.
יש כמה סוגי תשובות REDIRECT, הם מצויינים בקודים 3xx (כלומר 300, 301 וכו'). הסוג שנדרש לנו הוא 303 - See Other. הוא סוג פשוט שאומר, הכל בסדר, כעת תעבור לX.
המבנה של תשובת Redirect בבסיסה רק כותרת אחת ללא כל תוכן:
HTTP/1.1 303 See Other
Location: /
אבל לא נוכל לעשות זאת מייד,
א. כי בקוד שלנו לפני התנאי בכלל כבר שמנו כותרת בשם 'Content-Type', עם HTML, מה שלא מתאים מקרה הזה.
ב. בקוד שלנו שמנו אחרי התנאי res.end שזה אומר לגמור את התשובה ולסגור את החיבור. הבעיה שבמקרה של הpost שלנו שאנו מוסיפים אלמנט לרשימה, הres.end קורה עוד לפני הוספת האלמנט שכן הוספת האלמנט נעשית כcallback למתודה האסינכרונית parsePostData (כזכור, המתודה parsePostData ניגשת לגוף הבקשה וגישה זו בנויה באופן אסינכרוני = "נודיע לך כזה יקרה", כי הטיפול בבקשה מתחיל עוד לפני שהיא סיימה להגיע).
לשם כך נשנה את הקוד בכמה דברים:
function handleAllRequest(req, res) {
if (req.url == '/') {
var page = 'Hello To List Page!<br>' + list.join('<br>') + '<br><a href="/add-item">Add Item To List</a>';
sendHtml(res, page) ;
}
else if (req.url == '/add-item') {
if (req.method == "POST") {
parsePostData(req, function (result) {
list.push(result["the-name"]);
res.writeHead(303, { Location: '/' });
res.end();
});
} else {
sendHtml(res, `<form method="POST">
Enter the item value: <input type="text" name="the-name" >
<br>
<input type="submit" >
</form>`);
}
} else {
sendHtml(res, 'Opss... Not Found!');
}
}
function sendHtml(res, str){
res.setHeader('Content-Type', 'text/html');
res.end(str);
}
ראשית הסרנו את הres.setHeader והres.end שהיו לפני ואחרי התנאי, כי באחד המקרים של התנאי איננו רוצים את שניהם.
שנית בנינו פונקציה קטנה לשלוח html בתשובה, כדי לחסוך לכתוב שוב ושוב שלושה שורות: res.setHeader, ואז rew.write ולבסוף res.end. בעצם דילגנו על res.write לגמרי גם בפונקציה כי הres.end מקבלת כפרמטר תוכן בדיוק בשביל לחסוך מקרים כאלה (הwrite עושי למקרה של כתיבות רבות לאותה תשובה, במקרה של בודדת אפשר להשתמש בקיצור הזה).
שלישית, וזה הסיבה שעשינו את כל השינויים, הוספנו אחרי הוספת אלמנט (שורות 9-11) הפניה חזרה ל"דף הבית" כלומר לרשימה.
תוכלו כעת לראות אפליקציה לתפארת ששומרת רשימה, אבל כל הרצה הרשימה נמחקת שהרי הכל שמור במשתנה list שבריצת האפליקציה עדיין ריק. אם נרצה לשמור בעולם האמיתי נשתמש כנראה במסד נתונים או בקבצים.
יש לציין שההעברה לרשימה אמנם מקובלת אבל היא לא "100%" מבחינת חוויית משתמש, כיון שיש משתמשים שעד שלא יאמרו להם שזה הצליח, הם לא יהיו בטוחים אם המחשב עשה את מה שהם התכוונו.
בשביל אלו אפשר לעשות העברה לדף אחר, בו כתוב "ההוספה הצליחה!", ומתחת לזה לינק "חזרה לרשימה". יש בזה אבל משהו מייגע עבור יתר המשתמשים. לכן יש כאלה שיעשו שהדף הזה יופיע לשלוש שניות - יהיה כתוב "מעביר אותך לרשימה...". זה בעצם REDIRECT שנעשה בצד הלקוח עם קוד עם השהיית זמן.
יש סיטאוציות שהכי הגיוני זה להישאר באותו הדף של הטופס, למשל אם מטבע הדברים אמורים לשלוח טופס שוב ושוב רצוף (בדוגמה שלנו לשים כמה הפריטים ברשימה). במקרה כזה מחזירים את אותו דף של add-item אבל כותבים איזה חיווי בצד לקוח (למטה או בכותרת צפה זמנית) שהנתונים נשלחו בהצלחה.