Skip to content

Commit

Permalink
Merge pull request #1194 from facebookresearch/form-composer-add-css-…
Browse files Browse the repository at this point in the history
…files-support

Enable CSS insertions in FormComposer
  • Loading branch information
meta-paul authored Jun 27, 2024
2 parents 5a997c6 + 17e6257 commit ca36558
Show file tree
Hide file tree
Showing 20 changed files with 269 additions and 34 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ FormComposer allows using custom code insertions in these scenarios:
- Specify lengthy content of an attribute (e.g. "instruction") in a separate HTML file
- Define custom validators for form fileds in a JS file
- Define custom triggers for form fileds, sections and submit button in a JS file
- Define custom styles for the FormComposer UI in a CSS file

The inserted code must reside in separate files (called "insertion files") located in `insertions` subdirectory of your form config directory.
- _Remember that you can change default config directory path using `--directory` option of `form_composer_config` command_
Expand Down Expand Up @@ -230,3 +231,16 @@ During development of your form config, you can use a few available helper funct
```js
const valueIsValid = validateFieldValue(formFields.languageRadioSelector, {"en": true, "fr": false}, true);
```

#### CSS styles insertions

To customize UI appearance, and separate CSS styles from your HTML insertions,
you can create multiple CSS files in the `insertions` directory.
The only naming requirement is that their filenames should end with `.css` extension.
These CSS files will be automatically included in FormComposer UI via webpack build.

You can use CSS insertions to customize not only your HTML insertions pieces,
but also the standard Bootstrap form components themselves. There's two ways to do so:

- override existing styles (use CSS selectors for existing classes, names, ids, etc)
- add your own styles, and link them to ids and classes of form components (via their `id` and `classes` config attributes - see [Config files reference](/docs/guides/how_to_use/form_composer/configuration/config_files/))
3 changes: 2 additions & 1 deletion examples/form_composer_demo/run_task.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,14 +51,15 @@ def _build_custom_bundles(cfg: DictConfig) -> None:
cfg.task_dir,
force_rebuild=cfg.mephisto.task.force_rebuild,
webapp_name="webapp",
build_command="build:review",
build_command="build:simple:review",
)

# Build Task UI for the application
build_custom_bundle(
cfg.task_dir,
force_rebuild=cfg.mephisto.task.force_rebuild,
post_install_script=cfg.mephisto.task.post_install_script,
build_command="dev:simple",
)


Expand Down
4 changes: 3 additions & 1 deletion examples/form_composer_demo/webapp/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
"main": "webpack.config.js",
"scripts": {
"dev": "webpack --mode development",
"dev:simple": "type=simple webpack --mode development",
"dev:watch": "webpack --mode development --watch",
"test": "cypress open",
"build:review": "webpack --config=webpack.config.review.js --mode development",
"build:presigned_urls": "webpack --config=webpack.config.presigned_urls.js --mode development"
"build:presigned_urls": "webpack --config=webpack.config.presigned_urls.js --mode development",
"build:simple:review": "type=simple webpack --config=webpack.config.review.js --mode development"
},
"keywords": [],
"author": "",
Expand Down
42 changes: 42 additions & 0 deletions examples/form_composer_demo/webapp/src/app_dynamic.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
/*
* Copyright (c) Meta Platforms and its affiliates.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from "react";
import ReactDOM from "react-dom";
import {
FormComposerBaseFrontend,
LoadingScreen,
} from "./components/core_components_dynamic.jsx";
import { useMephistoTask, ErrorBoundary } from "mephisto-task-multipart";

/* ================= Application Components ================= */

function MainApp() {
const {
isLoading,
initialTaskData,
handleSubmit,
handleFatalError,
} = useMephistoTask();

if (isLoading || !initialTaskData) {
return <LoadingScreen />;
}

return (
<div>
<ErrorBoundary handleError={handleFatalError}>
<FormComposerBaseFrontend
taskData={initialTaskData}
onSubmit={handleSubmit}
onError={handleFatalError}
/>
</ErrorBoundary>
</div>
);
}

ReactDOM.render(<MainApp />, document.getElementById("app"));
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import ReactDOM from "react-dom";
import {
FormComposerBaseFrontend,
LoadingScreen,
} from "./components/core_components.jsx";
} from "./components/core_components_simple.jsx";
import { useMephistoTask, ErrorBoundary } from "mephisto-task-multipart";

/* ================= Application Components ================= */
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/*
* Copyright (c) Meta Platforms and its affiliates.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import React from "react";
import { FormComposer } from "react-form-composer";

function LoadingScreen() {
return <Directions>Loading...</Directions>;
}

function Directions({ children }) {
return (
<section className="hero is-light" data-cy="directions-container">
<div className="hero-body">
<div className="container">
<p className="subtitle is-5">{children}</p>
</div>
</div>
</section>
);
}

function FormComposerBaseFrontend({
taskData,
onSubmit,
onError,
finalResults = null,
}) {
let initialConfigFormData = taskData.form;

if (!initialConfigFormData) {
return <div>Passed form data is invalid... Recheck your task config.</div>;
}

return (
<div>
<FormComposer
data={initialConfigFormData}
onSubmit={onSubmit}
finalResults={finalResults}
/>
</div>
);
}

export { LoadingScreen, FormComposerBaseFrontend };
11 changes: 11 additions & 0 deletions examples/form_composer_demo/webapp/src/main_dynamic.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) Meta Platforms and its affiliates.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-select/dist/css/bootstrap-select.min.css";
import "./app_dynamic.jsx";
import "./css/style.css";
// Add your additional imports with styles here
11 changes: 11 additions & 0 deletions examples/form_composer_demo/webapp/src/main_simple.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright (c) Meta Platforms and its affiliates.
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-select/dist/css/bootstrap-select.min.css";
import "./app_simple.jsx";
import "./css/style.css";
// Add your additional imports with styles here
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-select/dist/css/bootstrap-select.min.css";
import "./reviewapp.jsx";
import "./reviewapp_dynamic.jsx";
import "./css/style.css";
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,5 @@

import "bootstrap/dist/css/bootstrap.css";
import "bootstrap-select/dist/css/bootstrap-select.min.css";
import "./app.jsx";
import "./reviewapp_simple.jsx";
import "./css/style.css";
62 changes: 62 additions & 0 deletions examples/form_composer_demo/webapp/src/reviewapp_dynamic.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
/*
* Copyright (c) 2017-present, Facebook, Inc.
* All rights reserved.
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*/

import React from "react";
import ReactDOM from "react-dom";
import { FormComposerBaseFrontend } from "./components/core_components_dynamic.jsx";

function Reviewapp_dynamic() {
const appRef = React.useRef(null);
const [reviewData, setReviewData] = React.useState(null);

// Requirement #1. Render review components after receiving Task data via message
window.onmessage = function (e) {
const data = JSON.parse(e.data);
setReviewData(data["REVIEW_DATA"]);
};

// Requirement #2. Resize iframe height to fit its content
React.useLayoutEffect(() => {
function updateSize() {
if (appRef.current) {
window.top.postMessage(
JSON.stringify({
IFRAME_DATA: {
height: appRef.current.offsetHeight,
},
}),
"*"
);
}
}
window.addEventListener("resize", updateSize);
updateSize();
// HACK: Catch-all resize, if normal resizes failed (e.g. acync long loading images)
setTimeout(() => {
updateSize();
}, 3000);
return () => window.removeEventListener("resize", updateSize);
}, []);

// Requirement #3. This component must return a div with `ref={appRef}`
// so we can get displayed height of this component (for iframe resizing)
return (
<div ref={appRef}>
{reviewData ? (
<FormComposerBaseFrontend
taskData={reviewData["inputs"]}
finalResults={reviewData["outputs"]}
/>
) : (
<div>Loading...</div>
)}
</div>
);
}

ReactDOM.render(<Reviewapp_dynamic />, document.getElementById("app"));
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,9 @@

import React from "react";
import ReactDOM from "react-dom";
import { FormComposerBaseFrontend } from "./components/core_components.jsx";
import { FormComposerBaseFrontend } from "./components/core_components_simple.jsx";

function ReviewApp() {
function Reviewapp_dynamic() {
const appRef = React.useRef(null);
const [reviewData, setReviewData] = React.useState(null);

Expand Down Expand Up @@ -59,4 +59,4 @@ function ReviewApp() {
);
}

ReactDOM.render(<ReviewApp />, document.getElementById("app"));
ReactDOM.render(<Reviewapp_dynamic />, document.getElementById("app"));
27 changes: 18 additions & 9 deletions examples/form_composer_demo/webapp/webpack.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,24 @@
var path = require("path");
var webpack = require("webpack");

var dynamicAliases = {};
var entry = "./src/main_simple.js";
if (process.env.type !== "simple") {
dynamicAliases = {
// Required for custom validators
"custom-validators": path.resolve(
process.env.WEBAPP__FORM_COMPOSER__CUSTOM_VALIDATORS
),
// Required for custom triggers
"custom-triggers": path.resolve(
process.env.WEBAPP__FORM_COMPOSER__CUSTOM_TRIGGERS
),
};
entry = "./src/main_dynamic.js";
}

module.exports = {
entry: "./src/main.js",
entry: entry,
output: {
path: __dirname,
filename: "build/bundle.js",
Expand All @@ -25,14 +41,7 @@ module.exports = {
__dirname,
"../../../packages/react-form-composer"
),
// Required for custom validators
"custom-validators": path.resolve(
process.env.WEBAPP__FORM_COMPOSER__CUSTOM_VALIDATORS
),
// Required for custom triggers
"custom-triggers": path.resolve(
process.env.WEBAPP__FORM_COMPOSER__CUSTOM_TRIGGERS
),
...dynamicAliases,
},
fallback: {
net: false,
Expand Down
27 changes: 18 additions & 9 deletions examples/form_composer_demo/webapp/webpack.config.review.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,24 @@
var path = require("path");
var webpack = require("webpack");

var dynamicAliases = {};
var entry = "./src/review_simple.js";
if (process.env.type !== "simple") {
dynamicAliases = {
// Required for custom validators
"custom-validators": path.resolve(
process.env.WEBAPP__FORM_COMPOSER__CUSTOM_VALIDATORS
),
// Required for custom triggers
"custom-triggers": path.resolve(
process.env.WEBAPP__FORM_COMPOSER__CUSTOM_TRIGGERS
),
};
entry = "./src/review_dynamic.js";
}

module.exports = {
entry: "./src/review.js",
entry: entry,
output: {
path: __dirname,
filename: "build/bundle.review.js",
Expand All @@ -21,14 +37,7 @@ module.exports = {
__dirname,
"../../../packages/react-form-composer"
),
// Required for custom validators
"custom-validators": path.resolve(
process.env.WEBAPP__FORM_COMPOSER__CUSTOM_VALIDATORS
),
// Required for custom triggers
"custom-triggers": path.resolve(
process.env.WEBAPP__FORM_COMPOSER__CUSTOM_TRIGGERS
),
...dynamicAliases,
},
fallback: {
net: false,
Expand Down
2 changes: 2 additions & 0 deletions examples/remote_procedure/mnist/webapp/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import "./app.jsx";
import "./css/style.css";
import "bootstrap/dist/css/bootstrap.min.css";
// Add your additional imports with styles here
2 changes: 2 additions & 0 deletions examples/static_react_task/webapp/src/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,7 @@
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/

import "./app.jsx";
import "./css/style.css";
// Add your additional imports with styles here
Loading

0 comments on commit ca36558

Please sign in to comment.