Teacher's Guide: Input/Output
Overview
This lesson introduces I/O using the jQuery-turtle load
and save
functions. These Pencil Code features facilitate reading and writing to the Pencil Code server as well as reading files stored on remote servers. The lesson focuses on these two functions, rather than the jQuery and/or JavaScript functions that underlie them (or on more modern tools for that matter), in order to introduce data permanance into student's programming logic without getting lost in the myriad complexities involved in loading and saving.
In support of this work, the lesson touches on the topic of asynchronous processes, recommending the use of the Iced CoffeeScript await
and defer
functions to simplify the I/O process. More modern tools (i.e., Promises) facilitate navigating asynchronous processes, but the older approaches are still in widespread use. The two introductory I/O lessons introduce direct use of Pencil Code's load
and save
functions. This is useful for pedagogical purposes, and also lays the groundwork for future advancement with the language. However, in practice, students will load and save data using special calls to await
/defer
.
Loading and saving data are important tasks in their own right. Yet, arguably, the most important goal of this lesson is to set the stage for loading and running exernal scripts, including third-party librares and custom code respositories created by the student him- or herself. This will be the topic of the next lesson, Script Recycling!.
More about the lesson
The Pencil Code load
function facilitates accessing files from the web, including files stored on the Pencil Code server and files stored anywhere on the web. (Note that load
cannot be used to read files from the user's computer; methods for doing that are described in these technical notes to this document.) Superficially, load
provides a simplified syntax. However, it is the additional steps it takes behind the scenes that is the most valuable to students at this point. For files saved on the Pencil Code server, load
conceals aspects of these files related to the inner workings of the Pencil Code site (discussed in the jQuery.ajax section of these notes). For files located on remote servers, load
takes the necessary steps to work around the obstacles presented by the same origin policy (see the Technical notes, below). The Pencil Code save
function likewise facilitates writing to the Pencil Code server, not only through its symplified syntax, but more substantially through additional tasks it handles behind the scene.
For most of their input/output needs in the Pencil Code environment, students will likely want to turn to the use of these functions in conjunction with await
and defer
. However, working with load
and save
directly forces students to contend with issues of timing and scope. Given the importance of understanding these concepts for students' long-term development as web programmers, these notes therefore start with a discussion of the load
and save
functions in standalone form.
Basics of load
load
accepts two arguments: the URL of the file that contains the data, and a callback function which gets executed once the HTTP request completes. The URL can be a fully specified address anywhere on the web that is publically accessible. Files without a fully-specified URL are read from the Pencil Code server. Such local filename references are specified using standard command-line relative paths. These involve combinations of /
, ./
, and ../
, as follows:
reference | result |
---|---|
"FileName" |
file in current directory |
"./FileName" |
file in current directory |
"../FileName" |
file in parent directory |
"../OtherFolder/FileName" |
file in a neighboring directory |
"/FileName" |
file in the top-level directory of this account |
The second argument to load
, a callback function, specifies what to do with the source file data once the load completes. Note the critical importance that the argument to the callback plays in this process—it is used to reference the data that is loaded from the source file. Without this reference, the data import serves no purpose!
Issues of scope
As with all function arguments, the callback's argument is only accessible in the body of the callback. For the data to be available more broadly in the program, one of two general strategies must be followed: either assign the data to a variable in the enclosing scope, or include all code that depends on the data import in the callback itself.
The simplest way to execute the former strategy is to define a variable in the enclosing scope and the data to that variable in the callback. A simple illustration of this idiom is provided below. The sole purpose of the callback in this case is to populate the variable enclosingScopeDataRef
with the contents of the source file:
enclosingScopeDataRef = undefined cb = (cbDataRef) -> enclosingScopeDataRef = cbDataRef load "Notes_SimpleSave.txt", cb
The input lesson's coding snippet provides a slightly more complicated variant of this same general approach. In that example, the callback first manipulates the data to get into a desired form, converting the import from a single string to individual Number values, which it then appends to the primes
array in the enclosing scope.
The alternative approach to preserve access to the data referenced by the callback's argument is to enclose all subsequent code that depends on that data in the callback (or in functions referenced from the callback). This is an example of a broader idiom known as continuation passing style (CPS). In the simplest case, such as programs that involve a single call to load at the beginning of the script, CPS entails enclosing all code that should be processed after the data is loaded in the callback:
cb = (str) -> # subsequent lines of the program go here # in particularly, anything that depends # on the data referenced by str load "Notes_SimpleSave.txt", cb
CPS is straightforward to implement in simple programs, though it does force a conceptual shift in how we manipulate program flow. Things quickly get more complicated in more extensive programs, such as those involving multiple loads. These can lead to multiple nested functions, which can get confusing—so much so, that coders have designated it "callback hell". This kind of complex nested callback structure diminishes code readability and thereby complicates maintainability.
Issues of timing
If the only benefit to CPS related to issues of scope, it might not be so widely used. However, CPS is also an effective way to tackle a thorny issue involving timing.
The lessons emphasize the fact that load
and save
(and, by default, all other HTTP requests) execute asynchronously. Consequently, subsequent statements in the script are likely to be executed before the call to these functions completes. For example, if the data to be imported with load
is referenced before the function completes, errors are certain to arise. CPS ensures that subsequent lines of code which depend on the results of the load
call are not executed prematurely by including them all in the callback itself.
await
/defer
The drawback to using CPS to resolve timing issues is complexity of code. For programs with multiple HTTP Requests (such as load
or save
), or in which requests are called at different points in the program, this approach can get rather convoluted (i.e., leading again to the aforementioned callback hell).
There are a variety of alternative to CPS to effectively incorporate asynchronous processes into programs. Some approaches include the use of timeouts, others turn to events, and in more recent versions of JavaScript, resources known as Promises. Thankfully, Pencil Code provides a powerful and effective workaround. As the lesson notes, both the scope and timing issues associated with load
and save
are sidestepped by use of await
/defer
.
Recall that with animations, the execution of code following a call to await done defer()
are not carried out until the done
function fires, which occurs after all previous animation has been carried out. The use of await
/defer
with I/O is structured analogously. await
delays execution of subsequent lines of code until the data import has completed, an event which triggers the the specially-constructed callback passed to load
(i.e., the output of defer(data)
to fire.
The await
/defer
syntax for use with load
, introduced in the lesson, is:
Note that this use of await
/defer
has the convenient benefit of creating the reference to data
in the enclosing scope, facilitating accessing the loaded information in subsequent lines of the script, without having to explicitly declare the variable beforehand. (The explanation for this is a nuance of how variables are declared in CoffeeScript: when CoffeeScript code is compiled to the JavaScript, all undeclared variables—including the done
variable passed to defer
—are declared at the top of the coding block for the current scope).
It is important that the syntax of the await
/defer
call be followed exactly. In the foregoing statement, defer(data)
creates a callback that is passed to load
, but does it in a way that forces await
to block processing of subequent lines of code in the file from being executed until the asynchronous process completes. In particular, the following will not work, as the callback created by defer
is passed as a second argument to await
.
await load(fileName), defer(data) #this will not work
Working with imported data
The I in I/O lesson describes reading in data in a plain text file. As noted in the lesson, the entirety of the data file is passed as a String to the callback that is invoked upon completion of the load. This data typically needs to be processed in some fashion. The lesson's coding snippet provides an example in which that string contains a comma-delimited sequence of numbers that need to be converted to type Number. In the lesson example, the String split
method is used to separate data, based on the delimeter ",". Note that for the split
method, the default delimeter is whitespace (e.g., a space or a tab). As the lesson example illustrates, for numeric data, we must take the additional step of converting the separate String values into thei Numeric equivalents, e.g., turning "5"
into 5
. The example provided in the data input lesson provides some general tools for breaking up strings and parsing integers; however, the actual "preprocessing" steps required will ultimately depend on the nature of the imported data and how it is formatted.
Frequently, this data manipulation is done within the callback to load
, as illustrated in the snippet in data input lesson. However, in scripts making use of await
/defer
, these steps would naturally follow on subsequent lines of code (as the callback passed to load
in that case is generated by the call to defer
).
This data processing step associated with important data is time-consuming and error prone. Thankfully, the task can be simplified using a special file format called JSON. JSON is an acronym for JavaScript Object Notation, though that is a bit of a misnomer. JSON has its roots in JavaScript, but it is such a useful format that it is now an industry standard for saving and sharing data.
The data output lesson introduces the JSON file format and the built-in functions JSON.stringify
and JSON.parse
. These facilitate reading and writing data from and to files, because they do all the work to convert our data (which could be an array or an object) into a specially formatted (JSON) string (hence the name stringify) for us to then save; and when we import data, JSON.parse
reverses the process, getting us back to the data format (Number, String, Object, etc.) we started with.
Be aware, however, that the JavaScript JSON
methods do have limitations. For example, a Date object converted to a string with JSON.stringify
will not be converted back to a Date object by JSON.parse
. Instead, JSON.parse
will return a string that can be used to create a new Date object using the Date
constructor. More details can be found in this mdn web docs reference.
Basics of save
The save
function accepts three arguments: the URL address specifying a filename, a reference to the data to be saved (again, in string form), and a callback which gets executed once the HTTP request completes. The filename can be specified without a complete web address; in this case, it is saved on the Pencil Code server, in the user's current folder. The callback accepts a single argument, which references a JavaScript object that provides information about the save process. A successful save will look something like this (generated using this script; line breaks added to the output below for readability):
{ saved: "/hacker/41-IO/Notes_SimpleSave.txt", mtime: 1661216467672, size: 21 }
In this example, the file is saved to the Pencil Code server at the time shown (in terms of milliseconds since 1970; pass the mtime
value to new Date()
to convert to a more recognizable, Gregorian-calendar date value), using 21 bytes of memory.
The save
function facilitates writing to the current account on the Pencil Code server. For this function to execute successfully, the user must currently be logged in. (Writing to other servers is beyond the scope of this lesson. It would require using either the jQuery
functions on which save
is based or some other tool; see the Technicalities section of this document for some further details).
A final note, of warning: save
will overwrite an existing file on the server, without warning. So be careful when using this function, or you can unintentionally lose a file!
Notes to activities
The lesson activities explore different aspects of the scope and timing issues described in the notes above. With respect to scope, all three activities in the The I in I/O! lesson involve straightforward use of the load
function. CodingInstructor and DadJokes can easily be solved with either await
/defer
or explicit use of CPS. The Asynchronicity program, in contrast, is specificially designed for use of load
without await
/defer
.
Several activities are designed to provide students the opportunity to work through the details of parsing text input, such as the lesson's DadJokes activity and also Additional Activities such as PiSpiral and GoogleSheetsGraph. Use of JSON is recommended for the activities in the The O in I/O! lesson. This makes sense, as students are creating the data stores themselves and they therefore can choose the most convenient format for later re-importing it (i.e., use JSON).
The math behind the spiral in PiSpiral is admittedly tricky to derive. Students should be encouraged to try other designs, such as listing characters on subsequent lines (perhaps with each line using a smaller and lighter font).
Much can be done to extend the Questionnaire concepts, to practice skills beyond the assigned task. For example, suggest creating graphical displays of the resulting data. One limitation to this approach for the survery is that responses can only be recorded if the user is logged in as the owner of the pencil code account. A more robust solution would require using other programming tools, such as PHP, to enable us to record values in a single file on the server for anonymous users.
Additional activities
- PiSpiral: Like many computer systems, JavaScript stores values of type Number using the double precision floating point format. As a result, the built-in variable Math.PI equals
3.141592653589793
. While sufficiently accurate for most purposes, sometimes we want more! One option is to read in a more precise decimal representation of Pi from a file. In this program, read in the data in this file and use these values to create some sort of pi-art, such as a spiral, a snake, or anything else you can come up with. GoogleSheetsGraph: To import data from a google sheets file, you must first set the permissions to of the doc to "Anyone with the link". Then modify the link so that it instructs Google to export the data as a comma-separated values (CSV) file. Do this by replacing
/edit?usp=sharing
at the end of the link to/export?format=csv
. Using this URL, the data can now be imported into your program usingload
Import and process the World Population data in this Google Sheets file; or import work with a comparable set of source data of your own.
- Vader: The data in https://hacker.pencilcode.net/edit/41-IO/Vader.data give the coordinates needed to draw a picture of an infamous Star Wars character. The data are in JSON format, so when you import them, use
JSON.parse
to convert them into their original form, which is an array of arrays of arrays. The outermost array contains arrays of coordinates that you should use to draw the picture. Jump to the first coordinate in each of these arrays and then usemoveto
to draw a line to each subsequent coordinate in that array. - Encryption: Sometimes the data we want to save needs to be kept confidential. One tool to protect privacy is data encryiption...
Beyond the lesson
Behind the scenes: XMLHttpRequest
The goal of reading and writing files is conceptually simple. However, behind the scenes the task involves a nuanced choreography between different systems and system resources, involving various hardware and software issues at different layers and using different protocols. And there are safety considerations, including built-in features meant to protect from harm but which complicate the task of legitimate data transfers. As a result, I/O in practice can be tricky to execute.
The "HTTP" we see in all complete web addresses stands for HyperText Transfer Protocol). It is a set of rules designed to enable communications between computers—formally, between a client (your computer) and a server (the computer hosting the web page you want to visit). For example, to visit a web address, the browser sends a request to the server, which in turn sends a response that contains the data needed to render the web page.
The traditional JavaScript tool that facilitates accesing external files is the XMLHttpRequest
object. XML stands for Extensible Markup Language, but that name is misleading. The name is simply a holdover from earlier variants of JavaScript, when a file format known as XML was dominant. (Nowadays, JSON is much more common.) XMLHttpRequest
can be used on any data, not only data in XML format.
Pencil Code uses XMLHttpRequest
objects for all its I/O needs, albeit indirectly. In the jQuery-turtle.js source code, one will find references to jQuery.ajax
(a.k.a., $.ajax
), a jQuery tool which, among other things, obviates the need to worry about browser-based differences in AJAX implementations. Without jQuery you would have to write extra code to test for different browsers, and code your script to adjust accordingly.
The details of loading or saving data with pure JavaScript are beyond the scope of this lesson. A simple, annotated example of loading data is provided in this script.
AJAX
AJAX is a technique for accessing web servers from a web page, described on this mdn web docs reference. As mdn notes, AJAX is not a technology in itself, but rather an approach to using a number of existing technologies together, including HTML, CSS, JavaScript, and most importantly the XMLHttpRequest
object. AJAX facilitates making HTTP requests without reloading the whole page. The acronym AJAX originated from "Asynchronous JavaScript and XML". However, AJAX is now just a term, not an acronym, as the original meaning no longer applies: AJAX need not function anynchronously and it can be used to transport data as plain text or as JSON text.
Client-side browser storage
Since the early days of the web, site have used cookies to store information to personalize user exerpience on websites. Cookies have declined in popularity, but they are still commonly used to store data related personalization and state, e.g., things like session IDs and access tokens. The Pencil Code site provides a good example. As demonstrated in this script, Pencil Code uses cookies to save login information, a unique user id, and the most recent file used. The example script also explores some other features of working with cookies (creating, modifying, deleting, etc.). For more details to working with cookies via JavaScript, see this w3schools reference.
More modern and convenient resources are provided by the the JavaScript Web Storage API and IndexedDB API. We will only consider the former here. The Web Storage API is a set of mechanisms that enable browsers to store key-value pairs with no expiration date, meaning that the stored data will persist even after the browser window is closed. The key-value pairs are accessible via the global, read-only variable localStorage
.
Local storage values are created (or modified), accessed, and deleted using the localStorage
methods setItem
, getItem
, and removeItem
. The setItem
method takes two arguments, both the key and the associated value, e.g., localStorage.setItem('high score','175')
. The other two methods take only a single argument, identifying the key.
Note that localStorage can only store strings, so data of non-string data types must be converted before they can be passed to the setItem
method. This conversion process, technically termed serializing the data, is facilitated using the JSON.stringify
and JSON.parse methods.
Examples of using local storage are provided in this script. As the script demonstrates, Pencil Code uses localStorage
for several purposes. For example, it maintains a log of the entries you enter into the test panel under the key _loghistory
. Pencil Code also caches complete programs that you have worked on in your space. Each program is saved as its own key-value pair, under a key with the format "backup:directory/filename". For example, on my laptop, "backup:01-DotArt/Bullseye"
is the key that links to a JSON string which when parsed yields the following object:
{ file: "/hacker/01-DotArt/Bullseye", data: "speed 2\n\ndot black, 150\ndot white, ... ht()\n", auth: true, mtime: 1585152933787, mime: "text/x-pencilcode;charset=utf-8" }
The value corresponding to data
is abridged above (shown by ellipsis) for display purposes; it actually includes the code for the entire program. Recall also that \n
is the escape character to record line breaks. mtime
is the number of seconds since 1970, a value which can be converted to the current date and time using new Date()
. Note that in Pencil Code, the JSON code for any program can be viewed by replacing edit in the file URL to load, e.g., https://hacker.pencilcode.net/load/01-DotArt/Bullseye.)
Deleting a file on the Pencil Code server, programmatically
To delete a file on the Pencil Code server using code, pass undefined
instead of a data reference when calling save to delete a file (see Snippets1Save):
# THIS DELETES THE FILE fileName save fileName, undefined, (xhr) -> see xhr
The data returned from the callback is of the format .
{deleted: "userID/folderName/fileName"}
What can go wrong
Misplaced comma
The syntax of await/defer when using save and load is complicated by the nature of the arguments:
Correct:
await load("Stormtrooper.data", defer(data))
Incorrect
await load("Stormtrooper.data"), defer(data)
Remember the logic: load
takes two arguments, the URL of the source data and a callback. defer(data)
creates the callback that is passed to load
, and that callback works in conjunction with await
to force the program to wait out the completion of the asynchronous process.
Files don't save, but no error message
You must be logged into your Pencil Code account for save
to succeed. Calls to save
when not throw an error (i.e., the program will not crash), and no warning will appear. However, the arg passed to the callback will take on the following value:
{error: "Password protected.", needauth: "key"}
This provides a good illustration of why it is a good idea to pass the optional callback to save
and use it to verify that the function executed as desired.
Failure to write to a remote server
The Pencil Code save
function facilitates saving files to the current account of the Pencil Code server. It cannot be used to save to remote servers. For such purposes, the JavaScript and/or jQuery tools that underlie the save
function must be used. However, such usage is beyond the scope of this course.
Pedagogy
I/O exposes students to a wide array of computer programming concepts, which can quickly overwhelm them. The issues of variable scope and timing are already enough technical content to maximize students' cognitive load. Even when relying on resources such as jQuery.ajax
, which remove much of the complexity of regular AJAX (by eliminating cross-browser differences in syntax), students must simultaneously juggle other concepts that are likely new to them. These include issues such as file types and formats, and the required data manipulation required to parse inputted text. The scaffolding provided by the Pencil Code load
and save
functions is extremely valuable to students first delving into I/O. The Pencil Code I/O function provide a gentler introduction, from which students can expand to less scaffolded tools, such as $.ajax
.
Technicalities
Calls to save
/load
involving .json
files
As a convenience feature, the Pencil Code load
and save
functions will automatically convert imported and outputed data to and from JSON format, if the filename has extension .json
. This obviates the need to convert data to a string using JSON.stringify
before output to file with save
and for parsing the read from a files read in with load
with JSON.parse
.
This lesson discourages the use of this feature, primarily because it obscures important aspects of the I/O process. Additionally, inconsistent use of this feature can lead to confusing bugs. For example, if JSON.stringify
gets called more than once on the same input, additional escape characters (such for quotes or slashes) can become embedded in the string.
jQuery.ajax
(and higher-level alternatives)
Writing regular AJAX using the XMLHttpRequest
object can be a bit tricky, because different browsers have different syntax for AJAX implementations. This means that you will have to write extra code to test for different browsers. As with most cross-browser headaches, jQuery provides a single interface that, for most purposes, takes care of the browser-specific technicalities for us. In short, jQuery lets us write AJAX functionality with only one single line of code, using jQuery.ajax
.
$.ajax
is a powerful and flexible tool; details are provided at api.jquery.com. It is also the resource upon which Pencil Code's load
and save
functions are built. However, even professional web programmers often find it cumbersome. To simplify use when all that is needed is a call to the function with the most common options, jQuery provides several higher-level alternatives for common tasks. For example, $.get
and $.getJSON
are two simplified alternatives used to load data and $.post
is a tool to save files. But at root, each of these functions represents a call to the underlying $.ajax
function.
Some examples illustrating the use of $.ajax and its higher-level alternatives can be found in here.
The structure of the Pencil Code server
Pencil Code provides this description of the workings of the Pencil Code server. An important takeaway from this documentation is that what we see in the Pencil Code editor generally isn't what is actually stored on the server. That is because files on the server include metadata, that is, additional data about the data. As an example, consider the simple script saved under the hacker
account, as viewed in the Pencil Code editor:
As the Pencil Coder server documentation notes, to see what is actually saved on the server, change edit
in the file's URL,
https://hacker.pencilcode.net/edit/41-IO/Notes_MetadataExample
to load
. The file's full content are now revealed:
{ "file":"/hacker/41-IO/Notes_MetadataExample", "data":"speed 10\nfor [1..4]\n fd 100\n dot blue, 100\n rt 90\n", "auth":true, "mtime":1671042737053, "mime":"text/x-pencilcode;charset=utf-8" }
When we use load
to read files from the Pencil Code server, it strips the metadata, which simplifies the process for novice coders. However, it can be very useful to gain access to the metadata. The function $.ajax
, as well as its derivatives (e.g., $.get
) and also use of the XMLHttpRequest
object directly, do return the full metadata.
The mtime
property records the "modified timestamp", the last time the data the contents of a file were modified. This number can be converted to a date by passing it to the Date
constructor; i.e., see new Date(1671042737053)
yields Wed Dec 14 2022 12:32:17 GMT-0600
.
The mime
property refers the the media type, i.e., it indicates the nature and format of the file. It is called MIME because its roots as a transfer protocol (described below). text/x-pencilcode
indicates it is a custom type, specifically, a file of code stored on the pencilcode server. Files with spedific file extensions (e.g., csv
or jpg
) will have standard MIME types (text/csv
or image/jpeg
). A reference of MIME types is provided in this MDN web docs reference.
A second feature of the Pencil Code server is that it facilitates opening files on remote servers via a Proxy Server feature. Once again, calls to the load
function automatically invoke this feature. For data imports using $.ajax
, one simply appends the path with /proxy/
. An example is provided in this annoted program. See the Pencil Code documentation for more details.
Internet transfer protocols
A protocal is a set of rules or procedures for transmitting data between electronic devices, such as computers. The XMLHttpRequest
object makes use of the HTTP protocal.
HTTP is one of several internet transfer protocols. Others include the following:
- FTP (File Transfer Protocol): used for transferring files
- POP (Post Office Protocol), IMAP (Internet Message Access Protocol), and SMPT (Simple Mail Transfer Protocol): standard protocols for transmitting email messages.
Additionally, the MIME (Multipurpose Internet Mail Extensions) file format is used in conjunction with these protocols to allow transmission of a broader range of character sets via the above-listed protocols (which initially relied on ASCII) and the the exchange of different kinds of data files besides plain text, including audio, video, images and application programs. A reference of common MIME types is provided in this MDN web docs reference.
Same Origin policy
The same-origin policy restricts scripts contained in a web page from accessing data in a second web page unless they share the same origin. It’s a security measure enforced by browsers that restricts malicious websites from running JavaScript inside of websites they don’t own.
Thankfully, Pencil Code has written the load
function in a way that circumvents this limitation for us. This solution relies in part on using a proxy server. See this article for additional details. Note that to make use of Pencil Code's proxy server when using one of the built-in or jQuery I/O functions (e.g., $.get
or $.ajax
). This script provides a simple example of using the proxy server when calling $.ajax
from the Pencil Code server .
Serialization
Serialization
is the process whereby an object or data structure is translated into a format suitable for transfer over a network. The load
and save
functions both make use of HTTP to store and retreive data from file, which is why we need to format the data we send or receive as strings. JSON.stringify
and JSON.parse
are tools that convert (a.k.a., "serialize") non-string data, notably objects, into string form, and convertthem back again.
Successor to XMLHttpRequest
(and hence to $.ajax
)
Pencil Code (and jQuery-turtle.js) was built when XMLHttpRequest
objects where the sole tool for importing data. This has been replaced with a more modern method, fetch
. fetch
will be introduced in a future lesson on promises.
FileReader
The JavaScript FileReader
object lets web applications asynchronously read the content of files stored on the user's computer, including images as well as text. This example script illustrates using FileReader
to upload a text or image file.
Note that there are some additional steps that one must take after uploading image data before displaying the image onscreen. As noted in the Notes to the Images! lesson, sprite constructors and the wear
function do not accept base-64 data-URI data directly. A workaround involving the img
function was described in the notes to the Images! lesson; more advanced methods are explored in the programs in this Pencil Code folder.
An additional challenge arises when attempting to save image data to the Pencil Code server. You cannot directly save image file info with typical extensions (png, jpg, gif, svg) using the Pencil Code save
function, as the resulting file will not subsequently be recognized by Pencil Code as an image . However, there are two valid approaches. One option is to save the raw (URI) data uploaded with the FileReader
object and save that to the Pencil Code server using save
. An illustration of this apporach is provided in this script. You can then later retrieve this data using load
and display it as an image using img
. The alternative approach is to to add an image displayed using img
to a sprite's canvas using wear
, and then save the image using the sprite's saveimg
method. An illustration of this alternative approach is provided here.