Getting Started
To get started, let’s add all of the packages that we’ll need for this recipe. Don’t worry, as we move through the recipe we’ll see how these come into play.
Terminal
We’ll rely on the aldeed:collection2 package to help us add schemas to our Meteor collections. This will help us control how and what data is allowed into our collections.
Terminal
We’ll use the reactive-dict package to help us control form state and make it a little easier to access the data in our forms.
Additional Packages
This recipe relies on several other packages that come as part of Base, the boilerplate kit used here on The Meteor Chef. The packages listed above are merely recipe-specific additions to the packages that are included by default in the kit. Make sure to reference the Packages Included list for Base to ensure you have fulfilled all of the dependencies.
What are we building?
Generally speaking, most forms in our applications are pretty simplistic. This is fine, but sometimes we need to build more complex forms. Forms that rely on multiple templates, have different variations of user input, and other features that require a little more work to implement. To help wrap our head around what it takes to develop complex forms in Meteor, we’re going to build a simple application for a pizzeria called Pizza Planet.
Our application is going to have two core features:
The ability to order a pizza: an existing custom pizza the user created, a pre-defined “Popular” pizza, or a new custom pizza.
The ability to create a “Pizza Profile” where the user can store their custom pizzas and contact information, as well as view their order history.
Time’s a wastin', let’s get to it. To start, we’re going to focus on setting up our collections and adding schemas to give our data some shape.
Defining collections
First, let’s go ahead and create a file for each of the collections that we’ll have in our application and fill them with a new collection definition assigned to a global variable.
/collections/customers.js
Our Customers collection will store contact information for each of our customers.
/collections/orders.js
Our Orders collection will store orders placed by our customers.
/collections/pizza.js
Our Pizza collection will store pizzas created by our customers and us.
Adding schemas to our collections
Great! Now that we have this in place, we need to add a schema to each of our collections.
Schema vs. Schemaless
When learning about NoSQL style databases like MongoDB, you may hear the term “schemaless” used. This means—quite literally—a database without a schema. A schema is a pre-defined structure for your database (a set of rules) that can be used to control the name and type of data inserted into your database, as well as when it’s allowed to be inserted.
To add schemas to our collections, we’re going to rely on the aldeed:collection2 package that we installed a little bit ago. This package allows us to design a set of rules for how documents being inserted into a collection should look. We define these rules on a per field basis and each field can have a handful of settings. Let’s see what it looks like to add a schema and then walk through the rules for each.
/collections/customers.js
Woah! Wait a minute! This isn’t as scary as it seems. This is a “schema” in all it’s glory. It’s quite literally an object of rules mapped to the names of fields. The idea here is simple: when we go to insert a document into this collection—in this case our Customers collection—the collection2 package will validate that data against the rules we define here.
So, notice that we’re planning on having a field in each of our customer documents called name. Using the options given to use by collection2, we’ve created a rule for this field that:
Make sure the data Type is a String value.
Set’s the defaultValue to an empty string (this will make sense later).
Gives the field an arbitrary label for the field to reference later (collection2 generally uses this to identify errors on fields).
Combined, these three options set to the field name ensure that every single document we insert will have a name field, but also, that that field will follow these specific rules. If the field doesn’t exist or doesn’t meet this criteria, collection2 will quietly reject the insert.
Let’s look at our other schemas and keep explaining how this is wired up.
/collections/orders.js
This one is a little simpler. Notice here we follow the same concept: for each field that will live in our document, we add a new rule with the exact name the field will carry in the collection, along with a few settings for how that field should take shape. Wait…what is this SimpleSchema thing, though?
SimpleSchema is another package by the author of collection2 that gives us the actual syntax for defining schemas along with the methods to actually validate that data. collection2, then, is best thought of as a more user-friendly wrapper around SimpleSchema that automates the validation of data by automatically applying it to a collection. We can see this happening in the example above on the last line Orders.attachSchema( OrdersSchema ).
This is taking the schema we’ve defined above it—assigned to the OrdersSchema variable—and “attaching” it to our Orders collection. Notice, this is just calling a method attachSchema that has been added by collection2 to our MongoDB collection definition. Neat!
With this in place, all of the data that we attempt to insert into our collection will be validated against this schema before we allow the insert. That’s it. Nice, eh? Real quick, let’s take a look at our last collection Pizza and see how its schema is taking shape.
This one has a few more fields with slightly less obvious settings on some of those fields. Let’s call attention to one of our field definitions: toppings.meats. This field has a weird type of [ String ]. Here, this means that we expect the value of toppings.meats to be equal to an array of strings. This syntax is a bit odd, I know. Notice, too, that we’re defining this field off a parent object toppings. Why don’t we have to add a rule for that?
Because toppings only contains this meats array and another array nonMeats—no top-level data in the parent object—we can forgo validating that and just focus on its contents. Something else that’s interesting too, here, is that we’re passing another option optional: true. This does exactly what you’d expect: make this field optional. So, when we go to insert a document into this collection, it doesn’t matter if a toppings.meats field is defined; collection2 will take it either way. Nice!
More on collection2
Our usage of collection2 here is pretty simple. If you’re curious about how to use collection2 and some of the gotchas involved in working with it, check out this snippet on the topic.
Now that we have some structure for our data, we’re going to add some publications. Wait…what? Bear with me. We’ll be diving into some pretty complicated stuff later on, so it’s important that we get this easy stuff out of the way now, even if it’s not 100% clear how it will come into play. Don’t worry, we’ll make sense of it all by the final sentence!
Defining publications
Okay. So, to make our publications a little more efficient, we’re going to think about how we define them in terms of where they’ll be used. In our app, we’ll have two distinct views: the order form and the pizza profile. Each view is going to have multiple pieces of data in it, but we don’t want to define multiple publications for each view. How do we get around that? Complex publications.
Complex publications are just a fancy TMC word for publications that return multiple MongoDB cursors. Generally when we think of publications, we only return a single cursor or Collection.method() call. Fortunately, we can return multiple cursors from the same publication, allowing us to consolidate our efforts. Let’s take a look at the publication for our order form to start.
/server/publications/order.js
Not too crazy. First, we give our publication a simple name to identify where it will be used, in this case the order form. Inside, we start by grabbing the current user’s ID. Remember, we have access to this from inside publications as an added security-minded bonus. Next, if we have a logged in user, we create a new variable data and assign it to an array with two methods inside: Pizza.find( { $or: [ { "custom": true, "ownerId": user }, { "custom": false } ] } ) and Customers.find( { "userId": user } ). Together, these two calls make up all of the data that we’ll need on the order form screen when a user is logged in.
That first one is pretty gnarly. What’s that { $or ... } business? This is pretty neat. Here, we can perform “multiple queries” on the same collection using a single call. Using the MongoDB $or operator, we can ask MongoDB to give us all of the documents that match different queries. We can pass our separate queries in array and get back a single cursor with the documents matching either query. Wow! This is handy because if we tried to pass two Pizza.find() calls in this array with our different queries, we’d get an error.
The second query here Customers.find() is a little more obvious. Here, we expect to have a userId field on documents in our Customers collection and we want to get back the documents that match the currently logged in user. Easy peasy.
Just after this, we provide an else statement, setting our data variable to a single query. Notice that in this case, if we don’t have a currently logged in user, we don’t want to look up a Customers document. Again, this will make sense later, so just follow along.
Lastly, we return our data if we have any and a call to this.ready() to make sure our publication completes.
Skipping our pizzaProfile publication
We have one more publication being defined in our app called pizzaProfile that will be used in our Pizza Profile template. It’s nearly identical to this publication, so we’re going to skip reviewing it here. If you’re curious, you can find the file on GitHub here.
We’ll cover subscribing to these publications in a little bit. For now, let’s setup a helper function on the server that will insert some pizzas into our Pizza collection when our app starts up.
Adding some dummy data
This is pretty simple, but will help us to understand how our schema from earlier comes into play; at least, in relation to our Pizza collection. Let’s take a look.
/server/admin/startup-functions/create-pizzas.js
Bog standard JavaScript. We create a function called createPizzas() that we can call later—we won’t show it here but we do this in /server/admin/startup.js—when our server starts up. Next, we define an array of objects that represent some “dummy” pizzas in our application. Later on, we’ll use these to represent “Popular Pizzas” in our application.
Notice, each of these pizzas follows our schema by the book. This is intentional. Below when we go to insert pizza’s into our collection, behind the scenes our schema will be called against each of these pizzas. Remember, if our data doesn’t meet our schemas criteria, it’s getting kicked to the curb by collection2.
Okay. Underwhelming, sure, but important (and illustrative)! Now, when our server starts up we’ll get some pizzas added to our Pizza collection to work with later.
Creating a pizza profile
Now that we’ve got the basic pieces in place for handling data, we need to figure out how users will move in-and-out of our application. To explain, here’s a quick peek at the home page users see when they first visit our application.
We’ll be giving our users two options when they first visit our site: starting a new order or gettng a pizza profile. Both will handle new user signups accordingly, but to get things underway, let’s look at that “Get a Pizza Profile” option first.
Sign up form
Sneaky, us. Signing up for a Pizza Profile is really just creating an account in our application with a twist. We’re going to piggyback on the signup feature of Base that’s already been built for us. Let’s fast forward and look at the template logic for our signup template, with a slight modification for our Pizza-rific use case here.
/client/templates/public/signup.js
What’s going on here? Well, inside of the submitHandler portion of our signup form’s validation—in the onRendered callback for the template—we’re calling to Accounts.createUser. The part to pay attention to is what data we’re passing. We’ve got our usual email and password fields…but why are we setting profile.customer to a bunch of empty stuff? Ah ha! This is black magic, plain and simple.
Tapping into the onCreateUser callback
When we create our user, we also want to create a blank customer profile for that user in our Customers collection. To do this, we can pass our placeholder data here. To make it work, though, we have to tap into our application’s onCreateUser function.
/server/create-user.js
Hot diggity dog, right? Here, we tap into the user creation process for our app. Though this isn’t added by default—we have to create this file and call this method—we can use this to modify the new user document and otherwise tap into the “signup” flow for our app.
For our purposes, notice that we look at the options.profile value here, and more specifically, check for the existence of the options.profile.customer value. If both exist, we grab the blank customer data, stash it in a variable customer, set the newly created userId on it, and then delete the profile all together. Without a trace!
Just after this, we go ahead and insert our new customer into the Customers collection. Rad! So now, without our user ordering a pizza, we create a complete Pizza Profile for them. Well, in theory. We haven’t created the profile view yet, but this gets all of the data we need in place.
That last part is just for safety’s sake. Even though we’ve already toasted our profile value, this will ensure that if you decide to add an actual profile later, it won’t get nuked. Yeah, the Chef cares. Group hug.
Enough! Let’s start to put this together. We’re going to start by wiring up the contact information portion of our pizzaProfile template that relies on this document we just inserted into our Customers collection. Let’s dig in.
Managing contact information in the pizza profile
To get started, we’re going to add two files to our app: our HTML template at /client/templates/authenticated/profile.html and our complimentary template logic at /client/templates/authenticated/profile.js. Got it? Okay, let’s check out our HTML first.
Routes setup for us
We won’t cover it here, but to get this working 100%, we’ve already setup a route that points to http://localhost:3000/profile and loads up our pizzaProfile template for us.
/client/templates/authenticated/pizza-profile.html
What in the heck is this Jetson’s business? Hang in there. To explain, we want to be as economical as possible with our templates. As we’ll see later on, we’ll need access to our contactInformation template in our order form, too. Instead of recreating the exact same template twice, we can use Meteor’s handy {{> template.dynamic}} feature to use our template in different contexts (i.e. different features in different places using the same template). Strap in!
First, we need to point out that we’re wrapping our dynamic template call in a {{#with customer}} block. This is our special sauce. Here, we’re telling Meteor to include our template, but making it’s data context equal to the {{customer}} helper defined on our pizzaProfile template’s logic. Wild. So, inside of our contactInformation template, we’ll have access to whatever data is returned to this {{customer}} helper. Let’s take a peek at the helper and then look at the contactInformation template to close the loop.
/client/templates/authenticated/pizza-profile.js
SO much sneakiness. We’re actually going to handle two things here. First, recall that earlier we set up a publication that in part exposes our current user’s customer profile via a call to Customers.find(). Here, we use template-level subscriptions to subscribe to our publication by calling this.subscribe( 'pizzaProfile' );. Boom! Now, whenver our pizzaProfile template is loaded up, we’ll get access to all of the data we need for our profile’s various pieces. Shred.
Okay, main event, that {{customer}} helper. Here, we do a standard .find() call on our Customers collection, pulling in the currently logged-in user. If we get the customer, we set an additional value on it called context, setting it to "profile". Again, we’ll do something similar later so we can reuse the template for our order form. Once that’s set, we return our customer! Now, our {{#with}} block will evaulate to true, or, “I have data,” and render our contactInformation template. To understand the significance of this, let’s take a peek at the logic for contactInformation.
/client/templates/public/contact-information.js
Womp. Pretty underwhelming, but very cool. Here, we create another helper that we can use in our contactInformation template that returns a true or false value based on the current context. Remember how we set that earlier in our {{customer}} helper? Well, because we used a {{#with}} block, we essentially “took over” the data context for our template instance.
This means that now, we can access the data assigned way up in the parent template using Template.instance().data. Here, then, we just point to our context value in our data and evaluate whether it’s equal to "profile" or not. Swish!
So what does this do? Let’s look at the markup.
/client/templates/public/contact-information.html
We’ll skip the boring form fields part and jump straight to the hotness. See what’s happening here? We’ve set up an {{#if isProfile}} block using our helper which spits out a “Save Profile” button if we’re on the profile! This may seem insignificant now, but later, this will ensure that this button does not show up when we use this same template in our order form. It’s the little things.
Skipping the update logic
To save some time—time is money after all—we’re going to skip reviewing the logic for updating our contact information from the Pizza Profile. Rest assured, it is imeplemented and you can find it in the source over on GitHub.
Now we’re cruising. Let’s upgrade to the big leagues, though, and start working on the reason we’re all here: the order form.
Creating an order form
Remember earlier when we showed the home page for our app that we also had a “Start an Order” button? Let’s get that thing wired up. Take a deep breath, we’re going to cover a lot in just a little bit of time. You’ll be a different person by the time you finish reading. Ready?
Adding the category tabs
Recall earlier that we outlined three possible types of orders our user will be able to place:
A custom pizza that they saved from an earlier visit.
A pre-made, “Popular Pizza” that already has it’s toppings picked out.
A new custom pizza.
To handle this, we’re going to create a tabbed interface that allows our users to switch between these three types of orders, tracking their movement in the UI so we know which type of order is being placed. To start, let’s look at the markup for our order form to see how these tabs will fit in.
/client/templates/public/order.html
When all is said and done, we’ll have three distinct sections in our order form. To start, we’re going to create a new template called pizzaCategories that will handle our tabbed interface. Here, though, we can see that we’re wrapping an include to that new template in a <form id="place-order"> tag. This isn’t important now, but it’s good to understand what we’re after. The goal here is to make one giant form out of several smaller forms and submit them as one. Before we get too far head of ourselves, let’s look at the markup for our pizzaCategories template.
/client/templates/public/pizza-categories.html
Quit yet? This is actually fairly harmless. Let’s step through it.
First, there are two parts to this: the tabs and the content. The tabs part is the <ul class="nav nav-tabs"> part, with the content located in <div class="tab-content"> down below. When we click on a tab, we’ll rely on Bootstrap to do it’s thing and reveal the correct content or “panel.” Let’s start with those tabs.
Here, we’re trying to cover two scenarios. First, if we have a logged in user, we want to show them their previously created pizzas, so we make this tab available if a currentUser is available. Additionally, we also pull in the Popular Pizza’s tab here. Why? Notice that if the “My Pizzas” tab is available, that tab should have an .active CSS class. If it’s not available, then we’d want our “Popular Pizzas” tab to be active as it moves into the first position if “My Pizzas” isn’t there. This is purely cosmetic and for UX-sake, but good to point out. Real quick, notice that extra data-pizza-type attribute we’re setting on each <li>?
This is what we’re going to use to identify which tab our user has clicked on in our form. To make sense of it, let’s look at the logic for our order template real quick.
/client/templates/public/order.js
A few things to point out. First, notice that in our order template’s onCreated we’re subscribing to our order publication similar to how we handled our pizzaProfile publication earlier. Next, we’re defining a new variable on our template instance this.currentOrder and assigning it to an instance of ReactiveDict().
If you’ve never seen it before, ReactiveDict—a package we added at the start of this recipe—allows us to keep a reactive dictionary of values. What’s neat about it is that we can use it locally like we’ve done here, assigning it to a template instance. This allows us to store reactive data, but avoid using something that’s defined on a more global-basis like Session variables. Fun fact: Session variables are just a wrapper around ReactiveDict.
Cool. So, just after we set our dictionary up, we also set some initial values on it. Here, we’re setting a type and pizza value. The type value here is simply saying that the type of order by default will be “My Pizzas” if there’s a current user, or “Popular Pizzas” if there is not (this accounts for our currentUser tab switching from a little bit ago). That “pizza” setting right now is just for show. That will be used to display some default values later on. For now, just know that we’ve set it.
Okay! The important part. Down in the event map for our order template, we can see we’re watching for clicks on our .nav-tabs (our tabs) element. Inside the handler, we grab that data attribute we set in the markup and set the type setting on our ReactiveDict accordingly. We also make sure to set our pizza value up with some default data, depending on which tab was clicked (“Custom Pizza” vs. “My Pizzas” or “Popular Pizzas”). Following along?
Again, it doesn’t seem like much but now we’ll be tracking which tab is toggled in our form. This will help us in a little bit.
Loading our templates for each tab
Now that we’ve got our tabs wired up, we need to assign a template to each of those tabs. To do this, we’re going to rely on that {{> Template.dynamic}} trick we learned about earlier. Let’s pop back over to the markup for our pizzaCategories template quick and then chat about the logic that’s making it all work.
/client/templates/public/pizza-categories.html
This should all be starting to look familiar. Here, we match our tabs check for a currentUser with our tab content. Same exact idea, but instead of tabs we’re loading in templates, assigning an .active class to the .tab-pane element wrapping our includes. Here, we’ve got four includes, but we’re only pulling in two templates. Let’s talk about that {{> Template.dynamic template="pizzaList" data=myPizzas}} one first.
Earlier when we learned about dynamic templates, we wrapped our template in a {{#with}} block to pass it some data. We did that there because we needed a way to pull in data and set context from within the template. This time around, though, we’re going to pass both our context and our data as a helper defined in our pizzaCategories template and pass that to our pizzaList template using the data attribute of our Template.dynamic include. Let’s take a look at the logic for our helpers and then see how that pizzaList template makes sense of it all.
/client/templates/public/pizza-categories.js
Interesting! We start with something familiar by calling a .find() on our Pizza collection for both of our different use cases (our logged in user’s pizzas and our auto-generated pizza’s from earlier). Next, instead of just returning our data directly, we return an object with two values: context and content. On context, we set the “location” where our template is being loaded and on content, we pass the data we just pulled out of the database. This may not make complete sense on it’s own. Let’s look at the pizzasList template’s logic. There, we’ll see how the data is handled.
/client/templates/public/pizza-list.js
Two birds, meet your stone. This is really cool. Because we need to use our pizzaList template several times, we need a way to pipe in data for it but also make note of its context. Just above when we set our helpers to an object, we were doing that so we could reference both the context and the data here. Recall that we passed our helpers to our dynamic template include’s data attribute.
Following that train of thought, we can see that we make an isProfile helper that can be used inside of our pizzaList template relying on our context value, and down below, our content value being set to a new pizzas helper that will be tied to an {{#each pizzas}} block to output each of our pizzas. What’s neat about this is that this pizzas helper isn’t tied to a specific type of data. We can send it any type of—pizza related—data and it will format the list accordingly in the template! This saves us a lot of duplication but also makes it very easy to understand the flow of data in our templates. Win/win!
/client/templates/public/pizza-categories.html
Back in our pizzaCategories template, we can see this technique being used for both our myPizzas and popularPizzas data sources. How cool is that? Using a single template, we get the same styles but pipe in different data. Polish off that Swiss Army Knife.
With this in place, we can move on to our third tab and template: “Build a Pizza” and the buildPizza template.
/client/templates/public/build-pizza.html
Despite it sounding like a really complicated step, our buildPizza template is actually quite simplistic. It’s got a few form fields, some dropdown lists, but nothing that wild. Hm. The tricky party with this will come later when we need to get these values back. For now, let’s talk about how this template is getting its data.
Default values in settings.json
As part of our ordering process, customers will be able to build a custom pizza. In order to facilitate this process, we need a way to give them all of the “parts” of a pizza. To do this, we can store some basic data in our settings.json file.
Just for simplicty sake
Here, we’re loading in this data using a settings file for simplicty sake. In a real application, we’d likely tie this into a collection that was managed via a backend interface. There isn’t anything wrong with this, but generally our settings file is reserved for application settings and configuration. Whether the info here should be added there is up for debate :)
First, let’s take a peek at our settings file and see what sort of data we’re making available. After that, we’ll look at the template logic and see how it’s all glued together.
settings-development.json
Very simple. Nested inside of our public object, we’ve added a few types of information that our user will be able to choose from when customizing their pizza: toppings, crusts, sauces, and sizes. In order to make these useful then, we need to map each to an element in our form we defined above. It’s really simple, let’s take a peek.
/client/templates/public/build-pizza.js
Well that’s certainly…spartan. Yep! The name of the game here is simplicity. Again, we could easily replace these with database queries, but this gives us a quick and easy way to manage some static data in our app without jumping through a lot of hoops. Now, when we load up our order form and view the “Build a Pizza” tab, we should see something like this:
Pretty cool, right? As you saw above, we’re just using a series of {{#each}} blocks to output the information we’ve piped in from our settings file. Incredibly simple, but has a huge impact on our form. With this in place, we’ve finished up our pizza categories portion of our order form. Next, let’s focus in on adding our contact information block from earlier along with a few other things.
/client/templates/public/order.html
A handful of items here, but don’t worry we’ll step through each. First and most exciting, we can see that we’ve added in a dynamic include to our contactInformation template. Using our {{#with}} block trick from earlier, we pull in our customer data. Real quick, let’s look at how that works at the logic level.
/client/templates/public/order.js
Some familiar stuff coming into play. Recall that we have to account for two states when it comes to our order form: when a user is logged in and when they’re not. Here, we can see ourselves handling this by using an if block where we return a customer from the Customers collection if we have a current user, and if not, just returning an empty object.
We also bring back our technique of setting a context on the customer document just before we return it to the template. Where earlier we set our context to "profile", now we rely on "order" because we’re in the order form. This ensures that all of that profile-only functionality doesn’t show up here. Though not terribly thrilling, here is the result we get:
Notice that unlike in our Pizza Profile from earlier, we don’t see the green “Save Profile Information” button because it’s blocked by our {{#if isProfile}} block inside of our contactInformation template. Think about how that’s working for a second. Isn’t that cool?
Hanging in there? We’ve got a few more things to cover. Next, let’s tackle that profileSignup form we include in our order form if there isn’t a current user logged in.
/client/templates/public/order.html
For reference, this is how our include lives in the order form. Let’s take a look at that profileSignup template real quick.
/client/templates/public/profile-signup.html
Well…okay then! Dirt simple. This gives us an emailAddress and password field to display if we don’t have a current user. This means that if a user attempts to place an order without being logged in, we ask them for an email address and password to “save” their order information. What’s neat about this is that we’ll be able to create a user account for this user without them having to do it first. The shortest path between yourself and pizza is a straight line. Repeat that.
Great. Now, let’s look at the very last part of our order form: the order confirmation area.
Because we want to be super helpful to our customers, it seems wise to add an order confirmation area just before we send in orders. This allows our customer to confirm that they’re getting the pizza they asked for as well as how much it will cost. We’ve watered this down a bit for simplicity, but you could beef it up quite a bit if you found yourself building a pizza ordering app! Real quick, let’s take a peek at the markup and then see how it’s wired up.
/client/templates/public/order-confirmation.html
Pretty basic. Here we just have a simple <table> element with a bit of structure and a few references to template helpers that we’ll see in a bit. First, though, we should call out to something unique here. Remember that we’ve defined the schema for our pizza to include a price field that expects a Number value. In expectation of a payment service—we don’t include this here but it’s good to practice—requiring us to deliver prices in cents, we’ve stored the price for each of our pizza’s with a cents value. So, a $10 pizza is referenced as 1000 or one thousand cents.
Of course, we don’t want to display 1000 in our template as that would be confusing. Instead, we’ve setup a template helper to convert this value to something that’s a little more human friendly. Let’s take a look.
/client/helpers/helpers-ui.js
Pretty simple, but good to see. Here, we take the value (in cents) passed to our helper and divide it by a hundred. Before we send it back to our helper, we prefix it with a $ USD symbol. Keep in mind, this is American Arrogance at work. If we were supporting other countries, too, this would be a little more complicated and we’d likely use a currency library to help us with the heavy lifting. Make sense? Cool!
Let’s look at the logic for our orderConfirmation template. Remember that we’re using a {{#with order}} block helper to pipe data in, so we’re actually going to look at the logic file for our order.html template.
/client/templates/public/order.js
A lot going on here. Remember earlier when we defined our ReactiveDict and assigned it to our template instance as this.currentOrder? This is where it really comes into play. Here, we’re creating a “summary” of our order and tweaking the information that’s displayed based on our different use cases. Here, our test case is for whether or not our order is for a Custom Pizza, or one of our customer’s existing pizzas or our Popular Pizzas. Let that soak in.
To test this, we call on the type value of our currentOrder variable that we set earlier as a default. Remember earlier when we set it up so that when our tab is clicked, we set the type? This is where it’s used. Here, if we’re not building a custom pizza, we want to lookup the pizza selected in our Pizza collection. Wait…when did we set that? Ah! We haven’t yet. Real quick, let’s scroll down a bit to our order template’s event maps to see how we’re setting which pizza gets selected.
/client/templates/public/order.js
This is interesting. Here, whenever an item inside our order template with the class .pizza is clicked, we want to get the data from that template instance and set it equal to the pizza property in our ReactiveDictionary this.currentOrder. Because each of our pizza’s is being output in an {{#each}} block within our pizzaList template, we know that this inside of our event handler is equal to the data for that template. That’s confusing. Here’s how it looks in action:
Make a little morse sense? So, when we click on a pizza—either a Popular Pizza or one of our customer’s custom pizzas—we set the pizza value on our this.currentOrder dictionary to the data context of the clicked pizza.
How is the pizza being selected?
We haven’t covered this here, but inside of the pizza template that’s output in our pizza list’s {{#each}} block, we’ve defined an event for setting a .selected class w