Skip to content

Commit

Permalink
New example: SimpleMVC
Browse files Browse the repository at this point in the history
  • Loading branch information
janodvarko committed Aug 26, 2015
1 parent a8b5db7 commit 00f2219
Show file tree
Hide file tree
Showing 11 changed files with 426 additions and 0 deletions.
27 changes: 27 additions & 0 deletions SimpleMVC/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
SimpleMVC
=========
This example extension shows how to use Model View Controller (MVC) pattern
in context of an extension for Firefox DevTools.

This example is based on [Hello World](https://github.com/firebug/devtools-extension-examples/tree/master/HelloWorld)
example and appends communication channels between various pars of the extension
(running in different scopes).

There are two scopes:
Chrome scope: Controller, Model
Content scope: View

Instructions
------------
1. Install the extension
2. Open developer tools toolbox (F12 or Menu -> Developer -> Toogle Tools)
3. See the `My Panel` panel
4. Follow instructions in the panel

Further Resources
-----------------
* Add-on SDK: https://developer.mozilla.org/en-US/Add-ons/SDK
* DevTools API: https://developer.mozilla.org/en-US/docs/Tools/DevToolsAPI
* Coding Style: https://github.com/mozilla/addon-sdk/wiki/Coding-style-guide
* DevTools Extension Examples: https://github.com/firebug/devtools-extension-examples
* DevTools/Hacking: https://wiki.mozilla.org/DevTools/Hacking
57 changes: 57 additions & 0 deletions SimpleMVC/data/frameScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/* See license.txt for terms of usage */

"use strict";

(function({
content,
addMessageListener,
sendAsyncMessage,
removeMessageListener,
addEventListener}) {

const Cu = Components.utils;
const Cc = Components.classes;
const Ci = Components.interfaces;

const document = content.document;
const window = content;

/**
* Register a listener for messages from myPanel.js (Controller, chrome scope).
* A message from chrome scope comes through a message manager.
* It's further distributed as DOM event, so it can be handled by
* myView.js (View, content scope).
*/
addMessageListener("my-extension/message", message => {
const { type, data } = message.data;
const event = new window.MessageEvent(type, {
data: data,
});

window.dispatchEvent(event);
});

/**
* Send a message to the parent myPanel.js (Controller, chrome scope).
*/
function postChromeMessage(id, args) {
const event = {
type: id,
args: args,
};

sendAsyncMessage("message", event);
}

/**
* Register a listener for DOM events from myView.js (View, content scope).
* It's further distributed as a message through message manager to
* myPanel.js (Controller, chrome scope).
*/
window.addEventListener("my-extension/event", function (event) {
const data = event.data;
postChromeMessage(data.type, data.args);
}, true);

// End of scope
})(this);
Binary file added SimpleMVC/data/icon-16.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
24 changes: 24 additions & 0 deletions SimpleMVC/data/myView.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/* See license.txt for terms of usage */

body {
background-color: white;
font-size: 12px;
font-family: Lucida Grande, Tahoma, sans-serif;
line-height: 15px;
}

code {
font-family: monospace;
background: rgb(249, 249, 249);
padding: 1px;
border: 1px solid lightgray;
border-radius: 2px;
}

#content {
padding-top: 7px;
}

.time {
color: green;
}
31 changes: 31 additions & 0 deletions SimpleMVC/data/myView.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<link href="myView.css" rel="stylesheet">
<script src="myView.js" type="application/javascript"></script>
</head>
<body>
<p>This example extension shows how to use <code>Model View Controller</code> (MVC) pattern
in context of an extension for Firefox DevTools. It also demonstrates how to
connect all components of the pattern together through events.</p>
<p>Use case:
<ol>
<li>Clicking on the button below generates standard click event handled
by this <code>View</code> (myView.html).</li>
<li>The event is forwarded throug a message manager to <code>Controller</code>
living in the chrome scope (myPanel.js) together with current time [ms] as a payload.</li>
<li>When the time arrives to the Controller it's further passed to <code>Model</code>
(myExtension.js, also living in the chrome scope) to nicely format it.</li>
<li>The formatted string is sent back to this View (through
message manager to the content scope and finally as DOM event to the View)</li>
</ol>
<p><i>
Checkout the Browser Console for logs.
</i></p>
<p>
<button id="my-button" onClick="onClick()">Click Me!</button>
</p>
<div id="content"></div>
</body>
</html>
48 changes: 48 additions & 0 deletions SimpleMVC/data/myView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/* See license.txt for terms of usage */

/* The following implementation serves as a View (the V in MVC pattern) */

/**
* Button click handler
*/
function onClick() {
postChromeMessage("format-time", {
time: Date.now()
})
}

/**
* Listen for 'update-view' event that is sent as a response
* to 'format-time'.
*/
addEventListener("update-view", function(event) {
console.log("myView.js: update-view", event);

var content = document.getElementById("content");
var node = document.createElement("div");
node.textContent = event.data;
node.className = "time";
content.appendChild(node);
});

/**
* Post events to the frameScript.js scope, it's consequently
* forwarded to the chrome scope through message manager and
* handled by myPanel.js (Controller, chrome scope).
*
* @param type {String} Type of the message.
* @param data {Object} Message data, must be serializable to JSON.
*/
function postChromeMessage(id, data) {
console.log("myView.js: postChromeMessage; " + id, data);

// Generate custom DOM event.
const event = new MessageEvent("my-extension/event", {
data: {
type: id,
args: data,
}
});

dispatchEvent(event);
}
23 changes: 23 additions & 0 deletions SimpleMVC/lib/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
/* See license.txt for terms of usage */

"use strict";

const { MyExtension } = require("./myExtension.js");
const { MyPanel } = require("./myPanel.js");

/**
* Application entry point. Read MDN to learn more about Add-on SDK:
* https://developer.mozilla.org/en-US/Add-ons/SDK
*/
function main(options, callbacks) {
// Initialize extension object (singleton).
MyExtension.initialize(options);
}

function onUnload(reason) {
MyExtension.shutdown(reason);
}

// Exports from this module
exports.main = main;
exports.onUnload = onUnload;
70 changes: 70 additions & 0 deletions SimpleMVC/lib/myExtension.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
/* See license.txt for terms of usage */

"use strict";

const { Cu } = require("chrome");

const { gDevTools } = Cu.import("resource:///modules/devtools/gDevTools.jsm", {});

/**
* This object represents the extension. It's a singleton (only one
* instance created). It also serves as a Model (the M in MVC pattern)
* and provides some model API (see formatTime method).
*/
const MyExtension =
/** @lends MyExtension */
{
initialize: function(options) {
// Hook developer tools events.
gDevTools.on("toolbox-ready", this.onToolboxReady);
gDevTools.on("toolbox-destroy", this.onToolboxDestroy);
gDevTools.on("toolbox-destroyed", this.onToolboxClosed);
},

shutdown: function(reason) {
gDevTools.off("toolbox-ready", this.onToolboxReady);
gDevTools.off("toolbox-destroy", this.onToolboxDestroy);
gDevTools.off("toolbox-destroyed", this.onToolboxClosed);
},

// Event Handlers

/**
* Executed by the framework when {@Toolbox} is opened and ready to use.
* There is one instance of the {@Toolbox} per browser window.
* The event is fired after the current panel is opened & loaded
* (happens asynchronously) and ready to use.
*/
onToolboxReady: function(event, toolbox) {
},

/**
* Executed by the framework at the beginning of the {@Toolbox} destroy
* process. All instantiated panel objects are still available, which
* makes this method suitable for e.g. removing event listeners.
*/
onToolboxDestroy: function(eventId, target) {
},

/**
* Executed by the framework at the end of the {@Toolbox} destroy
* process. All panel objects are destroyed at this moment.
*/
onToolboxClosed: function(eventId, target) {
},

// Model API

/**
* Returns nicely formated time string.
*
* @param time {Number} A time in milliseconds.
*/
formatTime: function(time) {
let date = new Date(time);
return date.toString();
}
};

// Exports from this module
exports.MyExtension = MyExtension;
Loading

0 comments on commit 00f2219

Please sign in to comment.