Skip to content
This repository has been archived by the owner. It is now read-only.

Google Maps service tutorial #1403

Merged
merged 1 commit into from May 10, 2016

Conversation

Projects
None yet
6 participants
@toddjordan
Copy link
Contributor

commented Apr 25, 2016

Part of issue #1374

This is our services tutorial page. features the following

  • incorporate testing (plan to do this for all applicable tutorial pages eventually)
  • doing some simple caching of maps to show the benefit of using a service
  • using a utility to abstract the details of a 3rd party api

I took an outside in approach, starting with the display and going back to the actual interfacing with the 3rd party api. I also start sections with tests.

@toddjordan toddjordan changed the title wip simple service tutorial simple service tutorial Apr 25, 2016

```

The CLI generate util command will create a utility file and a unit test.
We'll delete the unit test since we don't want to test Google's code.

This comment has been minimized.

Copy link
@toddjordan

toddjordan Apr 25, 2016

Author Contributor

I can add a unit test, but didn't feel like constantly hitting goole maps would be as helpful. We've already create a unit test with the maps service.

From your project's root directory, run the following command to put the Google maps script in your projects vendor folder as `gmaps.js`.

```shell
curl -o vendor/gmaps.js https://maps.googleapis.com/maps/api/js?v=3.22

This comment has been minimized.

Copy link
@toddjordan

toddjordan Apr 25, 2016

Author Contributor

I know this is unix-centric, but is this ok now that windows has a shell? 😀

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

Maybe note it above where I added a note?

createMapElement() {
let element = document.createElement('div');
element.className = 'map';
element.setAttribute('style', 'width:300px;height:300px;');

This comment has been minimized.

Copy link
@toddjordan

toddjordan Apr 25, 2016

Author Contributor

Not ok with hardcoding this. Alternatives are setting a classname here the dev can define in app css, or passing dimensions in thru the component.

This comment has been minimized.

Copy link
@toddjordan

toddjordan May 7, 2016

Author Contributor

Am going to keep for now until we add the style addon section.


1. A component to display a map on each rental listing.
2. A service to keep a cache of rendered maps to use in different places in the application.
3. A utility function to create a map from Google's map API

This comment has been minimized.

Copy link
@HeroicEric

HeroicEric Apr 25, 2016

Contributor

The period is missing from this list item

In this case we want our Google Maps service to provide us with the map display, so all we want our component to do is take the output of our maps service, which is a map element, and append it to an element in the component's template.

All we want to do in our component is make sure its appending the output of the maps service to an inner map container element.
To limit the test to validating just this behavior we'll take advantage of the registration api to provide a mock amps service.

This comment has been minimized.

Copy link
@HeroicEric

HeroicEric Apr 25, 2016

Contributor

"api" -> "API"
"amps" -> "maps"

To get the test to pass, add the container element to the component template.

```app/templates/components/location-map.hbs
<div class="map-container"/>

This comment has been minimized.

Copy link
@HeroicEric

HeroicEric Apr 25, 2016

Contributor

I don't think self-closing <div> tags are valid. As I understand it, only void elements are allowed to omit the end tag.

let MockMapsService = Ember.Service.extend({
getMapElement(location) {
this.set('calledWithLocation', location);
return document.createElement('div');

This comment has been minimized.

Copy link
@HeroicEric

HeroicEric Apr 25, 2016

Contributor

It's not clear to me why this function creates and returns a <div> element.

This comment has been minimized.

Copy link
@toddjordan

toddjordan Apr 26, 2016

Author Contributor

because the maps service returns an element that gets appended to the dom in the component. I can add some text around it.

didInsertElement() {
this._super(...arguments);
this.$('.map-container').append(this.get('maps').getMapElement(this.get('location')));

This comment has been minimized.

Copy link
@HeroicEric

HeroicEric Apr 25, 2016

Contributor

This line might be easier to read if it were broken up a little bit:

let location = this.get('location');
let mapElement = this.get('maps').getMapElement(location);

this.$('.map-container').append(mapElement);
@HeroicEric

This comment has been minimized.

Copy link
Contributor

commented Apr 25, 2016

@toddjordan lots of good stuff here 😄

Have you thought about building a more simple version of the component without the service and bringing in the service to improve it after? I think it might be easier to focus on understanding how the service is helpful if you've already already got the basic version of the component working. It would also let you more gradually step into some of the tests before needing to create and inject the mock service.

@toddjordan toddjordan force-pushed the toddjordan:simple-service branch from 2a0a814 to b7f882f Apr 26, 2016

@toddjordan

This comment has been minimized.

Copy link
Contributor Author

commented Apr 26, 2016

@HeroicEric Thank you for reviewing. I'm making updates based on your changes. RE: starting with a simpler component, I could create a component that just shows an image of a map based on location and then swap that impl out with the maps service. I like the idea and will consider it. It'll be a matter of still keeping the tutorial section relatively short.

@toddjordan toddjordan force-pushed the toddjordan:simple-service branch 4 times, most recently from 6a892d7 to f77493a Apr 26, 2016

@toddjordan toddjordan added the tutorial label Apr 27, 2016

@toddjordan

This comment has been minimized.

Copy link
Contributor Author

commented Apr 27, 2016

@locks @acorncom can you guys take a look when you get a chance? Moving onto autocomplete


1. A component to display a map on each rental listing.
2. A service to keep a cache of rendered maps to use in different places in the application.
3. A utility function to create a map from the Google map API.

This comment has been minimized.

Copy link
@acorncom

acorncom Apr 29, 2016

Member

Google Maps API

@@ -0,0 +1,293 @@
For Super Rentals, we want to add the ability to display maps of where each rental is located. To implement this feature, we will take advantage of several Ember concepts:

This comment has been minimized.

Copy link
@acorncom

acorncom Apr 29, 2016

Member

For Super Rentals, we want to be able to display a map showing where each rental is.

2. A service to keep a cache of rendered maps to use in different places in the application.
3. A utility function to create a map from the Google map API.

We'll work on this feature outside-in, meaning we'll start with the display of the map and work our way back to the actual access of the Google Maps API.

This comment has been minimized.

Copy link
@acorncom

acorncom Apr 29, 2016

Member

Might simplify here by saying something like:

We'll start by displaying the map and work our way back to using the Google Maps API


For our maps component, all we'll require is a simple component interface that we can simply drop into a template.
The component will render a map pinned to whatever location we provide it.
Lets start by appending the component as we want it to the end of the `rental-listing` template, and provide it with the city of the current rental.

This comment has been minimized.

Copy link
@acorncom

acorncom Apr 29, 2016

Member

Instead of the above paragraphs, I went to edit things a bit and simplified down to:

We'll start by adding a map component that shows the rental's city on a map

{{location-map location=rental.city}}
```

Next step is to get our page compiling by using Ember CLI to generate this component.

This comment has been minimized.

Copy link
@acorncom

acorncom Apr 29, 2016

Member

Next, generate the map component using Ember-CLI


The Ember CLI component generator will create for us a test file, a component JavaScript file, and a template.
To think about what we want our component to do, will write a test case.
In this case we want our Google Maps service to provide us with the map display, so all we want our component to do is take the output of our maps service, which is a map element, and append it to an element in the component's template.

This comment has been minimized.

Copy link
@acorncom

acorncom Apr 29, 2016

Member

Running this command generates three files: a component Javascript file, a template and a test file. To help think through what we want our component to do, we'll write a test case.

In this case, we plan on having our Google Maps service handle map display. So our component's job will be to take the results from the map service (which is a map element) and append it to an element in the component template.

@acorncom

This comment has been minimized.

Copy link
Member

commented Apr 29, 2016

@toddjordan I've got to shift to some other work, but will come back to this. Remember our caveats about tutorials getting more attention? I'm going to be pretty hard on your prose 😄 but I also really appreciate you putting this together

@toddjordan toddjordan force-pushed the toddjordan:simple-service branch from f77493a to 8c6b40a Apr 29, 2016

@toddjordan

This comment has been minimized.

Copy link
Contributor Author

commented Apr 29, 2016

@acorncom thanks for reviewing. Be as brutal as you need to be ;-)

@toddjordan toddjordan force-pushed the toddjordan:simple-service branch from 8c6b40a to 53a1450 Apr 29, 2016

@locks locks changed the title simple service tutorial Google Maps service tutorial Apr 29, 2016

this.set('myLocation', 'New York');
this.render(hbs`{{location-map location=myLocation}}`);
assert.equal(this.$('.map-container').children().length, 1, 'container should have one child');
assert.equal(this.get('mapsService.calledWithLocation'), 'New York', 'should call service with New York');

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

Same goes for this explanatory text. Maybe:

the service calledWithLocation variable should be set to New York

?

});
```

Notice that our mock service above sets a variable called `calledWithLocation`. We later assert on this variable to ensure that the service is being called with the same location provided to the component.

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

With the explanatory text in our test, think we can drop this sentence?

<div class="map-container"></div>
```

Then update the component to append the map output to our component.

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

to our container ?

```

Then update the component to append the map output to our component.
We'll add a maps service injection, and call the `getMapElement` function with the provided location.

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

Can we link to info about the service injection so folks can go off and read about this? Feels like we've got more magic here too ;-)

let location = this.get('location');
let mapElement = this.get('maps').getMapElement(location)
this.$('.map-container').append(mapElement);
}

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

Have we explained about didInsertElement and where that comes from? Maybe we could link to the guides as part of our discussion?

This comment has been minimized.

Copy link
@toddjordan

toddjordan May 4, 2016

Author Contributor

Added a link to the lifecycle rules page

### Fetching Maps With a Service

Now we have a passing component integration test, but no maps generating onscreen.
To generate the maps we'll implement a maps service.

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

Comma after "the maps"


### Fetching Maps With a Service

Now we have a passing component integration test, but no maps generating onscreen.

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

no maps showing up when we view our web page.


Accessing our maps API through a service will give us several benefits

* It is injected with a [service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern), meaning it will abstract the maps API from the code that uses it, allowing for easier refactoring and maintenance

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

Can we say this in less 'tech'? If I'm considering converting to Ember from a jQuery background, my eyes may have just glazed over ;-) I guess it's worth considering whether simple jQuery users are really our target audience here though ...

Accessing our maps API through a service will give us several benefits

* It is injected with a [service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern), meaning it will abstract the maps API from the code that uses it, allowing for easier refactoring and maintenance
* It is lazy-loaded, meaning it won't be initialized until its called the first time.

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

until it's called

And why would our newcomer think this is an advantage?

* It is injected with a [service locator pattern](https://en.wikipedia.org/wiki/Service_locator_pattern), meaning it will abstract the maps API from the code that uses it, allowing for easier refactoring and maintenance
* It is lazy-loaded, meaning it won't be initialized until its called the first time.
* It is a singleton, which will allow us cache map data.
* It follows a lifecycle, meaning we have hooks to execute cleanup code when the app stopped.

This comment has been minimized.

Copy link
@acorncom

acorncom May 3, 2016

Member

And why is this an advantage?

@acorncom

This comment has been minimized.

Copy link
Member

commented May 3, 2016

@toddjordan I took you at your word ;-) You've got some great material here, appreciate you putting this up so we can work on it. I need to take one more pass at this to look through the final section, but just ran out of time on this end. Will be back later this week :-)

@toddjordan toddjordan force-pushed the toddjordan:simple-service branch from 53a1450 to 041bc2b May 4, 2016

export default Ember.Object.extend({
init() {
this.set('geocoder', new google.maps.Geocoder());

This comment has been minimized.

Copy link
@chadhietala

chadhietala May 4, 2016

Member

There is no need to use this.set here instead you just assign it.

This comment has been minimized.

Copy link
@toddjordan

toddjordan May 4, 2016

Author Contributor

Agree that it doesn't buy me anything special, I just generally use set when setting properties for ember objects, and I think for the sake tutorial its fine to use set for setting object properties the get the reader in the mindset that in most cases they will need or want to.

const google = window.google;
export default Ember.Object.extend({

This comment has been minimized.

Copy link
@chadhietala

chadhietala May 4, 2016

Member

Is there a reason to use an Ember.Object here?

This comment has been minimized.

Copy link
@toddjordan

toddjordan May 4, 2016

Author Contributor

Generally, for the tutorial, I'm just using ember objects when there's a need for an object. I'm using an object in general here so that it can more easily be stubbed in tests. I could be swayed away from an ember object, but would need a good reason to not use it.

didInsertElement() {
this._super(...arguments);
let location = this.get('location');
let mapElement = this.get('maps').getMapElement(location)

This comment has been minimized.

Copy link
@chadhietala

chadhietala May 4, 2016

Member

Why use the lazy-injection version of a service, if all you are doing is immediately causing the lookup to occur?

This comment has been minimized.

Copy link
@toddjordan

toddjordan May 4, 2016

Author Contributor

Thanks for providing input, but I'm not sure what you are asking. Generally with services, you use the inject helper to make the service available for an object, but that service isn't available until it initially gets called with get. I agree in this case that the component is getting the service pretty soon after inject, but this is just showing a simple case. If you really wanted to go further and show off lazy loading, you could take the map lookup out of the didInsertElement and base it off of the component showing up in the viewport, but that would be beyond what we want to show in the tutorial.

@toddjordan toddjordan force-pushed the toddjordan:simple-service branch 2 times, most recently from b1e01e0 to cd6b944 May 4, 2016

Registration makes an object available to your Ember application for things like loading components from templates and injecting services in this case.

The call to the function `this.inject.service` injects the service we just registered into the context of the tests, so each test may access it through `this.mapsService`.
In the example we assert that `calledWithLocation` in our stub is set to the location we passed to the component.

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

Nicely done here, that's much clearer ...

* It is lazy-loaded, meaning it won't be initialized until it is called the first time.
In some cases this can reduce your app's processor load and memory consumption.
* It is a singleton, which will allow us cache map data.
* It follows a lifecycle, meaning we have hooks to execute cleanup code when the app stopped, preventing things like memory leaks and unnecessary processing.

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

when the app stops

Nicely done on explaining things here, I think that makes it far clearer to folks new to these ideas ...

* It is a singleton, which will allow us cache map data.
* It follows a lifecycle, meaning we have hooks to execute cleanup code when the app stopped, preventing things like memory leaks and unnecessary processing.

Lets get started creating our service by generating it through Ember CLI, which will create the service file, as well as a unit test for it.

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

Let's get started

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

Also, are we standardizing on calling it Ember-CLI?

});
```

Note that the test uses a dummy object as the returned map element. This can be any object because its only used to assert that the cache has been accessed.

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

because it's only used


### Making Google Maps Available

Before implementing the map utility we need to make the 3rd party map API available to our Ember app.

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

map utility comma


Since Google provides its map API as a remote script, we'll use curl to download it into our project's vendor directory.

From your project's root directory, run the following command to put the Google maps script in your projects vendor folder as `gmaps.js`.

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

vendor folder as gmaps.js (or if you're on Windows download the JS file and put it in vendor/gmaps.js):

```

Once in the vendor directory, the script can be built into the app.
We just need to import it into our build file.

This comment has been minimized.

Copy link
@acorncom

acorncom May 6, 2016

Member

We just need to tell Ember-CLI to import it using our build file:

@acorncom

This comment has been minimized.

Copy link
Member

commented May 6, 2016

@toddjordan Looks good to me, thanks for all the work! @locks any additional feedback here?

I'll plan on merging this after the weekend unless have other major changes to make. At some point, we need to ship it! ;-)

simple service tutorial
Add text to service tutorial

incorporate comments from heroiceric

more feedback incorporated

add highlighting for added lines of code

fix spelling errors

fix more spelling

incorporate initial feedback from @acorncom

fix misspell

though -> through

more @acorncom comments addressed

Add a clarifying statement around what register is

Additional comments from acorncom

@toddjordan toddjordan force-pushed the toddjordan:simple-service branch from cd6b944 to e98b888 May 7, 2016

@acorncom

This comment has been minimized.

Copy link
Member

commented May 10, 2016

Merging here, nice work!

@acorncom acorncom merged commit 85164fc into emberjs:master May 10, 2016

1 check passed

continuous-integration/travis-ci/pr The Travis CI build passed
Details
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
You can’t perform that action at this time.