-
Notifications
You must be signed in to change notification settings - Fork 8
Creating Widgets
Widgets are multi-purpose independent windows. These are essentially netnet.studio "addons" or "plugins." They can be used during tutorials to open up other media types (images, videos, gifs, audio, texts, 3D objects, etc). Widgets can also be their own miscellaneous utilities. For example we could create a widget that explains some concept using interactive graphics. Widgets can also be GUIs that interact with the netitor, for example a CSS generator that outputs to the line where the user's cursor is currently positioned. The sky's the limit!
The Widget class can be used to instantiate new widgets. A widget is a window styled to match netnet's current theme. This includes shadows that respond to the mouse's position. If the window manager update's netnet's theme all widgets will automatically update their themes to match. (for more info on changing netnet's state including how to dispatch a CHANGE_THEME
action see the State Management wiki page)
To instantiate a new widget:
const w = new Widget({
title: 'settings',
innerHTML: element
})
Where the value of the innerHTML property can either be an string (including an html string like <h1>Hello World!</h1>
) or an instance of HTMLElement (ie. a DOM element, const element = document.createElement('h1')
).
Widgets are resizable by default (the users can click and drag the bottom right corner to resize), but if we don't want a widget to be resizable by the user we can set the optional resizable: false
. When widgets are loaded into netnet using the LOAD_WIDGETS
action (see Instantiating Widgets below) it will automatically be indexed and tracked by the Widgets Menu. If for whatever reason the Widgets Menu should not include this widget in it's list you can set the optional listed: false
.
If we'd like to instantiate the widget at a particular location and/or at a particular size we can use these optional properties:
const w = new Widget({
title: 'settings', // required
innerHTML: element, // optional html string or HTMLElement
resizable: true, // optional (allow user to resize)
listed: true, // optional (allow user to star/display in Widgets Menu?)
left: 20, // optional
top: 20, // optional
zIndex: 100, // optional (make sure it's always between 100 && 200)
width: 500, // optional
height: 500 // optional
})
We can pass these properties a number or a CSS string, like x: '50vw'
and width: '100%'
and y: '100px'
.
All the properties passed in the constructor's options argument can also be accessed/updated via the instance's properties at any point, for example:
w.title = 'layout settings'
w.innerHTML = '<h1>these are the options</h1>'
w.left = innerWidth / 2
w.top = '50vh'
w.bottom = 20
w.right = '50vh'
w.zIndex = 100
w.width = '50vw'
w.height = '50vh'
w.opened // read only property, returns true/false
It's important to note that instantiating the widget creates it in memory, but it does not visually present it to the user. In order to display the widget we need to use the instance's .open()
method. There's also the .close()
method for hiding the widget (while maintaining it in memory)
w.open() // display
w.close() // hide
There are also a couple methods for positioning the Widget recenter()
which returns the widget to it's default position at the center the window, and update(cssObj, transitionTime)
which animates a move/tween from the current position to the position object passed as the first argument.
w.recenter() // recenters the widget
// when using update() to position the widget (left/right/top/bottom)
// you must pass number values (not strings), for example:
w.update({ top: 29, right: 20 }, 500)
You can also attach events to the Widget's open/close event using the on()
method:
// to create event listeners
w.on('open', () => {
// do a thing right after the widget opens
})
w.on('close', () => {
// do a thing right after the widget closes
})
In most cases we'll want the StateManager to keep track of the Widget instance, to make that happen we'll need to instantiate it like this:
// the keyname has to be unique among the other globally loaded
// widgets in the window.WIDGETS dictionary/object
STORE.dispatch('LOAD_WIDGETS', {
keyname: new Widget({ title: 'settings', innerHTML: element })
})
Of course if we instantiate the widget outside of the dispatch call we could also pass the instance into it like this: STORE.dispatch('LOAD_WIDGETS', { name: w })
assuming w
is an instance of a Widget
.
If you instantiate a widget without loading it into netnet's state by dispatching a 'LOAD_WIDGETS' action, then not only will it not be managed by the StateManager but it also won't show up in the Widgets Menu (regardless of it's .listed
value). NOTE: when instantiating a widget in a tutorial and including it in the TUTORIAL.widgets
array, the TutorialManager will take care of dispatching the LOAD_WIDGETS
action. See the Creating Tutorials wiki for more on that.
The basic widget class is great for doing things like loading up images or videos, but for more interesting/complicated widgets we can create our own widget classes by extending the base widget class, for example:
class RandomColor extends Widget {
constructor (opts) {
super(opts)
this.key = 'random-color'
this.keywords = ['rgb', 'colour', 'chance']
this.title = 'Random Color'
this.innerHTML = `<button>insert color</button>`
this.$('button').onclick = () => { this.click() }
}
click () {
const r = Maths.randomInt(255)
const g = Maths.randomInt(255)
const b = Maths.randomInt(255)
const code = `rgb(${r}, ${g}, ${b})`
NNE.cm.replaceSelection(code)
}
}
NOTE: Maths is one of netnet's global objects, see the Global Objects section in the Project Architecture wiki
The WindowManager handles loading all of netnet's widget classes when the page loads. In order for the WindowManager to find the widgets they need be saved in the www/widgets
directory. The directory also has an ExampleWidget.js
file which can be copied and used as a template or starting point. Assuming we want to WindowManager to automatically instantiate (via LOAD_WIDGETS
) for us, we need to make sure that the class name matches the name of the file itself and we also need to make sure we define a .key
property in our constructor.
If we want this widget to show up in netnet's SearchBar results then we also need to include the optional .keywords = []
array of tags/keywords.
The next section explains how to use the Convo class to create conversations, netnet's interactive speech bubbles.