-
Notifications
You must be signed in to change notification settings - Fork 6
/
README.update
390 lines (311 loc) · 13.5 KB
/
README.update
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
This describes what is needed to upgrade Quizly when App Inventor (AI) is upgraded.
It assumes that AI upgrades include upgrades to Blockly core files, such as
toolbox.js, even though these are not used by AI. If that is not the case, it
will be necessary to get some files (e.g., toolbox.js) directly from Blockly.
REVISED 10/16/14
---------------
* Replace quizly-gcb-blockly-all.js with:
[AppinventorRoot]/build/blocklyeditor/blockly-all.js
The quizly-gcb-blockly-all.js is the compiled javascript, kept to under 1 Mb
so that it can be loaded by GCB. For other embeds, a larger blockly-all.js
file can be used. plovr.config can be modified to produce different
compilations.
* Run and debug Quizly to determine what needs to be changed in blockly-all.js
and revise the appropriate source files, which should be stored
in appinv-revised.
* Revise plovr.config to reflect which App Inventor files have
been modified to support Quizly.
* Create a new quizly-blockly-all.js by running plovr.jar.
* Create a tarball and distribute.
QUICK OVERVIEW
--------------
* Replace blockly-all.js with:
[AppinventorRoot]/build/blocklyeditor/blockly-all.js
* Blockly-all.js is now compressed down to 800K.
* 9/27/14 In AI, Blockly.Language has been replaced by Blockly.Blocks: need
to do a global replace in quizme-helper, makequiz-helper, quizme-initblocklyeditor.
* 9/27/14 Not sure whether this still applies?
* Blockly.Language.INPUT and Blockly.Language.OUTPUT cause a crash because our
Blockly.Language is dynamic (whereas App Inventor's is static). These have
to be globally replaced in blockly-all.js with:
Blockly.Language_INPUT and Blockly.Language_OUTPUT.
NOTE: Do a global replace on the compressed blockly-all:
sed -i -e 's/Blockly\.Language\.INPUT/Blockly\.Language\_INPUT/g' blockly-all.js
sed -i -e 's/Blockly\.Language\.OUTPUT/Blockly\.Language\_OUTPUT/g' blockly-all.js
* Replace blockly-toolbox.js with:
[AppinventorRoot]/lib/blockly/src/core/toolbox.js
* In blockly.html, the Blockly.inject() call should be:
Blockly.inject(document.body,
{path: './',
backpack:true,
trashcan: true,
toolbox: '<xml id="toolbox" style="display:none">
<category name="TOOLBOX">
<block type="bogus"></block>
</category></xml>'
});
* In blockly.html, need to configure typeblocking:
// Typeblocking configuration
var typeblock_config = {
frame: 'ai_frame',
typeBlockDiv: 'ai_type_block',
inputText: 'ac_input_text'
};
* In blockly.html, after injection:
//This is what Blockly's init function does when passing options.
//We are overriding the init process so putting it here
goog.mixin(Blockly, {
collapse : true,
configForTypeBlock: typeblock_config
});
//This would also be done in Blockly init, but we need to do it here cause of
//the different init process in drawer (it'd be undefined at the time it hits
//init in Blockly)
if (Blockly.editable)
Blockly.TypeBlock(Blockly.configForTypeBlock);
* In blockly.html the body should include typeblock div:
<body id="ai_frame" onload="initBlockly()">
<div id="ai_type_block" style="display:none">
<p>
<input id="ac_input_text" />
</p>
</div>
</body>
* In quizme-helper.js, initQuizme, AppInventor's Drawer must point to our Toolbox:
Blockly.Drawer = Blockly.Toolbox;
That's it!
LONGER STORY
------------
Quizly integrates to AI through the blockly-all.js file, which is
copied from AI's build/blocklyeditor/blockly-all.js.
The main difference between Quizly and AI is that Quizly uses
Blockly's Toolbox whereas AI uses its own custom Drawer instead of
Toolbox. The reason for this is that AI's blocks editor, which is
based on Blockly, must interface with AI's Designer, which is written
in Java and uses Google's Google Web Toolkit (GWT). We don't need
to worry about GWT. Quizly is written entirely in Javascript, as is
Blockly.
The main task in upgrading Quizly has to do with the differences
between the way that the blocks editor is injected into the iframe
in Blockly, Quizly (which is more like Blockly), and AI.
Quizly is based on Blockly's code app (i.e., not directly on Blockly's core):
http://blockly-demo.appspot.com/static/apps/code/en.html
Blockly Injection
-----------------
For Blockly's code app, here is Blockly's init() function in frame.html:
function init() {
var rtl = false;
var toolbox = null;
if (window.parent.document) {
// document.dir fails in Mozilla, use document.body.parentNode.dir.
// https://bugzilla.mozilla.org/show_bug.cgi?id=151407
rtl = window.parent.document.body.parentNode.dir == 'rtl';
toolbox = window.parent.document.getElementById('toolbox');
}
Blockly.inject(document.body,
{path: '../../',
rtl: rtl,
toolbox: toolbox});
if (window.parent.init) {
// Let the top-level application know that Blockly is ready.
window.parent.init(Blockly);
} else {
// Attempt to diagnose the problem.
var msg = 'Error: Unable to communicate between frames.\n\n';
if (window.parent == window) {
msg += 'Try loading index.html instead of frame.html';
} else if (window.location.protocol == 'file:') {
msg += 'This may be due to a security restriction preventing\n' +
'access when using the file:// protocol.\n' +
'http://code.google.com/p/chromium/issues/detail?id=47416';
}
alert(msg);
}
}
Note that the Blockly toolbox is set from an svg element in the parent window:
window.parent.document.getElementById('toolbox').
This means that the toolbox is separate from the iframe that contains
the workspace. In Blockly, the toolbox is coded as a static XML
structure and injected into the page via a Javascript function in
templates.js:
codepage.toolbox = function(opt_data, opt_ignored, opt_ijData) {
return '<xml id="toolbox" style="display: none"><category name="' + '<LOTS LEFT OUT>' +
'</category></xml>';
};
The key point is that when Blockly.inject() is called
Blockly.inject = function(container, opt_options) {
// Verify that the container is in document.
if (!goog.dom.contains(document, container)) {
throw 'Error: container is not in current document.';
}
if (opt_options) {
// TODO(scr): don't mix this in to global variables.
goog.mixin(Blockly, Blockly.parseOptions_(opt_options));
}
Blockly.createDom_(container);
Blockly.init_();
};
the toolbox option has been set to the element in the iframe's enclosing window. This means
that when Blockly.init_() is called, the Blockly.Toolbox attribute is set and everything
loads nicely:
Blockly.init_ = function() {
// Stuff commented out above
var addScrollbars = true;
if (Blockly.languageTree) {
if (Blockly.Toolbox) {
Blockly.Toolbox.init();
} else if (Blockly.Flyout) {
// Build a fixed flyout with the root blocks.
Blockly.mainWorkspace.flyout_.init(Blockly.mainWorkspace,
Blockly.getMainWorkspaceMetrics, true);
Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes);
// Translate the workspace sideways to avoid the fixed flyout.
Blockly.mainWorkspace.scrollX = Blockly.mainWorkspace.flyout_.width_;
var translation = 'translate(' + Blockly.mainWorkspace.scrollX + ', 0)';
Blockly.mainWorkspace.getCanvas().setAttribute('transform', translation);
Blockly.mainWorkspace.getBubbleCanvas().setAttribute('transform',
translation);
addScrollbars = false;
}
}
// Stuff commented out below
};
App Inventor Injection
----------------------
In App Inventor, there is no Toolbox. Instead there is a Drawer. In AI
Blockly.inject() is called from Blockly.BlocklyEditor.startup(), which is
called in blocklyframe.html:
function init() {
// We expect window.location.hash to have the form:
// projectId_formName, and to be unique among
// all the Blockly instances created in this App Inventor session.
if (Blockly['ReplState'] == undefined)
Blockly.ReplState = new Blockly.ReplStateObj();
var formName = window.location.hash.substr(1);
Blockly.BlocklyEditor.startup(document.body, formName);
if (!window.parent.Blocklies) {
// We keep a set of Blockly objects indexed by name
// of the form for which they are the blocks editor
window.parent.Blocklies = {};
window.parent.ReplState = new Blockly.ReplStateObj(); // There should be one of these for the whole system
}
window.parent.Blocklies[formName] = Blockly;
Blockly.ReplMgr.ReplState = window.parent.ReplState;
Blockly.ReplMgr.formName = formName; <!-- So we can tell the AssetManager which form we are on. -->
window.parent.BlocklyPanel_initBlocksArea(formName);
}
In Blockly.BlocklyEditor.js (which is merged into blockly-all.js) here is the
injection code:
Blockly.BlocklyEditor.startup = function(documentBody, formName) {
Blockly.inject(documentBody);
Blockly.Drawer.createDom();
Blockly.Drawer.init();
Blockly.BlocklyEditor.formName_ = formName;
// Lots of other initialization stuff deleted here
}
Note that inject() is passed just one argument, which means that its options
parameter is left out:
Blockly.inject = function(container, opt_options) {
// Verify that the container is in document.
if (!goog.dom.contains(document, container)) {
throw 'Error: container is not in current document.';
}
if (opt_options) {
// TODO(scr): don't mix this in to global variables.
goog.mixin(Blockly, Blockly.parseOptions_(opt_options));
}
Blockly.createDom_(container);
Blockly.init_();
};
Thus, when Blockly.init_() is called, Blockly.Toolbox is null:
Blockly.init_ = function() {
// Stuff left out above
var addScrollbars = true;
if (Blockly.languageTree) {
if (Blockly.Toolbox) {
Blockly.Toolbox.init();
} else if (Blockly.Flyout) {
// Build a fixed flyout with the root blocks.
Blockly.mainWorkspace.flyout_.init(Blockly.mainWorkspace,
Blockly.getMainWorkspaceMetrics, true);
Blockly.mainWorkspace.flyout_.show(Blockly.languageTree.childNodes);
addScrollbars = false;
}
}
// Stuff left out below
};
Quizly Injection
----------------
Quizly differs slightly from both Blockly's code app and AI. Unlike
the Blockly.code app, which uses a static toolbox, Quizly's toolbox is
dynamic. The categories and blocks it contains depend on the type of quiz
being presented. In AI, the Drawer is dynamic, depending on which
Components are included in the project -- the built-in Language
blocks are static.
In Quizly:
* The Toolbox is a dynamic part of the iframe, so it is not available
in the iframe's parent window, as it is in Blockly.
* The Toolbox is implemented in blockly-toolbox.js -- i.e., separately
from blockly-all.js -- which is identicaly to Blockly/core/toolbox.js.
That is where the following stuff happens:
Blockly.Toolbox = {}; // Create the name space for the toolbox
Blockly.Toolbox.init() // Initialization function
These functions must be available during Blockly.init_().
blockly.html
------------
In Quizly, injection takes place in blockly.html as follows:
Blockly.inject(document.body,
{path: './',
backpack:true,
trashcan: true,
toolbox: '<xml id="toolbox" style="display:none">
<category name="TOOLBOX">
<block type="bogus"></block>
</category>
</xml>'
});
This toolbox property creates a static toolbox with the single category named
'TOOLBOX'. This will appear in Quizly's UI as a header for the toolbox. Dynamic
toolbox categories and blocks are loaded into the Toolbox whenever a new quiz
is selected.
Here's the code in quizme-helper that constructs the Blockly.languageTree and
loads it into the Toolbox:
function initToolboxLanguageTree(language) {
// Initialize languageTree for populating the toolbox
resetBlocklyLanguage(); // Hack to add some special Language properties
Blockly.languageTree = Blockly.Xml.textToDom("<xml id='toolbox' style='display:none'></xml>");
// Initialize the category list
var cats = [];
// Iterate through the blocks in the language to construct the toolbox languageTree
// for (var propname in Blockly.WholeLanguage) {
for (var propname in language) {
console.log("Adding to Blockly.languageTree " + propname);
var tempWorkspace = new Blockly.Workspace();
var blk = new Blockly.Block(tempWorkspace, propname);
// If this block has a category, append the category name to category list
var catname = blk['category'];
if (catname) {
var category = cats[catname];
if (!category) {
category = "";
cats[catname] = category;
}
category = category.concat("<block type='" + propname + "'></block>");
cats[catname] = category;
}
}
// Now build and return the categorized tree
var treeString = "<xml id='toolbox' style='display:none'>";
for (var cat in cats) {
treeString = treeString.concat("<category name='" + cat + "'>" + cats[cat] + "</category>");
}
treeString = treeString.concat("</xml>");
return Blockly.Xml.textToDom(treeString);
}
Also, the actual workspace in the UI has to be re-initialized every time a new quiz
is selected. This happens in quizme-helper.initializeBlocksWorkspace():
// Delete the current toolbox tree from the webpage
var html = Blockly.Toolbox.HtmlDiv;
var children = html.childNodes;
if (children[1])
children[1].remove();