Probably, because you know LC is not at an "end", it is at its usual endless tortuous way of getting somewhere.

Moderators: FourthWorld, heatherlaine, Klaus, kevinmiller, robinmiller
You are greatly underselling LCs capabilities but I hear you on the price aspect. While 9.6.3 may not run on your Mac you should check out the open source Fork maintaining this version - i have not, but I hear it runs OK.MichaelBluejay wrote: ↑Tue Aug 19, 2025 4:59 pmThank you, I didn't know about Metadata for text. The feature set of LiveCode is huge (and growing) and there's a lot I don't know about. (I couldn't find Metadata even when searching for that feature, because I didn't know what it was called.) Knowing about metadata sure would have saved me a lot of trouble when I was reordering lines of a list of note titles, where I needed an invisible ID to be associated with each line.
I'm still not planning to return to LiveCode, for these reasons:
• 9.6.3 doesn't run on my M1 Mac. (Won't launch.)
• I'm not paying $440/year to upgrade to a version that will run, just for some homegrown apps.
• Web apps are better with HTML/JS, because there's no massive LC engine to download every time.
• While I can embed metadata behind text in LC, with HTML I can *see* the metadata as part of the code (e.g., as element attributes or hidden fields).
• HTML tables are easy to work with. DataGrid is a royal pain.
• HTML tables scroll super fast. DataGrid scrolls super slow.
The advantages I can think of for LiveCode are:
• Changes are live as soon as you make them. No save file / switch from text editor to browser / reload page as with HTML/JS.
• Can make standalone apps.
I don't think I'll ever make a standalone app, but if I do, then I'd certainly use LiveCode.
I have been there. Like any dictionary, one has to know how to spell a word before one can find it. With LC, you are not even sure what word you are looking for....a lot I don't know about. (I couldn't find Metadata even when searching for that feature, because I didn't know what it was called
Okay, I'm listening, what am I missing? To me, if I'm going web-based, I can't see why I'd want the bloat of LiveCode for no discernible benefit when HTML/JS is easy to code, lightweight, and runs fast. If I'm making a standalone app, then sure, LiveCode or one of its competitors.
Thank you, I didn't know about that. I found OpenXTalk and downloaded it, but it wouldn't launch either. Even if it did, I just don't have a use for it any more, I already ported my apps to HTML/JS.stam wrote:While 9.6.3 may not run on your Mac you should check out the open source Fork maintaining this version.
My experience is different. Scrolling a 1000x1200-pixel, 6-column DataGrid on my old Mac (i7/4GHz CPU) is pretty slow. Maybe it would be fast enough on a new Mac, but HTML tables scroll like lightning on my old computer. I can't test DataGrid on my new computer because LC won't launch.stam wrote:I will have to openly disagree with you about the speed of the datagrid. It’s plenty fast. And it’s much easier to manipulate than html tables - obviously you have to bother to learn it…
I'm sure that's true though I haven't run across anything in HTML/JS that I felt was a lot easier in LC. I know, for example, that LC can refer to "words" of text, which is convenient, but I don't really use that feature. What other kinds of things are easier in LC vs. JS?stam wrote:But where LC shines is the backend of the app. There is a lot of code that is more laborious to do in JS.
Well that's not been my experience, even on my old Intel Mac (can't check again now as I'm abroad).MichaelBluejay wrote: ↑Tue Aug 19, 2025 9:54 pmMy experience is different. Scrolling a 1000x1200-pixel, 6-column DataGrid on my old Mac (i7/4GHz CPU) is pretty slow. Maybe it would be fast enough on a new Mac, but HTML tables scroll like lightning on my old computer. I can't test DataGrid on my new computer because LC won't launch.
I would argue LiveCode is hard - for developers used to other languages. But once you 'grok' it, it's the nicest and easiest language out there.MichaelBluejay wrote: ↑Tue Aug 19, 2025 9:54 pmAnd yeah, I did put some effort into learning DataGrid for my old LC-based accounting app, because I had to, because it was the opposite of intuitive. I I found myself constantly shaking my head at DataGrid's crazy syntax. I felt that DataGrid's difficulty went against the whole "XTalk is easy" idea. HTML/JS was way faster to learn by comparison.
To use the example from the mothership's site:MichaelBluejay wrote: ↑Tue Aug 19, 2025 9:54 pmI'm sure that's true though I haven't run across anything in HTML/JS that I felt was a lot easier in LC. I know, for example, that LC can refer to "words" of text, which is convenient, but I don't really use that feature. What other kinds of things are easier in LC vs. JS?
Code: Select all
sort lines of theText descending by last item of each
Code: Select all
theText = theText.split("\n");
theText = theText.sort(sort_item_3).join("\n");
function sort_item_3(line1, line2){
line1 = line1.split(",");
line2 = line2.split(",");
if(line1[2] == line2[2]) return 0;
else if(line1[2] > line2[2]) return -1;
else return 1;
}
Code: Select all
function pathsForDirectoryAndWildcardPattern pDirectory, pWildcardPattern
filter files(pDirectory) with pWildcardPattern
repeat for each line tFile in it
put pDirectory & slash & tFile & cr after tPaths
end repeat
filter folders(pDirectory) without ".."
repeat for each line tFolder in it
put pathsForDirectoryAndWildcardPattern(pDirectory & slash & tFolder, pWildcardPattern) after tPaths
end repeat
return tPaths
end pathsForDirectoryAndWildcardPattern
Code: Select all
var fso = new ActiveXObject("Scripting.FileSystemObject");
function walkDirectoryTree(folder, folder_name, re_pattern) {
WScript.Echo("Files in " + folder_name + " matching '" + re_pattern + "':");
walkDirectoryFilter(folder.files, re_pattern);
var subfolders = folder.SubFolders;
WScript.Echo("Folders in " + folder_name + " matching '" + re_pattern + "':");
walkDirectoryFilter(subfolders, re_pattern);
WScript.Echo();
var en = new Enumerator(subfolders);
while (! en.atEnd()) {
var subfolder = en.item();
walkDirectoryTree(subfolder, folder_name + "/" + subfolder.name, re_pattern);
en.moveNext();
}
}
function walkDirectoryFilter(items, re_pattern) {
var e = new Enumerator(items);
while (! e.atEnd()) {
var item = e.item();
if (item.name.match(re_pattern))
WScript.Echo(item.name);
e.moveNext();
}
}
walkDirectoryTree(dir, dir.name, '\\.txt$');
Code: Select all
<div id=output style="height:100vh; overflow:scroll; border:2px solid black"></div>
<script>
charset = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ01234567890!@#$%^&*()[]{}/=\?+|- '
colors = ['gainsboro', 'beige', 'aliceblue', 'lavenderblush', 'mintcream', 'mistyrose', 'antiquewhite']
output = "<table>"
for (row=1; row <= 20000; row++) {
color = colors[Math.floor(Math.random()*colors.length)]
output += `<tr bgcolor='${color}'</tr>`
for (cell = 1; cell <= 6; cell++) {
str = ''
for (chars = 1; chars <=7; chars++) {
str+= charset[Math.floor(Math.random()*charset.length)]
}
output += `<td>${str}</td>`
}
output +="</tr>"
}
output += "</table>"
document.getElementById('output').innerHTML = output
</script>
I erred by using my accounting app in my sample code example in this thread which was misleading. In reality, for my ACCOUNTING app, I did exactly as you describe, use DataGrid with hidden fields. For my NOTES app, I didn't use tables at all; the left-hand pane is a text list of notes, that can be reordered by dragging up and down, and each of those lines needs to be associated with a noteID, so without knowing about MetaData, I was stuck keeping a separate index of noteIDs.stam wrote:Regarding your question that culminated in a discussion of metadata - metadata is a bad fit...
Okay, first of all, it can be done in JS easier than the example code:stam wrote:[one line of LC code to reverse-sort a text table by the last item of each line, 8 lines of JS code to do the same]
Code: Select all
lines = theText.split("\n")
my2dArray = []
for (i=0; i < lines.length; i++) {
items = lines[i].split(' ')
my2dArray[i] = items
}
my2dArray.sort( function (a,b) { return b[items.length-1] - a[items.length-1]} )
ChatGPT's JS code is shorter than LC's, unless you count the lines that have only punctuation, and in that case, it's 12 lines for JS and 11 for LC.stam wrote:[shorter code in LC to walk a directory tree than in JS]
Code: Select all
const fs = require("fs");
const path = require("path");
/**
* Recursively walk a directory and print files matching a pattern
* @param {string} dir - Directory to start from
* @param {RegExp} pattern - Regex pattern to match filenames
*/
function walkDir(dir, pattern) {
fs.readdirSync(dir, { withFileTypes: true }).forEach((entry) => {
const fullPath = path.join(dir, entry.name);
if (entry.isDirectory()) {
// Recurse into subdirectory
walkDir(fullPath, pattern);
} else if (entry.isFile() && pattern.test(entry.name)) {
console.log(fullPath);
}
});
}
Code: Select all
on mouseup
local tText, tStart,tEnd
put empty into tText
repeat with x=1 to 10000
repeat with y=1 to 12
put random(5000) & tab after tText
end repeat
put cr after tText
end repeat
---lock screen
put the milliseconds into tStart
set the pgtext of widget 1 to tText
put the milliseconds into tEnd
put tEnd-tStart & "ms" into field "spdwidget"
put the milliseconds into tStart
set the text of field "test" to tText
put the milliseconds into tEnd
put tEnd-tStart & "ms" into field "spdfield"
put the milliseconds into tStart
set the dgtext of group "test2" to tText
put the milliseconds into tEnd
put tEnd-tStart & "ms" into field "spddg"
--unlock screen
end mouseup
Code: Select all
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>10,000×12 Table: Virtualized vs Full</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; margin: 24px; }
h2 { margin: 0 0 8px; }
.controls { display: flex; gap: 12px; align-items: center; margin: 12px 0 16px; flex-wrap: wrap; }
button { padding: 8px 12px; border: 1px solid #ccc; border-radius: 8px; cursor: pointer; background: #fafafa; }
.metrics { font-family: monospace; margin: 10px 0 16px; }
.container {
border: 1px solid #ccc;
height: 520px;
overflow: auto;
position: relative;
background: #fff;
}
table { border-collapse: collapse; width: 100%; table-layout: fixed; }
th, td { border: 1px solid #ddd; padding: 2px 6px; text-align: right; font-family: monospace; }
thead th { position: sticky; top: 0; background: #f3f3f3; z-index: 1; }
/* Virtualized-only spacer (fills the scroll height) */
.spacer { position: relative; width: 100%; }
/* Make header + body align in both modes */
.w-fixed thead, .w-fixed tbody tr { display: table; width: 100%; table-layout: fixed; }
.w-fixed tbody { display: block; }
</style>
</head>
<body>
<h2>10,000×12 Random Number Table</h2>
<div class="controls">
<button id="toggleBtn">Mode: Virtualized</button>
<button id="renderBtn">Render / Re-render</button>
<span class="metrics" id="metrics">—</span>
</div>
<div class="container" id="container"></div>
<script>
// ---- Config ----
const TOTAL_ROWS = 10000;
const COLS = 12;
const ROW_HEIGHT = 24; // px (approx)
const BUFFER = 20; // extra rows above/below viewport for virtualization
// ---- State ----
let mode = "virtualized"; // "virtualized" | "full"
let container = document.getElementById("container");
let metrics = document.getElementById("metrics");
let toggleBtn = document.getElementById("toggleBtn");
let renderBtn = document.getElementById("renderBtn");
// For virtualized mode
let vElems = { spacer: null, table: null, theadRow: null, tbody: null };
let onScrollHandler = null;
// Deterministic "random" so values don't change when scrolling
function valueAt(row, col) {
return Math.abs(Math.floor(Math.sin(row * 1000 + col) * 10000));
}
function buildHeaderHTML() {
let h = "";
for (let c = 1; c <= COLS; c++) h += `<th>Col ${c}</th>`;
return `<thead><tr>${h}</tr></thead>`;
}
// ----- FULL RENDER -----
function renderFull() {
teardownVirtualized();
const start = performance.now();
let bodyHTML = "";
for (let r = 0; r < TOTAL_ROWS; r++) {
bodyHTML += "<tr>";
for (let c = 0; c < COLS; c++) {
bodyHTML += `<td>${valueAt(r, c)}</td>`;
}
bodyHTML += "</tr>";
}
container.innerHTML = `
<table class="w-fixed">
${buildHeaderHTML()}
<tbody id="fullBody">${bodyHTML}</tbody>
</table>
`;
// Measure after paint
requestAnimationFrame(() => {
const end = performance.now();
const tbody = document.getElementById("fullBody");
const rowsInDOM = tbody ? tbody.childElementCount : 0;
updateMetrics(end - start, rowsInDOM, "Full");
});
}
// ----- VIRTUALIZED RENDER -----
function initVirtualized() {
// Build static shell
container.innerHTML = `
<div class="spacer" id="vSpacer" style="height:${TOTAL_ROWS * ROW_HEIGHT}px"></div>
<table id="vTable">
${buildHeaderHTML()}
<tbody id="vBody"></tbody>
</table>
`;
vElems.spacer = document.getElementById("vSpacer");
vElems.table = document.getElementById("vTable");
vElems.tbody = document.getElementById("vBody");
// Initially render visible slice
renderVirtualSlice();
// Scroll handler
onScrollHandler = () => renderVirtualSlice();
container.addEventListener("scroll", onScrollHandler, { passive: true });
}
function teardownVirtualized() {
if (onScrollHandler) {
container.removeEventListener("scroll", onScrollHandler);
onScrollHandler = null;
}
}
function renderVirtualSlice() {
const start = performance.now();
const scrollTop = container.scrollTop;
const viewportRows = Math.ceil(container.clientHeight / ROW_HEIGHT);
const startRow = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - BUFFER);
const endRow = Math.min(TOTAL_ROWS, startRow + viewportRows + 2 * BUFFER);
let html = "";
for (let r = startRow; r < endRow; r++) {
html += `<tr style="transform: translateY(${r * ROW_HEIGHT}px)">`;
for (let c = 0; c < COLS; c++) {
html += `<td>${valueAt(r, c)}</td>`;
}
html += `</tr>`;
}
vElems.tbody.innerHTML = html;
const end = performance.now();
// Report per-frame render time & current DOM row count
updateMetrics(end - start, vElems.tbody.childElementCount, "Virtualized");
}
// ----- UI Wiring -----
function updateMetrics(ms, domRows, label) {
metrics.textContent = `${label} render: ${ms.toFixed(2)} ms · DOM rows: ${domRows}/${TOTAL_ROWS}`;
}
function renderCurrentMode() {
if (mode === "virtualized") {
initVirtualized();
} else {
renderFull();
}
}
toggleBtn.addEventListener("click", () => {
mode = (mode === "virtualized") ? "full" : "virtualized";
toggleBtn.textContent = `Mode: ${mode.charAt(0).toUpperCase() + mode.slice(1)}`;
renderCurrentMode();
});
renderBtn.addEventListener("click", renderCurrentMode);
// Initial render
renderCurrentMode();
</script>
</body>
</html>
In VS Code you can do this using extensions such asMichaelBluejay wrote: ↑Tue Aug 19, 2025 4:59 pm
The advantages I can think of for LiveCode are:
• Changes are live as soon as you make them. No save file / switch from text editor to browser / reload page as with HTML/JS.
• Can make standalone apps.
I don't think I'll ever make a standalone app, but if I do, then I'd certainly use LiveCode.
It's "easy" to get an app up and running with Electron but with Nativefier you can make an app/EXE with Electron using only the command line.What is Nativefier?
Nativefier is a command-line tool that wraps a website for usage on macOS, Windows and/or Linux. Nativefier uses Electron under the hood to wrap these websites. Lots of companies, from Microsoft and Slack down to new startups use Electron for their web apps.
Dude, WTF?! I'm just relaying my actual experience. What, I'm supposed to make decisions based on *your* experience rather than mine?
How "TF" did you get that I'm trying to dictate to you what to do!?!!?!?!?!?!MichaelBluejay wrote: ↑Wed Aug 20, 2025 11:53 pmDude, WTF?! I'm just relaying my actual experience. What, I'm supposed to make decisions based on *your* experience rather than mine?
dude, if you want to use html/js then that's fine - millions do. Not quite sure why you're here arguing the case. if it suits you, then it's right for you. However, I do object to spouting incorrect information about LiveCode.
Lagi Pittas wrote: ↑Wed Aug 20, 2025 2:16 pmIn VS Code you can do this using extensions such asMichaelBluejay wrote: ↑Tue Aug 19, 2025 4:59 pm
The advantages I can think of for LiveCode are:
• Changes are live as soon as you make them. No save file / switch from text editor to browser / reload page as with HTML/JS.
• Can make standalone apps.
I don't think I'll ever make a standalone app, but if I do, then I'd certainly use LiveCode.
Live Server (Ritwick Dey) - it launches a local dev server and refreshes the browser automatically when you save a file.
Live Preview (official Microsoft extension) - shows a browser preview right inside VS Code.
For standalone you have a few options
Electron – Node.js + Chromium. Biggest ecosystem, but heavy installs.
Tauri – uses system webview. Tiny binaries, very fast (we are told).
Neutralino.js – Ultra-light, minimal features, and very small footprint.
Nativefier – Easiest option if you just want to wrap an existing site. It creates an electron exe with just one command.
It's "easy" to get an app up and running with Electron but with Nativefier you can make an app/EXE with Electron using only the command line.What is Nativefier?
Nativefier is a command-line tool that wraps a website for usage on macOS, Windows and/or Linux. Nativefier uses Electron under the hood to wrap these websites. Lots of companies, from Microsoft and Slack down to new startups use Electron for their web apps.
I've only ever used Electron (safety in what's been battle tested) but the exe has an overhead of about 60MB because you get a copy of Node and Chromium. Tauri and Neutralino us the builin Webview (maintained by Microsoft and based on Chromium). If you don't use node I would go with Tauri.
Also creating a PWA is another way to go for mobile - you don't need an app store and the bookmark on the home screen makes it act exactly like a mobile App.
I'm going to be checking this out sometime.
https://www.todesktop.com/guides/nativefier