Lesson 12

Posting new chirps

16

⚠️ This tutorial uses an old version of Ember. Some things might have changed!

In this lesson, we will build a modal component to let the user post new chirps.

Creating the modal

Modals can be a little tricky since they're supposed to always be on top of the rest of the elements. Again though, let's not reinvent the wheel, there's a great addon called ember-modal-dialog that makes this easy to implement!

As always with addons, restart the Ember server. Then add these two lines to your app.scss-file, right after you import the layout:

app/styles/app.scss

Now let's see if the modal works by adding some new Handlebars-code in our application template (right after the liquid-outlet-tag):

app/application/template.hbs


It works! Even though it's not super pretty :-)


Before we go on, let's fix the modal styling. We want to make sure that we have an overlay and box that fit with the rest of the app UI.

Restyling and adding functionality

Open up the file app/styles/ui.scss and add these lines at the bottom:

app/styles/ui.scss


That's a little cleaner!


Alright, now it's time to fix the functionality. We want the modal to be hidden by default, and appear when we click on the header's green "Chirp"-button.


We'll start by changing the markup a bit so that the modal only appears if the variable showingComposeModal is true. We'll also bind a close-action to the modal (this action is called when you click outside the modal on the translucent overlay):

app/application/template.hbs

showingComposeModal (in the if-statement) is a simple boolean. It is false by default and changes whenever toggleComposeModal is called.

app/application/controller.js

Finally we bind an action to the existing Chirp-button:

app/application/template.hbs

You should now be able to open the modal by clicking on the green Chirp-button in the header, and close it by clicking outside of the modal! Sweet!

Bubbling up actions

So we can now open the modal by clicking the button in the header. However, we also want to be able to open it from the "What are you doing"-speech bubble on our home page.


Right now if we click this, we just get a SweetAlert.


Let's fix the action in our component and remove the SweetAlert. We'll call the action "compose" instead of the old "showAlert".

app/components/status-update/template.hbs

Now we want the "compose"-action to "bubble up" until it reaches the controller that contains the toggleComposeModal-action. This is how we do it:

app/components/status-update/component.js
app/home/template.hbs

The compose action is now bubbling up and triggering the openComposeModal-action in the home controller. From here, we can inject the application controller on the page so that we can call its toggleComposeModal-action from the home controller!

app/home/controller.js


The final result!


Turning it into a component

Our modal is now opening and closing the way we want, so let's move on to the next step by replacing the "Trying out the modal"-text with the content that we need! For that, we'll create a new custom component and add some template markup (you probably know the drill by now).


This is the design we want in our modal!


app/application/template.hbs
app/components/compose-modal/template.hbs

Notice that we're binding a variable called chirpText to the textarea-element in the same way that we did for our login-form.


The p.remaining-chars-element displays the number of characters that you have left for your chirp, although right now it only shows a static number (140).


Almost there! But it needs some styling!


Let's add some CSS to this. Just like we've done with our previous components, we'll create a styles.scss-file inside the component's folder, which will be picked up automatically:

app/components/compose-modal/styles.scss


Your modal should now look like this. Awesome!


Adding functionality

The first thing we want to do in terms of functionality is to automatically focus on the textarea when the modal appears, so that the user can start typing directly. This can be done by listening to the didInsertElement event, which will trigger whenever the component is rendered on the page:

app/components/compose-modal/component.js

Okay, a few things to clarify here. First of all, what is Ember.run.scheduleOnce('afterRender')? This is a method that makes sure that all the elements we have in our markup have been loaded into the view before we start manipulating them (because in order to focus on the textarea, we need to know if it's actually there first)!


"But wait", you might say, "wasn't that what didInsertElement was supposed to check? Actually, the didInsertElement event only checks if the component's root element has been rendered, there's no guarantee that all of its children (including the textarea) have finished rendering as well. Therefore, if you try to focus() on the textarea without wrapping it in an afterRender, it might not work.


Secondly, you might not be familiar with the () =>-thing.


This is actually syntax from the new version of JavaScript: EcmaScript 2015 (previously called EcmaScript 6). Even though it's not available in browsers yet, Ember uses a library called Babel, which compiles ES6 syntax to code that works with current versions of JavaScript. This is awesome since it allows us to to use all the new language features without having to worry about backwards-compatibility.


The "fat-arrow", () =>, is just a short way of writing function(). There's one important difference though: the this inside of it keeps the scope of its parent. This comes in handy since we would otherwise have to declare a new variable at the top of the method (e.g. var self = this), just to be able to reference it inside the enclosing function.


Alright, that was a rather lengthy explanation for 5 lines of code. Next up we want to add a new chirp record to the store once the user clicks the "Chirp"-button. In our markup we've already specified {{action "postChirp"}}, so let's create that action!


But first, in order for a component to gain access to the store, we need to inject it as a service. Right after the end of the focusOnTextarea-method, add the following line:

app/components/compose-modal/component.js

Now we can access the store like we've done in our routes with this.store, but this time it will be through this.get('store') (because it's injected as a service). After that line, we can finally specify what the postChirp-action should do:

app/components/compose-modal/component.js

We're almost done! However, if you try posting a new chirp now, you will see this error message in the console:



You probably know what to do already! Open up the Mirage config.js-file and add the following line:

app/mirage/config.js

After typing in this code, you should now be able to write a chirp, click the "Chirp"-button, and see it appear almost instantly in the feed!


Don't worry if your username doesn't appear next to the chirp. This is just a limitation due to the fact that we're using fixtures instead of a real REST API, and it will be fixed soon!


Isn't it liberating not to have to deal with jQuery selectors or manually injecting pieces of markup into your HTML? Most of the time in Ember, the only thing you need to think about is the data in your models, and as long as your markup logic is solid, Ember will take care of the rest by updating all the occurrences of your data throughout the application.

Fixing the details

The last thing we want to fix is the character count. Right now it's static and always displays "140", but we want it to count down as we type, and turn red if the number becomes negative.


We'll start with the countdown. In our markup, we'll replace the static number with a variable called remainingChars. This variable will be a computed property in our component-file, based on the length of chirpText.

app/components/compose-modal/template.hbs
app/components/compose-modal/component.js

After this change, you should see the number at the bottom of the modal decrease as you type. Cool!


Now for the last feature, we want the number to turn red if you have no characters left. This will be done by adding the class "warning" to the text.


First, let's create yet another computed property. This one will just return a boolean depending on if the character limit has been reached or not.

app/components/compose-modal/component.js

Now that we have that variable, we can simply bind the class in our markup to it like this:

app/components/compose-modal/template.hbs

Finally, let's just make a slight modification to our postChirp action, so that it prevents the user from posting if they surpass the character limit:

app/components/compose-modal/component.js

Again, the use of HTMLBars and computed properties makes our code much more elegant, readable and maintainable compared to manually replacing the values through DOM selectors.


That's it! It all works!


By now, you might have noticed that, even though you can post new chirps, as soon as you refresh the page, they are gone! This is because they aren't stored in any database anywhere. Not to worry though, in our next chapter, we'll finally get rid of the Mirage fixtures and build a real Node.js backend with a database!

Comments

Daniel Jeffery

This would be a good candidate for toggleProperty() :)

Kirk Israel

In general the "store:Ember.inject.service()" could use some more explaining

Pavel Bastov

Agree with the comment above.

Jeffrey Horn

We've already seen it before, but an explanation of this.attrs() could be helpful.

Kirk Israel

can you explain a bit on "focusOnTextarea", like the key isn't special...

Tristan Edwards

@kirkisrael: Yes, for now it works exactly the same. The didInsertElement() -function is a so-called prototype extension, and it seems like the Ember core team wants to move away from that in the future which is why I use decorator-style methods instead. :-)

Kirk Israel

Looking at it more... as you say, "afterRender" is critical in this case (though my coworker has some other examples of similar things that didn't need to do that). But he points out


focusOnTextarea: Ember.on('didInsertElement', function() {


can be replaced with


didInsertElement() {


are those about the same?

Andy Pickler

Small thing...at this point in the source we don't need a comma at the end of Ember.inject.service() , because we don't have anything after in the Ember.Component.extend argument.

Daniel Jeffery

But then in the code block below there is ... :P!


Leave out at own peril ;)

Manvendra Singh

It's really a fun to see these types of comments here on this topic of injecting the service.. :D

Daniel Jeffery

Haha yeah!

Lynn Alexander

I'm getting a build error on this and the next one: <b style="color: rgb(0, 0, 0); font-size: 1em;">A block may only be used inside an HTML element or another block.</b>

Malik Dixon

Hello Tristan. I am getting an error which states the following: Assertion failed: A helper named 'compose-modal' could not be found. I can use some assistance with this one.

Tristan Edwards

@malikdixon47: That means that the compose-modal component hasn't been created. Maybe you mistyped something in the last bash command? Make sure that there's a folder at /app/components/compose-modal

Lynn Alexander

ok now it is showing, but it is open as soon as browser opens and can't close. What did I do wrong?

Lynn Alexander

never mind, forgot if statement

Alex Molina

<p class="p1">app/home/controller.js</p>

Tom Kay

"z-index: 999;" results in ember-modal-overlay laying on top of the ember-modal-dialog which has a z-index of 50. I had to remove this line in order to get proper behaviour.

Michael Bernal

Thank you for your comment!!! I too had this issue

William Fleming

Right on Tom. Dealt with the same issue.

William Fleming

Now set to: .ember-modal-overlay.translucent { background-color: rgba(#4C574A, 0.6); }

Ben Kolera

I also had to change the dialog selector to '.ember-modal-dialog.ember-modal-dialog-center' since '.ember-modal-dialog' was less specific than a style applying from the base style and was not doing the overriding we wanted.

Mark Carlson

Looking at .ember-modal-dialog: background-color: $bg-color; Is $bg-color defined somewhere?

Tristan Edwards

@markcarlson: $bg-color is in layout.scss (sorry for the late answer) :)

Mark Carlson

@tristan: You're right! Interestingly, my screen doesn't look right here. Seems as though the .ember-modal-dialog class from ember-modal-appearance.scss is taking precedence over the class in ui.scss.

Malik Dixon

Hey Tristan, When you input the information in the chirp, The chirp button is clicked and the modal widow will not close. I can use some assistance with this.

Tristan Edwards

@malikdixon47: Are you bubbling up the dismiss-action throughthis.attrs.dismiss() after posting? Also, check your JavaScript console to see if there are any errors there. :)

Malik Dixon

There are no errors in the console and this.attrs.dismiss(); is there as instructed.

Malik Dixon

Awaiting further instructions.

Tristan Edwards

@malikdixon47: I cannot reproduce the issue, even with updated package versions. I think the only way I could debug your particular issue would be if you uploaded your project to GitHub!

Malik Dixon

I have done that already. https://github.com/mdixon47/chirper please let me know what you see.

Malik Dixon

Thank you for the assist.

Tristan Edwards

@malikdixon47: Alright, seems like you forgot the if-statement in the template file. I submitted a pull request for you with the fix.