Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

React Wrapper Step 1: Simple React Wrapper that never updates #7000

Merged
merged 17 commits into from
Feb 26, 2016
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/src/StudioApp.js
Original file line number Diff line number Diff line change
Expand Up @@ -1629,7 +1629,9 @@ StudioApp.prototype.runButtonClickWrapper = function (callback) {
*/
StudioApp.prototype.configureDom = function (config) {
var container = document.getElementById(config.containerId);
container.innerHTML = config.html;
if (typeof config.html !== 'undefined') {
container.innerHTML = config.html;
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is it safe to silently ignore an undefined config.html?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I see, config.html comes from the server and can be empty. 👍

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we do our top-level rendering with React (prior to StudioApp.init) we want to skip this step, which would clobber everything React just rendered.

In my prototype I originally skipped this with a more explicit config variable:

if (!config.usingReactRendering) {
  container.innerHTML = config.html;
}

That felt a little like duplicating state though - why even assign config.html if we're not going to use it, and then why introduce another config variable to say we're not using config.html when we can just check if it's empty?

Alternatively, I could tear out this line altogether if I also give NetSim a top-level React wrapper (it's the one remaining user of this old approach).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for explaining, it was not clear that this conditional is based on whether this app is using react to render. I think it would suffice to add a comment noting that config.html will be empty for apps which are rendering via React.

if (!this.enableShowCode) {
document.getElementById('show-code-header').style.display = 'none';
}
Expand Down
188 changes: 97 additions & 91 deletions apps/src/applab/applab.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var api = require('./api');
var apiBlockly = require('./apiBlockly');
var dontMarshalApi = require('./dontMarshalApi');
var blocks = require('./blocks');
var AppView = require('../templates/AppView.jsx');
var page = require('../templates/page.html.ejs');
var dom = require('../dom');
var parseXmlElement = require('../xml').parseElement;
Expand Down Expand Up @@ -646,29 +647,6 @@ Applab.init = function(config) {
});
}

config.html = page({
assetUrl: studioApp.assetUrl,
data: {
localeDirection: studioApp.localeDirection(),
visualization: require('./visualization.html.ejs')({
appWidth: Applab.appWidth,
appHeight: Applab.footerlessAppHeight
}),
controls: firstControlsRow,
extraControlRows: extraControlRows,
blockUsed: undefined,
idealBlockNumber: undefined,
editCode: level.editCode,
blockCounterClass: 'block-counter-default',
pinWorkspaceToBottom: true,
// TODO (brent) - seems a little gross that we've made this part of a
// template shared across all apps
// disable designMode if we're readonly
hasDesignMode: !config.readonlyWorkspace,
readonlyWorkspace: config.readonlyWorkspace
}
});

config.loadAudio = function() {
studioApp.loadAudio(skin.failureSound, 'failure');
};
Expand Down Expand Up @@ -761,90 +739,118 @@ Applab.init = function(config) {
AppStorage.populateTable(level.dataTables, false); // overwrite = false
AppStorage.populateKeyValue(level.dataProperties, false); // overwrite = false

studioApp.init(config);

var viz = document.getElementById('visualization');
var vizCol = document.getElementById('visualizationColumn');

if (!config.noPadding) {
viz.className += " with_padding";
vizCol.className += " with_padding";
}

if (config.embed || config.hideSource) {
// no responsive styles active in embed or hideSource mode, so set sizes:
viz.style.width = Applab.appWidth + 'px';
viz.style.height = (shouldRenderFooter() ? Applab.appHeight : Applab.footerlessAppHeight) + 'px';
// Use offsetWidth of viz so we can include any possible border width:
vizCol.style.maxWidth = viz.offsetWidth + 'px';
}
React.render(React.createElement(AppView, {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a great way of transitioning from our current world to our new world. However, long term I think we want to discourage having this much content in our createElement call (to some extent referring to number of properties, but mostly referring to having full function definitions).

If it's not difficult, I'd love to get to the place in the nearish future where we can have JSX instead of createElement which will discourage huge objects like this.

In any case, no changes are needed in this regards for this PR

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd be happy to go through and extract the parameters into local variables, to make the expression of these props as compact as possible. That said, I think some of that will be mitigated as we get further into this process and break up page.html.ejs into smaller pieces that need fewer parameters.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Bjvanminnen would you like these better if the functions were simply defined outside the React.createElement call, then passed in by name?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yep, I would like that better. Though again, I think this is a fine first step (and results in a more minimal change with whitespace ignored). Moving out these functions might be a good step for a future PR.

renderCodeApp: function () {
return page({
assetUrl: studioApp.assetUrl,
data: {
localeDirection: studioApp.localeDirection(),
visualization: require('./visualization.html.ejs')({
appWidth: Applab.appWidth,
appHeight: Applab.footerlessAppHeight
}),
controls: firstControlsRow,
extraControlRows: extraControlRows,
blockUsed: undefined,
idealBlockNumber: undefined,
editCode: level.editCode,
blockCounterClass: 'block-counter-default',
pinWorkspaceToBottom: true,
// TODO (brent) - seems a little gross that we've made this part of a
// template shared across all apps
// disable designMode if we're readonly
hasDesignMode: !config.readonlyWorkspace,
readonlyWorkspace: config.readonlyWorkspace
}
});
}.bind(this),
onMount: function () {
studioApp.init(config);

if (debuggerUi) {
debuggerUi.initializeAfterDomCreated({
defaultStepSpeed: config.level.sliderSpeed
});
}
var viz = document.getElementById('visualization');
var vizCol = document.getElementById('visualizationColumn');

window.addEventListener('resize', Applab.renderVisualizationOverlay);
if (!config.noPadding) {
viz.className += " with_padding";
vizCol.className += " with_padding";
}

var finishButton = document.getElementById('finishButton');
if (finishButton) {
dom.addClickTouchEvent(finishButton, Applab.onPuzzleFinish);
}
if (config.embed || config.hideSource) {
// no responsive styles active in embed or hideSource mode, so set sizes:
viz.style.width = Applab.appWidth + 'px';
viz.style.height = (shouldRenderFooter() ? Applab.appHeight : Applab.footerlessAppHeight) + 'px';
// Use offsetWidth of viz so we can include any possible border width:
vizCol.style.maxWidth = viz.offsetWidth + 'px';
}

var submitButton = document.getElementById('submitButton');
if (submitButton) {
dom.addClickTouchEvent(submitButton, Applab.onPuzzleSubmit);
}
if (debuggerUi) {
debuggerUi.initializeAfterDomCreated({
defaultStepSpeed: config.level.sliderSpeed
});
}

var unsubmitButton = document.getElementById('unsubmitButton');
if (unsubmitButton) {
dom.addClickTouchEvent(unsubmitButton, Applab.onPuzzleUnsubmit);
}
window.addEventListener('resize', Applab.renderVisualizationOverlay);

if (level.editCode) {
// Prevent the backspace key from navigating back. Make sure it's still
// allowed on other elements.
// Based on http://stackoverflow.com/a/2768256/2506748
$(document).on('keydown', function (event) {
var doPrevent = false;
if (event.keyCode !== KeyCodes.BACKSPACE) {
return;
}
var d = event.srcElement || event.target;
if ((d.tagName.toUpperCase() === 'INPUT' && (
d.type.toUpperCase() === 'TEXT' ||
d.type.toUpperCase() === 'PASSWORD' ||
d.type.toUpperCase() === 'FILE' ||
d.type.toUpperCase() === 'EMAIL' ||
d.type.toUpperCase() === 'SEARCH' ||
d.type.toUpperCase() === 'NUMBER' ||
d.type.toUpperCase() === 'DATE' )) ||
d.tagName.toUpperCase() === 'TEXTAREA') {
doPrevent = d.readOnly || d.disabled;
var finishButton = document.getElementById('finishButton');
if (finishButton) {
dom.addClickTouchEvent(finishButton, Applab.onPuzzleFinish);
}
else {
doPrevent = !d.isContentEditable;

var submitButton = document.getElementById('submitButton');
if (submitButton) {
dom.addClickTouchEvent(submitButton, Applab.onPuzzleSubmit);
}

if (doPrevent) {
event.preventDefault();
var unsubmitButton = document.getElementById('unsubmitButton');
if (unsubmitButton) {
dom.addClickTouchEvent(unsubmitButton, Applab.onPuzzleUnsubmit);
}
});

designMode.addKeyboardHandlers();
if (level.editCode) {
// Prevent the backspace key from navigating back. Make sure it's still
// allowed on other elements.
// Based on http://stackoverflow.com/a/2768256/2506748
$(document).on('keydown', function (event) {
var doPrevent = false;
if (event.keyCode !== KeyCodes.BACKSPACE) {
return;
}
var d = event.srcElement || event.target;
if ((d.tagName.toUpperCase() === 'INPUT' && (
d.type.toUpperCase() === 'TEXT' ||
d.type.toUpperCase() === 'PASSWORD' ||
d.type.toUpperCase() === 'FILE' ||
d.type.toUpperCase() === 'EMAIL' ||
d.type.toUpperCase() === 'SEARCH' ||
d.type.toUpperCase() === 'NUMBER' ||
d.type.toUpperCase() === 'DATE' )) ||
d.tagName.toUpperCase() === 'TEXTAREA') {
doPrevent = d.readOnly || d.disabled;
}
else {
doPrevent = !d.isContentEditable;
}

if (doPrevent) {
event.preventDefault();
}
});

designMode.renderDesignWorkspace();
designMode.addKeyboardHandlers();

designMode.configurePlaySpaceHeader();
designMode.renderDesignWorkspace();

designMode.toggleDesignMode(Applab.startInDesignMode());
designMode.configurePlaySpaceHeader();

designMode.configureDragAndDrop();
designMode.toggleDesignMode(Applab.startInDesignMode());

var designModeViz = document.getElementById('designModeViz');
designModeViz.addEventListener('click', designMode.onDesignModeVizClick);
}
designMode.configureDragAndDrop();

var designModeViz = document.getElementById('designModeViz');
designModeViz.addEventListener('click', designMode.onDesignModeVizClick);
}
}.bind(this)
}), document.getElementById(config.containerId));
};

/**
Expand Down
40 changes: 23 additions & 17 deletions apps/src/bounce/bounce.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ var skins = require('../skins');
var tiles = require('./tiles');
var codegen = require('../codegen');
var api = require('./api');
var AppView = require('../templates/AppView.jsx');
var page = require('../templates/page.html.ejs');
var dom = require('../dom');
var Hammer = require('../hammer');
Expand Down Expand Up @@ -666,20 +667,6 @@ Bounce.init = function(config) {
window.addEventListener("keydown", Bounce.onKey, false);
window.addEventListener("keyup", Bounce.onKey, false);

config.html = page({
assetUrl: studioApp.assetUrl,
data: {
localeDirection: studioApp.localeDirection(),
visualization: require('./visualization.html.ejs')(),
controls: require('./controls.html.ejs')({assetUrl: studioApp.assetUrl}),
blockUsed: undefined,
idealBlockNumber: undefined,
editCode: level.editCode,
blockCounterClass: 'block-counter-default',
readonlyWorkspace: config.readonlyWorkspace
}
});

config.loadAudio = function() {
studioApp.loadAudio(skin.winSound, 'win');
studioApp.loadAudio(skin.startSound, 'start');
Expand Down Expand Up @@ -786,10 +773,29 @@ Bounce.init = function(config) {
config.enableShowCode = false;
config.enableShowBlockCount = false;

studioApp.init(config);
React.render(React.createElement(AppView, {
renderCodeApp: function () {
return page({
assetUrl: studioApp.assetUrl,
data: {
localeDirection: studioApp.localeDirection(),
visualization: require('./visualization.html.ejs')(),
controls: require('./controls.html.ejs')({assetUrl: studioApp.assetUrl}),
blockUsed: undefined,
idealBlockNumber: undefined,
editCode: level.editCode,
blockCounterClass: 'block-counter-default',
readonlyWorkspace: config.readonlyWorkspace
}
});
},
onMount: function () {
studioApp.init(config);

var finishButton = document.getElementById('finishButton');
dom.addClickTouchEvent(finishButton, Bounce.onPuzzleComplete);
var finishButton = document.getElementById('finishButton');
dom.addClickTouchEvent(finishButton, Bounce.onPuzzleComplete);
}
}), document.getElementById(config.containerId));
};

/**
Expand Down
42 changes: 24 additions & 18 deletions apps/src/calc/calc.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ var commonMsg = require('../locale');
var calcMsg = require('./locale');
var skins = require('../skins');
var levels = require('./levels');
var AppView = require('../templates/AppView.jsx');
var page = require('../templates/page.html.ejs');
var dom = require('../dom');
var blockUtils = require('../block_utils');
Expand Down Expand Up @@ -148,23 +149,6 @@ Calc.init = function(config) {
config.skin.failureAvatar = null;
config.skin.winAvatar = null;

config.html = page({
assetUrl: studioApp.assetUrl,
data: {
localeDirection: studioApp.localeDirection(),
visualization: require('./visualization.html.ejs')(),
controls: require('./controls.html.ejs')({
assetUrl: studioApp.assetUrl
}),
blockUsed : undefined,
idealBlockNumber : undefined,
editCode: level.editCode,
blockCounterClass : 'block-counter-default',
inputOutputTable: level.inputOutputTable,
readonlyWorkspace: config.readonlyWorkspace
}
});

config.loadAudio = function() {
studioApp.loadAudio(skin.winSound, 'win');
studioApp.loadAudio(skin.startSound, 'start');
Expand Down Expand Up @@ -215,7 +199,29 @@ Calc.init = function(config) {
}
};

studioApp.init(config);
React.render(React.createElement(AppView, {
renderCodeApp: function () {
return page({
assetUrl: studioApp.assetUrl,
data: {
localeDirection: studioApp.localeDirection(),
visualization: require('./visualization.html.ejs')(),
controls: require('./controls.html.ejs')({
assetUrl: studioApp.assetUrl
}),
blockUsed : undefined,
idealBlockNumber : undefined,
editCode: level.editCode,
blockCounterClass : 'block-counter-default',
inputOutputTable: level.inputOutputTable,
readonlyWorkspace: config.readonlyWorkspace
}
});
},
onMount: function () {
studioApp.init(config);
}
}), document.getElementById(config.containerId));
};

/**
Expand Down
Loading