qbdeferred

0.1.0 • Public • Published

QB Deferred

QB Deferred is a JavaScript library that implements an interface to the Quickbase API using jQuery's Deferred objects. It is entirely asynchronous. Most methods return jQuery Deferred objects that can be conveniently combined with pipe, when, fail, etc. It implements a higher-level API than what the Quickbase API itself provides. QB Deferred frees you from processing XML and concatenating strings to build Quickbase queries. Instead, QB Deferred lets you use plain old JavaScript objects and arrays. Often, a single call to a method of QB Deferred will implement an action that makes several calls to Quickbase.

Setup

To use QB Deferred, your page must include at least jQuery 1.5, and qbdeferred.js.

If your Quickbase application requires apptokens, include a call to setQBApptoken.

setQBApptoken("apptoken_here")

QBTable

new QBTable(dbid, fields)

All interaction with Quickbase is done through QBTable objects, each of which represents the interface to a single Quickbase table. The constructor takes the DBID and a object with key/value pairs for the fields of the table. The keys are names for the fields that can be used in method calls on the resulting QBTable instance – whenever you provide a field to QB Deferred, you can use either one of these names or a FID. The values give the FID and any conversions that should be applied when communicating with Quickbase. It can be in one of three forms:

  • It can be a number, to give a FID and apply no conversions.
  • It can be an object with a single key representing conversions to apply, with the FID as the value. The only keys supported are 'date' and 'numeric', for automatic conversion to and from JavaScript Date objects or Number values. Example: {date: 7}.
  • It can be an object with a fid property and optional inConverter and outConverter properties. inConverter and outConverter are functions. inConverter is used to automatically convert values coming from a Quickbase query, outConverter handles values going to Quickbase. Example: {fid: 9, outConverter: function (x) { return x / 1000 / 3600 }}

Note that you can still interact with Quickbase fields that you didn't specify in your fields object using a plain FID.

Example:

var qbtable = new QBTable(
  'DBID_HERE', {
    thebool: 6,
    thedate: {date: 7},
    thenumeric: {numeric: 10}
    thetext: 8,
    theduration: {fid: 9, outConverter: function (x) { return x / 1000 / 3600 }}
  }
)

theduration is a Duration field with its Value display set to Hours. The Quickbase API returns these values in milliseconds but expects them back in hours. The outConverter here allows you to simply work with milliseconds in all client code.

Deferred/Promise Objects

All of QBTable's methods return jQuery Promise objects. In jQuery's lingo, a Promise represent the future result of an operation, and a Deferred represents the requirement to fulfill a Promise. Most of Deferred's methods are available through a Promise, and the names are often used interchangeably. In this documentation, Deferred is used even when referring to Promise objects.

A Deferred is said to be "resolved" when its operation completes, and it is "resolved" with the result of that operation. If the operation does not complete successfully, it will instead be "rejected" with an error value. Deferred objects can be chained together, with values passing from one operation to the next.

Query Method

qbtable.query(query, clist, [slist], [options])

The QBTable's query method allows you to query QB records. query and clist are required, slist and options are optional.

Specifying a Query

You can use a string containing a Quickbase query for the query parameter. However, QB Deferred also supports a simpler, structured form for specifying queries. You can use an object, whose keys represent the fields to filter on and the values the filters to apply. The value can either be another object, whose key/value pairs are a query operator and right hand value, or a non-object to do only equality comparison.

Use the empty string to query all records.

Examples:

qbtable.query("{'3'.EX.'5'}OR{'3'.EX.'6'}", 3)
qbtable.query({thetext: "abc", thebool: true}, 3)
qbtable.query({thedate: {lt: new Date, gt: 0}, theduration: {xex: 0}}, 3)
qbtable.query('', 3)

Column List

The clist can either be a single field, or an array of fields.

Examples:

qbtable.query('', 3)
qbtable.query('', 'thetext')
qbtable.query('', [3, 'thetext', 'thedate'])

Sort List

The slist can either be a single field, or an array of fields. You can also use {desc: field} to specify descending sort order.

Examples:

qbtable.query('', 3, 3)
qbtable.query('', 3, {desc: 3})
qbtable.query('', 3, ['thetext', {desc: 'thedate'}])

Options

The options parameter is an object with optional key/value pairs. skip can be used to skip over a number of Quickbase records that would otherwise be returned. limit limits the query to returning a certain number of records.

Example:

qbtable.query('', 3, undefined, {skip: 10, limit: 10})

Return Value

The Deferred returned by the query method will be resolved with an array, with an element for each record. If you gave an array for the clist these elements will be objects. The objects will have a field for each column specified in the clist. If you specified a column by name, there will also be a field for the corresponding FID. If you used a simple value for your clist, the resulting array will be simply the values from that column.

Examples:

// Resolves with something like ['1', '2']
qbtable.query('', 3)
// Resolves with something like [{3: 1, thetext: 'abc', 8: 'abc'},
//                               {3: 2, thetext: 'def', 8: 'def'}]
qbtable.query('', [3, 'thetext'])

Count Method

qbtable.count(query)

The count method takes a query (see above) and returns a Deferred that resolves with the number of records that match that query.

Add Method

qbtable.add(obj)
qbtable.add(objs)

The add method takes an object or an array of objects that represent records to be created. Each key/value in an object is a field and the value to set it to. There is no requirement for each object to have the same set of fields.

If add is called with an array, the returned Deferred will be resolved with an array of new RIDs. Their order corresponds to the order of the objects in the array passed to add. If add is called with a single object, it will be resolved with the sole new RID.

Examples:

qbtable.add({thetext: 'abc'})
qbtable.add([
  {thetext: 'abc'},
  {thetext: 'def'},
  {thebool: true, thedate: new Date},
  {thetext: 'ghi', thebool: false, 17: 'fids work too'}
])

Update Method

qbtable.update(obj)
qbtable.update(objs)
qbtable.update(rid, obj)
qbtable.update(rids, obj)
qbtable.update(query, obj)

The update method can be called with either one or two arguments. With one argument, it works the same as the add method, but you must specify the RID in each object. With two arguments, you either provide a single rid, an array of rids, or a query (see above) to specify which records to update, and an object with the fields to update.

The Deferred returned by update is always resolved with undefined.

Examples:

qbtable.update({3: 5, thetext: 'abc'})
qbtable.update([{3: 5, thetext: 'abc'},
                {3: 6, thetext: 'def'},
                {3: 7, thebool: true, thedate: 0}])
qbtable.update(5, {thetext: 'abc'})
qbtable.update([5, 6], {thetext: 'abc'})
qbtable.update({thebool: true}, {thetext: 'abc'})

Delete Method

qbtable.delete(rid)
qbtable.delete(rids)
qbtable.delete(query)

The delete method takes a single argument, either a single rid, an array of rids, or a query (see above) to specify which records to delete. As a precaution, you cannot use an empty string as a query to delete all records, but see the deleteAll method below.

The Deferred returned by delete is resolved with the number of records deleted. Note that delete does not fail when records don't exist. If this matters, check the value it resolves with.

Examples:

qbtable.delete(5)
qbtable.delete([5, 6])
qbtable.delete({thebool: true})

DeleteAll Method

qbtable.deleteAll()

The deleteAll method deletes all records from a table.

The Deferred returned by deleteAll is resolved with the number of records deleted.

Controlling Flow

Operations can be strung together using deferred.pipe. This allows you to write asynchronous code similar to synchronous code, but with extra noise:

parent_table.query('', 3)
  .pipe(function (rids) {
    var objs = $.map(rids, function (rid) { return {rel_parent: rid} })
    return child_table.add(objs)
  })
  .pipe(function (rids) {
    var objs = $.map(rids, function (rid) { return {rel_parent: rid} })
    return grandchild_table.add(objs)
  })
  .pipe(function (rids) {
    return rids.length * 2
  })
  .pipe(function(numberCreated) {
    alert(numberCreated + " records created")
  })

This will create a child for every parent, and then a grandchild for every newly created child, then compute the number of created records, and finally inform the user of the number (The example is contrived, the last two steps could be combined). The pipe function takes a function that will be passed the resolved result of the Deferred. This function can then either return a new Deferred for the next operation to be piped onto, as the first two pipe calls above do passing along the Deferred from an add call, or it can return a value for the next operation, as above with return rids.length * 2.

Note that the jQuery docs say that pipe is deprecated in jQuery 1.8, and that you should use deferred.then instead. However, Quickbase is still using 1.7.2, which has a different then method than 1.8+. For the time being, it is best to use pipe with QB Deferred.

QB Deferred includes a monkey-patch to the pipe method and in jQuery 1.8+ the then method to make throwing exceptions inside the functions passed to them safe. Throwing an exception will result in a rejected Deferred.

Here is a more complex example:

var parentQuery = parent_table.query('', 3)
var branch1 = parentQuery
  .pipe(function (rids) {
    var objs = $.map(rids, function (rid) { return {rel_parent: rid} })
    return child_table1.add(objs)
  })
var branch2 = parentQuery
  .pipe(function (rids) {
    var objs = $.map(rids, function (rid) { return {rel_parent: rid} })
    return child_table2.add(objs)
  })
$.when(branch1, branch2)
  .pipe(function (rids1, rid2) {
    return rids1.length + rids2.length
  })
  .pipe(function(numberCreated) {
    alert(numberCreated + " records created")
  })
  .fail(function (err) {
    alert("Error: " + err)
  })

This will create a child for every parent in both child_table1 and child_table2, with the two creation operations running concurrently. After they complete, we join the two lines of work back together, and report either success or failure to the user.

We store the Deferred representing the result of querying the parent table in parentQuery, then pipe both creation operations off of it. We store the Deferred objects representing the result of these two operations into branch1 and branch2. The $.when function takes any number of Deferred objects, and returns a new Deferred object that resolves with the values of all the passed Deferred objects. Here, we use $.when to let us pipe new operations off of the results of both creation operations. The chain ends with a call to deferred.fail. The function passed to fail is called only if the Deferred fail is called on is rejected. Because of the way $.when and pipe work, a failure happening anywhere in the chain that precedes fail will result in the function being called. In the example, if querying the parent table failed, none of the functions passed to pipe would execute, but the one passed to fail would still receive the error.

PostQB Method and adding new methods

table.postQB(api_method, xml)

If you need to use a Quickbase API method that QB Deferred doesn't already have a specialized method for, the postQB method will let you create a request to Quickbase for any API method along with XML you provide. The API method should be given as a string, e.g. 'API_DoQuery'. The XML should also be provided as a string. QB Deferred will automatically include an <apptoken> element if needed and wrap the XML in a <qdbapi> element. You only need to provide the XML to put inside of the <qdbapi>.

QB Deferred will parse the incoming response from QB and check for any errors. If no errors occurred, it will resolve the Deferred with a JQuery object wrapping the XML. You can then use JQuery methods like find to extract data from the XML.

You can add new methods to QBTable objects by adding them to the prototype. As an example, here is the definition of the count method:

QBTable.prototype.count = function (query) {
  var data = '<query>' + this.makeQuery(query) + '</query>'
  return this.postQB('API_DoQueryCount', data).pipe(function (res) {
    return parseInt(res.find('numMatches').text())
  })
}

makeQuery is an internal method, it is used here to create the Quickbase query that we embed in the XML. The Deferred from postQB is piped to a function where the number of matching records is extracted from the response XML. This becomes the value that the Deferred returned by count resolves with.

Internals

The add and update methods sort records by which fields are being set, and for each group attempt to do an ImportCSV call. If a record contains a value that cannot be set through ImportCSV, e.g. a value with both , and " characters in it, then AddRecord or EditRecord will be used for that record instead.

The delete method uses PurgeRecords to quickly delete large numbers of records. It will look for groups of sequential RIDs and delete them with queries using GTE and LTE. For the remainder, it deletes records in batches using OR. With a single record delete will use DeleteRecord.

The multiple HTTP requests from add, update, and delete are done concurrently.

License

Copyright 2013 Stephen Marsh

Licensed under the Apache License, Version 2.0 (the "License"); you may not use these files except in compliance with the License. You may obtain a copy of the License at

http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License.

Readme

Keywords

none

Package Sidebar

Install

npm i qbdeferred

Weekly Downloads

1

Version

0.1.0

License

Apache-2.0

Last publish

Collaborators

  • stephendavidmarsh