The WISP (WIdget Server Page) is a simple file format that is used to develop Widgets.
This format is just a normal HTML web page that is augmented with a small number of wisp
tags.
When the server sees a file with the .wisp
extension,
it uses the data-injection logic to insert the data from the underlying SQLite DB into the page.
There are a variety of configuration options,
but for many use cases, you will need only a single wisp
tag.
<html> <head> <!-- other HEAD material --> <!-- Wisp tags should be directly enclosed within head tag --> <wisp/> </head> <body> <!-- normal page contents -->
This simple tag will import all of the DB tables corresponding to the Widget
(the Widget name is discovered by looking at the request URL).
It will also import all required JavaScript code to be able to use the Widget data.
So by using this single tag in a .wisp
file,
the main API methods such as W.getItemList(...)
and W.lookupItem(...)
will now work.
You can see more information about these methods on the Main Widget API page.
There are a variety of options you might want to use when including Widget data.
One important tool allows you to specific which tables you want to load.
The first option allows you to load data from another Widget.
By default, the server finds the Widget name from the HTTP request URL.
But sometimes you want to "cross-load" data,
which means to load data for Widget A in a page corresponding to Widget B.
To do this, you can use the widgetname
attribute:
<wisp widgetname="other"/>
tables
attribute.
The value for this attribute is a comma-separated list of table names that you want to load,
for example:
<wisp widgetname="other" tables="alc_log,money_log"/>
This command will load the alc_log
and money_log
tables from the other
Widget.
There are a couple of more advanced attributes you can use in conjunction with the wisp tags.
username
:: This allows you to import data owned by another Widgets account. If you use this option, please make sure that you have permission to load the data; if not, you will see an error message on page load. If you use this option you must also use a widgetname= argumentno_data
:: This option causes the table to be loaded with no data. This can be useful for situations where you only need to create data, not display it. For example, it is often good to load mailbox table with this option. okay_if_absent
:: Normally the WWIO server will throw an error on page load if a requested data source (user+widget+table) cannot be loaded. This option causes the server to simply skip tables that are not available, either because they do not exist, or because the user does not have permission to load them. This can be useful when a Widget is intended to be used by two types of users, but only one set of users has access to a particular data set.view_prefixes
:: This is a very advanced technique that causes data to be loaded from a SQL view instead of a table. To use this option, several requirements must be met. First, the view must have the exact same structure as the underlying table (this can be achieved by using SELECT * to define the view). Second, the name of the view must be written as some prefix plus the corresponding table. For example, if the table is mroutine_log
, then a view named recent_mroutine_log
can be loaded by using view_prefixes="recent"
The data from the view replaces the data from the table. This technique can be used to load smaller subsets of large tables, by using appropriate WHERE filters in the view definition.The WWIO server will only serve data to users with permission to view that data. The owner of a Widget has the option to assign read and write permissions to other accounts, this can be done in the Permission Control page of the Admin Console.
Every Widget page is has a specific, primary Widget DB associated with it,
and the WWIO server uses the request URL to determine this association.
If a user attempts to access a Widget page,
but does not have read permission for the primary Widget,
then the user is bounced to a log-in screen that informs them that they do not have the right permissions.
However, wisp
pages can also load data from secondary DBs using the
widgetname
tag mentioned above.
If a user can access the primary widget but not a secondary widget,
they will get an error message at page load time.
Thus, when using the cross-loading technique,
Widget developers should take care that users have the right permissions for the secondary DBs.
As of November 2023, the Wisp file format is the preferred way to develop Widget pages. Prior to this conversion, Widget pages were written using a strict subset of the Java Server Page format. This approach used a JSP tag that referred to a Java class:
<%= DataServer.include(request) %> <%= DataServer.include(request, "widgetname=sms_box&tables=outbox") %>
This old style of writing Widgets is still present in some places in the documentation.
The attribute names in the second argument of the DataServer.include
command correspond exactly to the Wisp tag attributes mentioned above.
This style is no longer allowed on the hosted WWIO platform for security reasons,
but users who are running their own instance of WWIO
may wish to use the old JSP for various purposes.
If you are interested about what the data-insertion logic is doing, you can take a look at the resulting page source using the View Source option in the browser.
<!-- Widget Core Asset Include --> <link rel="stylesheet" href="/u/shared/css/BasicWidget.css?modtime=1696701856211" /> <script src="/u/shared/jslib/TimeUtil.js?modtime=1702059034060"></script> <script src="/u/shared/jslib/LegacyApi.js?modtime=1702059034060"></script> <script src="/u/shared/jslib/WidgetUtil.js?modtime=1702059034060"></script> <script src="/u/shared/jslib/OptSelector.js?modtime=1702059034060"></script> <script src="/u/shared/jslib/WidgetMainApi.js?modtime=1702059034060"></script> <!-- End Core Asset Include --> <!-- Custom JS code for user --> <link rel="icon" type="image/x-icon" href="/u/testuser/MyFavIcon.png?modtime=1701993523963"></link> <!-- End custom JS code for user --> <script src="/u/testuser/autogenjs/links/LinkCateg__003.js"></script> <script> function __arr2DictLinkCateg(arr) { const d = {}; d['id'] = arr[0]; d['short_code'] = decodeURIComponent(arr[1]); d['full_desc'] = decodeURIComponent(arr[2]); d['is_active'] = arr[3]; return d; } [ [ 2, "Bills", "...", 1] , [ 3, "News", "...", 1] , [ 4, "Shopping", "...", 1] ].forEach(function(myrec) { LinkCategTable.register(W.buildItem("link_categ", __arr2DictLinkCateg(myrec))); }); // LiteTableInfo__LOAD_SUCCESS </script> <script src="/u/testuser/autogenjs/links/LinkMain__003.js"></script> <script>
There are a couple of sections here.
First, the server sends script
commands to instruct the browser to load various helper JS files.
These are the main utility classes that are required for the browser to interact with the server.
Then there are some user-generated files that are auto-included.
These files have modtime
attributes included in the URL,
which causes the browser to reload them, if new versions are sent to the server.
After this, there is the actual data objects - basically the contents of the DB tables,
expressed in JavaScript.
Finally there is the table-specific JS code that is created by the server by inspecting the SQLite table definitions.