diff --git a/demos/codelab/README.md b/demos/codelab/README.md
new file mode 100644
index 000000000..2a5e91315
--- /dev/null
+++ b/demos/codelab/README.md
@@ -0,0 +1,16 @@
+# Blockly for the Web codelab
+
+Code for the [Blockly for the Web codelab](https://developers.google.com/TODO).
+
+In this codelab, you'll learn how to use Blockly JavaScript library
+to add a block code editor to a web application.
+
+## What you'll learn
+
+* How to add Blockly to a sample web app.
+* How to set up a Blockly workspace.
+* How to create a new block in Blockly.
+* How to generate and run JavaScript code from blocks.
+
+Example code for each step of the codelab is available from
+the [completed](completed/) directory.
diff --git a/demos/codelab/app/index.html b/demos/codelab/app/index.html
new file mode 100644
index 000000000..31eeff776
--- /dev/null
+++ b/demos/codelab/app/index.html
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+ Blockly for the Web Codelab
+
+
+
+
+
+
+
+
Music Maker
+
Music Maker Configuration
+
+
+
+
+
+
+
Tap any button to edit its code. When complete, press Done.
+
+
+
+
1
+
2
+
3
+
+
+
4
+
5
+
6
+
+
+
7
+
8
+
9
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/codelab/app/scripts/main.js b/demos/codelab/app/scripts/main.js
new file mode 100644
index 000000000..31fba5a97
--- /dev/null
+++ b/demos/codelab/app/scripts/main.js
@@ -0,0 +1,61 @@
+/**
+ * Copyright 2017 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ (function() {
+
+ let currentButton;
+
+ function handlePlay(event) {
+ // Add code for playing sound.
+ }
+
+ function save(button) {
+ // Add code for saving the behavior of a button.
+ }
+
+ function handleSave() {
+ document.body.setAttribute('mode', 'edit');
+ save(currentButton);
+ }
+
+ function enableEditMode() {
+ document.body.setAttribute('mode', 'edit');
+ document.querySelectorAll('.button').forEach(btn => {
+ btn.removeEventListener('click', handlePlay);
+ btn.addEventListener('click', enableBlocklyMode);
+ });
+ }
+
+ function enableMakerMode() {
+ document.body.setAttribute('mode', 'maker');
+ document.querySelectorAll('.button').forEach(btn => {
+ btn.addEventListener('click', handlePlay);
+ btn.removeEventListener('click', enableBlocklyMode);
+ });
+ }
+
+ function enableBlocklyMode(e) {
+ document.body.setAttribute('mode', 'blockly');
+ currentButton = e.target;
+ }
+
+ document.querySelector('#edit').addEventListener('click', enableEditMode);
+ document.querySelector('#done').addEventListener('click', enableMakerMode);
+ document.querySelector('#save').addEventListener('click', handleSave);
+
+ enableMakerMode();
+
+})();
diff --git a/demos/codelab/app/scripts/music_maker.js b/demos/codelab/app/scripts/music_maker.js
new file mode 100644
index 000000000..50a8a3b7f
--- /dev/null
+++ b/demos/codelab/app/scripts/music_maker.js
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2017 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ const MusicMaker = {
+ queue_: [],
+ player_: new Audio(),
+ queueSound: function(soundUrl) {
+ this.queue_.push(soundUrl);
+ },
+ play: function() {
+ let next = this.queue_.shift();
+ if (next) {
+ this.player_.src = next;
+ this.player_.play();
+ }
+ },
+};
+
+MusicMaker.player_.addEventListener(
+ 'ended', MusicMaker.play.bind(MusicMaker));
diff --git a/demos/codelab/app/sounds/c4.m4a b/demos/codelab/app/sounds/c4.m4a
new file mode 100644
index 000000000..33941cfae
Binary files /dev/null and b/demos/codelab/app/sounds/c4.m4a differ
diff --git a/demos/codelab/app/sounds/c5.m4a b/demos/codelab/app/sounds/c5.m4a
new file mode 100644
index 000000000..49721cd31
Binary files /dev/null and b/demos/codelab/app/sounds/c5.m4a differ
diff --git a/demos/codelab/app/sounds/d4.m4a b/demos/codelab/app/sounds/d4.m4a
new file mode 100644
index 000000000..51bcad6c2
Binary files /dev/null and b/demos/codelab/app/sounds/d4.m4a differ
diff --git a/demos/codelab/app/sounds/e4.m4a b/demos/codelab/app/sounds/e4.m4a
new file mode 100644
index 000000000..d910052ef
Binary files /dev/null and b/demos/codelab/app/sounds/e4.m4a differ
diff --git a/demos/codelab/app/sounds/f4.m4a b/demos/codelab/app/sounds/f4.m4a
new file mode 100644
index 000000000..c80a0bfd3
Binary files /dev/null and b/demos/codelab/app/sounds/f4.m4a differ
diff --git a/demos/codelab/app/sounds/g4.m4a b/demos/codelab/app/sounds/g4.m4a
new file mode 100644
index 000000000..45ea44830
Binary files /dev/null and b/demos/codelab/app/sounds/g4.m4a differ
diff --git a/demos/codelab/app/styles/index.css b/demos/codelab/app/styles/index.css
new file mode 100644
index 000000000..0ef8f3bfc
--- /dev/null
+++ b/demos/codelab/app/styles/index.css
@@ -0,0 +1,75 @@
+main {
+ width: 400px;
+ position: relative;
+ margin: 0 auto;
+ overflow:hidden;
+ height: 600px;
+}
+
+header {
+ background-color: green;
+ width: 100%;
+}
+
+h1 {
+ width: 400px;
+ position: relative;
+ margin: 0 auto;
+ color: #fff;
+ font-size: 1.8em;
+ line-height: 2.4em;
+}
+
+.mode-edit,
+.mode-maker,
+.mode-blockly {
+ display: none;
+}
+
+[mode="maker"] .mode-maker,
+[mode="edit"] .mode-edit,
+[mode="blockly"] .mode-blockly {
+ display: block;
+}
+
+.blockly-editor {
+ position: absolute;
+ top: 64px;
+ left: -400px;
+ transition: left .4s;
+ height: 460px;
+ width: 400px;
+ background-color: #eee;
+}
+
+[mode="blockly"] .blockly-editor {
+ left: 0;
+}
+
+.maker {
+ display: flex;
+ flex-flow: column;
+ justify-content: space-between;
+ height: 460px;
+ width: 400px;
+}
+
+.maker > div {
+ display: flex;
+ justify-content: space-between;
+}
+
+.button {
+ width: 120px;
+ height: 140px;
+ color: #fff;
+ font-size: 3em;
+ text-align: center;
+ vertical-align: middle;
+ line-height: 140px;
+}
+
+.mdl-button {
+ margin: 1em 0;
+ float: right;
+}
diff --git a/demos/codelab/completed/finish/index.html b/demos/codelab/completed/finish/index.html
new file mode 100644
index 000000000..e2d12af1b
--- /dev/null
+++ b/demos/codelab/completed/finish/index.html
@@ -0,0 +1,73 @@
+
+
+
+
+
+
+
+ Blockly for the Web Codelab
+
+
+
+
+
+
+
+
Music Maker
+
Music Maker Configuration
+
+
+
+
+
+
+
Tap any button to edit its code. When complete, press Done.
+
+
+
+
1
+
2
+
3
+
+
+
4
+
5
+
6
+
+
+
7
+
8
+
9
+
+
+
+
+
+
+
+
+
+
+ 5
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/codelab/completed/finish/scripts/main.js b/demos/codelab/completed/finish/scripts/main.js
new file mode 100644
index 000000000..03a47e050
--- /dev/null
+++ b/demos/codelab/completed/finish/scripts/main.js
@@ -0,0 +1,88 @@
+/**
+ * Copyright 2017 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ (function() {
+
+ let currentButton;
+
+ function handlePlay(event) {
+ loadWorkspace(event.target);
+ Blockly.JavaScript.addReservedWords('code');
+ var code = Blockly.JavaScript.workspaceToCode(Blockly.getMainWorkspace());
+ code += 'MusicMaker.play();';
+ // Eval can be dangerous. For more controlled execution, check
+ // https://github.com/NeilFraser/JS-Interpreter.
+ try {
+ eval(code);
+ } catch (error) {
+ console.log(error);
+ }
+ }
+
+ function loadWorkspace(button) {
+ let workspace = Blockly.getMainWorkspace();
+ workspace.clear();
+ if (button.blocklyXml) {
+ Blockly.Xml.domToWorkspace(button.blocklyXml, workspace);
+ }
+ }
+
+ function save(button) {
+ let xml = Blockly.Xml.workspaceToDom(Blockly.getMainWorkspace());
+ button.blocklyXml = xml;
+ }
+
+ function handleSave() {
+ document.body.setAttribute('mode', 'edit');
+ save(currentButton);
+ }
+
+ function enableEditMode() {
+ document.body.setAttribute('mode', 'edit');
+ document.querySelectorAll('.button').forEach(btn => {
+ btn.removeEventListener('click', handlePlay);
+ btn.addEventListener('click', enableBlocklyMode);
+ });
+ }
+
+ function enableMakerMode() {
+ document.body.setAttribute('mode', 'maker');
+ document.querySelectorAll('.button').forEach(btn => {
+ btn.addEventListener('click', handlePlay);
+ btn.removeEventListener('click', enableBlocklyMode);
+ });
+ }
+
+ function enableBlocklyMode(e) {
+ document.body.setAttribute('mode', 'blockly');
+ currentButton = e.target;
+ loadWorkspace(currentButton);
+ }
+
+ document.querySelector('#edit').addEventListener('click', enableEditMode);
+ document.querySelector('#done').addEventListener('click', enableMakerMode);
+ document.querySelector('#save').addEventListener('click', handleSave);
+
+ enableMakerMode();
+
+ Blockly.inject('blockly-div', {
+ media: '/media/',
+ toolbox: document.getElementById('toolbox'),
+ toolboxPosition: 'end',
+ horizontalLayout: true,
+ scrollbars: false
+ });
+})();
diff --git a/demos/codelab/completed/finish/scripts/music_maker.js b/demos/codelab/completed/finish/scripts/music_maker.js
new file mode 100644
index 000000000..50a8a3b7f
--- /dev/null
+++ b/demos/codelab/completed/finish/scripts/music_maker.js
@@ -0,0 +1,33 @@
+/**
+ * Copyright 2017 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+ const MusicMaker = {
+ queue_: [],
+ player_: new Audio(),
+ queueSound: function(soundUrl) {
+ this.queue_.push(soundUrl);
+ },
+ play: function() {
+ let next = this.queue_.shift();
+ if (next) {
+ this.player_.src = next;
+ this.player_.play();
+ }
+ },
+};
+
+MusicMaker.player_.addEventListener(
+ 'ended', MusicMaker.play.bind(MusicMaker));
diff --git a/demos/codelab/completed/finish/scripts/sound_blocks.js b/demos/codelab/completed/finish/scripts/sound_blocks.js
new file mode 100644
index 000000000..b666d4f4d
--- /dev/null
+++ b/demos/codelab/completed/finish/scripts/sound_blocks.js
@@ -0,0 +1,50 @@
+/**
+ * Copyright 2017 Google Inc.
+ * https://developers.google.com/blockly/
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+Blockly.defineBlocksWithJsonArray([
+ // Block for colour picker.
+ {
+ "type": "play_sound",
+ "message0": "Play %1",
+ "args0": [
+ {
+ "type": "field_dropdown",
+ "name": "VALUE",
+ "options": [
+ ["C4", "sounds/c4.m4a"],
+ ["D4", "sounds/d4.m4a"],
+ ["E4", "sounds/e4.m4a"],
+ ["F4", "sounds/f4.m4a"],
+ ["G4", "sounds/g4.m4a"],
+ ["A5", "sounds/a5.m4a"],
+ ["B5", "sounds/b5.m4a"],
+ ["C5", "sounds/c5.m4a"]
+ ]
+ }
+ ],
+ "previousStatement": null,
+ "nextStatement": null,
+ "colour": 355,
+ "tooltip": "",
+ "helpUrl": ""
+ }
+]);
+
+Blockly.JavaScript['play_sound'] = function(block) {
+ var value = '\'' + block.getFieldValue('VALUE') + '\'';
+ return 'MusicMaker.queueSound(' + value + ');\n';
+};
diff --git a/demos/codelab/completed/finish/sounds/c4.m4a b/demos/codelab/completed/finish/sounds/c4.m4a
new file mode 100644
index 000000000..33941cfae
Binary files /dev/null and b/demos/codelab/completed/finish/sounds/c4.m4a differ
diff --git a/demos/codelab/completed/finish/sounds/c5.m4a b/demos/codelab/completed/finish/sounds/c5.m4a
new file mode 100644
index 000000000..49721cd31
Binary files /dev/null and b/demos/codelab/completed/finish/sounds/c5.m4a differ
diff --git a/demos/codelab/completed/finish/sounds/d4.m4a b/demos/codelab/completed/finish/sounds/d4.m4a
new file mode 100644
index 000000000..51bcad6c2
Binary files /dev/null and b/demos/codelab/completed/finish/sounds/d4.m4a differ
diff --git a/demos/codelab/completed/finish/sounds/e4.m4a b/demos/codelab/completed/finish/sounds/e4.m4a
new file mode 100644
index 000000000..d910052ef
Binary files /dev/null and b/demos/codelab/completed/finish/sounds/e4.m4a differ
diff --git a/demos/codelab/completed/finish/sounds/f4.m4a b/demos/codelab/completed/finish/sounds/f4.m4a
new file mode 100644
index 000000000..c80a0bfd3
Binary files /dev/null and b/demos/codelab/completed/finish/sounds/f4.m4a differ
diff --git a/demos/codelab/completed/finish/sounds/g4.m4a b/demos/codelab/completed/finish/sounds/g4.m4a
new file mode 100644
index 000000000..45ea44830
Binary files /dev/null and b/demos/codelab/completed/finish/sounds/g4.m4a differ
diff --git a/demos/codelab/completed/finish/styles/index.css b/demos/codelab/completed/finish/styles/index.css
new file mode 100644
index 000000000..0ef8f3bfc
--- /dev/null
+++ b/demos/codelab/completed/finish/styles/index.css
@@ -0,0 +1,75 @@
+main {
+ width: 400px;
+ position: relative;
+ margin: 0 auto;
+ overflow:hidden;
+ height: 600px;
+}
+
+header {
+ background-color: green;
+ width: 100%;
+}
+
+h1 {
+ width: 400px;
+ position: relative;
+ margin: 0 auto;
+ color: #fff;
+ font-size: 1.8em;
+ line-height: 2.4em;
+}
+
+.mode-edit,
+.mode-maker,
+.mode-blockly {
+ display: none;
+}
+
+[mode="maker"] .mode-maker,
+[mode="edit"] .mode-edit,
+[mode="blockly"] .mode-blockly {
+ display: block;
+}
+
+.blockly-editor {
+ position: absolute;
+ top: 64px;
+ left: -400px;
+ transition: left .4s;
+ height: 460px;
+ width: 400px;
+ background-color: #eee;
+}
+
+[mode="blockly"] .blockly-editor {
+ left: 0;
+}
+
+.maker {
+ display: flex;
+ flex-flow: column;
+ justify-content: space-between;
+ height: 460px;
+ width: 400px;
+}
+
+.maker > div {
+ display: flex;
+ justify-content: space-between;
+}
+
+.button {
+ width: 120px;
+ height: 140px;
+ color: #fff;
+ font-size: 3em;
+ text-align: center;
+ vertical-align: middle;
+ line-height: 140px;
+}
+
+.mdl-button {
+ margin: 1em 0;
+ float: right;
+}