Posts tagged 'javascript'

Vue.js reusable components

published on February 04, 2019.

A while ago I started learning bits and pieces about Vue.js by creating a single page application for one of my pet projects that I use to explore Domain-Driven Design. In general I know my way around a Javascript file, but wouldn’t call myself an expert with it. In the past I mostly used jquery, some mootools, and even Dojo. Ah, good old Zend Framework 1 times.

While reading up on Vue.js I came across the part of their documentation that talks about components and how to make them reusable. At the time I just glanced over it, thought “Neat.” to myself and went on with my day. Yesterday when I was done with creating a page that holds a list of tasks, I remembered that section about reusable components and wondered could I use that to clean up a bit of the code I wrote. Turns out, I can!

A simplified version of my initial Vue.js page was something like this:

./assets/vue/components/Journal.vue

<template>
    <div>
        <template v-if="journal.tasks.length !== 0">
            <div v-for="task in journal.tasks">
                <template v-if="task.status === 'todo'">
                    <button @click="markTaskAsDone(task.id)">
                        Mark as done
                    </button>
                    <span>{{ task.description }}</span>
                </template>
                <template v-if="task.status === 'done'">
                    <strong>{{ task.description }}</strong>
                </template>
            </div>
        </template>
    </div>
</template>

<script>
    export default {
        name: 'journal',
        data: function () {
            return {
                journal: {
                    tasks: [
                        {
                            'id': 1,
                            'description': "Task 1",
                            'status': "todo"
                        },
                        {
                            'id': 2,
                            'description': "Task 2",
                            'status': "done"
                        },
                    ],
                }
            }
        },
        methods: function () {
            markTaskAsDone: function(taskId) {
                alert("Marking task as done, ID: " + taskId);
            }
        }
    }
</script>

We check if there are any tasks in the journal and if so, iterate over them. For todo tasks show a button that we can use to mark that task as done and show the description of the task. For done tasks display the task description bolded.

Now, for the case when the journal has no tasks, I decided I want to show the organizer (an organizer is a person who organizes their tasks in that app) an example list of tasks instead of a blank page with a boring “No tasks” message. I’m also learning to make my designs a tiny bit better, courtesy of the Refactoring UI book.

How did I do that? By copy/pasting a bunch of times the HTML for the todo and done tasks.

./assets/vue/components/Journal.vue

<template>
    <div>
        <template v-if="journal.tasks.length !== 0">
            <div v-for="task in journal.tasks">
                <template v-if="task.status === 'todo'">
                    <button @click="markTaskAsDone(task.id)">
                        Mark as done
                    </button>
                    <span>{{ task.description }}</span>
                </template>
                <template v-if="task.status === 'done'">
                    <strong>{{ task.description }}</strong>
                </template>
            </div>
        </template>
        <template v-else>
            <div>
                <button @click="alert('Marking the task as done')">
                    Mark as done
                </button>
                <span>An example of a todo task</span>
            </div>
            <div>
                <strong>An example of a done task</strong>
            </div>
        </template>
    </div>
</template>

Remember, this is a simplified version of the code. Add to that a bunch of more divs, a bunch of CSS classes as I’m using TailwindCSS and what I had before me was a real nightmare. Almost a hundred lines of HTML.

Enter stage left… Reusable components.

What I did was I created two reusable components, one for a todo task, and one for a done task.

The component for the todo task looks something like this:

./assets/vue/components/Tasks/Todo.vue

<template>
    <div>
        <button @click="">
            Mark as done
        </button>
        <span><slot>An empty task</slot></span>
    </div>
</template>
<script>
    export default {
        name: 'todoTask',
    }
</script>

The <slot> element acts like a kind of a placeholder where Vue.js will insert whatever text we pass on later to that component. There are other ways to pass in data from parent to child components, but in this case, this was simple and enough for me. Note that the @click event handler for the button is empty, as at this time I had no idea what to do with it. The export part in the script tag is how we expose the component to be available for use in other components. I think?

The component for the done task is similar:

./assets/vue/components/Tasks/Done.vue

<template>
    <div>
        <strong><slot>An empty task</slot></strong>
    </div>
</template>
<script>
    export default {
        name: 'doneTask',
    }
</script>

To use these components we need to import them and list them under components within our component where we want to use them:

./assets/vue/components/Journal.vue

<script>
    import TodoTask from "./Tasks/Todo";
    import DoneTask from "./Tasks/Done";
    export default {
        name: 'journal',
        components: {
            TodoTask,
            DoneTask,
        },
        // Shortened the rest of it as nothing changed
    }
</script>

We take the name of the components we imported, turn them kebab-case, and use them as HTML tags. What we put between the opening and closing tags of our component will be inserted into the <slot> tag inside:

./assets/vue/components/Journal.vue

<template>
    <div>
        <template v-if="journal.tasks.length !== 0">
            <div v-for="task in journal.tasks">
                <todo-task v-if="task.status === 'todo'">{{ task.description }}</todo-task>

                <done-task v-if="task.status === 'done'">{{ task.description }}</done-task>
            </div>
        </template>
        <template v-else>
            <todo-task>An example of a todo task</todo-task>

            <done-task>An example of a done task</done-task>
        </template>
    </div>
</template>

Vue.js will make sure that we get the proper HTML rendered in the browser.

Events to the rescue

For a while I didn’t know what to do with the Mark as done button. The original implementation of the markTaskAsDone(taskId) method uses other methods local to the Journal component, so if I’d move that to the TodoTask child component, there’d be a mess on my hand real quick. I’ve tried passing in to the TodoTask a function from the parent component and a couple of other things…

Turns out the solution is quite elegant. On the button within the TodoTask we listen for the click event and trigger our own custom clickedToMarkTaskAsDone event:

./assets/vue/components/Tasks/Todo.vue

<template>
    <div>
        <button @click="clickedToMarkTaskAsDone">
            Mark as done
        </button>
        <span><slot>An empty task</slot></span>
    </div>
</template>
<script>
    export default {
        name: 'todoTask',
        methods: {
            clickedToMarkTaskAsDone: function () {
                this.$emit('clickedToMarkTaskAsDone');
            }
        }
    }
</script>

In the parent component where we use this TodoTask component, we create a handler for our custom event using the existing code we have:

./assets/vue/components/Journal.vue

<todo-task v-if="task.status === 'todo'" @clickedToMarkTaskAsDone="markTaskAsDone(task.id)">
    {{ task.description }}
</todo-task>

Nothing within the markTaskAsDone method changed.

The end result is much more nicer and the line count went down from a hundred to 25 lines. Isn’t that great? I think it’s great.

Happy hackin’!

Versions used for examples: Vue 2.5.
Tags: vue.js, reusable, components, javascript.
Categories: Programming, Development.

Frontend testing with phantomjs and casperjs

published on January 29, 2013.
Heads-up! You're reading an old post and the information in it is quite probably outdated.

I am not usually fond of doing much frontend stuff, but I do like to dable in some javascript from time to time. Nothing fancy, no node.js, coffeescript and the likes for me. I still feel like making applications on the server side, and have the client just show things to the user. If needed some 3rd party javascript library or framework to make my life easier, and that’s about it.

For a while I was reluctant on writing any frontend tests, integration tests, or whatever you want to call them, because, y’know, refresh-click-click-click and the testing is done. Easy. Except when it’s not. From time to time some piece of user interface gets wild on javascript and it turns out after a couple of weeks, half a dozen of bugs reported, and hundreds of refreshes and thousands of clicks, that whole thing becomes tiresome. Thus, I decided to dabble my toes deeper in the waters of the javascript world and try writing some tests for all this.

What I found first is that there’s a lot of testing libraries out there for javascript. Won’t even try listing them all. When I set out for the hunt I knew what I wanted and needed. I wanted a tool that will help me automate all my refreshclicks, or at least to some extent. Tell the tool through my tests: “go to that page, check what and how is rendered, do some things with the UI, test again”. The less dependencies it has on other things, the better. I asked around among people who are bit more fluent in javascript than I am, what are they using and what would they recommend. Not too much to my surprise, everyone recommended a different thing, whatever fits their problem. So I ended up picking the tools that fit my problem.

The tools chosen

The first tool I picked was phantomjs. It’s a “headless WebKit with JavaScript API. It has fast and native support for various web standards: DOM handling, CSS selector, JSON, Canvas, and SVG.” (shamelessly copy-pasted from their website). When installing it either download a binary from their website, or compile the source on your own. If you’re on Ubuntu, do not install it via apt-get. It installs some very old version, 1.4 I think, and phantomjs will just scream at you something about some X servers. The binaries are pre-built for any and all systems, so just use them (not that I tried all of them, but I trust they all work).

To cut the story short, writing tests with just phantomjs is difficult. If at all possible. Because hey! It’s not a testing library. I think. This part is a bit blurry for me.

Enter casperjs! These two together look like a perfect match for doing automated, frontend tests full of javascript.

Even though last week on Friday I was being a bit frustrated and a bit more of a dick and said bad things about casperjs over twitter which I shouldn’t have. Sorry. Broken things on a Friday at 5PM can do that to a man.

Examples!

That’s why you’re here, to see some examples. And this example, which, btw, is on Github here, will have a simple login page with a form and a logged in page with some UI elements that you can click around and do stuff! And we’re going to test all that with phatnomjs and casperjs. Just like real life. Cookies included.

When writing tests with phantomjs and casperjs, all one need to do is think in steps. Same simple steps like in the refreshclick procedure. First, open page. Make sure we are on the correct page. Make sure all the elements are there. Click something. Make sure that thing is clicked. Fill in a field. Make sure the field is filled. And so on.

Let’s have a look at the first part of the tests.js file:

casper.start('http://localhost/frontend-testing', function () {
    this.test.assertUrlMatch(/login.php$/, 'Redirected to login page');
    this.test.assertExist("#login_form", 'Login form exists');
    this.fill('#login_form', {
        'email': 'email@example.com'
    }, false); // false means don't autosubmit the form
    this.test.assertField('email', 'email@example.com');
});
casper.thenClick('#login', function () {
    this.test.assertUrlMatch(/index.php$/, 'Redirected to index page after login');
});

The tests are surprisingly self-explanatory: start by opening up the home page. Assert that we are (redirected to) on the login page and that the login form exists. Then fill the email field of the login form with a given value. Assert that the field was indeed filled with that value. Once that’s done, then click on the login button, and assert that we end up on the index page.

Easy!

And practically that goes on in the entire test. Start, assert, then do this, assert, then do that, assert, done.

Testing ajax calls isn’t difficult either:

casper.thenClick('#do_ajax', function () {
    this.waitForResource('http://localhost/frontend-testing/ajax.php');
});
casper.then(function () {
    // Sometimes we need to wait a bit more for ajax requests ...
    this.wait(50);
});
casper.then(function () {
    this.test.assertTextExist('Just some ajax response.', 'Ajax request was made');
});

Do some action that triggers an ajax request, wait for that ajax request to finish and assert that something was done with the response from that ajax call. Of course, in real life examples you will have a bit more complicated setup, but hey… As for faking ajax requests I hear that can be done with this cool sinonjs library, but I haven’t managed to get that working, yet. Mostly because I didn’t need to fake any ajax calls.

The most voodoo-like thing in these tests is probably the evaluate() part. I don’t think I really know what that it is, but what I think it is, that, whenever you want to do something in the actual webpage, but from within the tests, you use evaluate().

For example, I had to use evaluate() to determine is the checkbox checked or not:

casper.thenClick('#enable_ajax', function () {
    // I could swear I had this one working
    // this.test.assertEquals(this.getElementAttribute('#enable_ajax', 'checked'), 'checked', 'Checkbox is checked');
    this.test.assertTrue(this.evaluate(function () { 
        return document.getElementById("enable_ajax").checked;
    }), 'Checkbox is checked');
});

Not sure how, but the this.getElementAttribute() way does actually work in my other tests. Honest. Not sure why it didn’t work in this example. Maybe some other factors not present here, affected my other tests? I don’t know.

Helpful bits

What I found extremely helpful while writing the tests is the this.debugHtml() and this.debugPage() casper functions. The former will dump the entire HTML to the terminal, and the latter will dump just the text of the entire page. Can be useful to figure out what’s going on.

The other helpful debugging function I used a lot is this.getElementAttribute(<selector>). It retrieves a bunch of helpful information on an element, which you can then dump to the terminal, to further figure out things:

casper.then(function () {
    require('utils').dump(this.getElementAttribute('#enable_ajax'));
});

Will result in an output like this:

{
    "attributes": {
        "id": "enable_ajax",
        "name": "enable_ajax",
        "type": "checkbox"
    },
    "height": 13,
    "html": "",
    "nodeName": "input",
    "tag": "<input type=\"checkbox\" name=\"enable_ajax\" id=\"enable_ajax\">",
    "text": "",
    "visible": true,
    "width": 13,
    "x": 12,
    "y": 58
}

I mean, it even gives the positions of the checkbox on the page! Super helpful.

casperjs supports CSS3 selectors and, which gives me much joy, XPath. Makes getting those pesky little elements covered in layers of divs and tables a walk in the park.

One thing though: you can’t do things like selecting multiple elements at once and then .each()‘em or something like that. You have to select elements one by one and do assertions on each of them. Even if the selector matches multiple elements, it will just return the first element, so you’ll probably end up using lots of :nth-child(n) selectors. But that’s what copy-paste was invented for.

That’s about it, I guess. Of course, there’s much more to both phantomjs and casperjs, but I found the documentations well written, so I believe it’s fairly easy to tackle even the testing of more complicated web pages with these libraries.

Happy testing!

Robert Basic

Robert Basic

Software engineer, consultant, open source contributor.

Let's work together!

If you require outsourcing or consulting help on your projects, I'm available!

Robert Basic © 2008 — 2019
Get the feed