- Installing
- Quick Start
- Rendering
- Forms
- Lists
- Extending
- Extensions
- Improving Compatibility
- Utils
- Using with JQuery
- Using with Framework7
Installation instructions for node
or the web, are explained in the main README page.
If you have 5 minutes, check our Quick Start Guide.
To initialize M2D2, you have mainly two options:
The first one, is to execute some code after the DOM is ready:
m2d2.ready($ => { /* ... */ });
The variable $
can be anything you choose (any variable name allowed by Javascript).
Throughout all the documentation, examples and tutorials we use $
for simplicity
(please don't get confused with JQuery $
).
The other option is used when you want to have access to M2D2 before the DOM is ready or after you know for a fact that it is ready (for example, if you use your scripts at the end of your HTML document, you want to implement an extension, when using together with another framework/library, etc.):
$ = m2d2.load();
The most basic functionality is to access to DOM elements easily:
const user = $("#user");
In this example user
is an extended DOM object. In addition to all the methods and properties
that a DOM element (HTMLElement or Node) possess, we are adding other methods and properties
which are convenient to simplify your code, for example:
- text : Set or Get text in Nodes
user.text = 'Hello World';
console.log(user.text);
- html : Set or Get HTML in Nodes (useful if HTML comes from the server, not recommended otherwise)
const fromServer = `<div class='blue'>User</div>`;
user.html = fromServer;
console.log(user.html);
- find(selector) : find the child which matches the CSS selector
- findAll(selector) : find all children who match the CSS selector
- sibling(selector) : find the sibling which matches the CSS selector
user.find(".delete").click();
user.findAll("li").forEach(li => { li.delete() });
user.sibling(".message");
- index() : Get the index (position) of child within its parent node (for
<option>
useindex
property)
movies.find(".favorite").index()
- parent() : Get parent node
- posterior() : Get next sibling element
- anterior() : Get previous sibling element
movies.parent()
movies.find(".favorite").posterior()
More about index
, posterior
and anterior
in lists
Other additional methods/properties added to Nodes (explained later):
- css : Specify class name(s)
- getData() : get data object of a form
- inView : if element is in view
- show : show/hide element
Additional events:
- onload(event => { ... }) : element was processed
- onready(event => { ... }) : element is ready
- onshow(event => { ... }) : element is now shown
- onupdate(event => { ... }) : element was updated
To simplify your code, M2D2 will guess which properties you are trying to set. For example, you can place your data in this way:
const user = $("#user", {
name : {
text : "Yoda"
},
age : {
text : 900
},
email : {
text : "yoda@starwars.com"
}
});
But it is easier this way:
const user = $("#user", {
name : "Yoda",
age : 900,
email : "yoda@starwars.com"
});
Depending on the element in which you are trying to set this data, M2D2 will decide
which property to set it to. If, for example, age
is an span
element, it will set the text
property,
(or html
if HTML is detected) but if it is an input
element, it will set it as value
:
<span class="age">900</span>
<input type="number" name="age" value="900" />
This is specially useful if your data comes from a server (which usually does), for example:
$.get("/user/yoda", response => {
if(response.data) {
const user = $("#user", response.data);
}
});
In which, response.data
is an object (from JSON) which matches your structure (or vice-versa).
NOTE:
$.get
is from the XHR extension
In order to achieve this functionality, M2D2 uses a Proxy
object, which may not be supported
in old browsers. To turn this feature OFF, you can set: m2d2.short = false
at the beginning
of your code.
More about how to access the elements' data when using short assignment
in Linking Elements
M2D2 will try its best to guess which node or property you are trying to assign, for example:
<div id="user" title="User Info">
<span class="name"></span>
<form>
<label><input type="text" name="age" /></label>
</form>
</div>
const user = $("#user", {
title : "User : Karen McLaren", // <--- user.title property
name : "Karen McLaren", // <--- span.name element
age : 30, // <--- input[name=age] element
label : "Age: ", // <--- label element
});
M2D2 will first look for <tag>
, #id
, [names]
and .classes
in that order.
One of the most interesting features in M2D2 is that you don't need to represent your data exactly in the same way you have your HTML structure. For example:
<div id="user">
<form>
<div class="wrapper">
<fieldset>
<div class="name">
<label>First Name:</label>
<input type="text" name="first" />
<label>Middle Name:</label>
<input type="text" name="middle" />
<label>Last Name:</label>
<input type="text" name="last" />
</div>
</fieldset>
</div>
</form>
</div>
In order to set the "Last Name", you don't need to do: user.form.wrapper.fieldset.name.last
(which would be painful), you can skip to the important parts:
const user = $("#user", {
first : "Barney",
middle : "Waitforit",
last : "Stinson"
});
And later, you can access or modify it easily:
user.middle = "Legendary"
console.log(user.middle.value)
NOTE: The short assignment can only be used when setting a value into an element or property and not when accessing it.
What happens, is that M2D2 creates links in the user
object that points to the child elements
of our choice (so we don't have to specify each step in the DOM tree).
One advantage of doing so, is that if we redesign our HTML completely, as long as we
keep the classes, names or ids in place, you won't need to change your Javascript. For example,
the above user
example, will work as well in this HTML (no matter the order of the elements):
<section id="user">
<div class="last"></div>
<div class="first"></div>
<div class="middle"></div>
</section>
Keeping the previous example in mind, user
is an HTMLElement (or DOM Node), so you can set
its title
property as usual:
user.title = 'Title'
// and read it in the same way:
console.log(user.title)
But for child elements:
user.first = "Barry"
console.log(user.first) // This will print the HTMLElement object
console.log(user.first.text) // This will print the property "text"
You can not use short assignment in root elements, the following IS NOT CORRECT:
user = "Not Correct" // Trying to replace variable `user`
Fortunately we set user
as const
, so that will never be allowed. The only way it will work is
if we specify the property:
user.text = "Correct"
You can always (when needed) create different M2D2 objects using the same elements, for example:
<div id="user">
<div class="name"></div>
<div class="email"></div>
</div>
const basic = $("#user", {
name : "jannete"
});
const advanced = $("#user", {
email : "jan@example.com"
});
In this case, basic
and advanced
are just aliases:
console.log(basic.email.text); // prints "jan@example.com"
console.log(advanced.name.text); // prints "jannete"
Possible applications for these "double references" are:
- Parts of the data comes from different data sources
- Simplify code by referring to different parts of the layout (shallow vs deep)
- Keep a logical reference to an element which may change over time (like:
user.main
in the case of having several users)
There are times in which you may want to use a class or id of an existing property:
<div id="user" title="User Profile">
<h1 class="title">User :</h1>
</div>
const user = $("#user", {
title: "New Title"
});
As user.title
is an existing property (which can not be replaced), M2D2 will create a link to
the h1
element as: user.$title
(and report it in the console logs).
In order to prevent conflicts, it is better to replace the class name or if you can't replace it (due to style declarations), add another class to access it, for example:
<div id="user" title="User Profile">
<h1 class="title user_title">User :</h1>
</div>
const user = $("#user", {
user_title: "New Title"
});
Other way can be to handle h1
element separately from user
:
const user_title = $("#user .title");
To make things easier to manage CSS class names, all M2D2 elements
have the css
property, which you can set in this way:
const user = $("#user", {
css : "myclass", // `<div class='myclass' ...
css : ["full", "blue"], // `<div class='full blue' ...
});
// Or:
user.css = "myclass";
user.css = ["full", "blue"];
In both of the cases, css
will replace any existing classes with the ones specified.
If you want to add or remove classes, you need to set an object with true
(add),
or false
(remove) as values:
const user = $("#user", {
css : {
full : true, // will add "full" to existing classes
link : false // will remove "link" class if exists
},
});
// Or:
user.css = { full : true, link : false }
When you read the css
property, it will return the classList
Node property:
user.css.contains("link");
user.css.add("blue");
user.css.remove("red");
user.css.toggle("active");
user.css.length;
user.css.forEach(cls => { /* ... */ })
You can also set the style you want directly:
const user = $("#user", {
style : {
color : "green", // or any HTML supported value
backgroundColor : "#3338", // background-color becomes backgroundColor in Javascript
display : "inline"
}
});
// Or:
user.style.backgroundColor = "#3338";
M2D2 offers you a simple way to hide / show HTML elements by using the property show
:
const user = $("#user", {
show : false, // initial state will be hidden
text : "User"
});
// Show user when menu botton is clicked:
const menu = $("#menu", {
user : {
onclick : function (ev) {
user.show = true
}
}
});
M2D2 also provides an event to perform actions when an element is shown:
const user = $("#user", {
show : false, // initial state will be hidden
text : "User",
onshow : function (ev) {
this.text = "User is now shown"
}
});
After an object has been created the onload
event is fired:
const user = $("#user", {
text : "User",
onload: function (ev) {
this.text = "Loaded" // you can not use `user` here
}
});
After an object has been rendered the onready
event is fired:
const user = $("#user", {
text : "User",
onready: function (ev) {
user.text = "Ready" // you can use `user` here
}
});
The main difference between onload
and onready
is that onready
is executed asynchronously after the
object has been created, so at that moment, the assigment has been completed and the object can be accessed.
As onload
is executed during the object creation it happens before onready
and we can be sure that no
other code has been executed at that moment, while in onready
, it is possible that other parts of your code
has been executed.
You may want to use onready
to be sure that the object has been created. Triggering events like focus()
may
work better in onready
than onload
.
With M2D2 you can observe changes on objects values and properties, so you don't have to check for changes constantly. These are some way in which that is useful:
With this feature, you can update immediately one object when another changes, for example:
<div id="user">
<span class="username"></span>
</div>
<form id="form">
<input type="text" name="new_user_name" value="" />
</form>
const form = $("#form", {
new_user_name : ""
});
const user = $("#user", {
username : [form.new_user_name, "value"]
});
Whenever form.new_user_name.value
changes, user.username.text
will be updated automatically. You can use this feature
in any property, including dataset
and style
:
const form = $("#form", {
dataset : { id : 0 },
style : { color : "blue" }
});
const user = $("#user", {
uid : [form.dataset, "id"],
style : { backgroundColor : [form.style, "color"] }
});
Even better, you can edit the value before assigning it:
const user = $("#user", {
uid : {
text: [form.dataset, "id", (val) => {
return (val * 1) + 1000;
}],
}
});
When form.dataset.id
is updated, we update user.uid.text
with that value plus 1000.
If you need to perform some action which is not an assigment to a property or value, you can use the onupdate
event:
const form = $("#form", {
new_user_name : {
onupdate : function (ev) {
console.log(ev,detail);
}
}
});
In console will print something like:
{
"type" : "string",
"property" : "value",
"newValue" : "user1000",
"oldValue" : ""
}
So far we have always used M2D2 with a selector and an object ($(selector, object)
), but there is one more usage:
const fragment = $({
text : "I'm invisiblae"
})
When you don't use a selector, you are creating a Node which is not connected to the Document
(an HTML Fragment).
It can be used in many ways, but one useful way is to keep a shared object for references:
$.scope = $({
user : "peter600",
level : "moderator"
});
const user = $("#user", {
username : [$.scope, "user"]
})
NOTE:
$.scope
is assigned to the M2D2 instance. More on this on General Recommendations
The main advantage of using a fragment is that it doesn't depend on whether the element exists or not.
M2D2 will help you to handle input forms easily. These are the highlights:
<form id="user">
<input type="text" name="username" />
<input type="checkbox" value="1" name="active" />
<button type="submit">Send</button>
</form>
Handling properties is as simple as:
const user = $("#user", {
username : {
disabled : true
},
active : {
checked : true
}
})
// Changing the properties:
user.username.disabled = false
user.active.checked = false
M2D2 supports using tag names or classes to update a group of elements. This is particularly useful in forms:
const user =$("#user", {
input : {
disable : true,
onclick : function(ev) {
this.css.toggle("clicked");
}
}
})
That way, all <input>
elements will be disabled by default and all of them, will have the same onclick
event function.
You can use this feature to apply rules or values to several elements at once, and then set individual rules or values
for them (which reduce code by eliminating repetition):
const user =$("#user", {
input : {
disable : true,
onclick : function(ev) {
this.css.toggle("clicked");
}
},
username : "jennifer1",
active : {
checked : true
}
})
const user = $("#user", {
username : {
requried : true,
placeholder: "User name here",
pattern : "[a-z0-9]+",
title : "example: user100",
oninput : function (ev) {
this.css.add("active")
},
onblur : function (ev) {
this.css.remove("active")
}
},
// On form submit:
onsubmit : function (ev) {
const data = this.getData();
// data will contain an object with the values, for example:
/*
{
username : "Matt",
active : 1
}
*/
return false;
},
onload : function () {
this.username.focus();
}
})
When using getData()
it will validate the form first and then will return an object with all the visible
element's data (including type='hidden'
). If you want to include also fields that are not shown, set as argument: true
in
getData(true)
.
To generate a list of elements M2D2 has two properties: template
and items
:
const users = $("#users", {
template : { },
items : []
});
template
is an object which will define the structure and items
is an array which holds the data. You must specify
at least one of the two, for example:
<ul id="list"></ul>
const users = $("#users", {
items : [
"one",
"two",
"three"
]
});
it will generate:
<ul id="list">
<li>one</li>
<li>two</li>
<li>three</li>
</ul>
M2D2 will try to guess which template to use. In this case, as #list
is <ul>
it will create <li>
elements.
It is equivalent to:
const users = $("#users", {
template : "li", // You can specify the tag name you want for your items
items : [
"one",
"two",
"three"
]
});
If you want to specify a more complex structure, you have 2 ways to define templates:
- in HTML
- in Javascript
Templates can be specified using standard HTML:
<div id="users">
<template>
<div class="user">
<span>Name:</span>
<span class="name"></span>
<span>Age:</span>
<span class="age"></span>
</div>
</template>
</div>
const users = $("#users", {
items : [
{ name : "Paul", age : 23 },
{ name : "Sam", age : 72 },
{ name : "Tam", age : 44 },
]
});
If <template>
is specified, M2D2 will use its content to generate new items.
<div id="users"></div>
const users = $("#users", {
template : {
div : {
css : "user",
labelName : {
tagName : "span",
text : "Name:"
},
name : {
tagName : "span",
css : "name"
},
labelAge : {
tagName : "span",
text : "Age:"
},
age : {
tagName : "span",
css : "age"
}
}
},
items : [
{ name : "Paul", age : 23 },
{ name : "Sam", age : 72 },
{ name : "Tam", age : 44 },
]
});
You can also generate HTML inside your items, for example:
const users = $("#users", {
items : [
{
div : {
css : "user",
test : "Angeline"
}
},
{
div : {
css : "user",
test : "Anthony"
},
}
]
});
However, using templates is cleaner and easier to maintain (recommended way).
You most certainly will need to interact with your items once they are rendered. You can also specify events inside your templates:
const users = $("#users", {
template : {
name : {
tagName : "span",
css : "name",
onclick : function (ev) {
users.title = "Active user: " + this.text
}
}
},
items : [
{ name : "Paul" },
{ name : "Sam" },
{ name : "Tam" },
]
});
If you are using HTML templates you can set the events in Javascript:
<div id="users">
<template>
<div class="user">
<span>Name:</span>
<span class="name"></span>
</div>
</template>
</div>
const users = $("#users", {
template : {
name : {
onclick : function (ev) {
users.title = "Active user: " + this.text
}
}
},
items : [
{ name : "Paul" },
{ name : "Sam" },
{ name : "Tam" },
]
});
Also, you can set them in HTML (not recommended):
<div id="users">
<template>
<div class="user">
<span>Name:</span>
<span class="name" onclick="someFunction"></span>
</div>
</template>
</div>
Once your items have been rendered, you can access them in many ways:
// Get total of items:
users.items.length
// Traverse all items:
users.items.forEach(item => { /* ... */ })
// Clear all items:
users.items.clear()
// Get the first item:
users.items.first()
// Get the last item:
users.items.last()
NOTE: M2D2 will automatically add
dataset.id
to each item.
To select one item from the list:
const users = $("#users", {
template : {
onclick : function (ev) {
this.selected = true;
}
}
/* ... */
});
// You can also set it:
users.items.first().selected = true;
users.items.get(10).selected = true;
Once an item is selected, M2D2 will add the [selected]
attribute to the item (you can set special CSS rules for that).
Then, you can use:
users.items.selected() // get the selected item
users.items.selected().index() // get the selected item index
users.items.selected().dataset.id // get the selected item original index (this is preserved after removing or adding items)
users.items.selected().posterior().selected = true // select the next item from the selected one
users.items.selected().anterior().selected = true // select the previous item from the selected one
You can use almost all Array functions with items
, like:
- at
- concat
- copyWithin
- every
- fill
- filter
- find
- findAll
- findIndex
- forEach
- includes
- indexOf
- join
- keys
- lastIndexOf
- map
- reduce
- reduceRight
- reverse
- slice
- some
- splice
- values
In addition to those functions, we have added more, like:
- clear
- first
- get
- last
- pop
- push
- remove
- selected
- shift
- sort
- unshift
If you want to sort a simple list, list.items.sort()
and list.items.reverse()
are all you need.
If you need to sort more specifically, look at this example:
<table id="list">
<thead class="headers">
<tr>
<th class="fruit">Fruit</th>
<th class="qty">Qty</th>
</tr>
</thead>
<tbody class="data">
<template>
<tr>
<td class="fruit"></td>
<td class="qty"></td>
</tr>
</template>
</tbody>
</table>
const list = $("#list", {
dataset: { sortedBy: "" },
headers : {
th : {
onclick : function (ev) {
const th = this;
const label = th.text;
const prop = th.css[0];
const compare = ( a, b ) => {
return a[prop].text < b[prop].text ? -1 : (a[prop].text > b[prop].text ? 1 : 0)
}
if(list.dataset.sortedBy === label) {
list.data.items.reverse();
} else {
list.dataset.sortedBy = label;
list.data.items.sort(compare);
}
}
}
},
data : {
items: [
{ fruit : "Apple", qty: 19 },
{ fruit : "Orange", qty: 34 },
{ fruit : "Banana", qty: 62 },
{ fruit : "Peach", qty: 15 },
{ fruit : "Mango", qty: 18 },
{ fruit : "Watermelon", qty: 30 },
{ fruit : "Papaya", qty: 14 },
{ fruit : "Pear", qty: 22 },
{ fruit : "Strawberry", qty: 45 }
]
}
});
If you want to add some functionality that you will likely use in several parts of your code (globally) or perhaps in different projects, you can do so by extending M2D2:
// Here we don't use 'ready', we use 'load' (before DOM is ready)
m2d2.load($ => {
$.canvasEditor = () => {
// Do something here
}
})
// To use it:
m2d2.ready($ => {
// Anywhere:
$.canvasEditor();
});
M2D2 comes with some extensions that you may find useful. These are optional. We have also prepared bundles which contains these extensions according to functionality.
This extension makes it easy to display alerts, confirmation, input dialogs and more. Read more about the Alerts Extension
With this extension you can handle multiple languages easily. Read more about the Language Extension
This extension provides an easy way to save and restore data into localStorage and sessionStorage. Read more about the Storage Extension
This extension handles almost any kind of HTTP request to a server (e.g., GET, POST, PUT, DELETE, etc.) Read more about the XHR Extension
This extension makes it easy to upload files to a server via XHR (included in XHR bundle). Read more about the Upload XHR Extension
This extension gives you an easy-to-use WebSocket client. Read more about the WebSocket client Extension
Most of the modern browsers should work fine, but in case you want to support a browser which is complaining about
Proxy
or MutationObserver
objects, you can turn them off:
// At the beginning of your code
m2d2.short = false;
By turn this feature OFF, you won't be able to short assign values: user.name = "Kelly"
you will have to write: user.name.text = "Kelly"
// At the beginning of your code
m2d2.updates = false
By turning this feature OFF, you won't be able to use Linked References.
In addition to all the tools explained in this documentation, M2D2 also provides extra tools to handle nodes, types, properties, attributes, etc. Read more about Utils
In most cases you won't need JQuery at all, but there are some valid reason on why to use it together, for example, you may have your own libraries already written in JQuery, or you want to use some well-known libraries together with your code (like JQuery-UI, LightBox, Tabslet, FixText, etc.). Why having to reinvent the wheel?
To use it together with JQuery you may want to rename the $
variable (either in JQuery or in M2D2) if
you want to use JQuery inside the M2D2 scope (otherwise, you may still be able to use $
variable JQuery in its own scope),
for example:
To use them separately:
m2d2.ready($ => {
// Your M2D2 code here
const user = $("#user", {
show : false,
onclick : function (ev) { /* ... */ }
});
});
jQuery($ => {
// Your jQuery code here
$("#user").toggle().click();
});
Or together (M2D2 is $
):
m2d2.ready($ => {
const user = $("#user", {
show : true,
onclick : function (ev) {
jQuery(this).toggle();
}
});
});
Or (JQuery is $
):
const m2 = m2d2.load();
jQuery($ => {
const user = m2("#user", {
show : true,
onclick : function (ev) {
$(this).toggle();
}
});
});
- Install M2D2 using
npm
:
npm i m2d2
- Inside your
app.js
, add (at the top of your javascript imports):
// Import M2D2 with WS bundle
import m2d2 from 'm2d2/ws';
Read more in: Import other bundles
- Initialize pages:
// Initialize pages (at the end of app.js)
var app = new Framework7({
/* ... */
});
app.on('pageInit', (page) => {
const $ = m2d2.load();
if(page && pages[page.name] !== undefined) {
pages[page.name]($, app);
}
});
- Inside your pages (you don't use
m2d2.ready
):
export default ($, $f7) => {
const login = $("#login", {
form : {
onsubmit : function(ev) {
const data = this.getData();
$f7.dialog.alert('Username: ' + data.username + '<br/>Password: ' + data.password, () => {
$f7.loginScreen.close();
});
return false;
}
},
username : "admin",
password : "******"
});
}