Virtual DOM and CSS framework for parsing usable browser data.
npm install squared> [!NOTE]
> Content in the README was migrated into Read the Docs. The file is no longer fully maintained.
* squared-express
* E-mc
* Pi-r
* Pi-r2
* NodeJS 18.20.5
``sh
> npm init
> npm i sqd-cli sqd-serve
> npx sqd init
> npx sqd serve --access-all
`sh
> npm init
> npm i squared sqd-serve> mkdir dist html
> cp -r ./node_modules/squared/dist/* ./dist
> cp ./node_modules/squared/html/* ./html # optional
> cp ./node_modules/sqd-serve/config/json/* . # yaml
> npx serve
`$3
`sh
> git clone https://github.com/anpham6/squared
> cd squared> npm i
> npm run prod
> cd ..
> git clone https://github.com/anpham6/squared-express
> cd squared-express
> npm i
> npm run prod
> npm run deploy # deploy:yaml
> cd ../squared
> node serve.cjs --access-all # squared.{json,yml}
`$3
* Python 3.6+
* Commands
#### Install
`sh
mkdir -p ~/bin/repo
PATH="${HOME}/bin:${PATH}"curl https://storage.googleapis.com/git-repo-downloads/repo > ~/bin/repo
chmod a+rx ~/bin/repo
OR
scripts/repo-install.sh ~/bin
`#### Usage
`sh
mkdir workspaces
cd workspacesrepo init -u https://github.com/anpham6/squared-repo -m latest.xml
repo sync -j4
cd squared
npm i
`#### Ruby
Workspace management uses Ruby for syncing and building. It is not installed by default on most operating systems.
`sh
mkdir workspaces
cd workspaces # REPO_ROOTwget https://unpkg.com/squared/Rakefile
rake -T # List tasks
REPO_BUILD={dev,prod}
PIPE_FAIL=1
rake repo:init # nightly
rake repo:init[latest] # squared
rake repo:init[0.13.x] # e-mc
OR
REPO_ROOT=/tmp/123 NODE_INSTALL=pnpm repo:init
OR - without Repo
rake clone:node # master
rake clone # node + docs
`> [!TIP]
> Use the supplied Rakefile inside the squared project folder once the source has been downloaded.
#### Docker
`sh
NODE_TAG=latest
RUBY_VERSION=2.7.0-3.4.0
MANIFEST=nightly,prod,latest,android
BUILD={dev,prod}
DEV={0,1,local}
DOCS=any
PIPE_FAIL={0,1}
PORT=3000
docker build -t squared --build-arg MANIFEST=prod --build-arg NODE_ENV=production .
docker build -t node --build-arg NODE_TAG=24 --build-arg NODE_INSTALL=pnpm -f Dockerfile.slim .
NODE=24 docker buildx bake node
OR
docker build -t ruby --build-arg RUBY_TAG=3.4.0 --build-arg NODE_VERSION=24 --build-arg PIPE_FAIL=0 -f Dockerfile.ruby .
RUBY=3.4.0 docker buildx bake ruby
OR
docker build -t nginx --build-arg NGINX_VERSION=1.29 --build-arg PORT=3000 --build-arg NODE_VERSION=22 -f Dockerfile.nginx .
NGINX=1.29 docker buildx bake nginxExpress
docker run -it --name express --rm -p 3000:3000 \
--mount type=bind,src=${PWD},dst=/workspaces/squared/.config \
--mount type=bind,src=${PWD}/html,dst=/workspaces/squared/www \
squareddocker run -it --name express --rm -p 443:443 --build-arg PORT=443 squared \
serve --access-all --https --env=production
Terminal
docker run -it --name debian squared /bin/bash # irb
`$3
* Download (squared@version): https://unpkg.com/squared
* Global JS variable: squared
* ES2018
> - https://unpkg.com/squared/dist/squared.min.js
> - https://unpkg.com/squared/dist/squared.base-dom.min.js
> - https://unpkg.com/squared/dist/vdom.framework.min.js
> * https://unpkg.com/squared/dist/squared.min.js
> * https://unpkg.com/squared/dist/vdom-lite.framework.min.js
Usage
Library files are in the
/dist folder. A minimum of two files are required to run squared.1. squared
2. squared-base - required: except vdom-lite
3. squared-svg - optional
4. framework (e.g. android | chrome | vdom | vdom-lite)
5. extensions - optional
Usable combinations: 1-2-4 + 1-2-4-5 + 1-2-3-4-5 + 1-vdom-lite
File bundles for common combinations are available in the
/dist/bundles folder and do not require a call to setFramework.> [!WARNING]
> Libraries in bold are transpiled with ES2020.
$3
The primary function
parseDocument can be called on multiple elements and multiple times per session. The application will continuously and progressively build the layout files into a single entity with combined shared resources.* ES2020
> [!CAUTION]
> Using
parseDocumentSync is not recommended when your page has images or fonts.`html
`> [!CAUTION]
> Calling
saveAs or copyTo methods before the images have completely loaded can cause them to be excluded from the generated layout. In these cases you should use the asynchronous parseDocument method to set a callback for your commands.$3
Used primarly for developing single page layouts but can also bundle assets using query selector syntax. It is adequate for most projects and gives you the ability to develop your application as a module in place.
* ES2020
`html
`$3
The most minimal framework possible (80kb gzipped) and can be useful when debugging through DevTools. The
lite version is about half the bundle size and is recommended for most browser applications.* ES2018
`html
`There are ES2018 minified versions (\*.min.js) and also ES2018 non-minified versions.
User Settings
These settings are available in the global variable
squared to customize your desired output structure. Each framework shares a common set of settings and also a subset of their own settings.$3
`javascript
squared.settings = {
targetAPI: 36,
supportRTL: true,
supportNegativeLeftTop: true,
supportUnicode: ["emoji"], // true | "utf-16" | "emoji"
preloadImages: true,
preloadFonts: true,
preloadLocalFonts: true, // Chromium
preloadCustomElements: true,
enabledSVG: true,
enabledMultiline: true,
enabledViewModel: true,
enabledIncludes: false,
enabledFragment: false,
enabledSubstitute: false,
enabledCompose: false,
dataBindableElements: [], // { selector, attr, expression, namespace?, twoWay? } (see Data Binding section)
includableElements: [], // { selectorStart, selectorEnd, pathname?, filename?, merge?, viewModel? }
substitutableElements: [], // { selector, tag, tagChild?, renderChildren?, autoLayout? }
fragmentableElements: [], // selector | ExtensionFragmentElement
composableElements: [], // selector or property (see Jetpack Compose section)
baseLayoutAsFragment: false | "fragment-name" | ["fragment-name", "fragment-tag"] | { selector, pathname?, filename?, name?, tag? }, // ExtensionFragmentElement
baseLayoutToolsIgnore: "", // Android Studio (e.g. "TooManyViews, HardcodedText")
fontMeasureAdjust: 0.75, // thicker < 0 | thinner > 0 (data-android-font-measure-adjust)
lineHeightAdjust: 1.1, // shorter < 1 | taller > 1 (data-android-line-height-adjust)
preferMaterialDesign: false | "MaterialComponents" | "Material3", // Default is "Material3"
createDownloadableFonts: true,
createElementMap: false,
pierceShadowRoot: true,
adaptStyleMap: true, // Use rendered values for output
lockElementSettings: true,
customizationsBaseAPI: 0, // 0 - All | -1 - None
customizationsBaseAPI: [0, 33, 34], // Multiple
removeDeprecatedAttributes: true, // Remove all
removeDeprecatedAttributes: ["enabled", "singleLine"], // Remove all except "enabled" + "singleLine"
removeUnusedResourceViewId: false,
idNamingStyle: "android",
idNamingStyle: "html", // Use element tagName
idNamingStyle: {
"__default__": "html", // Optional
"DIV": "comments", // HTML is uppercase (comments_1 then comments_2)
"svg": ["vector", 0], // SVG elements areis lowercase (vector_0 then vector_1)
"#text": "text", // Plain text
"::first-letter": "dropcap", // Pseudo element
"main > section": ["content", 1, 2], // content_1 then content_3
"form input[type=submit]": function(node) {
return "submit_" + node.id;
}
},
customizationsOverwritePrivilege: true,
outputMainFileName: "activity_main.xml",
outputFragmentFileName: "fragment_main.xml",
/ Project - parseDocument (first only) /
supportUnicode: "utf-16",
resourceQualifier: "", // "land" -> "res/layout-land" | "port" -> "res/layout-port" (appended to every "res" folder)
resourceSystemColors: {
"system_accent1_100": "white", // Will be converted to ARGB
"system_accent1_200": ['#ff0000', 0.75], // opacity
"system_accent1_300": squared.lib.color.parseColor("#000", 1)
},
manifestPackage: "", // OR: RequestData<{ namespace: "android.application.id" }>
manifestLabelAppName: "android",
manifestThemeName: "AppTheme",
manifestParentThemeName: "Theme.AppCompat.Light.NoActionBar",
manifestActivityName: ".MainActivity",
outputDocumentEditing: true,
outputDocumentCSS: [], // CSS properties to be processed at server (e.g. "boxShadow")
outputDirectory: "app/src/main",
createManifest: false, // Update AndroidManifest.xml
createBuildDependencies: false | "ktx" | "baseline-profile" | "navigation" | ["ktx", "baseline-profile"], // Update build.gradle // Not customizable with parseDocument
builtInExtensions: [
"squared.accessibility",
"android.delegate.background",
"android.delegate.negative-x",
"android.delegate.positive-x",
"android.delegate.max-width-height",
"android.delegate.percent",
"android.delegate.content",
"android.delegate.scrollbar",
"android.delegate.radiogroup",
"android.delegate.multiline",
"squared.relative",
"squared.css-grid",
"squared.flexbox",
"squared.table",
"squared.column",
"squared.list",
"squared.grid",
"squared.sprite",
"squared.whitespace",
"android.resource.background",
"android.resource.svg",
"android.resource.strings",
"android.resource.fonts",
"android.resource.dimens",
"android.resource.styles",
"android.resource.data",
/ EXCLUDED (breaks layout) /
"android.resource.includes", // enabledIncludes
"android.substitute", // enabledSubstitute
"android.resource.fragment", // enabledFragment
"jetpack.compose.view" // enabledCompose
],
compressImages: false, // TinyPNG API Key
compressImages: "", // API key
compressImages: [{ plugin: "imagemin-pngquant", format: "png", options: { quality: [0.6, 0.8] } }],
convertImages: "", // png | jpeg | webp | gif | bmp
showAttributes: true,
showAttributes: {
"hyphenationFrequency": "full", // Replace all ("android" is the default namespace)
"android:fontFeatureSettings": null, // Delete all
"app:menu": [
"@menu/menu_1", "@menu/menu_2", // Replace with "@menu/menu_2" when value is "@menu/menu_1"
"@menu/menu_3", null // Delete attribute when value is "@menu/menu_3"
],
/ OR /
"app:menu": {
"@menu/menu_1": "@menu/menu_2",
"@menu/menu_3": null
}
},
showComments: false | ["boxShadow"] | { self: ["boxShadow"], nextSibling: ["marginBottom"], previousSibling: ["marginTop"], parent: ["position", "top", "left"] }, // TODO in layout.xml
showComments: { include: { tagName: true | ["button"], attributes: true | ["style"], dataset: false, bounds: true }, self: ["boxShadow", ".className"] },
showErrorMessages: false,
convertPixels: "dp", // "sp" | "pt" | "in" | "mm"
convertLineHeight: "sp", // "dp" | "pt" | "in" | "mm"
convertEntities: ["numeric"],
convertEntities: ["codepoints", {/ JSON (last) /}], // https://html.spec.whatwg.org/entities.json
insertSpaces: 4,
outputDocumentHandler: "android",
outputEmptyCopyDirectory: false, // Sub directories within target directory (OR: RequestData<{ emptyDir: false }>)
outputSummaryModal: false | "path/summary.css" | ".status-4 { color: purple; }",
outputTasks: {
"*/drawable/.xml": { handler: "gulp", task: "minify" }
},
outputWatch: {
"*/drawable/.png": true,
"*/drawable/.jpg": { interval: 1000, expires: "2h" }
},
outputArchiveName: "android-xml",
outputArchiveFormat: "zip", // tar | 7z | gz
outputArchiveCache: false // Downloadable URL in ResponseData
};
// Optional
squared.settings = {
resolutionDPI: 160, // 320dpi = 2560x1600
resolutionScreenWidth: 1280,
resolutionScreenHeight: 800,
framesPerSecond: 60,
useShapeGeometryBox: true, // Bounding box uses native SVG method getBbox
formatUUID: "8-4-4-4-12", // UUID: 8-4-[12345]3-[89ab]3-12
formatDictionary: "0123456789abcdef",
outputConfigName: "sqd.config",
observePort: 8080,
observeSecurePort: 8443,
observeExpires: "1h", // Server defaults will be used
broadcastPort: 3080,
broadcastSecurePort: 3443
};
`$3
`javascript
squared.settings = {
preloadImages: false,
preloadFonts: false,
preloadLocalFonts: false,
preloadCustomElements: false,
excludePlainText: true,
createElementMap: true,
pierceShadowRoot: true,
adaptStyleMap: false,
builtInExtensions: [],
showErrorMessages: false,
webSocketPort: 80,
webSocketSecurePort: 443,
outputDocumentHandler: "chrome",
outputEmptyCopyDirectory: false,
outputSummaryModal: false,
outputTasks: {
"*.js": [{ handler: "gulp", task: "minify" }, { handler: "gulp", task: "beautify" }]
},
outputWatch: { "*": true },
outputArchiveName: "chrome-data",
outputArchiveFormat: "zip",
outputArchiveCache: false
};// Optional (Same as Android)
`$3
`javascript
squared.settings = {
createElementMap: true,
pierceShadowRoot: false,
adaptStyleMap: false,
builtInExtensions: [],
showErrorMessages: false
};
`Local Storage
Custom named user settings per framework can be saved to local storage as JSON and reused across all pages in the same domain. Extensions are configured using the same procedure.
`javascript
// Save
squared.setFramework(android, { compressImages: true }, "android-example");// Load
squared.setFramework(android, "android-example");
``javascript
// Save
await squared.copyTo("/path/project", {/ options will be saved /}, "copy-example", true); // Will overwrite and not merge with previously saved settings// Load
await squared.copyTo("/path/project", {/ takes precedence /}, "http://localhost:3000/copy-to/base-config.json"); // Object.assign({ base-config.json }, options)
await squared.copyTo("/path/project", {/ takes precedence /}, "copy-example"); // Object.assign({ copy-example }, options)
await squared.copyTo("/path/project", "http://localhost:3000/copy-to/base-config.json"); // options = { base-config.json }
await squared.copyTo("/path/project", "copy-example"); // options = { copy_example }
`Public Properties and Methods
`javascript
.settings // See user preferences sectionsetFramework(app: {}, options?: PlainObject, setting?: string, cache?: boolean) // Install application interpreter
setFramework(app: {}, loadName: string, cache?: boolean) // Load settings from local storage
// http - hostname(:port)? | https - hostname:443
setHostname(value: string / http(s)://hostname(:port) /) // Use another cors-enabled server for processing files (--cors )
setEndpoint(name: string, value: string) // Set alternate pathname for API v1 functions (ASSETS_COPY | ASSETS_ARCHIVE | LOADER_DATA | THREADS_KILL | WEBSOCKET_OBSERVE)
setLocalAddress(...values: (string | URL | Location)[]) // Additional hostnames which are interpreted as localhost (e.g. http://127.0.0.1)
prefetch(type: "css" | "javascript" | "image" | "svg", all?: boolean, ...targets: unknown[]) // Cross-origin support for CSS
parseDocument(...elements: (HTMLElement | string | ElementSettings)[]) // See installation section (Promise)
parseDocumentSync(...elements: (HTMLElement | string | ElementSettings)[]) // Skips preloadImages and preloadFonts (synchronous)
latest(count?: number) // Most recent parseDocument session ids (1 newest / -1 oldest: string, other: string[])
auth(token: string) // Set JWT authorization token for all requests
save(projectId?: string) // Save current session to a new archive using default settings
save(projectId?: string, broadcastId?: string)
save(projectId?: string, timeout?: number)
close(projectId?: string) // Close current session
reset(projectId?: string) // Clear cache and reopen new session
clear() // Clear all data stored in memory
toString() // Current framework loaded
toString(projectId: string) // await squared.close(projectId) (required)
add(...names: (string | Extension | ExtensionRequestObject)[]) // See extension configuration section
remove(...names: (string | Extension)[]) // Remove extensions by namespace or control
get(...names: string[]) // Retrieve extensions by namespace
attr(name: string | Extension, attrName: string, value?: unknown) // Set or get extension options attribute value
apply(name: string | Extension, options: PlainObject, setting?: string) // See extension configuration section
extend(functionMap: {}, framework?: / 0 - ALL | 1 - vdom | 2 - android | 4 - chrome /) // Add extension functions and properties to Node prototype
observe(value?: boolean | MutationObserverInit) // Start after DOM and third-party libraries initialization
broadcast(callback: BroadcastMessageCallback, options: FileBroadcastOptions | string) // Redirect stdout messages to DevTools console
// Promise (Recommended "cache": createElementMap - true)
getElementById(value: string, sync?: boolean, cache?: boolean) // sync - false | cache - true (default)
querySelector(value: string, sync?: boolean, cache?: boolean)
querySelectorAll(value: string, sync?: boolean, cache?: boolean)
fromElement(element: HTMLElement | string, sync?: boolean, cache?: boolean) // sync - false | cache - false (default)
fromNode(node: Node, sync?: boolean, cache?: boolean)
findDocumentNode(element: HTMLElement | string / querySelector | elementId | controlId /, all?: boolean) // Use before saving to modify internal Node attributes
observeSrc(element: HTMLElement | string / querySelector /, callback: WebSocketMessageChange, options?: FileObserveOptions) // Can be used to watch any element with externally hosted files (src/href)
observeSrc(element: HTMLElement | string, options: FileObserveOptions) // Uses location.reload (reload - true)
`Packaging methods will return a Promise and requires a squared-express installation. These features are not supported when the framework is VDOM.
`javascript
saveAs(filename: string, options?: {}, setting?: string, overwrite?: boolean) // Save current session as a new archive
saveFiles(filename: string, options: {}, setting?: string, overwrite?: boolean) // Create new archive from FileAsset[]// Required (local archives): --disk-read | --unc-read | --access-all (command-line)
appendTo(pathname: string, options?: {}, setting?: string, overwrite?: boolean) // Create new archive from a preexisting archive and current session
appendFiles(pathname: string, options: {}, setting?: string, overwrite?: boolean) // Create new archive from a preexisting archive and FileAsset[]
// Required (all): --disk-write | --unc-write | --access-all (command-line)
copyTo(pathname: string | string[], options?: {}, setting?: string, overwrite?: boolean) // Copy current session to local
copyFiles(pathname: string | string[], options: {}, setting?: string, overwrite?: boolean) // Copy FileAsset[] to local
kill(pid: number, timeout?: number) // Use -1 or options.pid (set by system) + seconds
kill(timeout: string)
kill() // Terminate previous request
kill(0) // By username (auth required)
kill(-1, 10) // Terminate previous request in 10 seconds
kill(NaN, 10) // Terminate in 10 seconds (next request) ("timeout" is required)
kill("10s") // Only "s" + "ms" (next request)
`Extending Node object
You can add functions and initial variables to the Node object including overwriting preexisting class definitions per framework. Accessor properties are also supported using the get/set object syntax.
`javascript
squared.extend({
_id: 1,
altId: {
get() {
return this._id;
},
set(value) {
this._id += value;
}
},
customId: {
value: 2,
configurable: false,
enumerable: false
},
addEvent(eventName, callback) {
this.element.addEventListener(eventName, callback);
}
});
squared.setFramework(vdom);const body = await squared.fromElement(document.body);
body.altId = 2; // body.altId: 3
body.addEvent("click", function (ev) {
this.classList.toggle("example");
});
`Forwarding Request
Using another identical remote server to build the project when performing a
saveAs or copyTo request can be achieved by changing only the origin address.`javascript
squared.setHostname("http://hostname:8000");
// OR
squared.setHostname(); // Reset to window.location (e.g. localhost:3000)await squared.saveAs("chrome.zip"); // Current browser
// OR
await squared.copyTo("/path/project"); // Remote server
`Broadcasting
Console messages (stdout) can be sent to the browser console instead through DevTools.
`javascript
squared.broadcast(result => { console.log(result.value); }, "111-111-111"); // System messages from squared-express
squared.broadcast(result => { console.log(result.value); }, "222-222-222"); // Messages from "project-1" project
squared.broadcast(result => { console.log(result.value); }, { socketId: "333-333-333", socketKey: "socket_id" }); // Messages sent from another channel (default is "socketId")await squared.copyTo("/path/project/project-1", {
projectId: "project-1",
log: { useColor: true }, // Chromium
broadcastId: "222-222-222" // Specific use alias for "socketId"
});
`Extension Configuration
Layout rendering can be customized using extensions as the program was built to be nearly completely modular. Some of the common layouts already have built-in extensions which you can load or unload based on your preference.
`typescript
// Create an extension
class Sample extends squared.base.Extension {
options = {
attributeName: [];
}; constructor(name, framework = 0, options = {}) { // 0 - ALL | 1 - vdom | 2 - android | 4 - chrome (framework)
super(name, framework, options);
}
processNode(node: NodeUI) {
const data = this.project.get(node.element, node.localSettings.projectId);
if (data) {
node.each((child, index) => child.element.title = data[index]);
}
}
}
// Install an extension
const sample = new Sample("widget.example.com", 0, {/ Same as configure /});
squared.add(sample);
// OR
squared.add([sample, {/ config /}]);
// Configure an extension
squared.attr("widget.example.com", "attributeName", ["width", "height"]); // typeof is enforced and will only set existing attributes
// Add project data
const ext = squared.get("widget.example.com");
ext.project.set(element, await fetch(url?id=1)); // Map interface with optional "projectId" argument
ext.project.set(element, await fetch(url?id=2), "project-1");
const data = ext.project.get(element, "project-2"); // Returns data from default project (id=1)
`Some extensions have a few settings which can be configured. The default settings usually achieve the best overall rendering accuracy without noticeably affecting performance.
ANDROID
$3
`javascript
android.setViewModel(data: {}, sessionId?: string) // Object data for layout bindings
android.setViewModelByProject(data: {}, projectId?: string)
android.removeObserver(element: HTMLElement) // Disconnect an observed element from "parseDocument"
android.addXmlNs(name: string, uri: string) // Add global namespaces for third-party controls
android.addDependency(group: string, name: string, version?: string, type?: number) // Add application dependency implementation (build.gradle)
android.addDependencyByProject(projectId: string, group: string, name: string, version?: string, type?: number) // DEPENDENCY_TYPE: 0 - implementation 1 - api 2 - compileOnly 3 - compileOnlyApi 4 - runtimeOnly 5 - testImplementation 8 - androidTestImplementation
android.customize(build: number, tagNameOrWidget: string, options: {}) // Global attributes applied to specific views
android.loadCustomizations(name: string) // Load customizations from Local Storage
android.saveCustomizations(name: string) // Save "customize" data into Local Storage (includes xmlns)
android.resetCustomizations() // All session customizations are deleted
android.addFontProvider(authority: string, package: string, certs: string[], webFonts: string | {}) // Add additional Web fonts (Google Fonts already included)
android.setResolutionByDeviceName(value: string) // Settings prefixed with "resolution" (e.g. Pixel C)
android.getLocalSettings() // Modify controller styles and parsing rules
``javascript
// NOTE: squared.settings.targetAPI is always parsed (Except: customizationsBaseAPI = -1)android.customize(android.lib.constant.BUILD_VERSION.ALL / 0 /, "Button", {
android: {
minWidth: "35px",
minHeight: "25px"
},
"_": { // Non-namespaced attributes
style: "@style/Widget.Material3.Button.TextButton"
}
});
android.customize(android.lib.constant.BUILD_VERSION.KITKAT / 19 /, "svg", {
android: {
"[src]": "app:srcCompat" // Change namespace to "app"
}
});
// Local Storage
android.saveCustomizations("customize-example"); // Save at least once in one layout
android.loadCustomizations("customize-example"); // Load in any other layout
``javascript
android.addXmlNs("tools", "http://schemas.android.com/tools");android.customize(16 / Jelly Bean /, "ImageView", {
tools: {
ignore: "ContentDescription",
targetApi: "16"
}
});
`$3
Project resources can include additional values that are required during compilation. TypeScript definitions are available in the
types/android directory.`javascript
squared.parseDocument().then(node => {
const resourceId = node.localSettings.resourceId;
android.base.Resource.addString(resourceId, value, / name /);
android.base.Resource.addArray(resourceId, name, items);
android.base.Resource.addColor(resourceId, color);
android.base.Resource.addDimen(resourceId, name, value);
android.base.Resource.addTheme(resourceId, theme);
squared.save();
});
`$3
View model data can be applied to most HTML elements using the dataset attribute. Different view models can be used for every
parseDocument session.Leaving the
sessionId empty uses the default view model which is searched last for all projects when attempting a bind.`javascript
// NOTE: latest(undefined = 1): string (1: most recent sessionId | -1: first sessionId)squared.parseDocument("id-1", "id-2", "id-3").then(nodes => {
const sessions = squared.latest(2); // ["00001", "00002", "00003"] => ["00002", "00003"]
android.setViewModel(
{
import: ["java.util.Map", "java.util.List"],
variable: [
{ name: "user", type: "com.example.User" },
{ name: "list", type: "List<String>" },
{ name: "map", type: "Map<String, String>" },
{ name: "index", type: "int" },
{ name: "key", type: "String" }
]
},
sessions[0] // nodes[1].sessionId
);
android.setViewModel(
{
import: ["java.util.Map"],
variable: [
{ name: "map", type: "Map<String, String>" }
]
},
sessions[1] // nodes[2].sessionId
);
});
squared.parseDocument({
element: "main",
enabledViewModel: true,
dataBindableElements: [
{
selector: "#first_name",
namespace: "android", // "android" is default
attr: "text",
expression: "user.firstName"
},
{
selector: "#last_name",
attr: "text",
expression: "user.lastName"
},
{
selector: "#remember_me",
attr: "checked",
expression: "user.rememberMe",
twoWay: true
}
],
data: {
viewModel: {
import: ["java.util.Map"],
variable: [
{ name: "map", type: "Map<String, String>" }
]
}
}
});
squared.save();
`Inlining is also supported and might be more convenient for simple layouts. JavaScript is recommended when you are calling
parseDocument multiple times.`
data-viewmodel-{namespace}-{attribute} -> data-viewmodel-android-text
`These two additional output parameters are required when using the "data-viewmodel" prefix.
`html
``xml
android:id="@+id/first_name"
android:inputType="text"
android:text="@{user.firstName}" />
android:id="@+id/last_name"
android:inputType="text"
android:text="@{user.lastName}" />
android:id="@+id/remember_me"
android:checked="@={user.rememberMe}" />
`$3
Some applications can benefit from using includes or merge tags to share common templates. Nested includes is supported.
`html
Item 1
Item 2
Item 3
Item 4
Item 5
``javascript
android.setViewModelByProject({ variable: [{ name: "exampleData", type: "com.example.ExampleData" }] }, "project-1"); // Default is "_"squared.parseDocument({
element: document.body,
projectId: "project-1", // Affects all layouts in same project
enabledIncludes: true,
includableElements: [
{
selectorStart: "#item2",
selectorEnd: "#item4",
pathname: "app/src/main/res/layout-land",
filename: "filename1.xml",
merge: true // Multiple elements will auto-merge
},
{
selectorStart: "#item5",
selectorEnd: "#item5",
filename: "filename2",
viewModel: "exampleData" // One element only (merge=false)
}
]
});
`
> [!NOTE]
> By sessionId has precedence when associating a view model.`xml
Item 1
Item 2
Item 3
Item 4
Item 5
`The attributes "data-android-include-start" and "data-android-include-end" can only be applied to elements which share the same parent container. See
/demos/gradient.html for usage instructions.> [!TIP]
> "data-pathname-android" AND "data-filename-android" can also be used with any
parseDocument base element.$3
Sometimes it is necessary to extract elements and append them into other containers for it to look identical on the Android device. Redirection will fail if the target location is not a block/container element.
`html
Item 1
Item 2
Item 3
- Item 4
- Item 5
``xml
Item 1
Item 4
Item 3
Item 5
Item 2
`Using
target into a ConstraintLayout or RelativeLayout container will not include automatic positioning.$3
System or extension generated attributes can be overridden preceding finalization. They will only be visible on the declared framework.
`
data-android-attr-{namespace}? -> Default is "android"
``html
data-android-attr="layout_width::match_parent;layout_height::match_parent"
data-android-attr-app="layout_scrollFlags::scroll|exitUntilCollapsed">
``xml
android:id="@+id/customId"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_scrollFlags="scroll|exitUntilCollapsed" />
``javascript
const node = squared.findDocumentNode("customId"); // querySelector is supported
node.android("layout_width", "match_parent");
node.android("layout_height", "match_parent");
node.app("layout_scrollFlags", "scroll|exitUntilCollapsed");
`$3
Only the XML based layout and resource files can be viewed on the Android device/emulator without any Java/Kotlin backend code. To play animations in the emulator you also have to
start the animation in MainActivity.java.`java
import android.graphics.drawable.Animatable;android.widget.ImageView imageView1 = findViewById(R.id.imageview_1);
if (imageView1 != null) {
Animatable animatable = (Animatable) imageView1.getDrawable();
animatable.start();
}
`$3
Most mobile applications do not have a deeply nested hierarchy and are generally better to implement using declarative programming.
`javascript
squared.settings.composableElements = ["main", "#content", "--boxShadow", "--height=300px", {
selector: "main",
android: {
layout_height: "match_parent"
},
tools: {
composableName: "com.example.compose.Preview"
}
}];
squared.settings.createBuildDependencies = true; // Optional
`You can also do it using the "android.substitute" extension directly inside the HTML element.
`javascript
// android.substitute is only used here to demonstrate using extensionssquared.add(["android.substitute", {
element: {
content: { android: { layout_width: "match_parent" } }
}
}]);
const items = squared.attr("android.substitute", "viewAttributes");
items.push("hint", "buttonTint");
/ OR /
squared.attr("android.substitute", "viewAttributes", items.concat(["hint", "buttonTint"])); // Attributes to preserve (default is "android.view.View")
squared.attr("android.substitute", "attributeMapping", { "android:src": "app:srcCompat", "icon": "navigationIcon" / android /});
squared.parseDocument({
element: document.body,
substitutableElements: [{
selector: "#content",
tag: "androidx.compose.ui.platform.ComposeView",
renderChildren: false
}],
// Some extensions have convenience properties
enabledSubstitute: true,
/ OR /
include: ["android.substitute"]
});
``html
data-use="android.substitute"
data-android-substitute-tag="androidx.compose.ui.platform.ComposeView"
style="height: 300px; box-shadow: 10px 5px 5px black;">
`Compose will remove child elements by default. You can preserve them by explictly using the renderChildren property. (data-android-substitute-render-children="true")
`html
data-use="android.substitute"
data-android-substitute-tag="androidx.fragment.app.FragmentContainerView"
data-android-substitute-render-children="false"
data-android-attr="name::com.github.fragment;tag::example">
`You can also use "android.substitute" to create fragments within a layout similar to Compose.
Usually you do not render child elements when using Compose View. There are some cases where it can be used effectively to reproduce the desired layout.
`javascript
squared.parseDocument({
element: document.body,
include: ["android.substitute"], // OR: settings.enabledSubstitute
substitutableElements: [{
selector: "#navigation",
tag: "com.google.android.material.tabs.TabLayout",
tagChild: "com.google.android.material.tabs.TabItem",
tagChildAttr: {
android: {
layout_height: "match_parent"
}
},
renderChildren: true,
autoLayout: true
}]
});
``html
data-use-android="android.substitute"
data-android-attr="layout_height::match_parent"
data-android-substitute-tag="com.google.android.material.tabs.TabLayout"
data-android-substitute-tag-child="com.google.android.material.tabs.TabItem"
data-android-substitute-tag-child-attr="layout_height::match_parent"
data-android-substitute-auto-layout="true">
TAB 1
TAB 2
TAB 3
``xml
android:id="@+id/navigation"
android:layout_height="match_parent"
android:layout_width="wrap_content">
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:text="@string/tab_1" />
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:text="@string/tab_2" />
android:layout_height="match_parent"
android:layout_width="wrap_content"
android:text="@string/tab_3" />
`$3
Google Fonts are pre-installed and can be used without any additional configuration.
* Guide
`xml
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.0'
implementation 'com.android.support:appcompat-v7:28.0.0'
}
``javascript
// https://developers.google.com/fonts/docs/developer_apiawait android.addFontProvider(
"com.google.android.gms.fonts",
"com.google.android.gms",
["MIIEqDCCA5CgAwIBAgIJANWFuGx9007...", "MIIEQzCCAyugAwIBAgIJAMLgh0Zk..."],
"https://www.googleapis.com/webfonts/v1/webfonts?key=1234567890" // JSON object is synchronous
);
/ OR /
squared.attr("android.resource.fonts", "installGoogleFonts", false); // Use browser and local fonts only
`$3
Most attributes can be excluded from the generated XML using the dataset feature in HTML. One or more can be applied to any tag using the OR "|" operator. These may cause warnings when you compile your project and should only be used when an extension has their custom attributes overwritten.
NOTE: Defining an element "id" will prevent it from being removed during the optimization phase.
`html
data-exclude-procedure="CONSTRAINT | LAYOUT | ALIGNMENT | ACCESSIBILITY | LOCALIZATION | CUSTOMIZATION | OPTIMIZATION | ALL"
data-exclude-resource="BOX_STYLE | BOX_SPACING | FONT_STYLE | VALUE_STRING | IMAGE_SOURCE | ASSET | ALL"
data-exclude-optimization="EXCLUDE | INHERIT | ALIGNMENT | POSITION | DIMENSION | MARGIN | PADDING | BASELINE | WHITESPACE | TRANSLATE | TRANSFORM | SCALING">
BSD 3-Clause