caching submitted form while we are offline?

M

Matìj Cepl

Hi,

I wrote a jetpack (http://bit.ly/rhbzjetpack) for working at the Red Hat
Bugzilla. I am playing with the idea, whether it wouldn't be possible to
add offline capability to my bugzilla processing (there is no Internet
connection in the Prague metro ;)). Would it be possible to catch
"submit" event with addEventListener, and if we are offline, store the
request (somehow) to the local storage (jetpack.simple.storage or DOM
Storage or ???), and when switching online (or restarting Firefox when
online) pushing these requests (somehow) to the bugzilla.

Of course the foundation of this would be something like this:

------------------------------------------------------------------------

var win = jetpack.tabs.focused.contentWindow;

function newsubmit(event) {
var target = event ? event.target : this;

// do anything you like here
console.log('Submitting form to ' + target.action);

// call real submit function
if ( navigator.online ) {
this._submit();
} else {
reqQueue.push( <something> ); // HOW?
}
}

function submitQueue() {
reqQueue.forEach( submitItem ); // HOW?
}

// in the initialization part of the jetpack

win.addEventListener( "submit", newsubmit, false);
// If a script calls someForm.submit(), the onsubmit event does not
// fire, so we need to redefine the submit method of the
// HTMLFormElement class.
HTMLFormElement.prototype._submit =
HTMLFormElement.prototype.submit;
HTMLFormElement.prototype.submit = newsubmit;

win.addEventListener("online", submitQueue, false);

------------------------------------------------------------------------

Now the question is how to store the request in some form somewhere, and
when it is restored, how to submit it.

Any ideas? URLs for some related articles?

Thanks a lot for any suggestions,

Matìj

--
http://www.ceplovi.cz/matej/, Jabber: mcepl<at>ceplovi.cz
GPG Finger: 89EF 4BC6 288A BF43 1BAB 25C3 E09F EF25 D964 84AC

I didn't attend the funeral, but I sent a nice letter saying
I approved of it.
-- Mark Twain
 
T

Thomas 'PointedEars' Lahn

Matěj Cepl said:
I wrote a jetpack (http://bit.ly/rhbzjetpack) for working at the Red Hat
Bugzilla. I am playing with the idea, whether it wouldn't be possible to
add offline capability to my bugzilla processing (there is no Internet
connection in the Prague metro ;)). Would it be possible to catch
"submit" event with addEventListener,

Yes, but unnecessary. Use the `onsubmit' attribute.
and if we are offline, store the
request (somehow) to the local storage (jetpack.simple.storage or DOM
Storage or ???),
Yes.

and when switching online (or restarting Firefox when
online) pushing these requests (somehow) to the bugzilla.
Yes.

[...]
Now the question is how to store the request in some form somewhere, and
when it is restored, how to submit it.

Any ideas? URLs for some related articles?

You can store the form data in a cookie or Storage, and restore the form
data from it when applicable. You could make an non-modifying XHR request
to your server and handle it synchronously to see if you are online. You
need to assume you are online if XHR is not supported.


PointedEars
 
M

Matěj Cepl

Dne 16.12.2009 22:55, Thomas 'PointedEars' Lahn napsal(a):
Yes, but unnecessary. Use the `onsubmit' attribute.

According to
http://www.oreillynet.com/pub/a/network/2005/11/01/avoid-common-greasemonkey-pitfalls.html?page=3
XPCNativeWrappers (which is what jetpack gives me) have problems with
You can store the form data in a cookie or Storage, and restore the form
data from it when applicable. You could make an non-modifying XHR request
to your server and handle it synchronously to see if you are online. You
need to assume you are online if XHR is not supported.

Hmm, sounds interesting. I have in meantime dived a little bit more into
forms submission technology and I think I am getting some ideas now. It
will be fun to put it all together but it sounds doable. If I have
enough time, that is :(.

Best,

Matěj

--
http://www.ceplovi.cz/matej/, Jabber: mcepl<at>ceplovi.cz
GPG Finger: 89EF 4BC6 288A BF43 1BAB 25C3 E09F EF25 D964 84AC

Q: Is vi an easy editor to learn, is it intuitive?
A: Yes, some of us think so. But most people think that we are
crazy.
-- vi FAQ
 
T

Thomas 'PointedEars' Lahn

Matěj Cepl said:
Dne 16.12.2009 22:55, Thomas 'PointedEars' Lahn napsal(a):

According to
http://www.oreillynet.com/pub/a/network/2005/11/01/avoid-common- greasemonkey-pitfalls.html?page=3
XPCNativeWrappers (which is what jetpack gives me) have problems with
.on<event> attributes.

Those are event-handler *properties*; I was talking about the `onsubmit'
*attribute* instead:

<form ... onsubmit="...">
...
</form>

Anyhow, if you are writing a GreaseMonkey script, you need to use
addEventListener('submit', ...), indeed. You may also need to remove
already added listeners beforehand.
Hmm, sounds interesting. I have in meantime dived a little bit more into
forms submission technology and I think I am getting some ideas now. It
will be fun to put it all together but it sounds doable. If I have
enough time, that is :(.

Following the beta of a form serialization method that I am going to
distribute as free software (under the GNU GPL v3) as part of
httprequest.js, which should be enough to get you started:

/**
* Retrieves the data to send in the request, and optionally the request
* method, from an (X)HTML form. TODO: select[multiple] elements
*
* @param f : HTMLFormElement
* @param bUseFormMethod: optional boolean
* If <code>true</code>, the form's request method becomes the
* <code>HTTPRequest</code> object's request method. The default
* is <code>false</code>.
* @return boolean
*/
getDataFromForm: function(f, bUseFormMethod) {
var result = false, es, len;

if (f && (es = f.elements) && (len = es.length))
{
if (bUseFormMethod) this.method = f.method;

var aData = [];

for (var i = 0; i < len; i++)
{
var o = es;
if (o.name)
{
aData.push(esc(o.name) + "=" + esc(o.value != "" ? o.value : ""));
}
}

this.data = aData.join("&");
result = true;
}

return result;
},


HTH

PointedEars
 
T

Thomas 'PointedEars' Lahn

Thomas said:
Matěj Cepl said:
Dne 16.12.2009 22:55, Thomas 'PointedEars' Lahn napsal(a):

Hmm, sounds interesting. I have in meantime dived a little bit more into
forms submission technology and I think I am getting some ideas now. It
will be fun to put it all together but it sounds doable. If I have
enough time, that is :(.

Following the beta of a form serialization method that I am going to
distribute as free software (under the GNU GPL v3) as part of
httprequest.js, which should be enough to get you started:

[...]

And here is a better (but still improvable), production code from another
project that I have not backported yet:

/* ------------------------------------------------------------------ */

/**
* @param f : Form|HTMLFormElement
* @return string
*/
function serializeForm(f)
{
var gotSubmit = false;

/**
* @param o
*/
function serializeControl(o)
{
/* HTML 4.01: Controls that are disabled cannot be successful. */
if (!o.disabled)
{
/*
* If a form contains more than one submit button,
* only the activated submit button is successful.
* (here: the first one)
*/
var isSubmit = /(^|\s)(submit|image)(\s|$)/i.test(o.type);
if (!gotSubmit || !isSubmit)
{
if (isSubmit) gotSubmit = true;

/*
* For menus, the control name is provided by a SELECT element
* and values are provided by OPTION elements. Only selected
* options may be successful. When no options are selected,
* the control is not successful and neither the name nor any
* values are submitted to the server when the form is submitted.
*/
var m = /(^|\s)(select(-one)?|undefined)(\s|$)/i.exec(o.type);
if (m)
{
/* select-one */
if (m[3])
{
if (o.selectedIndex > -1)
{
items.add(o.name, o.options[o.selectedIndex].value);
}
}

/* select */
else if (m[2])
{
for (var i = 0, opts = o.options, len = opts && opts.length;
i < len; i++)
{
var opt = opts;
if (opt.selected)
{
items.add(o.name, opt.value);
}
}
}
}

/*
* All "on" checkboxes may be successful.
* For radio buttons that share the same value of the
* name attribute, only the "on" radio button may be successful.
*/
else if (!/(^|\s)file|reset(\s|$)/i.test(o.type)
&& !(/(^|\s)object(\s|$)/i.test(o.tagName) && o.declare)
&& !/(^|\s)(checkbox|radio)(\s|$)/i.test(o.type)
|| o.checked)
{
items.add(o.name, o.value);
}
}
}
}

var es = getFeature(f, "elements");
if (es)
{
var items = [];

items.add = function(sName, sValue) {
var s = esc(sName) + "=" + esc(sValue);
this.push(s);
};

if (!isMethod(items, "push"))
{
items.push = function() {
for (var i = 0, len = arguments.length; i < len; i++)
{
items[items.length] = arguments;
}
};
}

for (var i = 0, len = es.length; i < len; i++)
{
var e = es;

/*
* Elements with the same name create a NodeList object,
* however options of select objects are also indexable in Gecko.
*/
if (typeof e[0] != "undefined" && typeof e.options == "undefined")
{
for (var j = 0, len2 = e.length; j < len2; j++)
{
serializeControl(e[j]);
}
}
else
{
serializeControl(e);
}
}

return items.join("&");
}

return "";
}

/* ------------------------------------------------------------------ */

esc(), getFeature() and isMethod() are user-defined, of course, but you
probably get the idea..


PointedEars
 
M

Matěj Cepl

Dne 18.12.2009 17:52, Thomas 'PointedEars' Lahn napsal(a):
And here is a better (but still improvable), production code from another
project that I have not backported yet:

I will gladly improve it, if I can ... what's the license? You mentioned
in the previous post GPLv3, but I would much prefer (for many reasons)
MIT/X11. Can I, please, or do I have to rewrite it from scratch?

Thanks a lot for the ideas anyway,

Matěj

--
http://www.ceplovi.cz/matej/, Jabber: mcepl<at>ceplovi.cz
GPG Finger: 89EF 4BC6 288A BF43 1BAB 25C3 E09F EF25 D964 84AC

Our lives are spectacles of powerlessness.
-- Richard Rohr
 
T

Thomas 'PointedEars' Lahn

Matěj Cepl said:
Thomas 'PointedEars' Lahn napsal(a):

I will gladly improve it, if I can ... what's the license?

This will be probably be distributed under the GPLv3, too, as it will
replace what I had in mind for the next release for the most part.
You mentioned in the previous post GPLv3, but I would much prefer (for
many reasons) MIT/X11. Can I, please, or do I have to rewrite it from
scratch?

AFAIK, the MIT License without modifications is fully GPL-compatible. AIUI,
that means code under the former license can be combined with a program
licensed under the GPL without conflict, but not vice-versa. So, IIUC, if
you want to use this code with an MIT-licensed product you will need to
rewrite it from scratch.
Thanks a lot for the ideas anyway,

You are welcome.


PointedEars
 
D

David Mark

Following the beta of a form serialization method that I am going to
distribute as free software (under the GNU GPL v3) as part of
httprequest.js, which should be enough to get you started:

And here is a better (but still improvable), production code from another
project that I have not backported yet:

/* ------------------------------------------------------------------ */

/**
 * @param f : Form|HTMLFormElement
 * @return string
 */
function serializeForm(f)
{
  var gotSubmit = false;

  /**
   * @param o
   */
  function serializeControl(o)
  {
    /* HTML 4.01: Controls that are disabled cannot be successful. */
    if (!o.disabled)
    {
      /*
       * If a form contains more than one submit button,
       * only the activated submit button is successful.
       * (here: the first one)
       */
      var isSubmit = /(^|\s)(submit|image)(\s|$)/i.test(o.type);
      if (!gotSubmit || !isSubmit)
      {
        if (isSubmit) gotSubmit = true;

        /*
         * For menus, the control name is provided by a SELECT element
         * and values are provided by OPTION elements. Only selected
         * options may be successful. When no options are selected,
         * the control is not successful and neither the name nor any
         * values are submitted to the server when the form is submitted.
         */
        var m = /(^|\s)(select(-one)?|undefined)(\s|$)/i.exec(o.type);
        if (m)
        {
          /* select-one */
          if (m[3])
          {
            if (o.selectedIndex > -1)
            {
              items.add(o.name, o.options[o.selectedIndex].value);

This is a problem. You need to know if the value attribute is
present. Depends on the context, of course.

            }
          }

          /* select */
          else if (m[2])
          {
            for (var i = 0, opts = o.options, len = opts && opts.length;
                 i < len; i++)
            {
              var opt = opts;
              if (opt.selected)
              {
                items.add(o.name,opt.value);


Same here.
              }
            }
          }
        }

        /*
         * All "on" checkboxes may be successful..
         * For radio buttons that share the samevalue of the
         * name attribute, only the "on" radio button may be successful.
         */
        else if (!/(^|\s)file|reset(\s|$)/i.test(o.type)
                 && !(/(^|\s)object(\s|$)/i.test(o.tagName) && o.declare)
                 && !/(^|\s)(checkbox|radio)(\s|$)/i.test(o.type)
                 || o.checked)
        {
          items.add(o.name, o.value);
        }
      }
    }
  }

  var es = getFeature(f, "elements");
  if (es)
  {
    var items = [];

    items.add = function(sName, sValue) {
      var s = esc(sName) + "=" + esc(sValue);
      this.push(s);
    };

    if (!isMethod(items, "push"))

Is this the host method test function?
    {
      items.push = function() {
        for (var i = 0, len = arguments.length; i< len; i++)
        {
          items[items.length] = arguments;
        }
      };
    }

    for (var i = 0, len = es.length; i < len; i++)
    {
      var e = es;

      /*
       * Elements with the same name create a NodeList object,
       * however options of select objects are also indexable in Gecko.
       */
      if (typeof e[0] != "undefined" && typeof e.options== "undefined")


I don't care for that.
      {
        for (var j = 0, len2 = e.length; j < len2; j++)
        {
          serializeControl(e[j]);
        }
      }
      else
      {
        serializeControl(e);
      }
    }

    return items.join("&");
  }

  return "";

}

/* ------------------------------------------------------------------ */

esc(), getFeature() and isMethod() are user-defined, of course, but you
probably get the idea..

Mostly.
 
T

Thomas 'PointedEars' Lahn

David said:
Thomas said:
items.add(o.name, o.options[o.selectedIndex].value);

This is a problem. You need to know if the value attribute is
present. Depends on the context, of course.

You are mistaken. Keep in mind that this is supposed to serialize the form
as if it was submitted as usual.
[...]
for (var i = 0, opts = o.options, len = opts && opts.length;
i < len; i++)
{
var opt = opts;
if (opt.selected)
{
items.add(o.name, opt.value);


Same here.
No.
if (!isMethod(items, "push"))

Is this the host method test function?


It is a general one. I make no distinction there.
/*
* Elements with the same name create a NodeList object,
* however options of select objects are also indexable in Gecko.
*/
if (typeof e[0] != "undefined" && typeof e.options == "undefined")

I don't care for that.

You should. If the second `typeof' test would be omitted, execution would
enter the loop with a SELECT element in a Gecko-based UA.
{
for (var j = 0, len2 = e.length; j < len2; j++)
{
serializeControl(e[j]);
}
}
else
{
serializeControl(e);
}
[...]

PointedEars
 
D

David Mark

David said:
Thomas said:
items.add(o.name, o.options[o.selectedIndex].value);
This is a problem.  You need to know if the value attribute is
present.  Depends on the context, of course.

You are mistaken.  Keep in mind that this is supposed to serialize the form
as if it was submitted as usual.

I know what it is supposed to do and you are mistaken. What do you
make of these?

<option></option>
<option>test</option>
<option value=""></option>
[...]
for (var i = 0, opts = o.options, len = opts && opts.length;
i < len; i++)
{
var opt = opts;
if (opt.selected)
{
items.add(o.name, opt.value);

Same here.

No.


Yes. :)
Is this the host method test function?

It is a general one.  I make no distinction there.
/*
* Elements with the same name create a NodeList object,
* however options of select objects are also indexable in Gecko.
*/
if (typeof e[0] != "undefined" && typeof e.options == "undefined")
I don't care for that.

You should.  If the second `typeof' test would be omitted, execution would
enter the loop with a SELECT element in a Gecko-based UA.

I understand why, but don't like the way you did it.
 
T

Thomas 'PointedEars' Lahn

David said:
Thomas said:
David said:
Thomas 'PointedEars' Lahn wrote:
items.add(o.name, o.options[o.selectedIndex].value);

This is a problem. You need to know if the value attribute is
present. Depends on the context, of course.
You are mistaken. Keep in mind that this is supposed to serialize the
form as if it was submitted as usual.

I know what it is supposed to do and you are mistaken. What do you
make of these?

<option></option>

No problem here. The current value of the SELECT control would be the empty
string then, and that is what the `value' property of the HTMLOptionElement
implementation yields in all tested DOMs, that is:

- MSHTML 6.0 as of IE 6.0 on Wine for Debian
- Gecko 1.9.1.6 as of Iceweasel 3.5.6 (Firefox for Debian)
- WebCore 531.21.10 as of Safari 4.0.4 on Wine for Debian
- Presto 2.2.15 as of Opera 10.10 (for Debian/Ubuntu and on Wine for Debian)
- KHTML 4.3.4 as of Konqueror 4.3.4 for Debian

The test case can be found at
<http://PointedEars.de/scripts/test/dom/select-value>

Do you know a DOM where it does not work?
<option>test</option>

Bugger. In all tested DOMs the `value' property is "test" as specified in
HTML 4.01, section 17.6.1, *except* MSHTML 6.0 (luckily enough, all my
OPTIONs had a `value' attribute to date). Can't they do anything right? :-(
And is it the same in later versions? (No virtualization set up yet, and I
cannot reboot now.)

What would you do for a workaround, given that MSHTML 6.0 does not support
hasAttribute() (there) and getAttribute("value") == "" as if value="", and
select.value == ""? Parse option.outerHTML?
<option value=""></option>
<option value="">test</option>

No problem here. The value to be submitted is the empty string. That value
URL-encoded remains the empty string (which reminds me that I should
probably test for the empty string before calling esc()). Works in all
tested DOMs.

Do you know a DOM where it does not work?
/*
* Elements with the same name create a NodeList object,
* however options of select objects are also indexable in Gecko.
*/
if (typeof e[0] != "undefined" && typeof e.options == "undefined")

I don't care for that.
You should. If the second `typeof' test would be omitted, execution
would enter the loop with a SELECT element in a Gecko-based UA.

I understand why, but don't like the way you did it.

How would you do it, then?


PointedEars
 
M

Matěj Cepl

Dne 18.12.2009 20:46, Thomas 'PointedEars' Lahn napsal(a):
So, IIUC, if
you want to use this code with an MIT-licensed product you will need to
rewrite it from scratch.

OK, I will. Thanks.

Matěj
--
http://www.ceplovi.cz/matej/, Jabber: mcepl<at>ceplovi.cz
GPG Finger: 89EF 4BC6 288A BF43 1BAB 25C3 E09F EF25 D964 84AC

If Patrick Henry thought that taxation without representation was
bad, he should see how bad it is with representation.
 
D

David Mark

David said:
Thomas said:
David Mark wrote:
Thomas 'PointedEars' Lahn wrote:
items.add(o.name, o.options[o.selectedIndex].value);
This is a problem.  You need to know if the value attribute is
present.  Depends on the context, of course.
You are mistaken.  Keep in mind that this is supposed to serialize the
form as if it was submitted as usual.
I know what it is supposed to do and you are mistaken.  What do you
make of these?
<option></option>

No problem here.  The current value of the SELECT control would be the empty
string then, and that is what the `value' property of the HTMLOptionElement
implementation yields in all tested DOMs, that is:

- MSHTML 6.0 as of IE 6.0 on Wine for Debian
- Gecko 1.9.1.6 as of Iceweasel 3.5.6 (Firefox for Debian)
- WebCore 531.21.10 as of Safari 4.0.4 on Wine for Debian
- Presto 2.2.15 as of Opera 10.10 (for Debian/Ubuntu and on Wine for Debian)
- KHTML 4.3.4 as of Konqueror 4.3.4 for Debian

Yes, that was a control case.
The test case can be found at
<http://PointedEars.de/scripts/test/dom/select-value>

Do you know a DOM where it does not work?

That one? No. What else could the value be but an empty string?
Bugger.  In all tested DOMs the `value' property is "test" as specifiedin
HTML 4.01, section 17.6.1, *except* MSHTML 6.0 (luckily enough, all my
OPTIONs had a `value' attribute to date).

A good strategy is avoidance. ;) As mentioned, context is key here.
Can't they do anything right? :-(  

It would seem not.
And is it the same in later versions?  (No virtualization set up yet, and I
cannot reboot now.)

I believe the quirk is present in all IE versions (even 8 in
compatibility mode). I don't keep track of such things. Logic tells
you from looking at that example that it _could_ be ambiguous. That's
all I need to know. ;)
What would you do for a workaround, given that MSHTML 6.0 does not support
hasAttribute() (there) and getAttribute("value") == "" as if value="", and
select.value == ""?  Parse option.outerHTML?

It is a very rare case where you would need to resort to matching a
pattern in outerHTML. It is certainly not necessary in this case.
See the old CWR posts on the subjects or the related code in My
Library, or:-

http://www.cinsoft.net/attributes.html
No problem here.  The value to be submitted is the empty string.  That value
URL-encoded remains the empty string (which reminds me that I should
probably test for the empty string before calling esc()).  Works in all
tested DOMs.

Do you know a DOM where it does not work?

I leave DOM's to my scripts. ;)
/*
* Elements with the same name create a NodeList object,
* however options of select objects are also indexable in Gecko.
*/
if (typeof e[0] != "undefined" && typeof e.options == "undefined")
I don't care for that.
You should.  If the second `typeof' test would be omitted, execution
would enter the loop with a SELECT element in a Gecko-based UA.
I understand why, but don't like the way you did it.

How would you do it, then?

How would I tell whether it was a SELECT element? The tagName I
imagine. Also, you might want to try this out:-

var elForm = document.createElement('form');
elForm.appendChild(document.createElement('fieldset'));
window.alert(elForm.elements[0]);

....and looking closer, I don't see how your logic will work at all
with respect to NodeLists:-

var e = es;

What makes you think that will result in a NodeList (under any
circumstance?)
 
T

Thomas 'PointedEars' Lahn

David said:
Thomas said:
David said:
Thomas 'PointedEars' Lahn wrote:
David Mark wrote:
Thomas 'PointedEars' Lahn wrote:
/*
* Elements with the same name create a NodeList object,
* however options of select objects are also indexable in Gecko.
*/
if (typeof e[0] != "undefined" && typeof e.options == "undefined")

I don't care for that.
You should. If the second `typeof' test would be omitted, execution
would enter the loop with a SELECT element in a Gecko-based UA.
I understand why, but don't like the way you did it.
How would you do it, then?

How would I tell whether it was a SELECT element? The tagName I
imagine. Also, you might want to try this out:-

So we have something that is by definition a form control object to be
detected either by a RegExp match against its `tagName' property, or a type
test against its `options' property, whereas it is exactly that property
that later needed to determine its value(s). Yes, I do very much prefer the
latter over the former.
var elForm = document.createElement('form');
elForm.appendChild(document.createElement('fieldset'));
window.alert(elForm.elements[0]);

ACK, that displays "[object HTMLFieldSetElement]" in Iceweasel 3.5.6, so
another exclusion must be added to or before serializeControl() to avoid
"undefined=undefined" in the serialization, or worse.

It is of no further concern, though, as child form controls are still
represented as items of the `elements' collection of the form object:

var elForm = document.createElement('form');
var elFSet = document.createElement('fieldset');
elForm.appendChild(elFSet);
elFSet.appendChild(document.createElement("input"));
window.alert(Array.prototype.slice.call(elForm.elements, 0));

(That yields "[object HTMLFieldSetElement],[object HTMLInputElement]" in the
same runtime environment.)
...and looking closer, I don't see how your logic will work at all
with respect to NodeLists:-

var e = es;

What makes you think that will result in a NodeList (under any
circumstance?)


Look even closer. `es' is a reference to the form object's `elements'
collection, which makes `e' a reference to an element object or a NodeList
implementation, and `e[0]', if `e' refers to a NodeList implementation, the
first element of that NodeList.


PointedEars
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,744
Messages
2,569,482
Members
44,901
Latest member
Noble71S45

Latest Threads

Top