From b3fcc0c737ccb255eed6855d66a108ecdff557b8 Mon Sep 17 00:00:00 2001 From: Chris Gomez Date: Tue, 10 Mar 2015 08:19:14 -0400 Subject: [PATCH 1/2] First round --- 1_ApplicationFoundations.md | 2 +- 2_FirstComponent.md | 82 +++++++++++-------------------------- II_Setup.md | 18 ++++---- I_Introduction.md | 12 +++--- 4 files changed, 42 insertions(+), 72 deletions(-) diff --git a/1_ApplicationFoundations.md b/1_ApplicationFoundations.md index 291f325..e50e1b2 100644 --- a/1_ApplicationFoundations.md +++ b/1_ApplicationFoundations.md @@ -59,7 +59,7 @@ It should look like this: - + diff --git a/2_FirstComponent.md b/2_FirstComponent.md index 5b01a9f..6b58768 100644 --- a/2_FirstComponent.md +++ b/2_FirstComponent.md @@ -14,52 +14,6 @@ - - - -##Constructors in CanJS - -Before we work with any of the objects in CanJS, it will be helpful for us if we understand can.Construct. Most of the objects in CanJS are derived from can.Construct. can.Construct provides a way to easily use the power of prototypal inheritance without worrying about hooking up all the particulars yourself. - -Without going into exhaustive detail[^ConstructDetail], can.Construct contains a few methods we'll encounter frequently in other objects: - -[^ConstructDetail]: If you want to go into exhaustive detail on can.Construct, you can consult the [CanJS docs](http://canjs.com/docs/can.Construct.prototype.init.html). - -- Prototype - - init -- Static - - extend - -We'll look at the extend method first. - -###The extend method -can.Construct's `extend` method is used to create "constructor functions". Constructor functions create instances of objects. The extend method can take up to three arguments: - -1. name: string -2. staticProperties: object -3. instanceProperties: object - -The extend method behaves differently depending on the number of arguments you pass it. The name and staticProperties arugments are optional. For example, if you pass it one argument, it will be use the value you pass it to set its instanceProperties. If you pass it two arguments, it use the first to set its staticProperties, and the second to set its instanceProperties. Finally, if you pass in all three arguments, the first will set its name, the second in staticProperties, and the third its instanceProperties. - -In the example below, I only want to pass in staticProperties. Therefore, I must call the method as follows: - - can.Construct.extend({ - //Static properties here - }, - //Blank object as second parameter - {}); - -This pattern will apply to all objects in CanJS that have an extend method. - -###The init method -The `init` method is called whenever a new instance of a can.Construct is created. Init is where the bulk of your initialization code should go. Inside of the init function, the `this` keyword will refer to the new instance, and `this` will contains the arguments passed to the constructor. A common thing to do in init is save the arguments passed into the constructor. An example is below: - - var Person = can.Construct.extend({ - init: function(first, last) { - this.first = first; - this.last = last; - } - }); - - var actor = new Person("Abe", "Vigoda"); - ##First can.Component If you recall from the introduction, a can.Component is like a self-contained, mini web application---i.e., it's encapsulated. Because can.Components are encapsulated, they should each contain their own: @@ -67,9 +21,9 @@ If you recall from the introduction, a can.Component is like a self-contained, m - JS - CSS -This is why we created a components folder for our app---instead of, say, a JS folder. Each component we develop will be in a folder that contains all the files that support that component. This makes components portable, enabling you to reuse them across projects. It also isolates them, making them easier to test and maintain. +This is why we created a components directory for our app---instead of, say, a JS directory. Each component we develop will be in a directory that contains all the files that support that component. This makes components portable, enabling you to reuse them across projects. It also isolates them, making them easier to test and maintain. -In the components folder, create a subfolder called "restaurant_list". Inside that, create the following files: +In the components directory, create a subfolder called "restaurant_list". Inside that, create the following files: - restaurant_list_component.js - restaurant_list.stache @@ -91,7 +45,7 @@ Put the following code inside restaurant_list_component.js: Add the following code to restaurant_list.stache: -
{{currentRestaurant}}
+

{{currentRestaurant}}

Add the code below to the /app/base_template.stache file: @@ -123,7 +77,7 @@ Now, go back out to your app in the browser, and refresh it. You should see it p If you recall from the discussion above regarding can.Construct, whenever you declare an object using can.Construct it must be instantiated. Normally, you would either directly instantiate objects using the `new` keyword, or pass the constructor to an object that would create instances of it. *can.Component is an exception*. -All we have to do is declare the can.Component using its `extend` method. Once you declare your can.Component, you've registered your component with the system. When CanJS parses the base_template.stache file, and encounters the restaurant-list tag, it will automatically instantiate the can.Component associated with it, generate the Component's view inside of its custom tag, and bind that view to your component's scope. +All we have to do is declare the can.Component using its `extend` method. Once you declare your can.Component, you've registered your component with the system. When CanJS parses the base_template.stache file, and encounters the "restaurant-list" tag, it will automatically instantiate the can.Component associated with it, generate the Component's view inside of its custom tag, and bind that view to your component's scope. Let's look at an image that describes how all of this works, to make it clearer: @@ -154,16 +108,30 @@ The `scope` object is the can.Component's view model. The view model is an abstr ![](images/2_first_component/ComponentScopeTemplateLink.png) #####can.Map & can.List -The scope is a special type of object, called a "can.Map". can.Map objects are observable. Observable objects provide a way for you to listen for and keep track of changes to them. What this means, in this instance, is that if you make a change to your scope, those changes will be reflected automatically in your view. If you've cross-bound the values between your scope and your view, changes to your view will also be reflected in your scope. We'll see how this works in the next chapter. +The scope is a special type of object, called a "can.Map". can.Map objects are observable. Observable objects emit events whenever their properties change. can.view (and subsequently can.Component) subscribe to these events and update the DOM as needed. If you've cross-bound the values between your scope and your view, changes to your view will also be reflected in your scope. We'll see how this works in the next chapter. + +can.Map is able to emit these events because of the `attr` method. This is important. In order to broadcast the associated events when you change a property on a `can.Map`, you must use the `attr` method when setting or getting a value. + +The `attr` method can be used to either get or set a property on a can.Map. + + var myCanMapInstance = new can.Map({ + id: 12, + person: { + name: { + last: 'Ludwick' + last: 'Beethoven' + } + } + }); -can.Map objects listen for changes made using their `attr` method. This is important. In order to broadcast the associated events when you change a property on a can.Map, you must use the attr method when setting a value. + myCanMapInstance.attr('id'); // -> 12 -The attr method can be used to either get or set a property on a can.Map. `attr` works with deep properties---i.e., properties within properties. Here's an example: +`attr` works with deep properties---i.e., properties within properties. Here's an example: - //Get the first property off of the name property off of person - myCanMapInstance.attr('person.name.first'); + //Get the first property off of the name property off of person + myCanMapInstance.attr('person.name.first'); - //Set the last property of the person's name property - myCanMapInstance.attr('person.name.last', 'Bach'); + //Set the last property of the person's name property + myCanMapInstance.attr('person.name.last', 'Bach'); Observable arrays are also available with can.List, which is based on can.Map. \ No newline at end of file diff --git a/II_Setup.md b/II_Setup.md index 449a577..fd8c407 100644 --- a/II_Setup.md +++ b/II_Setup.md @@ -3,7 +3,7 @@ - - - **In this Chapter** - Configuring and Downloading CanJS - - Recommended folder structure + - Recommended directory structure - - - @@ -19,19 +19,19 @@ At the bottom of the page, click the download button. You'll be promoted to down There is one additional file we need, which we won't download. This file is special. You normally wouldn't want it to be a part of your final application, but it can be very helpful during development. The file is can.fixture.js. can.fixture allows you to simulate RESTful services. We'll cover how to include can.fixture in the next chapter. -In the next step, we'll set up the application's folder structure, and move the can.custom.js file into its appropriate folder in the app. +In the next step, we'll set up the application's directory structure, and move the can.custom.js file into its appropriate folder in the app. -##Folder Structure +##Directory Structure -A lot of frameworks recommend a structure that has separate JS, CSS, and HTML folders. When building a CanJS app, because our application will be built using components, we use a component-based folder structure. Off of a root folder called "PlaceMyOrder", create the following subfolders: +A lot of frameworks recommend a structure that has separate JS, CSS, and HTML folders. When building a CanJS app, because our application will be built using components, we use a component-based directory structure. Off of a root folder called "PlaceMyOrder", create the following subfolders: - PlaceMyOrder - - app + - app - - components - - models - - site_css - - libs + - components + - models + - site_css + - libs Copy the CSS file that accompanies this guide into your CSS folder. We won't be covering any of the CSS here. Next, copy the can.custom.js file you downloaded in the previous step into the "libs" folder. diff --git a/I_Introduction.md b/I_Introduction.md index 4361a28..be2ba86 100644 --- a/I_Introduction.md +++ b/I_Introduction.md @@ -1,7 +1,9 @@ #Up and Running with CanJS *Better Apps, Faster* -CanJS is a lightweight, modern JavaScript [MVVM](https://en.wikipedia.org/wiki/Model_View_ViewModel) framework that's fast and easy to use, while remaining robust and extensible enough to power some of the most trafficked websites in the world. To see just how simple it is to get an application up and running using CanJS, we'll develop one together. +CanJS is a lightweight, modern JavaScript [MVVM](https://en.wikipedia.org/wiki/Model_View_ViewModel) framework that's fast and easy to use, while remaining robust and extensible enough to power some of the most trafficked websites in the world. + +This guide will walk you through the steps of building a small e-commerce app with CanJS... ##The Basics Every CanJS application contains: @@ -13,15 +15,15 @@ Every CanJS application contains: - Routing ###Models -Models manage the data of an application. A model notifies the elements associated with it when its state has changed. In CanJS this is the can.Model object. can.Model handles all of your CRUD operations (Create, Read, Update, and Delete). They can also provide validation for your data, as well---serving as a single source for handling your data acquisition, and managing its integrity. +Models provide an interface for getting data from a server. In CanJS this is the can.Model object. can.Model handles all of your CRUD operations (Create, Read, Update, and Delete). They can also provide validation for your data, as well---serving as a single source for handling your data acquisition, and managing its integrity. ###Views -Views request information from the model, and use the data it provides to generate visual output that's meaningful to a user---in our case HTML. In CanJS, views are created using: +Views define the UI of the application. In CanJS, views are created using: -1. View Templates, +1. A Template 2. The can.view object -The view template can be plain HTML, or it can utilize a template library to provide it with more functionality. Most of the time, your views will work with a template library. CanJs supports several JS template libraries, including: +The template can be plain HTML, or it can utilize a template library to provide it with more functionality. CanJs supports several JS template libraries, including: - Stache - Mustache From 2cc376098153cee449c597f8aa066a2c46e2b678 Mon Sep 17 00:00:00 2001 From: Chris Gomez Date: Thu, 12 Mar 2015 16:28:39 -0400 Subject: [PATCH 2/2] Second round --- 2_FirstComponent.md | 22 +++++++++++++--------- 3_FirstComponentContinued.md | 6 +++--- 4_Models.md | 29 +++++++++++++++++++++++------ 5_ModelsContinued.md | 12 ++++++------ 4 files changed, 45 insertions(+), 24 deletions(-) diff --git a/2_FirstComponent.md b/2_FirstComponent.md index 6b58768..aab932f 100644 --- a/2_FirstComponent.md +++ b/2_FirstComponent.md @@ -73,11 +73,15 @@ Finally, we need to add a reference to restaurant_list_component.js in the index Now, go back out to your app in the browser, and refresh it. You should see it printing: "Hello Restaurant Customer". +#### Component tag names + +A component's tag name must contain a dash (-). So for example, ``, ``, and `` are all valid names, while `` and `` are not. + ###Auto Instantiation -If you recall from the discussion above regarding can.Construct, whenever you declare an object using can.Construct it must be instantiated. Normally, you would either directly instantiate objects using the `new` keyword, or pass the constructor to an object that would create instances of it. *can.Component is an exception*. +Typically in an object oriented programming environment you instantiate objects using the `new` keyword, or pass the constructor to an object that would create instances of it. *can.Component is an exception*. -All we have to do is declare the can.Component using its `extend` method. Once you declare your can.Component, you've registered your component with the system. When CanJS parses the base_template.stache file, and encounters the "restaurant-list" tag, it will automatically instantiate the can.Component associated with it, generate the Component's view inside of its custom tag, and bind that view to your component's scope. +All we have to do is declare the can.Component using its `extend` method. By extending can.Component, you've registered your component with the system. When CanJS parses the base_template.stache file, and encounters the "restaurant-list" tag, it will automatically instantiate the can.Component associated with it, generate the Component's view inside of its custom tag, and bind that view to your component's scope. Let's look at an image that describes how all of this works, to make it clearer: @@ -100,28 +104,28 @@ As mentioned above, when the template containing the can.Component's tag is pars ![](images/2_first_component/ComponentTagRenderedHTML.png) ####Template -The `template` property of the can.Component contains the string value of the can.Component's template. Note that the template property just contains a string value. You can inline the template, if it is small. However, the recommended way of working with templates, to maintain separation of concerns, is to keep them in their own files and load them using can.view, as we have done here. +The `template` property of the `can.Component` contains the string value of the `can.Component`'s template. Note that the template property just contains a string value. You can inline the template, if it is small. However, the recommended way of working with templates, to maintain separation of concerns, is to keep them in their own files and load them using `can.view`, as we have done here. ####Scope -The `scope` object is the can.Component's view model. The view model is an abstraction of the view that exposes public properties and functions. Any property or method defined on the scope object is available from the can.Component's template as either a Stache data key, or a function. In our example above, we created a property, "currentRestaurant", and then referenced it as a Stache data key in our template. +The `scope` object is the can.Component's view model. The view model is an abstraction of the view that exposes public properties and functions. Any property or method defined on the scope object is available from the can.Component's template as either a Stache data key, or a function. In our example above, we created the property "currentRestaurant", then referenced it as a Stache data key in our template. ![](images/2_first_component/ComponentScopeTemplateLink.png) #####can.Map & can.List The scope is a special type of object, called a "can.Map". can.Map objects are observable. Observable objects emit events whenever their properties change. can.view (and subsequently can.Component) subscribe to these events and update the DOM as needed. If you've cross-bound the values between your scope and your view, changes to your view will also be reflected in your scope. We'll see how this works in the next chapter. -can.Map is able to emit these events because of the `attr` method. This is important. In order to broadcast the associated events when you change a property on a `can.Map`, you must use the `attr` method when setting or getting a value. +can.Map is able to emit these events because of the `attr` method. This is important. In order to dispatch the associated events when a property is changed on a `can.Map`, you must use the `attr` method when setting or getting a value. + +The `attr` method can be used to either get or set a property on a can.Map. -The `attr` method can be used to either get or set a property on a can.Map. - var myCanMapInstance = new can.Map({ id: 12, person: { name: { - last: 'Ludwick' + last: 'Ludwig' last: 'Beethoven' } - } + } }); myCanMapInstance.attr('id'); // -> 12 diff --git a/3_FirstComponentContinued.md b/3_FirstComponentContinued.md index c90705d..58dec29 100644 --- a/3_FirstComponentContinued.md +++ b/3_FirstComponentContinued.md @@ -142,10 +142,10 @@ Next, you'll notice that when you select a restaurant from the list, the followi ![](images/3_first_continued/RestaurantDetailsFirstDisplay.png) -We set up the display of the current restaurant section earlier in the template. The default value for currentRestaurant, when the RestaurantListComponent is first loaded is 'undefined'. Setting the value to 'undefined' causes the Stache template to remove it from the DOM. As soon as we set currentRestaurant to a valid value, the scope, which is an observable can.Map, broadcasts this change, and the template refreshes automatically, rendering the current restaurant section. +We set up the display of the current restaurant section earlier in the template. The default value for `currentRestaurant`, when the `RestaurantListComponent` is first loaded is `undefined`. Setting the value to `undefined` causes the view to remove it from the DOM. By changing the `currentRestaurant` of the scope (which is an observable `can.Map`) to a valid value a "change" event is triggered. Having known that the template depends on this value, the view is listening for this change event and makes the appropriate changes to the DOM based on the new value and the template. -##View Models -It's considered a best practice to keep your can.Components thin. This helps maintain readability, and maintainability. To accomplish, you extract your scope from the can.Component into a can.Map. +## View Models +It's considered a best practice to keep your can.Components thin. This helps maintain readability, and maintainability. To accomplish this, extract your scope from the can.Component into a can.Map. Open up restaurant_list_component.js, and add the following code: diff --git a/4_Models.md b/4_Models.md index edb4fa1..4a5e3db 100644 --- a/4_Models.md +++ b/4_Models.md @@ -10,7 +10,7 @@ - - - -The next item we're going to go over is can.Model. Models make interacting with JSON REST services *really easy*. They do this by encapsulating most of the code required to connect to a service, and managing the data the service returns. Additionally, can.Model extends can.Map, meaning that the objects returned have all of the features of a can.Map, such as being observable. +The next item we're going to go over is can.Model. Models make interacting with JSON REST services *really easy*. They do this by abstracting most of the code required to connect to a service, and managing the data the service returns. Additionally, can.Model extends can.Map, meaning that the objects returned have all of the features of a can.Map, such as being observable. We'll use a can.Model to provide data for our restaurant list. @@ -22,11 +22,11 @@ In the models folder, create a file called "site_models.js". Add the following c var RestaurantModel = can.Model.extend({ findAll: "GET /restaurants" }, - //Include second, blank parameter object to set staticProperties + //Include second, blank parameter object to set instanceProperties {}); -Because it is a can.Construct, can.Model.extend can take up to three parameters: +Like most constructors in CanJS, can.Model.extend can take up to three parameters: 1. name 2. staticProperties @@ -40,13 +40,30 @@ A can.Model's staticProperties parameter has several reserved properties you can 4. update 5. destroy -The findXxx methods are available directly off of the object definition (i.e., they are static). The create, update, and destroy methods are available off of specific instances of a can.Model. We'll see how to use these below. +The findXxx methods are properties of the contructor (i.e., they are static). The `create`, `update`, and `destroy` methods are properties of the prototype. So for example.. + +``` +var MyModel = can.Model.extend({ + findAll: function () { + // Static method + } +}, { + destroy: function () { + // Instance method + } +}); + +MyModel.findAll(); // Reference a method defined on the contructor + +var modelInstance = new MyModel(); +modelInstance.destroy(); // Reference a method defined on the prototype +``` **Reminder**: The number of parameters you pass in to an extend method is important. If you pass in a single parameter object, the extend method will use that to set the instanceProperties. If you pass in two parameter objects, the *first* object passed in will be used to set the *staticProperties*. The second parameter will be used to set the *instanceProperties*. Here, we only want to set the staticProperties, so we must pass in a second, blank object. ##The Data for Our Model -We're not going to connect to a server to retrieve our data; however, we're going code our model as if we were. How can this possibly work? CanJS provides a handy utility, can.fixture, that we can use to mimic the functionality of connecting to a server. As the CanJS docs say, "can.fixture intercepts an AJAX request and simulates the response with a file or a function. You can [can.fixutres] to develop JavaScript independently of backend services." +We're not going to connect to a server to retrieve our data; however, we're going to code our model as if we were. How can this possibly work? CanJS provides a handy utility, can.fixture, that we can use to mimic the functionality of connecting to a server. As the CanJS docs say, "can.fixture intercepts an AJAX request and simulates the response with a file or a function. You can use [can.fixture] to develop JavaScript independently of backend services." can.fixture is not included with the base CanJS package. It's a good practice to keep it separate from your production CanJS library, which is why we downloaded it from its CDN in a separate script tag, rather than including it with our custom download. *If you use can.fixture during development, remember to remove it once you are connecting to your REST services*. @@ -81,7 +98,7 @@ Let's create a fixture that will respond to our requests for menu item data. Cre ]; }); -The first argument to can.fixture, "GET /restaurants", tells CanJS to intercept any GET requests to the resource "/restaurants". The second argument is a function that returns the data we want to get when the application makes a service call. Because we're simulating a findAll method, we need to return an array. The findAll method expects an array. By default, if it does not receive one, it will throw an error. If you need to connect to services that return data that doesn't match the expected return type of the findXxx methods, don't fret. There are ways to manage this, which we'll work with later on. +The first argument to can.fixture, "GET /restaurants", tells CanJS to intercept any GET requests to the resource "/restaurants". The second argument is a function that returns the data we want to get when the application makes a service call. Because we're simulating a findAll method, we need to return an array. The findAll method expects an array. By default, if it does not receive one, it will throw an error. If you need to connect to services that return data that doesn't match the expected return type of the findXxx methods, don't fret. There are ways to manage this, which we'll learn later on. ##Connecting the Model to the Component diff --git a/5_ModelsContinued.md b/5_ModelsContinued.md index 4b866b8..b5e9dd3 100644 --- a/5_ModelsContinued.md +++ b/5_ModelsContinued.md @@ -33,7 +33,7 @@ Add the following to order_form.stache: {{/each}} - {{#delivery}} + {{#each delivery}}
- {{/delivery}} + {{/each}} @@ -62,7 +62,7 @@ In the template above, we're binding the values: - address - telephone -to the "delivery" object of the View Model. We do that using both the delivery section, defined by {{#delivery}} ... {{/delivery}}, and the `can-value` attribute. `can-value` is a can.view attribute that establishes two-way binding between an element in a template and its associated View Model. +to the "delivery" object of the View Model. We do that using both the delivery section, defined by {{#each deliver}} ... {{/each}}, and the `can-value` attribute. `can-value` is a can.view attribute that establishes two-way binding between an element in a template and its associated View Model. Add the following to order_form_component.js: @@ -119,12 +119,12 @@ Add the following to order_form_component.js: }); ##Saving and updating a model -Let's look at a few items in the code above. Notice that we're creating a new instance of a model (MenuOrderModel) in the createOrder function. Unlike data access methods, which are called statically off of the prototype, the save, update, and delete methods are called off of a specific instance of a model. So, if we want to create a new order, we will need to work with an instance of the MenuOrderModel. +Let's look at a few items in the code above. Notice that we're creating a new instance of a model (MenuOrderModel) in the createOrder function. Unlike data access methods, which are called statically off of the prototype, the `save`, `update`, and `delete methods are called off of a specific instance of a model. So, if we want to create a new order, we will need to work with an instance of the MenuOrderModel. -We assign the value of this.attr('details') to the MenuOrderModel's delivery property. If you recall, we bound the values of the name, address, and telephone number fields to the "delivery" object in the order_form.stache view template. Now, all we need to do to get the values of those fields is reference them off of the View Model's delivery property. +We assign the value of this.attr('order') to the MenuOrderModel's delivery property. If you recall, we bound the values of the name, address, and telephone number fields to the "delivery" object in the order_form.stache view template. Now, all we need to do to get the values of those fields is reference them off of the View Model's delivery property. ###Moving from DOM to the model -When we created the RestaurantListComponent, we used the {{data '...'}} Stache key, and jQuery to obtain a reference to the restaurant object associated with the choice the user selected in the restaurants dropdown. Ideally, we don't want to be interacting with the DOM directly in our application. We want CanJS to do that for us, so we can focus on the application itself. In the createOrder function, instead of getting our data from the DOM, we get it from our scope. +When we created the RestaurantListComponent, we used the {{data '...'}} Stache key, and jQuery to obtain a reference to the restaurant object associated with the choice the user selected in the restaurants dropdown. We almost never want to be interacting with the DOM directly in our application. We want CanJS to do that for us, so we can focus on the application itself. In the createOrder function, instead of getting our data from the DOM, we get it from our scope. ###Save fixture Open up fixtures.js (in the models folder), and add the following fixture: