Building Modern Web Apps with
HTML5 and DART
-
Building Modern Web Apps with
-
aka "drfibonacci"


Media Queries for Style Sheets
<link rel="stylesheet" media="all" href="/static/css/base.min.css" /> <link rel="stylesheet" media="only screen and (max-width: 800px)" href="/static/css/mobile.min.css" />
Testing CSS media queries in JavaScript with window.matchMedia()
if (window.matchMedia('only screen and (max-width: 480px)').matches) {
// Asynchronously provide experience optimized for phone
} else if (window.matchMedia('only screen and (min-width: 481px) and ' +
'(max-width: 1024px)').matches) {
// Asynchronously provide experience optimized for table or smaller screen
} else {
// Asynchronously provide full screen experience
}
Include FormFactor module from mobilewebapp sample
<inherits name='com.google.gwt.sample.mobilewebapp.FormFactor'/>
Use formfactor property to select UI factory impl
<!-- Use ClientFactoryImplMobile for mobile form factor. -->
<replace-with class="ClientFactoryImplMobile">
<when-type-is class="ClientFactory"/>
<when-property-is name="formfactor" value="mobile"/>
</replace-with>
function updateHash(push) {
if (push) {
var slideNo = curSlide + 1;
var hash = '#' + slideNo;
window.history.pushState(slideNo, 'Slide ' + slideNo, hash);
}
};
window.addEventListener('popstate', handlePopState, false);
function handlePopState(event) {
if (event.state != null) {
curSlide = event.state - 1;
updateSlides(true);
}
};
GWT's History object
See also my two talks in Developer Tools track from I/O
<input type="file" id="dir-select" webkitdirectory />
document.querySelector('#dir-select').onchange = function(e) {
var out = [];
for (var i = 0, f; f = e.target.files[i]; ++i) {
out.push(f.webkitRelativePath);
}
document.querySelector('output').textContent = out.join('/n');
};
var files = document.querySelectorAll('.dragout');
for (var i = 0, file; file = files[i]; ++i) {
file.addEventListener('dragstart', function(e) {
e.dataTransfer.setData('DownloadURL', this.dataset.downloadurl);
}, false);
}
( this feature is only available in Google Chrome )
document.querySelector('#dropzone').
addEventListener('drop', function(e) {
var reader = new FileReader();
reader.onload = function(evt) {
document.querySelector('img').src = evt.target.result;
};
reader.readAsDataURL(e.dataTransfer.files[0]);
}, false);
window.requestFileSystem(TEMPORARY, 1048576, initFs, fsError);
function saveFile(arrayBuffer, filename, type, callback) {
fs.root.getFile(filename, {create: true}, function(fileEntry) {
fileEntry.createWriter(function(fileWriter) {
var bb = new BlobBuilder();
bb.append(arrayBuffer);
fileWriter.write(bb.getBlob(type));
}, fsError);
callback(fileEntry);
}, fsError);
}
Browse File System
Application Cache to enable the application
<html manifest="cache.appcache">
GWT can generate for you: see AppCacheLinker in mobilewebapp sample
Store data locally
window.requestFileSystem(PERSISTENT, 1048576, initFs, fsError);
var idbRequest = window.indexedDB.open('Database Name');
localStorage["key"] = "value";
Track online and offline states
window.addEventListener('online', function(e) {
// Re-sync data with server.
}, false);
var elems = document.querySelectorAll("textarea, input");
var len = elems.length;
for (var i = 0; i < len; i++) {
var elem = elems[i];
elem.addEventListener("input", function(item) {
localStorage[formName + "-" + item.srcElement.id] =
item.srcElement.value;
var debug = document.getElementById("data-persistence-debug");
debug.innerHTML = "Last auto-saved at: " + new Date();
}, false);
}
Using the LocalStorage API in GWT 2.3+
import com.google.gwt.storage.client.Storage;
private FlexTable stocksFlexTable = new FlexTable();
private Storage stockstore = null;
stockStore = Storage.getLocalStorageIfSupported();
if (stockStore != null){
for (int i = 0; i < stockStore.getLength(); i++){
String key = stockStore.key(i);
stocksFlexTable.setText(i+1, 0, stockStore.getItem(key));
stocksFlexTable.setWidget(i+1, 2, new Label());
}
}
<input type="text" required />
<input type="email" value="some@email.com" />
<input type="date" min="2010-08-14" max="2011-08-14" value="2010-08-14"/>
<input type="range" min="0" max="50" value="10" />
<input type="search" results="10" placeholder="Search..." />
<input type="tel" placeholder="(555) 555-5555"
pattern="^\(?\d{3}\)?[-\s]\d{3}[-\s]\d{4}.*?$" />
<input type="color" placeholder="e.g. #bbbbbb" />
<input type="number" step="1" min="-5" max="10" value="0" />
<input type="text" x-webkit-speech />
function startSearch(event) {
if (event.target.results.length > 1) {
var second = event.target.results[1].utterance;
document.getElementById("second_best").value = second;
}
event.target.form.submit();
}
function showNotifications(pic, title, text) {
if (window.webkitNotifications.checkPermission() == 0) {
var notificationWindow =
window.webkitNotifications.createNotification(pic, title, text);
notificationWindow.show();
// close notification automatically after a timeout
setTimeout(function(popup) {
popup.cancel();
}, 6000, notificationWindow);
}
}
Use Application Cache to provide local caching
<html manifest="cache.appcache">
Store data locally
window.requestFileSystem(PERSISTENT, 1048576, initFs, fsError);
var idbRequest = window.indexedDB.open('Database Name');
localStorage["key"] = "value";
Use CSS3 transitions, transforms and animations
transition: all 1s ease-in-out;
Use WebWorkers for non-blocking JavaScript
var worker = new Worker('myworker.js');
More great tips at http://bit.ly/rizNVE

Activities and Places (not MVC proper)
gwt-platform (also dispatcher, Eclipse plugin)
mvp4g (event-centric)
gwt-mpv (generate views from ui.xml)

interface Shape {
perimeter(); // return type optional
}
class Rectangle implements Shape {
final num height, width;
// Compact constructor syntax
Rectangle(num this.height, num this.width);
// Terse function syntax
perimeter() => 2*height + 2*width;
}
class Square extends Rectangle {
Square(num size) : super(size, size);
}
main() {
var p = new Square(5).perimeter(); // dynamic type
print('Perimeter=$p'); // String interpolation
}
elem.on.click.add( (event) => print('click!') );
addHandlers() {
canvasElement.onmousemove = onMouseMove;
// No anonymous inner classes!
canvasElement.onmousedown = (Event e) { window.alert('click'); };
}
onMouseMove(MouseEvent event) {
int x = event.offsetX;
int y = event.offsetY - 40;
if (( y < 0) || (x >= width)) {
return;
}
ctx.setFillStyle(getHexString(getColorIndex(x, y)));
ctx.fillRect(0, 0, width/2, 30);
}
String get selectedColor() => _selectedColor; void set selectedColor(num i) { _selectedColor = getHexString(i.floor()); showSelected(); fireSelected(); }
newColor = selectedColor;
selectedColor = getColorIndex(x, y);
Play 23% more and spend 147% more
100% more engagement
{ "name": "My Chrome Web Store",
"description": "Discover great apps, games, extensions and...",
"version": "1.0",
"icons": {
"16": "16.png",
"128": "128.png"
},
"app": {
"urls": ["*://chrome.google.com/webstore"],
"launch": {
"web_url": "https://chrome.google.com/webstore"
}
},
"permissions": ["unlimitedStorage", "notifications"]
}
In App Payments
Aim for a great experience, everything else will follow.