Step By Step Code Example Prototype V9
Details
- The example below is built directly from the HTML and JavaScript code snippets that follow
- The goal is to produce a step by step example to use with Rust playgrounds on typing-rust.alanwsmith.com
- This one uses the ace editor.
EXAMPLE
fn main() { // Full source code
let alfa = String::from("apple");
let charlie = widget(alfa);
println!("charlie is {charlie}");
}
fn widget(thing: String) -> String {
println!("widget got {thing}");
let bravo = String::from("berry");
bravo
}
HTML Source
<!-- prettier-ignore -->
<div id="wrapper">
<div id="editor"></div>
</div>
CSS Source
#editor {
min-height: 22rem;
}
#stepByStepRangeSlider {
min-width: 20rem;
}
JavaScript Source
const makeElement = (
_type,
_id,
_html,
_childOf,
_event,
_function,
_classes
) => {
const newElement = document.createElement(_type)
newElement.id = _id
newElement.innerHTML = _html
window[_childOf].appendChild(newElement)
if (_event !== null) {
newElement.addEventListener(_event, _function)
}
if (_classes) {
newElement.classList.add(_classes)
}
}
const handleNextButtonClick = () => {
if (c.set < c.sets.length - 1) {
updateEverything(c.set + 1)
}
}
const handleNumberButtonClick = (event) => {
const newIndex = parseInt(event.target.id.split('_')[1])
updateEverything(newIndex)
}
const handlePreviousButtonClick = () => {
if (c.set > 0) {
updateEverything(c.set - 1)
}
}
const makeNextButton = () => {
makeElement(
'button',
'stepByStepNextButton',
'->',
'wrapper',
'click',
handleNextButtonClick,
'stepByStepButton'
)
}
const makeNumberButtons = () => {
for (let i = 0; i < c.sets.length; i++) {
let buttonText = i
if (i === 0) {
buttonText = 'Start'
} else if (i === c.sets.length - 1) {
buttonText = 'Complete'
}
makeElement(
'button',
`stepByStepNumberButton_${i}`,
buttonText,
'wrapper',
'click',
handleNumberButtonClick,
'stepByStepButton'
)
}
}
const makePreviousButton = () => {
makeElement(
'button',
'stepByStepPreviousButton',
'<-',
'wrapper',
'click',
handlePreviousButtonClick,
'stepByStepButton'
)
}
const handleRangeSliderChange = (event) => {
updateEverything(parseInt(event.target.value))
}
const makeRangeSlider = () => {
const newEl = document.createElement('input')
newEl.id = 'stepByStepRangeSlider'
newEl.type = 'range'
newEl.min = 0
newEl.max = 9
newEl.value = 0
newEl.addEventListener('input', handleRangeSliderChange)
window.wrapper.appendChild(newEl)
}
const loadLines = () => {
c.lines = c.source.split('\n')
}
const removeHighlights = () => {
// console.log('removeHighlights')
// bail if no lines were selected
//
if (c.sets[c.set].highlights.length === 0) {
c.styleOverride.innerHTML = `.ace-monokai .ace_line .ace_comment { color: #eee; }`
return
}
const selectors = [
'.ace_keyword',
'.ace_lparen',
'.ace_source',
'.ace_rust',
'.ace_paren',
'.ace_function',
'.ace_entity',
'.ace_identifier',
'.ace_operator',
'.ace_support',
'.ace_constant',
'.ace_quoted',
'.ace_string',
'.ace_double',
'.ace_punctuation',
'.ace_rparen',
]
// console.log(c.lines.length)
const removers = []
for (let i = 0; i < c.lines.length; i++) {
const lineNumber = i + 1
if (!c.sets[c.set].highlights.includes(lineNumber)) {
for (let x = 0; x < selectors.length; x++) {
removers.push(
`.ace-monokai .ace_line:nth-child(${lineNumber}) ${selectors[x]} { color: #667; }`
)
// console.log(selectors[x])
}
removers.push(
`.ace-monokai .ace_line:nth-child(${lineNumber}) .ace_comment { color: #eee; }`
)
}
// set the comment color for the lines with content
else {
removers.push(
`.ace-monokai .ace_line:nth-child(${lineNumber}) .ace_comment { color: #eee; }`
)
}
}
c.styleOverride.innerHTML = `${removers.join(' ')}`
// console.log(c.styleOverride.innerHTML)
}
const updateContent = () => {
parts = []
for (let i = 0; i < c.lines.length; i++) {
if (c.sets[c.set].lines.includes(i + 1)) {
parts.push(`${c.lines[i]}`)
} else {
parts.push('')
}
}
// add the notes
const noteParts = c.sets[c.set].note.split('\n').filter((line) => line !== '')
for (let n = 0; n < noteParts.length; n++) {
const outputLine = n + c.sets[c.set].noteCoords[0] - 1
const padding = c.sets[c.set].noteCoords[1] - parts[outputLine].length - 1
// console.log(padding)
for (let pad = 0; pad < padding; pad++) {
parts[outputLine] += ' '
}
parts[outputLine] += noteParts[n]
}
if (c.sets[c.set].overrides) {
for (let o = 0; o < c.sets[c.set].overrides.length; o++) {
const override = c.sets[c.set].overrides[0]
parts[override.line - 1] = override.text
}
}
c.editor.setValue(parts.join('\n'), 1)
}
const updateEverything = (set) => {
c.set = set
window.stepByStepRangeSlider.value = set
updateContent()
removeHighlights()
}
const init = () => {
loadLines()
c.editor = ace.edit('editor')
c.editor.setTheme('ace/theme/monokai')
c.editor.session.setMode('ace/mode/rust')
c.editor.setHighlightActiveLine(false)
c.styleOverride = document.createElement('style')
document.body.appendChild(c.styleOverride)
makePreviousButton()
//makeNumberButtons()
makeRangeSlider()
makeNextButton()
updateEverything(0)
}
document.addEventListener('DOMContentLoaded', init)
JSON Data Source
{
"note": "Example JSON data file"
}
Config JS Source
// This file can be used to represent
// and data that should be held outside
// of the main script
const c = {
source: `fn main() {
let alfa = String::from("apple");
let charlie = widget(alfa);
println!("charlie is {charlie}");
}
fn widget(thing: String) -> String {
println!("widget got {thing}");
let bravo = String::from("berry");
bravo
}`,
sets: [
{
lines: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
highlights: [],
note: `// Full source code`,
noteCoords: [1, 40],
},
{
lines: [7, 11],
highlights: [7, 11],
overrides: [{ line: 7, text: `fn widget() {` }],
note: `
// Start by creating a basic \`widget()\`
// function
`,
noteCoords: [3, 0],
},
{
lines: [7, 11],
highlights: [7],
overrides: [{ line: 7, text: `fn widget(thing: String) {` }],
note: `
// Setup \`widget\` to accept a \`String\`
// argument and bind it to the local
// variable \`thing\`
`,
noteCoords: [3, 0],
},
{
lines: [7, 11],
highlights: [7],
overrides: [{ line: 7, text: `fn widget(thing: String) -> String {` }],
note: `
// Add a \`String\` return type
`,
noteCoords: [3, 0],
},
{
lines: [7, 8, 11],
highlights: [8],
note: `
// Create a \`println!()\` output that shows
// the \`thing\` variable that gets passed
// into the function
`,
noteCoords: [3, 0],
},
{
lines: [7, 8, 9, 10, 11],
highlights: [9, 10],
note: `
// Create a \`bravo\` variable and assign it a
// \`String\` so it matches the return type then
// return it as the last expression of the function
`,
noteCoords: [3, 0],
},
{
lines: [1, 2, 5, 7, 8, 9, 10, 11],
highlights: [1, 2, 5],
overrides: [],
noteCoords: [1, 40],
note: `
// Create the \`main()\` function and a
// variable named \`alfa\` with a \`String\`
// bound to it.
`,
},
{
lines: [1, 2, 3, 5, 7, 8, 9, 10, 11],
highlights: [3],
overrides: [],
noteCoords: [1, 40],
note: `
// Add a variable named \`charlie\` that
// passes the \`alfa\` variable to \`widget\`
`,
},
{
lines: [1, 2, 3, 4, 5, 7, 8, 9, 10, 11],
highlights: [4],
overrides: [],
noteCoords: [1, 40],
note: `
// Finally print out the value in
// \`charlie\` to show the value it
// received from \`widget\`
`,
},
{
lines: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11],
highlights: [],
overrides: [],
note: `// Click run to see the output`,
noteCoords: [1, 40],
},
],
}