FREELessons: 17Length: 2.7 hours

Next lesson playing in 5 seconds

  • Overview
  • Transcript

3.3 Adding Settings

Many of the web applications that you'll use today probably have a settings page, just like a desktop application. And this is a place where you can set preferences, or options, that in some way changes how your application works. So in this screencast, we're gonna add one of those for our Angular application. Now since we have such a simple application, it's just gonna be a simple feature, but I think you'll find it interesting to implement nonetheless. The feature that we're going to implement is allowing our user to choose what columns are displayed in this table here in the contacts list. If we go into one of these records you can see that they have fields, for example here we have Homephone, or if we go into Maria Quinn, well she doesn't have any but I know Sally has an email address that's blank but she also has a favorite color field. And we have all these other fields that are not being shown here. Here we only have first name and last name. So what we're gonna do in this screen cast is create a way to allow our users to choose which columns should be displayed in this table. Now to start with we need a way to get and set that information on the user's record in the database. And since we have our accounts.js file here that runs on the server side, and is the place where we have access to our users.json database, I'm gonna add these routes in here. So, we'll just go right down to the bottom here. And we'll add a call to get /options/displayed_fields, and we'll also have a post request going to this route soon as well. And we should first check to see if the user has logged in, so we'll say if req.user, actually we'll say if there's no request.user, then what we'll do is just say res.json and we'll just send an empty array back. Otherwise if the user is logged in we can do res.json and we can say req.user.options, remember we created this object above here when we created our user objects, .displayed_fields. Now, it's possible that the user doesn't actually have this displayed_fields object yet. If nothing has been set, the default will just be first name and last name, and there will be nothing in the user account. So, if that doesn't exist, we'll say, or just send an empty array. So that is how we can get them. Now, we also need a way to set them, so we'll say we also accept post requests to options/displayed_fields. And we'll start by updating the req.user objects. We'll say req.user.options.displayed_fields = req.body.fields, which will be the property that we send these to the server as, and now we need to do our updates. We'll say db.update and we'll look for an object that has an ID of, and we will replace it with the req.user object. And then in response we'll just send the same fields back just to verify that. This has actually happened, so we'll say res.json(data[0], because the update method here can update multiple rows at once even though we are only updating one here at a raised overturn. So we'll update [0].options.displayed_fields. All right, and that's all we need to do to be able to get and set our displayed fields options on the server. So now we want to create our settings page on the front end. However before we create our settings page, let's open up public/src/factories. Now currently we only have one factory, and that is our Contact factory, which is actually an angular resource. But what we're going to do in this screen cast is create another factory, and this is the Fields factory. And this Fields factory is what we will use to get and set those optional fields. A lot of the functionality that we're gonna put into Fields factory here could be broken up and put in different places. For example, some of it will go in our settings controller. However, I like to put it in its own factory so that it's all encapsulated and in one place. So we need a couple of things here, we want the cue promise library. We also want $http, which is how we can make an AJAX request. And we also need the Contact factory from right up above. So now, let's set a variable here, we'll start with url which is going to be /options/displayed_fields. And this is the URL which we get, or post to, as you saw the one we just created here in accounts.js, to get or set our users options. We also need a set of fields that we want to ignore, now like I said first name and last name will be default but they will also be non-optional, so you have to have first name and last name so when getting your setting fields we don't want to deal with first name and last name. We also don't wanna show our user the ID field or the user ID field so you won't give them the option to display those. So these are the four options, so these are the four field that are either required or not allowed at all. Next we wanna create an array of all the fields that will eventually be filled. We also want to create a deferred object and we say, can say q.defer. And finally we need to get the contacts. So we'll say Contacts.query. Now, the way this works is this is an asynchronous request, right? So to actually use the contacts we need to pass a function here. But the contacts will actually be applied to this contacts object out here that is our variable. And so inside this callback we refer to the contacts variable outside, which will be populated after our query has finished running. So what we wanna do is a contacts.forEach and we'll loop over each contact individually. And we need to find every key that is on this contact object. So we'll say contact Object.key for the contact, and then we'll do a forEach and loop over every key. Now in here we want to do allFields.push(k) to push the key into this array. However, we're gonna have duplicates, right, because for starters, we know that every contact is going to have a first name and last name key and then most likely, multiple contacts will have optional keys as well. For example, I can imagine email would be a very popular field. So we only want to push this into allFields if it's not all ready there, but also if it is not in the ignore array. So in front of this, let's say if allFields.indexOf(k), if it's less than 0, meaning it is not all ready in the array and also if ignore.indexOf(k) should also be less than 0, meaning that it is not in either of these arrays. So before pushing it into all fields we wanna make sure that it is not in all fields yet, and that it is not a field that we want to ignore. If both of these things are true then we can push it into all fields without any problem at all. Okay, so after we've looped over every contact and every key in every contact we can do deferred.resolve and pass in allFields. Okay, so if you're not familiar with the promises work I do recommend you check them out, they are extremely useful in JavaScript in general and here we're finding them very useful in Angular. Basically they allow us to return what's called a promise and basically that's a promise of future information. So we'll return this to deferred objects promise in just a second here. And basically, we can go ahead and use that object before the information has actually finished being created. But once the deferred object is resolved, all those fields will be made available in whatever place we want them to be. So you'll see how that works in just a second. So we can say, return, at the bottom here. And we want to go ahead and return our object. And it's going to have a couple of methods. First it's going to be the get method, which will go ahead and get whatever the user has stored in their database record on the server, right? So return $http.get to the url. So we're just gonna call this function right here in the server. And this will be returned whatever array we have here, whether it's an empty array or some values that were previously stored. Now when we want to set the fields, we'll take the newFields as an array and we can return $, and we wanna post to the url. And for the data, we'll pass at fields, and this is going to be newFields. Excellent. So, this gets what the user has previously stored, and this sets what the user is wanting to store, but what we also need is a way to get what they can store if they want to. Basically we need a way for them to see their options. And this is where our promise comes into effect. We want a headers function here, and this headers function is going to return the deferred object that we created, .promise and so basically this is going to return our all fields array. All right so there is our Fields factory and now let's actually put this into use so let's go ahead and open up public/src/app.js and let's add another route here so we can say when the route is /settings, then we want to use the controller we'll call it the SettingsController and the templateUrl is going to be views/settings.html. There we go. Let's go ahead and start with the views. So public/views/settings.html. So we'll start with just an h2 at the top which will say Settings then underneath that we can have a paragraph which will say Choose fields to display in the list view and then under here let's have a div with a class of checkbox and then we will say ng-repeat and we're gonna repeat this for each field in allFields. That's the property name that we will give to the array. >> And then we'll have our label here which actually with a checkbox input can surround the label, so we'll set input.type equals checkbox and the value of this is going to be whatever the field is so this is gonna be the name of the field but then we have to decide if it's checked or not and we can't use the normal checked property because this is an Angular app so we'll use ng-checked. And this way we can actually write an expression. So we'll say ng-checked if fields, now notice this is fields not all fields, this is just fields which have been chosen if fields.indexOf(field) is and we'll say if it is greater than minus 1, and that means that the field that we're currently looking at is in this fields of array which is the array which the user has chosen to show and so we want it to be checked. Now when the user clicks this, we want to add it to the field so we'll say ng-click and we will call the toggle function and we'll pass that field in. And that is our entire input, so that underneath this we will set field and we will pass it through a labelCase filter so that it displays nicely. This is all we need to do for our settings view here because this div here of course will be repeated for every field in all fields. Now, you may think the next step is to create our settings controller, but before we do that we need to think about one more thing. The SettingsController is basically going to take a list of fields and offer them to the user and say, choose the ones you wanna keep. And when the user selects the fields to keep, it will go ahead and save those to the server. But then what we, but then the other thing we need to think about is actually using those fields in our front end application here, cuz just setting them is not enough, we need to actually use them as part of our application. So to do this, we're here in our app.js file here and let's go ahead and create a value, and this value is gonna be called options and for now it's just gonna be an empty object. And basically this is where we could set any options that we want for our application, and this is where we can set any preferences or options that we are offering the user. Now, of course by default it's an empty object here, but what we need to do is load in, and any options that the user has on the servers, so let's add a run block here, which will run immediately when our application is started. And in here we need to take that options value that we just set above here. And we also wanna take the Fields factory that we just created. And we wanna run Fields.get and you might recall this function here will return the http.get request that we made which is actually a promise of its own, and so we can say http.get and then we can call .then, which will happen when the response is returned. So we can say .then, and then the data here is the data that we will get back from the server. And then inside of this, we can just options.displayed_fields = data. So that way, this options object will be set, and so now we can use this options object inside of our SettingsController, and actually, you know, instead of using then let's use the success callback. It just makes sure that we have a positive response. So now let's go ahead and open up our public source controllers file and down here at the bottom we're gonna create our new controller. And as we said we're gonna call it the SettingsController, and it needs to take the scope, and the rootScope, and we also wanna take that options object that we just created, and finally, we're gonna take Fields, as well. So let's just get that use of rootScope out of the way. So we'll say rootScope.PAGE = settings. And this is a reminder that we actually need to add a link to our settings page in our navigation. Okay, now let's worry about the local scope, so we'll say scope.allFields, we'll start out as an empty array. We'll fill that in just a second, we can say scope.fields = options.displayed_fields. So these are fields that the user has decided to display. Then we can use Fields.headers. And remember, this is going to return all of the options, this is not gonna return one's they have chosen but all of the ones that they can choose, so we'll say Fields.headers and then we can say then, and we'll have a function here which takes that data. Now the data that will be passed to this function here is going to be the data that we resolve our deferred object promise with and so that will be allFields. So here in controllers, we can then say, $scope.allFields equals the data there. Now, at this point, we should be able to come to our application here and we'll just head manually to the settings page. And you can see we have a list of all of the different properties that are available. And that's just fine. And I can go ahead and check some of these. However, if I go ahead and check those, and I go back to All Contacts, notice that nothing else has shown up here. If I go back again, you can see that those are now unchecked. And the problem is, if we come back to our Settings template, you can see that, for those to actually be saved, we need to create this toggle function here, which will be called when we do ng-click. So, let's go ahead and do $scope.toggle, and this is a function that takes a field name. And what we need to do is say var i = options.displayed_fields.indexOf(field) that we have right here. So this will tell us if the field that was clicked is already in the display fields or not. Now, it's important to know this because if it is there that means they are unchecking the box and they wanna remove it, so we need to remove it from that array. However, if it's not there then we wanna add it. So, we can say if i is greater than minus 1, then we know that it is already in there, and we wanna remove it. So we'll say options.displayed_fields.splice, and we splice from i, and we go a distance of 1. So that will just remove, in place, this one object from the array. Otherwise, we'll go ahead and say options.displayed_fields.push, and we'll go ahead and push that field into the array. And now, after we have done that, we can do Fields.set and we can say options.displayed_fields. And this we'll go ahead and make our post request to the server and set those, and once that's been set, it'll be saved to the server. But we don't actually have to worry about that request going to the server and coming back before our user moves back to the list page because we are actually changing it right on this options object here which we now need to use in way up here in our list controller. And so up here in the list controller let's go ahead and request options and where we're setting the fields here that we want to display with the firstName and lastName. So, we'll say firstName and lastName .concat and we will concatenate the array first and last name with options.displayed_fields and it's as easy as that. Now one more thing before we are finished with this, let's open up public/views/nav.html, and I'm just gonna copy our all contacts link here and I will paste it down here below in our right navbar. I'll change all here to settings, so that this will only be highlighted if we're on the settings page. We"ll change the link to go to the settings page, and, of course, we'll change the text to say, Settings. There we go. Now we'll have to log in to our application to see this feature in action. Notice we now have a Settings button over here, I can click the Settings button. So I'm going to choose Email, Homephone, and Favorite Color. And those should automatically be saved. So, now if I click all Contacts, notice we have Email, Home Phone, and Favorite Color showing up there. Excellent. So, now if I refresh this page, you can see that those values have been saved to the server, so they are still there in this table. If I log out and log back in, you can see that those values are there. Let's go ahead and add an email to one of these fields to see, so we'll add an email address here. We'll just call this email and we'll say We'll add that and now if we go back to Contacts you can see it shows up right there in our added fields, excellent. And so there we have the final feature of our application.

Back to the top