Coding the Add Books Functionality
Learn and build the functionality that adds books to the bookshop.
Adding books
Books are added to the books
collection through Meteor Methods called from the client. The functionality that adds books is an admin-only function. The admin enters details about the book through the user interface. The authors of the entered books are searched using the firstname
of an author. A list of authors is published to the client from the server.
The added book details are saved into the book
collection. The saved book’s _id
is returned from the server. This _id
is passed to the component used to upload files. The upload files component uploads the pdf and cover image of the book to the server, where they’re handled.
Creating an index for the author’s first name
In the user interface, we want to present the admin with the ability to search for the author of a book using the first name of the author. When searching for a piece of data, MongoDB performs a complete scan of all records in a collection, if the collection isn’t indexed.
An index
is like a lookup page that the database can query to retrieve the information instead of scanning all the records one at a time. It functions like the index page of a book, where it’s easy to look up where specific information in the book is located.
Scroll down and open the imports/api/authors/authors.js
file in the coding playground at the end of this section. On line 20, an index
is created for the firstname
property of the author
collection. Creating an index
is done on the server, hence the method is wrapped in a Meteor.isServer
check. The 1
in the AuthorsCollection.createIndex({firstname: 1})
call means that the index is stored in ascending order. If we want the index saved in descending order, -1
is used.
AuthorsCollection.createIndex({firstname: 1});
Author search data publication
Open the imports/api/authors/server/publication.js
file at the bottom of this page. The publication is named authors.findAuthor
, and this is the name that the client subscribes to.
The publication takes a parameter that we call search
. A check is performed on the search
input parameter to ascertain if it belongs to one of the following—String
, undefined
, and null
.
On line 7, a regular expression is created using RegExp. The i flag passed to RegExp denotes that the search term is case insensitive. The author collection is searched to find the records that meet the regular expression that has been passed in.
Author search subscription
Open the imports/ui/addBook.jsx
file. On the textbox defined in line 146–152, an author
record is found using the firstname
property of the author
collection. Typing in the textbox triggers the handleTextInputChange
function on line 38. This updates the authorFirstName
state, causing the component to rerender.
On line 23, the useTracker
function is defined and an async
function is passed to it. The authorFirstName
is on the dependency array. If the variable in the dependency array changes, the useTracker
function is rerun. A subscription inside the useTracker
function is made to the authors.findAuthor
publication, passing as a parameter the value of the text input box, which is saved inside the authorFirstName
state.
const handle = Meteor.subscribe("authors.findAuthor", authorFirstName);
Next, we set the loading state with the value of !handle.ready()
on line 25. A regular expression is created on line 26 that turns the search term into a regular expression that’s case-insensitive.
On line 27, there’s an if
statement that checks if the subscription is ready and also checks if the length of the text in the input textbox is greater than 2
. If all of the if
statement checks pass, we await the result of the call that we made to the searchFirstName
function.
The searchFirstName
accepts the collection as the first input parameter and the search term as the second input parameter. The function returns a JavaScript Promise
that resolves after 100
milliseconds using setTimeout
. The setTimeout
function calls its callback at the end of the time duration. The example below will resolve
the Promise
with the value returned by collection.find()
.
setTimeout(() => {
resolve(collection.find({ firstname:
searchTerm }).fetch());
}, 100)
The await
keyword is used to wait for the result of a Promise
as a Promise
is an asynchronous function that does not return its value immediately. Using await
paused further execution until the Promise
is fulfilled.
We await
the return value of searchFirstName
and pass the value of the function to setAuthors
so as to set the React state named authors
.
On line 161 the authors
state which is an array is looped through to render a paragraph of authors that match the search term. If the search produces no result “No data” is shown on a </p>
tag. Take a look at lines 175-177. If the length of the search term which is stored in the authorFirstName
state is greater than 2 and the authors
array length is 0, it means the search query does not bring back any data, so “No data” is displayed.
On line 166, we have defined a click handler so that the user can select a particular author by clicking on the corresponding paragraph.
{
authorFirstName.length > 2 &&
authors.length === 0 && (
<p className="lead text-primary">No
data
</p>
)
}
The selected author is saved in a state called selectedAuthors
, which is an array. The selectedAuthors
state on line 183 is looped over, and the author is rendered in a paragraph. We have the ability to remove the selection by clicking the “X,” which calls the removeSelectedAuthor
function.
The submitBookDetails
function
The submitBookDetails
function defined on line 56 is attached to the submit event in the form. The function gathers all values from the state of the input textbox in the form. The default action of the form is prevented by calling e.preventDefault()
. This prevents the form from submitting and reloading the web page.
On line 59, we loop over the selectedAuthors
state and return an array of author
objects, each with an authorId
and a name
property.
let authors = selectedAuthors.map((a) =>
{
let author = {
authorId: a._id,
name: `${a.firstname}${a.lastname}`,
};
return author;
}
);
The returned array from above contains an object with keys of authorId
and name
in accordance with the object defined in imports/api/books/books.js
.
The Meteor Method, books.insertNewBook
, which has a corresponding method defined at the server (imports/api/books/methods.js
), is called on line 88. This returns the bookId
if the call was a success. The state of all the text input is cleared to its default value.
Task
Run the application, and create a book detail from the UI. A list of three authors is added to the author
collection of the server/main.js
file if no author
data already exists during application startup. Log in as an admin user with the username (“admin”) and the password (“password”). Search using the firstname
of any of the authors in server/main.js
and save a new book detail.
Get hands-on with 1400+ tech skills courses.