Browse Source

Challenge 2: Search function.

Frederic G. MARAND 8 years ago
parent
commit
0f09a9e2df
10 changed files with 114 additions and 32 deletions
  1. 1 0
      .eslintrc.js
  2. 11 0
      client/client.js
  3. 4 0
      client/helpers.js
  4. 13 1
      client/navbar.html
  5. 14 0
      client/navbar.js
  6. 17 0
      client/page_search.html
  7. 4 7
      client/website_form.js
  8. 21 0
      lib/lib.js
  9. 2 1
      server/collections.js
  10. 27 23
      server/startup.js

+ 1 - 0
.eslintrc.js

@@ -30,6 +30,7 @@ module.exports = {
 
     // from our app.
     "isLoggedIn": true,
+    "toWords": true,
     "Websites": true
   },
 

+ 11 - 0
client/client.js

@@ -14,6 +14,17 @@ Router.route("/", function () {
   this.render("page_home", { to: "contents" });
 });
 
+Router.route("/search/:search", function () {
+  const search = this.params.search;
+  this.render("page_search", {
+    to: "contents",
+    data: {
+      search,
+      result: Websites.find({ words: { $regex: new RegExp(search) } }).fetch()
+    }
+  });
+});
+
 Router.route("/site/:id", function () {
   const id = this.params.id;
   const doc = _.extend({ onPage: true }, Websites.findOne({ _id: id }));

+ 4 - 0
client/helpers.js

@@ -3,3 +3,7 @@
  */
 
 Template.registerHelper("isLoggedIn", isLoggedIn);
+
+Template.registerHelper("log", function () {
+  console.log(this);
+});

+ 13 - 1
client/navbar.html

@@ -1,16 +1,28 @@
 <!-- navbar  - you will be putting the login functions here -->
 <template name="navbar">
-  <nav class="navbar navbar-default">
+  <nav class="navbar navbar-default" role="navigation">
     <div class="container-fluid">
       <div class="navbar-header">
         <a class="navbar-brand" href="/">
           Site Ace
         </a>
       </div>
+
       <div id="navbar" class="navbar-collapse collapse">
+        <!-- navbar-rights stacks right to left -->
         <ul class="nav navbar-nav navbar-right">
           <li class="active"><a name="login-buttons">{{> loginButtons align="right" }}</a></li>
         </ul>
+
+        <form class="nav navbar-form navbar-right js-search-form" role="search">
+          <div class="input-group">
+            <input type="text" class="form-control" placeholder="Search" name="search-term" id="search-term" />
+            <div class="input-group-btn">
+              <button class="btn btn-default" type="submit"><i class="glyphicon glyphicon-search"></i></button>
+            </div>
+          </div>
+        </form>
+
       </div>
     </div>
   </nav>

+ 14 - 0
client/navbar.js

@@ -0,0 +1,14 @@
+/**
+ * Client code for the navbar template.
+ *
+ * - events
+ */
+
+Template.navbar.events({
+  "submit .js-search-form": function (event) {
+    event.preventDefault();
+    const search = $(event.target["search-term"]).val();
+    // FIXME Prevent XSS and CSRF.
+    Router.go("/search/" + search);
+  }
+});

+ 17 - 0
client/page_search.html

@@ -0,0 +1,17 @@
+<template name="page_search">
+  <ol class="breadcrumb">
+    <li><a href="/">Home</a></li>
+    <li class="active">Search results</li>
+  </ol>
+
+  <blockquote>
+    <h2>{{ search }}</h2>
+    <footer><cite title="Search query">Search query</cite></footer>
+  </blockquote>
+
+  <ul class="list-group">
+    {{#each doc in result }}
+      {{> website_item doc }}
+    {{/each}}
+  </ul>
+</template>

+ 4 - 7
client/website_form.js

@@ -6,7 +6,7 @@
 
 Template.website_form.events({
   "click .js-toggle-website-form": function () {
-    $("#website_form").toggle("slow");
+    $("#website_form").toggle("fast");
   },
 
   "click #site-lookup": function (event) {
@@ -22,12 +22,10 @@ Template.website_form.events({
       $("#title").val(title);
       $("#description").val(metaDescription);
     });
-
-    // Stop the form submit from reloading the page
-    return false;
   },
 
   "submit .js-save-website-form": function (event) {
+    event.preventDefault();
     // here is an example of how to get the url out of the form:
     const url = event.target.url.value;
     const title = event.target.title.value;
@@ -40,10 +38,9 @@ Template.website_form.events({
 
     //  put your website saving code in here!
     const doc = { url, title, description, poster, postDate };
+    doc.words = toWords(doc);
+
     Meteor._debug("Client trying to insert", doc);
     Websites.insert(doc);
-
-    // Stop the form submit from reloading the page
-    return false;
   }
 });

+ 21 - 0
lib/lib.js

@@ -7,3 +7,24 @@
 isLoggedIn = () => {
   return !!Meteor.userId();
 };
+
+/**
+ * Convert a document to a string containing its unique words.
+ *
+ * @param {Object} doc
+ *   The source Website document. Extraction uses title and description only.
+ * @returns {string}
+ *   A string made of the lower-case, orderd, deduplicated words in the document.
+ */
+toWords = (doc) => {
+  const NONWORD_REGEX = /[\W]/g;
+  const aTitle = doc.title.split(NONWORD_REGEX);
+  const aDescription = doc.description.split(NONWORD_REGEX);
+  const aWords = aTitle.concat(aDescription);
+  const aLCWords = aWords.map(function (s) { return s.toLocaleLowerCase(); });
+  const aDWords = _.uniq(aLCWords);
+  const aDSWords = aDWords.sort();
+  const result = aDSWords.join(" ");
+  Meteor._debug(doc, result);
+  return result;
+};

+ 2 - 1
server/collections.js

@@ -6,7 +6,8 @@ Websites.allow({
       title: String,
       description: String,
       poster: Object,
-      postDate: Date
+      postDate: Date,
+      words: String
     });
 
     // Reject anonymous inserts.

+ 27 - 23
server/startup.js

@@ -3,29 +3,33 @@ Meteor.startup(function () {
   // code to run on server at startup
   if (!Websites.findOne()) {
     console.log("No websites yet. Creating starter data.");
-    Websites.insert({
-      title: "Goldsmiths Computing Department",
-      url: "http://www.gold.ac.uk/computing/",
-      description: "This is where this course was developed.",
-      createdOn: new Date()
-    });
-    Websites.insert({
-      title: "University of London",
-      url: "http://www.londoninternational.ac.uk/courses/undergraduate/goldsmiths/bsc-creative-computing-bsc-diploma-work-entry-route",
-      description: "University of London International Programme.",
-      createdOn: new Date()
-    });
-    Websites.insert({
-      title: "Coursera",
-      url: "http://www.coursera.org",
-      description: "Universal access to the world’s best education.",
-      createdOn: new Date()
-    });
-    Websites.insert({
-      title: "Google",
-      url: "http://www.google.com",
-      description: "Popular search engine.",
-      createdOn: new Date()
+    let docs = [
+      {
+        title: "Goldsmiths Computing Department",
+        url: "http://www.gold.ac.uk/computing/",
+        description: "This is where this course was developed."
+      },
+      {
+        title: "University of London",
+        url: "http://www.londoninternational.ac.uk/courses/undergraduate/goldsmiths/bsc-creative-computing-bsc-diploma-work-entry-route",
+        description: "University of London International Programme."
+      },
+      {
+        title: "Coursera",
+        url: "http://www.coursera.org",
+        description: "Universal access to the world’s best education."
+      },
+      {
+        title: "Google",
+        url: "http://www.google.com",
+        description: "Popular search engine."
+      }
+    ];
+
+    docs.forEach(function (doc) {
+      doc.createdOn = new Date();
+      doc.words = toWords(doc);
+      Websites.insert(doc);
     });
   }
 });