0

I'm developing a client-side library for a web-based service and I'm having some issues with setting an object variable and later retrieving it.

Here is the start of the library

var QuickBase = function(username, password, apptoken, realm) {

    this.username = username;
    this.password = password;
    this.apptoken = (typeof apptoken === "undefined") ? '' : apptoken;
    this.realm = (typeof realm === "undefined") ? 'www' : realm;
    this.ticket = '';
    this.dbid = '';
    this.payload = '<qdbapi>';

    this.init = function() {
        var self = this;

        this.authenticate(this.username, this.password, null, null, function(data) {
            var errcode = $(data).find('errcode').text();
            if(errcode > 0)
                throw new Error($(data).find('errtext').text());

            self.ticket = $(data).find('ticket').text();
        });
    }

    this.setBaseUrl = function() {
        var endpoint = this.dbid == '' ? 'main' : this.dbid;
        this.baseUrl = 'https://' + this.realm + '.quickbase.com/db/' + endpoint;
    }

    this.transmit = function(method, callback) {
        this.setBaseUrl();

        if(this.apptoken)
            this.payload += '<apptoken>' + this.apptoken + '</apptoken>';

        if(this.ticket)
            this.payload += '<ticket>' + this.ticket + '</ticket>';

        this.payload += '</qdbapi>';

        console.log(this.payload);

        $.ajax({
            url: this.baseUrl,
            type: 'POST',
            data: this.payload,
            dataType: 'xml',
            headers: {
                'Content-Type': 'application/xml',
                'QUICKBASE-ACTION': method
            },
            success: callback
        });

        this.payload = '<qdbapi>';
    }

    this.addSettingsToPayload = function(settings) {
        for(var key in settings) {
            this.payload += '<' + key + '>' + settings[key] + '</' + key + '>';
        }
    }

    this.authenticate = function(username, password, hours, udata, callback) {
        this.payload += '<username>' + username + '</username>';
        this.payload += '<password>' + password + '</password>';

        this.payload += (typeof hours === "undefined") ? '' : '<hours>' + hours + '</hours>';
        this.payload += (typeof udata === "undefined") ? '' : '<udata>' + udata + '</udata>';

        this.transmit('API_Authenticate', callback);
    }

And here's the use case:

var username = 'foo', password = 'bar', token = 'footoken', realm = 'foorealm';

window.qb = new QuickBase(username, password, token, realm);
$.when(qb.init()).then(function(){ 
  console.log(qb); // shows the object with ticket set
  console.log(qb.ticket); // empty
  qb.doQuery(); // breaks because internal this.ticket is empty
});

So my question is why is qb.ticket not being set and not available in future function calls?

In addition, is there a way that I don't have to wrap .init() in .when?

Basically, init sets the ticket that all future API methods will need. If I just call qb.init() and then qb.doQuery(), there is no guarantee init() will have finished - but if I use .when, won't that mean all future method calls would need to be inside of the .then callback? That seems ugly.

doremi
  • 14,921
  • 30
  • 93
  • 148

2 Answers2

0

$.when expects a promise object. If it doesn't get one it will execute the callback immediately. You have to return the promise from the $.ajax call, or create your own.

But since you are only working with one promise you don't even need when. You could do:

qb.init().done(qb.doQuery.bind(qb));

Example:

var QuickBase = function(username, password, apptoken, realm) {
    // ...

    this.init = function() {
        var self = this;

        return this.authenticate(this.username, this.password, null, null).done(function(data) {
            var errcode = $(data).find('errcode').text();
            if(errcode > 0)
                throw new Error($(data).find('errtext').text());

            self.ticket = $(data).find('ticket').text();
        });
    }

    this.transmit = function(method) {
        // ...

        var promise = $.ajax({
            url: this.baseUrl,
            type: 'POST',
            data: this.payload,
            dataType: 'xml',
            headers: {
                'Content-Type': 'application/xml',
                'QUICKBASE-ACTION': method
            }
        });

        this.payload = '<qdbapi>';
        return promise;
    }

    this.authenticate = function(username, password, hours, udata) {
        // ..
        return this.transmit('API_Authenticate');
    }
}

As alternative, you can just make .init accept a callback:

this.init = function(callback) {
    var self = this;

    this.authenticate(this.username, this.password, null, null, function(data) {
        var errcode = $(data).find('errcode').text();
        if(errcode > 0)
            throw new Error($(data).find('errtext').text());

        self.ticket = $(data).find('ticket').text();
        callback();
    });
};

and then:

qb.init(function(){ 
  qb.doQuery();
});
Felix Kling
  • 795,719
  • 175
  • 1,089
  • 1,143
  • But you completely killed the callback that transmit accepted as an arg and used to call? I think I need this so that the user doQuery, for example, could process the return data when it returns don't I? – doremi Apr 26 '13 at 00:22
  • I did? Where? You mean in `transmit` or `authenticate`? You don't need the `callback` parameter anymore because you are returning a promise. – Felix Kling Apr 26 '13 at 00:23
  • What the heck is a promise? First time I've heard of this concept. – doremi Apr 26 '13 at 00:25
  • Then I wonder why you used `$.when` in the first place. Have a look at http://api.jquery.com/category/deferred-object/, http://api.jquery.com/deferred.promise/ and maybe http://stackoverflow.com/q/6801283/218196. – Felix Kling Apr 26 '13 at 00:25
  • It was an attempt (albeit poor) at solving the underlying issue. With this approach, how will the user know when say qb.doQuery returns if they can't pass in a callback function on success? An example would be helpful. – doremi Apr 26 '13 at 00:26
  • The first sentence in the `$.when` documentation says: *"If a single Deferred is passed to `jQuery.when`, its Promise object (a subset of the Deferred methods) is returned by the method."*... that should have made you think about it ;) – Felix Kling Apr 26 '13 at 00:27
  • If the users knows that `doQuery` returns a promise, he can use `.done` or `.fail` to attach callbacks. I really recommend to have a look at deferreds/promises, then everything should become clear. There is lots of info about it on the web and SO. – Felix Kling Apr 26 '13 at 00:28
  • I see. Is there a way to do this such that I don't need to depend on jQuery for attaching callbacks. Something closer to what I was originally trying to do which was to allow the methods to accept a callback function and execute it on completion? – doremi Apr 26 '13 at 00:30
  • Sure, just let `.init` accept a callback. See my update. Promises are much more flexible though, it's definitely worth it to make yourself familiar with them. – Felix Kling Apr 26 '13 at 00:32
  • I just don't like that my users will need to do qb.init().done(qb.doQuery.bind(qb)) instead of just qb.init(); It's not intuitive and will make it a bit more difficult for my audience of technically-inclined business users. I will indeed learn about promises. Thank you for all of the info and the banter. – doremi Apr 26 '13 at 01:35
0

Easiest approach is probably to return a promise from init(), as follows :

this.init = function() {
    var self = this;
    var dfrd = $.Deferred();
    this.authenticate(this.username, this.password, null, null, function(data) {
        var errcode = $(data).find('errcode').text();
        if(errcode > 0)
            throw new Error($(data).find('errtext').text());
        self.ticket = $(data).find('ticket').text();
        dfrd.resolve();
    });
    return dfrd.promise();
}

Then :

window.qb = new QuickBase(username, password, token, realm);
qb.init().then(function() {
  console.log(qb);
  console.log(qb.ticket);
  qb.doQuery();
});
Beetroot-Beetroot
  • 18,022
  • 3
  • 37
  • 44