EasyFiles

A library for BYOND by Lummox JR

Version 1.0

EasyFiles is a library designed to let you choose a file to load or save on the host server via a popup browser window, instead of using a clunky input() box. It does this by creating a new datum, /filedialog, and adding commands to client/Topic() to use it. When a file is selected, a proc of your choice is called with the result.

Using EasyFiles

When you use EasyFiles for file selection, the entire process no longer occurs in a single proc. Instead, some user action like a verb creates the /filedialog datum and tells it all the information, then the datum will call a proc to complete the task. This is how a callback proc might be used:

mob/verb/Load_Character()
    // create a new dialog with new(A,B)
    // it will call A.B() when a file is picked
    var/filedialog/F = new(usr, /mob/proc/LoadSaveChar)

    F.msg = "Choose a character."   // message in the window
    F.title = "Load Character"      // popup window title
    F.root = "chars/"               // directory to use
    F.saving = 0                    // saving? (false is default)
    F.default_file = "[key].sav"    // default file name
    F.ext = ".sav"                  // default extension

    F.Create(usr.client)            // now display the dialog

mob/proc/LoadSaveChar(filename, saving)
    var/savefile/F = new(filename)
    if(saving)
        F["mob"] << src
        F["x"] << x
        F["y"] << y
        F["z"] << z
    else
        var/mob/M
        var/sx,sy,sz
        F["mob"] >> M
        F["x"] >> sx
        F["y"] >> sy
        F["z"] >> sz
        M.loc = locate(sx, sy, sz)
        if(loc) loc.Entered(M)
        del(src)

In this example, instead of calling usr.LoadSaveChar() right away with a filename, we create a new /filedialog and tell it to call that proc when it's ready. By picking a default directory of chars/ we've limited the choices to just that directory and any subdirectories. The player won't be able to pick out a character from one of the files in houses/, for example.

Creating a Dialog

To create your popup file selection dialog, you can use the regular /filedialog datum or you can override it. To use it normally, you have to set a few of the vars used by the datum after calling new(). But first, create the datum new():

filedialog/New(callback_src, callback_proc)

The arguments there are pretty straightforward. When a file is selected, it will be called like so:

callback_src.callback_proc(file,saving)

If you use null as the first argument, callback_proc will be treated as a global proc instead.

Creating the dialog will not display it. To do that you have to call Create(). But first, the dialog has to be set up by setting a few vars.

Basic vars

The var msg is a message displayed inside the popup box with instructions such as "Please choose a file." title is a title that will appear on the popup window, like "Load" or "Save".

root is the default save directory. If blank, then the game's main directory will be used. Otherwise this should end with a slash, like "monsters/". The user can't go up past this directory to its parent, so this can protect the game directory. You can also set cd, which is the starting directory, in case the user saved in a subdirectory and you wanted to remember their last choice.

Set default_file if you wish a default name choice for saving a new file. ext is the default extension of the new file, including a period in front, such as ".sav". Also you may optionally set filter, which is either an extension like ext or a list of possible extensions, and will limit what displays in the box to only those types of files. filter is very useful if you don't set root, because you can keep different file types in the main directory.

saving determines if this dialog is for saving files or not. This is normally a false value, which means loading. This value will be sent to the callback proc when a file is chosen. In a save dialog, if a filename is picked that already exists, the user will be asked whether they want to overwrite it.

Display vars

You also have the option of setting bodyspec which is a value like "bgcolor=#fff text=#000" to go in the HTML <body> tag. And there is another value, cssfile, which can be set to a stylesheet like 'mygame.css' (note the single quotes) which will be used in the popup. The popup uses two style classes you can define in your stylesheet: class=file and class=fileconfirm. The first one is used for files in the list and for the current directory. The second says how the filename should appear when asking a question about whether to overwrite it (if saving).

Finally, you may also change size from the default which is "320x320" to another value. This is of course the size of the popup box.

Displaying the dialog

When everything is set, call Create(client) to display the box. This will set the var owner to that client, for future reference, and it also sets the client's fdlg var which says which /filedialog is being displayed.

Custom Dialogs

It's even easier to use a file dialog if you set one up with a few vars in advance. Here's a simple example:

filedialog/quest
  root = "quests/"
  default_file = "quest.qst"
  ext = ".qst"
  filter = list(".qst", ".txt", ".sav")

  cssfile = 'rpg.css'

  New(client/C, save)
    if(!C) del(src)
    ..(C.mob, /mob/proc/LoadOrSaveQuest)
    if(save)
      saving = 1
      title = "Save Quest"
      msg = "Please choose a filename to save your quest."
    else
      title = "Load Quest"
      msg = "Please choose a quest to load."
    Create(C)

This custom dialog, /filedialog/quest, can be started with just two arguments: a client and whether it is saving. It sets up all the vars for you, and even calls Create() so there's no need to manually display it.

More Customization

You can also override some of the procs included in /filedialog to have greater control over the appearance of the popup box.

SendRscFiles() sends a copy of cssfile, if any, to the client before any HTML is displayed. If you want to include any images, now is the time to do it. The client who will see the popup box is owner, so send any images to the client like so:

SendRscFiles()
  ..()
  owner << browse_rsc('folder.png')
  owner << browse_rsc('file.png')

PageHead() and PageFoot() control how the top and bottom of the page will appear. Both return text which will be included in the final popup. Normally you won't want to change these, but if you do then calling ..() is usually a good idea.

PageHead()
  return ..() + "<center><img src=gamelogo.png width=200 height=100></center>\n"

PageLayout(filelist) returns text defining how most of the page will appear. This prints out msg and also calls the ListFiles(filelist) proc. By default this also creates a table so most of the text is nicely aligned in the popup window.

ListFiles(filelist) is a meaty routine that handles how a list of files is displayed. It calls DisplayDir() to display the directory, and then shows a list of files using DisplayFile(file) for each.

PageLayout(filelist)
  . = "<p>[html_encode(msg)]</p>\n"
  . += "<table border=0>[ListFiles(filelist)]</table>\n"

// 3-column layout
ListFiles(filelist)
  . = "<tr><th colspan=3>[DisplayDir()]</th></tr>\n"
  var/i=0
  for(var/F in filelist)
    if(!(i%3)) . += "<tr>"
    if(F || i==2) . += "<td>"
    else
      . += "<td colspan=[3-i]>"
      i = 2
    . += "[DisplayFile(F)]</td>\n"
    ++i
    if(!(i%3)) . += "</tr>"
  if(i%3)
    . += "<td colspan=[3-i]>&nbsp;</td></tr>"

The last value in filelist is null if this popup is for saving, which tells DisplayFile() to include a form asking for a new filename.

DisplayDir() returns HTML-formatted text which shows the current directory. By default it appears in boldface, but you can change that easily by changing this proc.

DisplayFile(file) returns the HTML for a link to click on a file so it may be selected. If the file argument is null, a button "New file" appears next to a text box where a new filename may be entered. By overriding this proc you could for example display an image next to each filename. The filename will end with a slash ("/") if it is a directory; you can use the IsDir(file) proc to test that.

CSS Files

If you supply a CSS file, you'll want to define classes in it for file and fileconfirm. Here's an example of how part of that file might look:

.file a, .fileconfirm {
  font: 12pt Verdana,Arial,Helvetica,sans-serif;
  text-decoration: none
  }
.file a:link, .file a:visited {color: black}
.file a:active {color: red}

In CSS you have to take care that you don't have another style conflicting with these of course, so you may have to use font-weight:normal or other style attributes to make your link look exactly right. If you want to tweak the way this appears, save a copy of your file dialog's source as a new HTML file, and play around with the CSS until the file you saved looks right.