This article applies to version 1 of the Zendesk Apps framework. A newer way of building apps is available. For more information see Zendesk App Framework v2.

Wouldn't it be handy to escalate a ticket with a button?

Grab the popcorn because this is the story of building a Zendesk app to do that.

Before we start - attached to this document is the full code if you'd like to follow along yourself while reading this - which I highly recommend for those that are more hands-on like myself.

Update 4/12/2014: This is Part 1 of the diary. Part 2 adds more functionality to the app. It continues here .

December 10, 2013

Today I checked out the app docs Getting Started and the Apps Framework Reference to get a handle on building an app using the Zendesk Apps Framework. I installed the Zendesk apps tools (ZAT) and created the basic skeleton of my app with ZAT.

December 11, 2013

The first step is setting the goals for my app, keeping in mind the constraints of the environment I'm working in. What should this app do? I want my app to have the following features to start:

  1. A button that submits the ticket when clicked.
  2. Logic that says 'Re-assign this ticket from the current user's group to the next group in the chain of escalation'.

My idea is to break this up into smaller, more digestible pieces. Before I start building anything, I want to check the code used in other apps to see if there's anything that might be useful for my app. Wouldn't want to re-invent the wheel.

December 14, 2013

I checked out the Answer Suggestion App today (on Github here ). It has a cool feature that lets you collapse the app down to one line. It goes from this:

To this:

Well, I know this app is using templates so I checked through the app's template files on Github for clues. The layout.hdbs template has three things: a header, a <section data-main/> tag, and a footer. Since all the other templates are injected into this template at <section data-main/> , I'll stick to investigating just the layout.hdbs file.

I found the following snippet:

<h3><a class="toggle-app"><i class="icon-minus"></i> {{setting "name"}}</a></h3>

The snippet has toggle-app and icon-minus classes and the following placeholder:

{{setting "name"}}

What does the placeholder do? Time to check the docs . It states that the placeholder renders the value of a setting defined in the manifest.json file. The setting in the manifest file designates "name" as "Answer Suggestion".

Some clarification about the consequences of making changes to the placeholder for my app:

  • If I don't change the manifest file but add additional text after the placeholder in the layout.hdbs file, my app will render "Answer Suggestion" plus the extra text.
  • If I change the name in the manifest file from "Answer Suggestion" to "Escalate Ticket" in addition to adding the additional text after the placeholder in layout.hdbs , the app would render "Escalate Ticket" plus the additional text.

Enough about the placeholder already. I want to know more about those two CSS classes. I can look for clues in other files but I think it's time to stop for today.

December 18, 2013

OK, let's see about those CSS classes I mentioned in my last entry. I looked for the strings "toggle-app" and "icon-minus" in the other files of the Answer Suggestion app.

Neither of the strings appeared in the app.css file so it was time to dig into the app.js file. I reviewed the events defined in the file and saw the following DOM event that looked promising. (Keep in mind that I looked for anything that refers to clicking something that collapses the app.)

'click .toggle-app': 'toggleAppContainer',

First I checked to see the purpose of this event. I opened the app.js file in my text editor and searched for the word "toggle". It appeared in the following code snippet:

toggleAppContainer: function(){    var $container = this.$('.app-container'),    $icon = this.$('.toggle-app i');
    if ($container.is(':visible')){        $container.hide();        $icon.prop('class', 'icon-plus');    } else {        $container.show();        $icon.prop('class', 'icon-minus');    }}

Before I go any further, some names in the app.js are prefixed with a dollar sign and others are not. Why might that be?

It turns out some people prefer using a dollar sign to tell the difference between regular variables and jQuery objects.

var self = 'some string';var $self = 'another string';

These are declared as two different variables. It's like putting underscore before private variables. I found the info on Stackoverflow .

That's the reason I'm seeing some names with dollar signs and some without it. Sometimes it's JQuery and sometimes it's not. I get it now.

So it looks like the toggleAppContainer function hides the container with the click of a button.

I see there's a variable defined as $container , which is equal to the .app-container CSS selector.

Where are CSS selectors used? That's right! I'm going to check the layout.hdbs file I was looking at earlier. Sure enough, the file contains the following code block that refers to the .app-container class:

<div class="app-container">    <div class="input-append custom-search">        <input type="text" placeholder="{{t "layout.placeholder_text"}}">        <button class="btn" type="button">{{t "layout.search_button"}}</button>    </div>...</div>

This container is hidden by the code mentioned above in the app.js file.

I can see that the div wrapped around this container has a class of app-container . Clicking the minus icon hides the entire div element using the JQuery code. To refresh my memory, here's the function in the app.js file that hides the container:

toggleAppContainer: function(){        var $container = this.$('.app-container'),    $icon = this.$('.toggle-app i');
    if ($container.is(':visible')){        $container.hide();        $icon.prop('class', 'icon-plus');    } else {        $container.show();        $icon.prop('class', 'icon-minus');    }}

Essentially, if the UI element assigned to the $container variable is visible, the function hides the element when the function is run. It also changes the icon in the header to a plus sign:

$container.hide();$icon.prop('class', 'icon-plus');

I can see that the $icon variable is defined as equal to the element with a class of .toggle-app i . This is a reference to the icon in the h3 tag in the header of layout.hdbs .

As a side note, the JQuery documentation on icons outlines a ton of other options beside the plus and minus signs. I can change the plus sign to a lock or a heart or basically anything else in the list of options. Pretty cool, huh? I think so.

Here's the code and the elements it affects in the app:

Going home for the day. Until next time.

December 20th, 2013

I have an idea of how I'll need to code my app to make it collapsible like the Answer Suggestion app discussed in my previous entries. Before I start piecing the code together, I want to explore a couple of other things I'm curious about in this bit of code:

<div class="app-container">    <div class="input-append custom-search">        <input type="text" placeholder="{{t "layout.placeholder_text"}}">        <button class="btn" type="button">{{t "layout.search_button"}}</button>    </div>...</div>

There's a lot of stuff packed in this short snippet, but what does it all do? Start with the easiest first, the inner div with the input-append custom-search class. If I search the app.js file for the custom-search CSS selector, I find the following event:

'click .custom-search button': 'processSearchFromInput'

Cool. Based on all I learned from the .toggle-app selector, I know that the code above executes a function called processSearchFromInput when the .custom-search button is clicked. Just trying to figure out what that function is supposed to do is making my brain hurt. I'm going to go down an easier road. Because I'm trying to make an app that lets me escalate a ticket with the click of a button, I want to learn if this app has elements that can help me do that.

Again, the code from the layout.hdbs file contains:

<div class="app-container">    <div class="input-append custom-search">        <input type="text" placeholder="{{t "layout.placeholder_text"}}">        <button class="btn" type="button">{{t "layout.search_button"}}</button>    </div>...</div>

The input tag and button tags tell me there's going to be a field and a button users can use to enter and submit data. (You can learn more on input fields and buttons here and here .) That all makes sense so far. But what's the purpose of the placeholder in each tag?

Because it's a question about the app's template, I check the templates documentation . The doc gives the following example:

// Example from the App Templates documentation on http://developer.zendesk.com/documentation/apps/reference/templates...
{{t "name" [key=value]}}
// Code from the app
{{t "layout.search_button"}}

This example looks like the placeholders I'm seeing. The syntax means this Handlebars helper will render translated text for a given key. More on Handlebar.js and using helpers here .

To figure out what this placeholder will display inside the button as text, I'll need to review the respective file in the translations folder. Because my language is set to English, I go to the en.json file and see the following relevant code:

"layout": {    "placeholder_text": "Type your search here",    "search_button": "Search"},

What happens when I change the text in "search_button" from "Search" to "Pizza" ?

BEFORE:

AFTER:

That's neat.

December 21, 2013

I managed to reverse-engineer the Answer Suggestion app to see how they made the app container collapsible and create the input field for typing in the search query. I also learned out how to create and use the button to submit the search query. Time to figure out how to put this functionality into my Escalate app.

Right now, my app is just a skeleton. The layout.hdbs file looks like this:

<header>    <span class="logo"/>    <h3>{{setting "name"}}</h3></header>
<section data-main/>
<footer>    <a href="mailto:{{author.email}}">    {{author.name}}    </a></footer>

I'm going to mess with this a little bit. I don't want the default logo in the top right of the app. I don't want any logo at this point, to be honest.

Farewell span tag. Nice knowing you. Perhaps we'll cross paths again someday.

I know the Answer Suggestion app has some stuff in the h3 tag in the header , so I'll add that to mine.

My header now looks like this:

<header>    <h3>        <a class="toggle-app"><i class="icon-minus"></i> {{setting "name"}}</a>    </h3></header>

Here's what my app looks like now:

Cool! I have a minus sign icon and the name of the app. I need to be able to hide the app container. I'll do that next.

I add a div element with a class of "app-container" that has a nested div to house the button that the user will click to escalate the ticket. The relevant part of my layout.hdbs file now looks like this:

<div class="app-container">    <div>        <button class="btn" type="button">HELLO WORLD</button>    </div>
    <section data-main/>...</div>

And my app looks like this now:

I have the minus sign next to the app name and my button with some sample text.

To make the app-container div element collapse when the user clicks the minus sign, I'll need to modify my app.js file.

December 23, 2013

Today I'll make my app collapsible. I need to add code to my app.js file. The default ZAT-generated code consists of this:

(function() {
    return {        events: {            'app.activated':'doSomething'        },
        doSomething: function() {        }    };
}());

First, I remove the placeholder event and the sample function. Next, I add a DOM event so that when you click the minus sign in the header, the app-container div is no longer be displayed. I add the toggleAppContainer function from the Answer Suggestion app that hides the app in response to a click.

My code now looks like this:

(function() {
    return {        events: {            'click .toggle-app': 'toggleAppContainer'        },
        toggleAppContainer: function(){            var $container = this.$('.app-container'),            $icon = this.$('.toggle-app i');            if ($container.is(':visible')){                $container.hide();                $icon.prop('class', 'icon-plus');            } else {                $container.show();                $icon.prop('class', 'icon-minus');            }        }    };}());

My DOM event calls the toggleAppContainer function in response to a click event on the element with a class of .toggle-app . The element is an anchor in the header of the layout.hdbs file.

The toggleAppContainer function defines a variable named $container that refers to the DOM element with a class of .app-container . That would be the div container element we added earlier in the layout.hdbs file.

Now when I click the minus sign after my app loads, the button is hidden. Pretty neat, but the button itself displays the boring "HELLO WORLD" text. I want something with a little more chutzpah .

I want to try to figure out how to use a picture of a button instead of text. Back to the Templates documentation , where I see that I can render the relative path for a given asset file name. In other words, I can display an image in my app's assets folder.

To display an image, this code example appears most relevant:

{{assetURL "name"}}

Example in the documentation:

<img src="{{assetURL "logo-small.png"}}" alt="logo" />

I have an image of a red button that I want to use, so I need to complete the following steps:

  1. In my layout.hdbs file, delete "HELLO WORLD".
  2. Insert the example img tag above.
  3. Change the file name in the img tag to red-small.png , the name for the button image in my assets folder.

My updated code looks like this:

<div class="app-container">    <div>        <button class="btn" type="button"><img src="{{assetURL "red-small.png"}}" alt="logo" /></button>    </div>...

And the app now looks like this when I reload it:

I can hide or display my big red button, but when I click the big red button nothing happens. That's all about to change with my next post when I get back from the holidays. See you in 2014.

January 2, 2014

I'm back from vacation and ready to pick up where I left off -- adding functionality to the big red button! What I want this thing to do is change the group assigned to the ticket and add a tag. I want these actions performed when somebody clicks the button.

First things first. I need to define an event that executes a function when somebody clicks the button. Where might I put that event? The events property in the app.js file looks like a good place. This is what the events property looks like before I start:

events: {    // DOM events    'click .toggle-app' : 'toggleAppContainer'},

Here's what it looks like after I add my button event:

events: {    // DOM events    'click .toggle-app' : 'toggleAppContainer',    'click .btn' : 'tagTicketChangeGroup'},

I added an event that runs a function named tagTicketChangeGroup when the element with the CSS class of .btn is clicked. My .btn element is in my layout.hdbs file:

<div>    <button class="btn" type="button"><img src="{{assetURL "red-small.png"}}" alt="logo" /></button></div>

Now for the function to run when the button is clicked. Here's a stab at the initial code:

tagTicketChangeGroup: function() {    console.log("function fired, great job!");},

The function prints "function fired, great job!" in the browser's console when I click the big red button. The console.log("some_string"); function is really helpful to test if a function fired or not.

Because my app is located in the ticket sidebar, I can use the ticket data API . The API has a bunch of useful JavaScript functions.

I have to get data from the current ticket object and assign it to a variable. Then I need to somehow add a tag to the ticket, and change the assignee to a group ID value in the app settings. How?

To be continued in the next entry of this diary.

January 5, 2014

Picking up where I left off.

I want to write a function that assigns all data from the current ticket to a variable named "ticket". It also needs to call the tags() function on the "ticket" variable to return all tags on the current ticket as an array of strings. Lastly, the function needs to call the add() function to add a tag to the ticket. The tag I want to add is the string "escalate".

Putting it all together, the function looks like this:

tagTicketChangeGroup: function() {    var ticket = this.ticket();    ticket.tags().add("escalate");},

To set the assignee to a group, I found the following bit of code in the Ticket Object doc:

ticket.assignee({ groupId: groupId })

Great, but I don't want to hard-code the 'groupId' value into the app because the group id will be different for each instance of Zendesk Support. I want the id to be specified by an admin when they install the app in Zendesk Support. I can do this with app settings .

Settings are defined in my manifest.json file as parameters. The Apps framework automatically generates a settings page with form fields for each parameter and displays the page when the user installs the app.

To create a groudId setting, I add the following "group_id" parameter to my manifest file:

{    "name": "ESCALATE TO NEXT GROUP",    "author": {        "name": "Jeremiah Rockefeller",        "email": "[email protected]"    },    // snip         "parameters": [        {            "name" : "group_id",            "type" : "number",            "required" : true        }    ]}

I defined a parameter named "group_id". It's type is a number and it's required. The user won't be able to install my app unless they specify a group id.

Next, I call the setting's value in my app.js file:

ticket.assignee({ groupId: this.setting('group_id') });

My event handler function now looks like this:

tagTicketChangeGroup: function() {    var ticket = this.ticket();    ticket.tags().add("escalate");    ticket.assignee({ groupId: this.setting('group_id') });},

Just for fun, I'll add a services notification that appears when this function runs. To do that, I add this little line of code:

services.notify('string');

The final version of the function that will run when the big red button is pressed winds up looking like this:

tagTicketChangeGroup: function() {    var ticket = this.ticket();    ticket.tags().add("escalate");    ticket.assignee({ groupId: this.setting('group_id') });    services.notify('tag added & group changed :) ');},

This app will appear on existing tickets. When the agent clicks the big red button, the app adds a tag, displays a notification, and changes the assigned group on the ticket. It also contains a cool bit of code that makes the app collapsible if real estate is an issue in the Zendesk Support product where the app is installed.

To see this app in action, check out this video .

January 8, 2014

Cleaning up my files. Here's the full code of my app:

manifest.json

{
"name": "ESCALATE TO NEXT GROUP",
"author": {
"name": "Jeremiah Gatsby",
"email": "[email protected]"
},
"defaultLocale": "en",
"private": true,
"location": "ticket_sidebar",
"version": "1.0",
"frameworkVersion": "0.5",
"parameters": [
{
"name" : "group_id",
"type" : "number",
"required" : true
}
]
}

app.js

(function() {

return {

events: {
// AJAX events & callbacks (NONE)
// App events (NONE)

// DOM events
'click .toggle-app' : 'toggleAppContainer',
'click .btn' : 'tagTicketChangeGroup'
},

tagTicketChangeGroup: function() {
var ticket = this.ticket();
ticket.tags().add("escalate");
ticket.assignee({ groupId: this.setting('group_id') });
services.notify('tag added & group changed :) ');
},

toggleAppContainer: function() {
var $container = this.$('.app-container'),
$icon = this.$('.toggle-app i');
if ($container.is(':visible')) {
$container.hide();
$icon.prop('class', 'icon-plus');
} else {
$container.show();
$icon.prop('class', 'icon-minus');
}
}
};
}());

layout.hdbs

{{setting "name"}}

Closing thoughts

Could this app be optimized in many ways? Absolutely! That's what this whole process is about -- constantly refining and improving.

But that's a conversation for another diary.

That's it folks. Ciao!