Manipulating local files

XForms processors are not required to support the file: protocol, but both formsPlayer and the Firefox extension do. The methods applicable to file: will usually be get and put. In formsPlayer, the approach is to prompt the user on any load or save of a local file, since this is obviously a very powerful technique.

In addition to the file: protocol, formsPlayer implements an experimental protocol called cookie:, which allows forms to load and save XML documents locally without the user needing to be prompted. This is deemed safe because the location for the file is only ever going to be in a sub-directory in the user's local store (i.e., home directory). There is therefore no way for a form to get out of this area and load or overwrite system files.

In addition, the URL for the sub-directory is based on the domain of the site being used, so there is also no way for a form from one site to get access to data from other sites (sites can share data across forms, though, which is very useful).

Typical uses are chaining forms together, where the output for one form is the input to the next, or saving state across an application. An example of the latter is illustrated in our Google Desktop app (to be added) which allows you to save searches across sessions.

Initialising a local file

Whether using file: or cookie:, a common requirement is the need to create the initial file on first use of a form. This is a little tricky, because if you initialise your instance using @src you have no way to detect that the file doesn't exist. The following technique is used by our RSS Reader side-bar (part of the formsPlayer install, and discussed at [to be added]):

  1. Create an instance that will hold the contents of the file. Give it an inline instance of some default values--in our case it contains a basic list of RSS feeds--rather than referring to the file using @src. In the rest of this example, the instance is referred to as i-list.
  2. Set up a submission to load the required document. In our case it's going to be a configuration file, loaded via the cookie: protocol, but it could be loaded via the file: protocol:
    <xf:submission
     id="sub-open-list"
     method="get"
     ref="instance('i-list')"
     action="cookie://feed-list.xml"
     replace="instance"
    >
      <!--
        If we fail to load the list, then this is probably
        the first time we've been run, so save the default
        list.
      -->
      <xf:action ev:event="xforms-submit-error">
        <xf:message level="modal">
          RSS Reader has failed to open your local data file.
          This may be because this is the first time you have
          used the RSS Reader. A default data file will be
          created which includes a small number of feeds.
        </xf:message>
        <xf:send submission="sub-save-list" />
      </xf:action>
    </xf:submission>
    

    Note the behaviour in the error handler (when the file doesn't exist)--we simply save our initial instance data, which has been placed inline in the instance i-list).

  3. Create an xforms-ready handler that triggers this get, i.e., which loads your local document:
    <xf:send submission="sub-open-list" ev:event="xforms-ready" />
    

    (Note that you have now effectively emulated the behaviour of @src, but with all the benefits of submission, such as having notification events as well as the ability to use instance data.)

    In normal operation, using sub-open-list will load the previously saved file, overwritting the inline instance data. But on first use there will be no file, and so the error handler will be run, and cause the inline instance data to be saved.

  4. Set up a submission to save the list to the file (you don't need to set a 'dirty flag' but it is quite useful):
    <xf:submission
     id="sub-save-list"
     method="put"
     ref="instance('i-list')"
     action="cookie://feed-list.xml"
     replace="none"
     encoding="ISO-8859-1"
     omit-xml-declaration="false"
     indent="true"
    >
      <xf:setvalue bind="dirty-flag" ev:event="xforms-submit-done">
        false
      </xf:setvalue>
    </xf:submission>
    

    Note that you would need these two submissions anyway, to load and save the file; the only additional bit we're adding here is the use of xforms-ready to invoke the submission that does the loading, and the use of xforms-submit-error on that load to invoke a save of our initial data.