ODK 2.0 - Error when opening survey from tables index file

What is the problem? Please be detailed.
When trying to open a survey from an ODK tables home screen using:
odkTables.addRowWithSurvey(‘sdcstest’, ‘sdcstest’, null, null);

This is based on the survey access screen in the “hope” tables demo project.

ODK Survey opens, and then I get the error: “getFormFolder: formId does not begin with a letter and contain only letters, digits, or underscores!”.

I’ve reviewed the formId and tried a few different versions, I don’t see a problem (it is “sdcstest”) in either the index.html file, or the survey form definition.

Also, it isn’t clear to me what a “dispatch structure” is in this context, can someone clarify that?

What ODK tool and version are you using? And on what device and operating system version?
ODK Survey 2.0.2 rev 218
ODK Tables 2.0.2 rev 218

What steps can we take to reproduce the problem?
Open a survey using odkTables.addRowWithSurvey()

What you have you tried to fix the problem?
Changing formId
Forcing formId to String()

Anything else we should know or have? If you have a test form or screenshots or logs, attach here.

Hi Jason,

I think your issue is that you have 4 arguments and you need 5. If you look at the snippet from app/config/assets/hope.html:

    newClient.onclick = function() {
        odkTables.addRowWithSurvey(null,
            'femaleClients',
            'screenClient',
             null,
             null);
     }

The formatting hides it a bit, but the 5 args are actually (null, 'femaleClients, 'screenClient, null, null);

That first argument is the dispatchStruct, as you have noticed. The dispatchStruct is used to trigger the action callback and pass data to the callback function.

The dispatchStruct is any (stringified) JSON object you want. ODK doesn’t actually do anything with it, just checks if it is null.

  • If dispatchStruct is null, then there is no callback function triggered.
  • If dispatchStruct is anothing non-null, then a callback function is triggered.

I will provide example code for this workflow below. First, the function call:

var callSurvey() {
    var dispatchStr = JSON.stringify( { "myKey": "myVal"} );
    odkTables.addRowWithSurvey(dispatchStruct, tableId, formId, null, null);
}

Next we’ll see how to register for the callback. Assume the function “display” is the first function called when your js file loads.

var display = function() {

    // Register the callback function listener
    odkCommon.registerListener(function() {
        actionCBFn();
    });
   
    // Trigger the callback function in case the callback event occurred
    // before this loaded
    actionCBFn();

    // Start your actual load logic here
    odkData.getViewData(cbSuccess, cbFailure);
    ...
}

// Handles callback events from odkCommon.doAction calls.
// Note that most odkTables functions are wrappers for doAction calls
function actionCBFn() {
    // Check if there is an action queued to be handled
    var action = odkCommon.viewFirstQueuedAction();
    if (action === null || action === undefined) {
        // The queue is empty. No pending actions
        return;
    }

    var dispatchStr = JSON.parse(action.dispatchStruct);
    if (dispatchStr === null || dispatchStr === undefined) {
        // This should never happen, if the dispatchStruct is null
        // then this callback shouldn't be triggered
        console.log('Error: missing dispatch struct');
        return;
    }

    // Parse your dispatch struct here and perform your follow up
    var value = dispatchStr["myKey"];

    // Do some work with the retrieved value
    ...

    // Now that we are done with handling the callback, we need to
    //  remove it from the queue. This does not happen unless you
    //  explicitly remove it. All subsequent calls will queue up behind
    odkCommon.removeFirstQueuedAction();

}

As you can see, the value of the dispatchStruct is used exclusively by user level code. If you have more than one function triggering callbacks, it is usually a good idea to add some meta information about the caller to filter out where this callback came from.

One example in which this might be useful is to open a detail view of the row you just created in survey, after it is created. Another possibility would be to add another row to another table depending on responses from the first.

In the short term I’d recommend you pass a null dispatchStruct and see if it fixes your issue. Hopefully that helps, but please let me know if you continue to get this error.

EDIT: I wanted to add, if you want to trigger a callback but don’t need to send any particular values you can pass an empty (non-null) object as a dispatch struct and it will still trigger the callback.

-Jeff

Thanks Jeff, this is great.

You’re right, I lost track of the first argument on the addRowWithSurvey function. In fact I had lost it in my reference hope.html doc, no idea how. Going back to the original I see it is there, and adding it as null got everything working as expected. Thanks for helping with that.

I also appreciate the detailed explanation of dispatchStruct, I think I have a decent handle on it. Basically you are triggering this callback function that runs when any function with a dispatchStructure argument is called. Took me a bit to digest but I think I have a good enough grasp on it that I could try it when it seems to be the right tool for the job.

Jason

Yes that’s how it works. Sorry it was a lot of information to pack in. In answering your question I realized we didn’t have this feature documented anywhere, so I’ll be writing something up for that.

Glad to hear your problem is solved.