קורס Node.JS שיעור תרגול הצגת מידע באקספרס


זה נושא דיון מלווה לערך המקורי ב- https://www.tocode.co.il/bundles/nodejs/lessons/26-express-views-lab

Hi Ynon!
,Regarding first Directory Explorer system
,I set /files route, set root folder with some directories and files
then I need to pass path value for nested directory or file.
And the path may be nested,
such as /dir1/dir11 or /dir1/dir11/f1.txt

Option1
One option I found is
files?path=/d1/d2f1.txt/
with query string

Option 2
Yet I would like to API style with param such as
files/:path/
yet path contains / character needs encoding
/files/${encodeURIComponent('dir1/dir11/f1.txt')}
produces
/files/dir1%2Fdir11%2Ff1.txt

and route with param
/files/:path catches it
and using decodeURIComponent decodes back.

Which option is better?
Thanks

I would use option1 - I think it makes more sense in this context

However for option 2 keep in mind you can use this:

כמעת סיימתי את התרגיל הראשון של סייר קבצים - אימון טוב.
יש לי עוד לשפר כמה דברים, אך נראה לי שלרוב סייימתי.
The main point was using fs and middleware,
also simple error handling and simple log.

אודה על הערות

הי,

עבודה יפה! כמה מחשבות והצעות לשיפור:

  1. לא הבנתי את כל המנגנון שעשית עם root-folder - למה צריך להוסיף אותו להתחלה של כל ספריה שאני רוצה להציג?

  2. הקוד של is-valid-directory ו is-valid-file נראה מאוד דומה. אולי אפשר לארגן אותו אחרת כדי לצמצם כפילויות?

  3. בשלב מסוים קיבלתי את השגיאה הזאת, לא הבנתי למה:


Cannot convert undefined or null to object
    at Function.entries (<anonymous>)
    at eval (eval at compile (/home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/node_modules/ejs/lib/ejs.js:633:12), <anonymous>:18:52)
    at returnedFn (/home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/node_modules/ejs/lib/ejs.js:668:17)
    at tryHandleCache (/home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/node_modules/ejs/lib/ejs.js:254:36)
    at View.exports.renderFile [as engine] (/home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/node_modules/ejs/lib/ejs.js:485:10)
    at View.render (/home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/node_modules/express/lib/view.js:135:8)
    at tryRender (/home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/node_modules/express/lib/application.js:640:10)
    at Function.render (/home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/node_modules/express/lib/application.js:592:3)
    at ServerResponse.render (/home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/node_modules/express/lib/response.js:1008:7)
    at /home/ynon/tmp/students/ynonp-nodejs-exercises/26-express/files-explorer/files-ex/routes/directory.js:14:7
  1. לא בטוח שיש טעם להשאיר את ה indexRouter. עדיף להציג את הקבצים בתיקיה הנוכחית מ directoryRouter בכניסה לנתיב הראשי. אותו דבר לגבי usersRouter

  2. עדיף להפעיל את ה Middlewares בתוך ה Router שמשתמש בהן, כלומר בתוך Directory Router לכתוב:

router.use('/', isValidDirectory);
  1. נושא אחד שלא הכנסתי לקורס (ובטח אכניס בגירסאות עתידיות שלו) הוא בדיקות יחידה. בינתיים אם יש לך זמן שווה לנסות את ה Tutorial כאן:
    https://jestjs.io/docs/getting-started
    ולחשוב איך להוסיף בדיקות לכל המנגנון של חישוב עץ התיקיות.

סך הכל עבודה יפה ואני מקווה שנתתי לך כמה רעיונות להמשיך להתקדם עם הפרויקט הזה.

בהצלחה
ינון

תודה על שעברת על התרגיל שלי, על החיזוק החיובי ועל ההערות לשיפור.
עלי לציין שהעורך של האתר מערבב עברית ואנגלית, כך שלא תמיד קל להבין את הכוונת המקורית.
עם זאת כשאיתות על תגובה מגיע למייל - התוכן כן מגיע בצורה מסודרת.

I set
const rootPath = ‘…/root-folder/’; // from where the app is called - project root
it is just folder on level up of project root,
the “root” of exercise file system - to put directories and files there to play with.

is-valid-target middleware came instead is-valid-file and is-valid-directory,
3), 4),
Default index and users routers were deleted.
I added redirect hack inside app.js for ‘/’ route

app.use('/', function(req, res, next) {
  res.redirect('/directory');
});

router.use(’/’, isValidRouter);
is set inside directory and file routers.

Sure, I will take a look at

I saw there is Jasmin tests course in the site
https://www.tocode.co.il/bundles/jasmine
is it still valid?

תודה

Many companies still use Jasmine (or its counterpart Mocha) and both projects are still maintained. Jasmine has the advantage that it works in the browser so it’s easier to test client side code.

I think Jest is more popular these days and provides a better solution when testing backend code


About (1) - Notice that it’s possible, and very easy, to “break out” of the root folder by just appending … to the path, for example:

?path=./../../../../../C:/Whatever-you-want

So it’s more of an initial directory than a root directory
Also I would use some internal temp folder and create it from within the code if it doesn’t exist

You can find a short discussion on directory traversal here:

לא הבנתי את תרגיל 1, מה צריך לעשות עם התיקיות וכ’ו

האם יש דוגמא של וידיאו או משהו איך זה צריך להראות?

הי
יש בתרגיל 5 סעיפים, איזה מהם לא הבנת?

ינון שלום
בעיה בתרגיל 2 (פיד חדשות )סעיף 2
כתבתי את המחלקה הבאה

const fs = require('fs');

const linksFileName = 'links.json';

class LinkItems {

    constructor() {

        this.links = [

            { id: 0, link: 'seenet.co.il', rank: 0 },

        ];

       

        this.nextId = 1;

        console.log('constructor LinkItems');

    }

    deleteLink(id) {

        const idx = this.links.findIndex(item => item.id === Number(id));

        this.links.splice(idx, 1);

    }

    listLinks() {

        return this.links;

    }

    addLink(link, rank) {

        const newLink = { id: this.nextId++, link: link, rank: rank };

        this.links.push(newLink);

        return newLink.id;

    }

    updateRank(id, newRank) {

        const link = this.findContactById(id);

        link.rank = Number(link.rank) + Number(newRank);

    }

    findContactById(id) {

        return this.links.find(item => item.id === Number(id));

    }

    shortLinksByRank() {

        this.links.sort((a, b) => (Number(a.rank) > Number(b.rank)) ? 1 : -1);

    }

    storeData() {

        try {

            console.log(`storeData = ${JSON.stringify(this.links)}`);

            fs.writeFileSync(linksFileName, JSON.stringify(this.links));

        } catch (err) {

            console.error(err);

        }

    }

}

module.exports = new LinkItems();

הבעיה היא שבכל פעם שאני קורא לפנקציה
storeData()
מהראוטר הקונסטרקטור של המחלקה פועל שוב.
אשמח להבין את ההתנהגות הזו
גל

מצאתי את הבעיה
שמרתי את קובץ הנתונים בתוך תיקיית הפרויקט
והפעלתי את הקוד דרך
nodemon www/bin
לכן כל פעם שה
nodemon
ראה שינוי בקבצי הפרויקט הוא בנה והריץ מחדש את הקוד.

לייק 1

מזכיר לי את הפוסט הזה שכתבתי פעם:

היי,
לא הבנתי לצערי את כל מה שקשור לaction בטפסים.
מה צריך להיות כתוב פה?

<form method="POST"  action="???????">
    <label>
        add a Link: 
        <input type="text" name="link"/>
    </label>
    <input type="submit" value="Save" />
    <ul>
        <% for (let l of links) { %>
            <li>
             <%= l.linkName %>
               <button value="like">like</button>
               <button value="dislike">dislike</button>
               <h1><%= l.likes %></h1>           
            </li>
        <% } %>
    </ul>
</body>
</form>

אשמח לתשובה…

הי מלכה

ה action זה הנתיב בשרת שאמור לטפל בבקשה. בשביל לדעת מה לכתוב שם צריך להסתכל גם על קוד צד השרת. לדוגמה אם בשרת יש קוד שמטפל בפניה לנתיב בשם /message אז נכתוב ב action את הנתיב:

<form method="POST" action="/message">

אקשן זה תמיד הנתיב על השרת שמטפל בבקשה, ו method זה סוג הבקשה. שניהם צריכים להתאים למה שהוגדר בקוד הניתוב בשרת

תודה על התשובה.
שאלה נוספת:
הקוד שלי בHTML הוא כזה:

<form method="POST"  action="/news">
    <label>
        add a Link: 
        <input type="text" name="link"/>
    </label>
    <input type="submit" value="Save" />
    <ul>
        <% for (let l of links) { %>
            <li>
             <%= l.linkName %>
               <button value="like">like</button>
               <button value="dislike">dislike</button>
                <% if (l.liks > 0) { %>
                    <h2> <%= l.liks %></h2>
                  <% } %> 
            </li>
          <% } %>
    </ul>
</body>
</form>

image

כאשר אני לוחצת על אחד הכפתורים של הוספת או הסרת לייק, נוצרת עוד שורה של שני כתפורים. מדוע זה?
אשמח לקבל תשובה. תודה!

כאשר לוחצים על כפתור זה גורם לפעולת Submit של הטופס,
אבל בגלל שאין קלט בתיבת ה input אז נוסף לינק ריק בצד השרת וזו השורה הריקה שאת רואה.

מה את רוצה שיקרה כשאת לוחצת על ה Like או על ה Dislike? אפשר לשים כל כפתור כזה ב form משלו, עם action משלו שישלח לנתיב אחר בשרת מאשר news ושם לעדכן את הלייקים.
או אפשרות נוספת להוסיף name לכפתור ואז בקוד שמטפל בנתיב news על השרת לבדוק אם התקבל ערך ל name הזה אז לעדכן לייקים ואם התקבל ערך ל link אז להוסיף את הלינק.

ובכל מקרה בצד השרת כדאי לעשות וולידציה על הטקסט שעבר בפרמטר link, לפחות לבדוק שהוא לא ריק לפני שמוסיפים לרשימה

תודה.
אתייחס להצעה השניה שכתבת :להוסיף name לכפתור ואז בקוד שמטפל בנתיב news על השרת לבדוק אם התקבל ערך ל name הזה אז לעדכן לייקים ואם התקבל ערך ל link אז להוסיף את הלינק.
אני רוצה שבעת לחיצה על הלייק או דיסלייק יתווסף או ירד לייק (עוד לא הראיתי את זה בתצוגה)
השאלה איך אני מתעסקת עם ערך הname שאקבל (זה כפתור ולא שדה)
ז"א: הבנתי שכאשר יש לי שדה קלט כותבים כך לדוג’ :

const {XXX}= req.body;

ואז קיבלתי את הערך.
אבל כאשר אני רוצה לדעת האם לחצתי על הכפתור מה יש לי בתוך הname?
לדוג’:

 <button value="like"  name="like">like</button>

מה עלי לעשות בנתיב בשרת?
מקווה שהובנתי נכון.

הי

כן זה בדיוק אותו דבר. בקוד ה ejs כותבים:

<!DOCTYPE html>
<html>
  <head>
    <title><%= title %></title>
    <link rel='stylesheet' href='/stylesheets/style.css' />
  </head>
  <body>
    <h1><%= title %></h1>
    <p>Likes: <%= likes %></p>
    <form method="POST" action="/news">
      <button  name="like" value="like" >Like</button>
      <button  name="dislike" value="dislike" >Dislike</button>
    </form>
  </body>
</html>

ובקוד ה route בשרת נכתוב:

let likes = 0;

router.post('/news', function(req, res, next) {
  const {like, dislike} = req.body;
  if (like) {
    likes += 1;
  } else if (dislike) {
    likes -= 1;
  }
  res.render('index', { title: 'Express POST /news', likes});
});

כפתור שיש לו name מגיע לשרת בתור פרמטר בדיוק כמו input עם name