Hi, It's been a long time since I was published my last tutorial post.
In this post, I'll explain how to write a web extension for Firefox using Vue.js.
Before starting, I'm sorry for my grammar mistakes.
In this example, we will create a to-do list app by overriding the browser's new tab.
Pre Requirements
You need to have knowledge about Vue to understand this post. But you don't need to Vue to create an extension like this. You can create one for yourself using Vanilla JS.
Creating Vue Project
vue create todo-list-extension
The name doesn't matter. I just like meaningful names. We will not use vuex or router. We will use localStorage as a database.
Replacing Default Component.
I'll replace the default component under the /src/components/
as TodoList. You also need to change its name in the App.vue
App.vue
App.vue should be like this;
<template>
<div>
<todo-list />
</div>
</template>
<script>
import TodoList from './components/TodoList.vue'
import './components/TodoList.css'
export default {
name: 'app',
components: {
TodoList
}
}
</script>
TodoList.css
I created a CSS file named TodoList.css in the components directory. The CSS will be like this. You can find this CSS if you googled for "Todo MVC"
TodoList.vue
Now we will create our application. Firstly, the template
will be like this;
<template>
<div>
<section class="todoapp">
<header class="header">
<h1>To Do List</h1>
<input class="new-todo"
autofocus autocomplete="off"
placeholder="What needs to be done?"
v-model="newTodo"
@keyup.enter="addTodo">
</header>
<section class="main" v-show="todos.length" >
<input id="toggle-all" class="toggle-all" type="checkbox" v-model="allDone">
<label for="toggle-all"></label>
<ul class="todo-list">
<li v-for="todo in filteredTodos"
class="todo"
:key="todo.id"
:class="{ completed: todo.completed, editing: todo == editedTodo }">
<div class="view">
<input class="toggle" type="checkbox" v-model="todo.completed">
<label @dblclick="editTodo(todo)">{{ todo.title }}</label>
<button class="destroy" @click="removeTodo(todo)"></button>
</div>
<input class="edit" type="text"
v-model="todo.title" v-todo-focus="todo == editedTodo"
@blur="doneEdit(todo)"
@keyup.enter="doneEdit(todo)"
@keyup.esc="cancelEdit(todo)" />
</li>
</ul>
</section>
<footer class="footer" v-show="todos.length">
<span class="todo-count">
<strong>{{ remaining }}</strong> {{ remaining | pluralize }} left
</span>
<ul class="filters">
<li><a href="#" @click="filterTodos('all')" :class="{ selected: visibility == 'all' }">All</a></li>
<li><a href="#" @click="filterTodos('active')" :class="{ selected: visibility == 'active' }">Active</a></li>
<li><a href="#" @click="filterTodos('completed')" :class="{ selected: visibility == 'completed' }">Completed</a></li>
</ul>
<button class="clear-completed" @click="removeCompleted" v-show="todos.length > remaining">
Clear completed
</button>
</footer>
</section>
<footer class="info">
<p>Double-click to edit a todo</p>
<p>Written by <a href="http://evanyou.me">Evan You</a></p>
<p>Part of <a href="http://todomvc.com">TodoMVC</a></p>
</footer>
</div>
</template>
After that, the component's script will be like this;
export default {
name: 'TodoList',
data() {
return {
newTodo: null,
todos: [],
filteredTodos: [],
visibility: 'all',
editedTodo: null,
STORAGE_KEY: 'todo-list-v2'
}
},
computed: {
remaining: function() {
return this.todos.filter(todo => !todo.completed).length
},
allDone: {
get: function() {
return this.remaining === 0
},
set: function(value) {
this.todos.map(todo => todo.completed = value)
this.listTodos()
}
}
},
mounted() {
this.todos = JSON.parse(localStorage.getItem(this.STORAGE_KEY)) || []
this.listTodos()
},
methods: {
listTodos() {
this.filteredTodos = []
if (this.visibility == 'all') {
this.todos.forEach(todo => {
this.filteredTodos.push(todo)
})
} else if(this.visibility == 'active') {
this.todos.filter(todo => !todo.completed).forEach(todo => {
this.filteredTodos.push(todo)
})
} else if(this.visibility == 'completed') {
this.todos.filter(todo => todo.completed).forEach(todo => {
this.filteredTodos.push(todo)
})
}
},
addTodo() {
this.todos.push({
id: this.todos.length + 1,
title: this.newTodo,
completed: false
})
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.todos))
this.listTodos()
this.newTodo = null
},
editTodo(todo) {
this.editedTodo = todo
},
removeTodo(data) {
this.todos = this.todos.filter(todo => todo.id != data.id)
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.todos))
this.listTodos()
},
doneEdit() {
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.todos))
this.editedTodo = null
},
cancelEdit() {
this.editedTodo = null
},
removeCompleted() {
this.todos = this.todos.filter(todo => !todo.completed)
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(this.todos))
this.listTodos()
},
filterTodos(type) {
this.visibility = type
this.listTodos()
}
},
filters: {
pluralize: function (n) {
if (n <= 0) {
return 'item'
} else if(n === 1) {
return 'item'
}
return n === 1 ? 'item' : 'items'
}
},
directives: {
'todo-focus': function (el, binding) {
if (binding.value) {
el.focus()
}
}
}
}
Actually, you can find a lot of example on Google for Todo MVC. This example one of these. So, I will not explain how methods work, what are the directives, filters or computed properties.
Building Vue Application
If you used Vue in your projects, you should know, Vue project's default output folder is dist
folder.
By default, after yarn build
command, the dist folder removes and re-creates To prevent this, we need to change the package.json file's script section like that.
//....
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build --no-clean",
"lint": "vue-cli-service lint"
}
//....
We just added the --no-clean flag for build operations. I'm doing this because I wanted to use the dist folder for this post.
yarn build
With this command, we've built our todo app.
manifest.json file
We'll create manifest.json file in the dist
folder. Its content will be like this;
{
"manifest_version": 2,
"name": "To Do List",
"version": "1.0.0",
"chrome_url_overrides" : {
"newtab": "index.html"
}
}
Using chrome_url_overrides key, you can override browser's default behavior for the new tab feature. That doesn't do that directly, it has subkey to do that. If you use newtab key, you can do it.
Debugging Web Extension
Okay, we finished everything, now let's open a new tab in our browser and typing this command to the address bar;
about:debugging
If you use any web extension, you will see them here.
If you see the **Load Temporary Add-on..." button, click it. It will open a file dialog. We need to select the manifest.json file we created.
If you didn't see any error, we'll see our extension in the extension dashboard.
Let's open a new tab :)
Conclusion
We learned how to write a basic Web Extension app for Firefox using Vue.js
We learned chrome_url_overrides key can be used by Firefox.
We learned if we want to override new tab we have to use chrome_url_overrides and newtab key.
Thanks for reading. I hope this helps you.
Top comments (3)
use "default_popup" like this:
{
"manifest_version": 2,
"name": "MyExtension",
"version": "1.0.0",
"icons": {
"48": "assets/img/logo.png"
},
"browser_action": {
"default_icon": "assets/img/logo.png",
"default_title": "Opens MyExtension",
"default_popup": "app.html"
}
}
Source: javascript.plainenglish.io/i-built...
thanks for the awesome tutorial :)