-
Notifications
You must be signed in to change notification settings - Fork 3
Feedback #3
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Feedback #3
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -14,62 +14,16 @@ | |
|
|
||
| - - - | ||
|
|
||
| ##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 <a name="first-component"></a> | ||
| 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: | ||
|
|
||
| - View template (.stache file) | ||
| - 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: | ||
|
|
||
| <div>{{currentRestaurant}}</div> | ||
| <h1>{{currentRestaurant}}</h1> | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wish this example wasn't made irrelevant by the next example. |
||
|
|
||
| Add the code below to the /app/base_template.stache file: | ||
|
|
||
|
|
@@ -119,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, `<x-tags>`, `<my-element>`, and `<my-awesome-app>` are all valid names, while `<tabs>` and `<foo_bar>` 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*. | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's necessary to introduce |
||
|
|
||
| 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: | ||
|
|
||
|
|
@@ -146,24 +104,38 @@ As mentioned above, when the template containing the can.Component's tag is pars | |
|  | ||
|
|
||
| ####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. | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I really like to format code strings. Not sure how that works with DocumentJS though.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What do you mean?
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure myself. I think what I was trying to say is that DocumentJS probably has a link/format for these things already.
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do you mean a format for
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same syntax?
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, it uses the same markdown syntax that github supports.
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Cool. I'll keep formatting them then. |
||
|
|
||
| ####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. | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I removed a few commas here. |
||
|
|
||
|  | ||
|
|
||
| #####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. | ||
|
|
||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I wonder if going into
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Or maybe it should be moved into it's own section? I think going into |
||
| 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. | ||
|
|
||
| var myCanMapInstance = new can.Map({ | ||
| id: 12, | ||
| person: { | ||
| name: { | ||
| last: 'Ludwig' | ||
| last: 'Beethoven' | ||
| } | ||
| } | ||
| }); | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I felt like going into "dot" notation/deep properties was a bit of a leap from the first paragraph, so I added this example. But now both of these examples seem out of place. |
||
|
|
||
| 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. | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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 | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typo. |
||
| {}); | ||
|
|
||
|
|
||
| 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: | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Again, trying to avoid referring to |
||
|
|
||
| 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 | ||
| ``` | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe unnecessary, but I thought I'd try it out for size. |
||
|
|
||
| **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." | ||
|
|
||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Typos. |
||
| 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. | ||
|
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
||
| ##Connecting the Model to the Component | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For some strange reason, "folder structure" threw me off. "directory structure" sounds more correct to me, but that's completely subjective.