Zafu: CouchDB and KendoUI (part 4)


In recent posts I’ve shown how to use KendoUI cascading ComboBox and map/reduce in CouchApps, Lets combine the two of them.

Populating KendoUI ComboBox

NOTE: I’m going to be using the database used in here, refer to that post for details.

Lets start defining the map and reduce JavaScript code using in this view:

// Map function
function(doc) {
    if (doc.type==="cities") emit(doc.state, doc.city );
}
// Reduce function
function(keys, values) {
    return true;
}

The map function emits pairs of records with a key that is the state and a value that is the city. This allows me to use the same map/reduce for getting the states (setting reduce and group to true) and for getting the cities in a state (by setting as key the name of the state).

Then I will start populating the first ComboBox with the name of the states:

$("#state").kendoComboBox({
    autoBind      :true,
    ignoreCase    :true,
    highlightFirst:true,
    delay         :100,
    placeholder   :"State",
    suggest       :true,
    dataSource    :{
        serverFiltering:true,
        transport      :{
            read:function (operation) {
                db.view("post20/getCities", {
                    success:function (data) {
                        var states = [];
                        var rows = data.rows;
                        $.each(rows, function (idx, value) {
                            states.push(value.key);
                        });
                        operation.success(states);
                    },
                    error  :function (status) {
                        console.log(status);
                    },
                    group  :true,
                    reduce :true
                });
            }
        }
    }
}).data("kendoComboBox");

In the success function what I do is extracting the keys retrieved that are the name of the states.

Populating the cities uses the same view but disables reduce by setting it to false, In addition I sort the results in order to get the cities order alphabetically.

$("#city").kendoComboBox({
    autoBind      :false,
    cascadeFrom   :"state",
    highlightFirst:true,
    delay         :100,
    index         :0,
    placeholder   :"City",
    suggest       :true,
    dataSource    :{
        serverFiltering:true,
        transport      :{
            read:function (operation) {
                db.view("post20/getCities", {
                    success:function (data) {
                        var cities = [];
                        var rows = data.rows;
                        $.each(rows, function (idx, value) {
                            cities.push(value.value);
                        });
                        operation.success(cities.sort());
                    },
                    error  :function (status) {
                        console.log(status);
                    },
                    key    :$("#state").val(),
                    reduce :false
                });
            }
        }
    }
}).data("kendoComboBox");

Optimizations

Both the data organization in the documents and views are not optimal neither in time nor in space but designed for illustrating map/reduce and KendoUI Cascading ComboBoxes.

Advertisements

CouchApp: how to use views map/reduce


There is a JavaScript embedded in CouchDB: the one that is used in Futon. Lets see how to use it from your CouchApp for executing map/reduce views. This post is a follow on of this one.

Executing a view using jquery.couchdb.js

Background

Invoking a view is done by using:

db.view("design/view", {
    success:function (data) {
        // Got response from CouchDB
        // Process data
        // and then return retrieved data
    },
    error  :function (status) {
        // Error function...
    }
});

Where design is the CouchDB design document storing the view (view).

Using a key in a map function

Lets consider a CouchDB database with cities and states from USA stored in documents with the following structure:

{
   "_id": "0254D19B-7360-459E-A9F2-715E36AD1822",
   "_rev": "1-d6c3ca78003b4116ecef930d538b2d94",
   "type": "cities",
   "city": "Pasadena",
   "state": "California"
},
{
   "_id": "05C7AD05-0CC6-4C6E-A21B-E4C8344C242A",
   "_rev": "1-e0a64cd86be76166d3b1592376b8c23c",
   "type": "cities",
   "city": "Lakewood",
   "state": "Colorado"
}

I.e., Documents with field type value cities that allows me to isolate records containing the pairs (city – state) from others stored in the same database; and the the name of the city and the state itself.

Then I have defined the following map function:

function(doc) {
    if (doc.type==="cities") emit(doc.state, doc.city );
}

That emits every document of type cities. These emitted records will have as key the name of the state and as value, the name of the city.

If I execute the view using:

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    }
});

Where post20 is the design document and getCities is the name of the view.

I get something like:

{
    "total_rows": 285, 
    "offset": 0,
    "rows": [
        {"id":"34716A25-2D33-43F7-BDA2-CBC063BC04A4", "key":"Alabama", "value":"Mobile"},
        {"id":"4F5D34B0-8F24-4C84-98D3-6F104EB19C42", "key":"Alabama", "value":"Montgomery"},
        {"id":"B3BB27B0-9D6E-4776-A685-67378E9198A3", "key":"Alabama", "value":"Birmingham"},
        {"id":"91B00C11-6648-4E15-83F2-957561ACE157", "key":"Alaska", "value":"Anchorage"},
        {"id":"212EA2DA-40C3-4443-AFB7-91366891D3D5", "key":"Arizona", "value":"Tucson"},
        {"id":"7132A69F-B1CE-4AB6-ABF8-22EC6745C35D", "key":"Arizona", "value":"Phoenix"},
        {"id":"9938B89F-E119-47AA-9186-F15BDEF557E1", "key":"Arizona", "value":"Surprise"},
        {"id":"A2E9C9F5-033F-454D-8B54-3A303F2E13C2", "key":"Arizona", "value":"Peoria"},
        {"id":"BFD01743-2297-47B1-B997-4B63B9BF4213", "key":"Arizona", "value":"Mesa"},
        ...
}

But, I want to choose only the cities from California then I have to set California as key (and that’s why I defined the map function that emits the state as key. To send the key to CouchDB I have to say:

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    },
    key    :"California"
});

and I get as result:

{
    "total_rows":285,
    "offset"    :16,
    "rows"      :[
        {"id":"0254D19B-7360-459E-A9F2-715E36AD1822", "key":"California", "value":"Pasadena"},
        {"id":"07A64E55-2AB1-4AA2-81E8-3D1D54508A26", "key":"California", "value":"Vallejo"},
        {"id":"07CB593A-8F8A-4100-B650-BB949775EBA4", "key":"California", "value":"Ontario"},
        {"id":"0884A3CD-2D54-4B4C-9492-4BCF168F04DA", "key":"California", "value":"Salinas"},
        {"id":"0C835A0A-6109-4986-904B-5C6102D86EB4", "key":"California", "value":"San Buenaventura (Ventura)"},
        {"id":"10687A90-F40B-4154-BF46-A1D976C8E800", "key":"California", "value":"Fresno"},
        ...
    ]
}

Neat!

What about if I want to retrieve the list of cities of Alaska and Wisconsin?

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    },
    keys   :[ "Alaska", "Wisconsin" ]
});

and I get:

{"total_rows":285, "offset":4, "rows":[
    {"id":"91B00C11-6648-4E15-83F2-957561ACE157", "key":"Alaska", "value":"Anchorage"},
    {"id":"0A2056EC-4C2E-4285-8CCE-EA0F4C71D353", "key":"Wisconsin", "value":"Milwaukee"},
    {"id":"322DC6B3-3B5E-4310-B674-6957A40A4D03", "key":"Wisconsin", "value":"Green Bay"},
    {"id":"E1371AA5-18DB-48A4-AD90-B212030E6C6F", "key":"Wisconsin", "value":"Madison"}
]}

NOTE: Realize that I have had to change key by keys (plural) and then define the list of values to choose from.

Using reduce

Lets define a reduce function for getting the states of our database.

Getting unique values in CouchDB

Using this same database, if I want to retrieve the states selecting the key of the already defined map function or even if I define a map function as:

function(doc) {
    if (doc.type==="cities") emit(null, doc.state );
}

I get the states as many times, as cities for that state I have in the database (I get states duplicated).

{
    "total_rows":285,
    "offset"    :0,
    "rows"      :[
        {"id":"0254D19B-7360-459E-A9F2-715E36AD1822", "key":null, "value":"California"},
        {"id":"02C0F725-A811-492F-BCC0-8921E04899F4", "key":null, "value":"Indiana"},
        {"id":"04A29C30-CCDC-4D73-B9D4-D8ABBBA9AB46", "key":null, "value":"Texas"},
        {"id":"05C7AD05-0CC6-4C6E-A21B-E4C8344C242A", "key":null, "value":"Colorado"},
        {"id":"07A64E55-2AB1-4AA2-81E8-3D1D54508A26", "key":null, "value":"California"},
        {"id":"07CB593A-8F8A-4100-B650-BB949775EBA4", "key":null, "value":"California"},
        {"id":"07F27430-1A5A-4F12-9116-C720AE84E246", "key":null, "value":"Utah"},
        {"id":"0884A3CD-2D54-4B4C-9492-4BCF168F04DA", "key":null, "value":"California"},
        {"id":"090E0E6C-A4EC-4E84-B071-5C771ADA3DB2", "key":null, "value":"Minnesota"},
        {"id":"094E4363-69EA-443A-964D-29F3B2F04546", "key":null, "value":"Texas"},
        {"id":"0A2056EC-4C2E-4285-8CCE-EA0F4C71D353", "key":null, "value":"Wisconsin"},
        {"id":"0C835A0A-6109-4986-904B-5C6102D86EB4", "key":null, "value":"California"},
        {"id":"0CFD3158-AD42-448B-83A8-BB13C5FF87EF", "key":null, "value":"Washington"},
        {"id":"0D3EE837-0BC1-47CE-A8FA-3FA54400549C", "key":null, "value":"Ohio"},
        {"id":"10687A90-F40B-4154-BF46-A1D976C8E800", "key":null, "value":"California"}
        ...
    ]
}

The way of getting only unique values in CouchDB is defining a reduce function as follow:

function(keys, values) {
    return true;
}

and then ask CouchDB for reducing and grouping the result of map function.

Using map/reduce in jquery.couchdb.js

Choosing to reduce the result is not much different, basically I have to define the reduce function for our view:

function(keys, values) {
    return true;
}

And invoking view in jquery.couchdb is:

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    },
    reduce: true,
    group: true
});

And I get:

{"rows":[
    {"key":"Alabama", "value":true},
    {"key":"Alaska", "value":true},
    {"key":"Arizona", "value":true},
    {"key":"Arkansas", "value":true},
    {"key":"California", "value":true},
    ...
]}

NOTE: As an alternative to this reduce function, I might have decided to provide a little more functionality by choosing to return as value the number of cities found for that state. If so, I can simply use a built-in function called _count and my reduce would be:

_count

Yes! just _count, as I said its a built-in function and then I don’t need to provide the body of the function, the name is enough.

I would invoke the view in the same way (with reduce and group set to true) and I get:

{
    "rows":[
        {"key":"Alabama", "value":4},
        {"key":"Alaska", "value":1},
        {"key":"Arizona", "value":10},
        {"key":"Arkansas", "value":1},
        {"key":"California", "value":69},
        ...
    ]
} 

Summarizing

Adding extra attributes to a view is adding extra pairs of keyvalue to the options (second argument) of view method. Here I’ve covered key, keys, reduce and group but you can also use others as startkey and endkey:

db.view("post20/getCities", {
    success:function (data) {
        console.log(JSON.stringify(data));
    },
    error  :function (status) {
        console.log(status);
    },
    group  :true,
    reduce :true,
    startkey: "Arizona",
    endkey: "Colorado"
});

That retrieves:

{"rows":[
    {"key":"Arizona", "value":true},
    {"key":"Arkansas", "value":true},
    {"key":"California", "value":true},
    {"key":"Colorado", "value":true}
]} 

CouchApp uploading attachments (part 2)


In my previous post I presented how to upload attachments to a document using HTML (there were few lines of JavaScript for retrieving the last revision of the document).

In this post I will shift to almost doing everything in JavaScript in order to have more control.

Uploading an attachment into CouchDB using JavaScript

Simplest case: validation

The very first step is introduce a validation for checking that we have chosen a file to upload. I want to get something like:

Error on CouchApp attachment upload
Error on CouchApp attachment upload

So I start with the following HTML:

<form id="upload" method="post" enctype="multipart/form-data">
    <label style="display:none; visibility: hidden;">
        Revision :
        <input id="revision" type="text" name="_rev"/>
    </label>
    <input id="attachment" type="file" name="_attachments"/>
    <br/>
    <input type="button" value="Submit"/>
</form>

Basically it’s a form with three input: one for the revision (that is hidden and filled from JavaScript); while the second is for the attachment (the file); the last one is just a button that I will use for invoking the validation procedure.

This is the piece of code for getting document revision.

// Get the document revision
var db = $.couch.db("hermesapp");
var id = "doc_with_attachment";
db.openDoc(id, {
    success:function (result) {
        // Update revision input field
        $("#revision").val(result._rev);
    },
    error:function (a, b, c) {
        alert("Error: " + c);
        console.log("Error on openDoc:", a, b, c);
    }
});

Now, the validation code that has to run when the button is clicked:

$("input[type='button']", "#upload").click(function (ev) {
    var empty = false;
    var form = $("#upload");
    $.each($("input[type='file'][name='_attachments']", form), function (idx, input) {
        if (input.value === "") {
            empty = true;
        }
    });
    if (empty) {
        alert("No attachment file defined!");
        return;
    }
    // Submit the form with the attachment.
    ...
})

I get a reference to the form and inside the form to any input which type is file and which name is _attachments and check that all of them are not an empty string (the loop is because we can send multiple attachments by repeating this input).

Submitting the validated form might be as simple as:

// Submit the form with the attachment
form.attr("action", "/hermesapp/" + $.couch.encodeDocId(id));
form.submit();

or if I want to use ajaxSubmit from jquery.form.js:

// Submit the form with the attachment
form.ajaxSubmit({
    url:"/hermesapp/" + $.couch.encodeDocId(id),
    success:function (response) {
        alert("saved");
        console.log("response", response);
    },
    error:function (a, b, c) {
        alert("error");
        console.log("errors", a, b, c);
    }
});

Avoiding the fact of showing the message received from the server:

CouchDB message on successful upload
CouchDB message on successful upload

and getting instead:

Message on successful attachment upload in CouchDB
Message on successful attachment upload in CouchDB

NOTE: this solution requires that you use jquery.forms.js that you can already find it in your CouchDB server (since futon uses it). In order to download it in your couchapp as we did with jquery.couch.js you should access http://localhost:5984/_utils/script/jquery.form.js (replacing localhost by the IP address of your CouchDB server and saving it with the other JavaScript files (see here for more details on setting up a CouchApp).

Stay tuned!

Next post convers Uploading attachments from JavaScript in CouchDB using PUT instead of POST: why and how. Subscribe to this blog to receive notice.

CouchApp uploading attachments (part 1)


Not sure why but jquery.couchdb.js does not include a method for uploading CouchDB attachments… But I need it, so I started googling trying to find how and found this and this.

Since I’m not just a developer but also software architect and I want clean and nice solutions (avoiding subsequent problems) I tried to understand what was proposed and how it works.

CouchDB uploading attachments: the basics

For uploading an attachment I need:

  1. Database URL (ex: /couchapp)
  2. The document id (ex: doc_with_attachment)
  3. The revision number (ex: 1-967a00dff5e02add41819138abb3284d).
  4. The file(s) being uploaded.

With this information I have to do a POST to a multipart form to an URL that I build up as follow:

var url = "/couchapp/" + $.couch.encodeDocId("doc_with_attachment")

Next, I need an HTML form:

<form id="upload" method="post" action="/couchapp/doc_with_attachment">
    <label>Revision : <input id="revision" type="text" name="_rev"/></label><br/>
    <input id="attachment" type="file" name="_attachments"/><br/>
    <br/>
    <input type="submit"/>
</form>

And _rev is retrieved in HTML load time using:

// Access the DataBase
var db = $.couch.db("couchapp");
db.openDoc("doc_with_attachment", {
    success:function (result) {
        $("#revision").val(result._rev);
    },
    error:function (a, b, c) {
        alert("Error: " + c);
        console.log("Error on openDoc:", a, b, c);
    }
});

If i select a file to upload and click on Submit button what I get is:

Trying to upload an attachment in CouchDB
Trying to upload an attachment in CouchDB

That is more or less what Chris Strom already mention on this first post.

So, lets add enctype and set it to multipart/form-data:

<form id="upload" method="post" 
      action="/couchapp/doc_with_attachment" 
      enctype="multipart/form-data">
    <label>Revision : <input id="revision" type="text" name="_rev"/></label><br/>
    <input id="attachment" type="file" name="_attachments"/><br/>
    <br/>
    <input type="submit"/>
</form>

If I run it now, I get:

CouchApp upload attachment with multipart/form-data
CouchApp upload attachment with multipart/form-data

And futon shows me that the file has been actually uploaded:

Futon showing uploaded file
Futon showing uploaded file

Of course, you can hide the revision input:

CouchDB / CouchApp uploading attachment
CouchDB / CouchApp uploading attachment

and still works!
CouchDB / CouchApp uploading attachment: result
CouchDB / CouchApp uploading attachment: result

Good! pretty simple.

Stay tuned!

Next post convers Uploading attachments from JavaScript in CouchDB. Subscribe to this blog to receive notice.

Zafu: CouchDB and KendoUI (part 1)


I know that Couch is about relaxing and Kendo is about fighting but wikipedia says:

kendo developed under the strong influence of Zen Buddhism.

So, lets practice some meditation about CouchDB, CouchApp, KendoUI in nothing better that a Zafu (meditation seat used in zazen).

CouchDB and KendoUI

Since CouchDB might work as a web server, you might deploy documents into it and get them served to a browser.
For deploying the documents in a CouchDB database, you might use couchapp that in some way allows to synchronize a local folder structure into the database.

Step1: minimum couchapp

Create the following folder structure:

Simple, just one folder (_attachments) containing a file (index.html). The content of index.html is:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Zafu: CouchDB and KendoUI</title>

    <!-- Web Page styling -->
    <style type="text/css">
        html {
            font-family: Verdana, sans-serif;
            font-size: 12px;
            background-color: #a9a9a9;
        }
    </style>
</head>
<body>
<div id="window">
    Hello, CouchApp!<br/>
    Hello, KendoUI!
</div>
</body>
</html>

Now, push it into your CouchDB server by running:

couchapp push http://localhost:5984/app1

or if you have to supply a user and password:

couchapp push http://user:password@localhost:5984/app1

NOTE: You should change user and password by the user name and his/her password.

And if I open the URL that couchapp displayed I get…

Realize by the URL that the HTML page is actually being served by CouchDB.

But wait! This is just an HTML page in CouchDB. Where is KendoUI?

Step2: deploying KendoUI inside CouchDB

Go to you _attachments folder and copy KendoUI minimum JavaScript and CSS files:

If we change index.html for using KendoUI window as:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8"/>
    <title>Zafu: CouchDB and KendoUI</title>
    <!-- Kendo UI Web styles-->
    <link href="styles/kendo.common.min.css" rel="stylesheet" type="text/css"/>
    <link href="styles/kendo.black.min.css" rel="stylesheet" type="text/css"/>

    <!-- Kendo UI Web scripts-->
    <script src="js/jquery.min.js" type="text/javascript"></script>
    <script src="js/kendo.web.min.js" type="text/javascript"></script>

    <!-- Kendo Globalization -->
    <script src="js/cultures/kendo.culture.en-US.min.js"></script>

    <!-- Web Page styling -->
    <style type="text/css">
        html {
            font-family: Verdana, sans-serif;
            font-size: 12px;
            background-color: #a9a9a9;
        }
    </style>

    <!-- Initialize Form Elements -->
    <script type="text/javascript">
        $(document).ready(function () {
            kendo.culture("en-US");
            var window = $("#window").kendoWindow({
                width:"200",
                height:"60px",
                resizable:true,
                title:"Welcome to Zafu!"
            }).data("kendoWindow");
            window.center();
        });
    </script>

</head>
<body>
<div id="window">
    Hello, CouchApp!<br/>
    Hello, KendoUI!
</div>
</body>
</html>

and push it in couchdb using couchapp, I get:

Next posts (here and here) on how to actually get data from CouchDB and the final one about using KendoUI for displaying it.

JSON floats become BigDecimal using JCoachDB…


When converting  types from one representation into another we always know that there are some issues with serialization and deserialization. Of course with JSON is not different!

Type representation representation

Assumptions:

  1. We are using Java for interfacing with CouchDB
  2. We have chosen jcouchdb.
  3. jcouchdb uses svenson for converting from Java into JSON and viceversa.
  4. We have a CouchDB BaseDocument and we want to store an Integer, a Long, a Float and a Double.

Our code is something like:

// Create a document
BaseDocument doc1 = new BaseDocument();
doc1.setId("test_0001");
doc1.setProperty("Integer", new Integer(10));
doc1.setProperty("Long", new Long(10));
doc1.setProperty("Float", new Float(10));
doc1.setProperty("Double", new Double(10));

// Persist it into the database
db.createDocument(doc1);

Lets see what we get when we read that document from the database and print its values.

// Retrieve it
BaseDocument doc2 = db.getDocument(BaseDocument.class, doc1.getId());

// Print values
System.out.println("Values...");
System.out.println("Integer      " +
        doc1.getProperty("Integer") + " / " +
        doc2.getProperty("Integer"));
System.out.println("Long         " +
        doc1.getProperty("Long") + " / " +
        doc2.getProperty("Long"));
System.out.println("Float        " +
        doc1.getProperty("Float") + " / " +
        doc2.getProperty("Float"));
System.out.println("Double       " +
        doc1.getProperty("Double") + " / " +
        doc2.getProperty("Double"));

And the output is…

Values...
Integer      10 / 10
Long         10 / 10
Float        10.0 / 10.0
Double       10.0 / 10.0

Seems fine! But… what happens if we print the types of the arguments…

// Print types
System.out.println("Types...");
System.out.println("Integer      " +
        doc1.getProperty("Integer").getClass().getSimpleName() + " / " +
        doc2.getProperty("Integer").getClass().getSimpleName());
System.out.println("Long         " +
        doc1.getProperty("Long").getClass().getSimpleName() + " / " +
        doc2.getProperty("Long").getClass().getSimpleName());
System.out.println("Float        " +
        doc1.getProperty("Float").getClass().getSimpleName() + " / " +
        doc2.getProperty("Float").getClass().getSimpleName());
System.out.println("Double       " +
        doc1.getProperty("Double").getClass().getSimpleName() + " / " +
        doc2.getProperty("Double").getClass().getSimpleName());

Now we get as output…

Types...
Integer      Integer / Long
Long         Long / Long
Float        Float / BigDecimal
Double       Double / BigDecimal

We see that types have changed integer numbers are always Long while floating point numbers are BigDecimal.

Problems with conversion and precision

The question on this is problems with precision.
The following example creates a BaseDocument with an Integer, Long, Float and Double but now instead of 10 we have a value of 123.456.

// Create a document
BaseDocument doc1 = new BaseDocument();
doc1.setId("test_0002");
doc1.setProperty("Float", new Float(123.456));
doc1.setProperty("Double", new Double(123.456));

// Persist it into the database
db.createDocument(doc1);

// Retrieve it
BaseDocument doc2 = db.getDocument(BaseDocument.class, doc1.getId());

And when we print the value using:

// Print values
System.out.println("Values...");
System.out.println("Float        " +
        doc1.getProperty("Float") + " / " +
        doc2.getProperty("Float"));
System.out.println("Double       " +
        doc1.getProperty("Double") + " / " +
        doc2.getProperty("Double"));

we get:

Values...
Float        123.456 / 123.45600000000000307
Double       123.456 / 123.45600000000000307

It is not such a big difference but if you compare numbers, of course, they will not be the same…
And the problem is with the representation of 123.456 using the different data types.

System.out.println("Float      : " + new Float(123.456));
System.out.println("Double     : " + new Double(123.456));
System.out.println("BigDecimal : " + new BigDecimal(123.456));

shows

Float      : 123.456
Double     : 123.456
BigDecimal : 123.4560000000000030695446184836328029632568359375

CouchDB poll!!!


I’d like to know how people is using CouchDB and I couldn’t find a better way than asking it!

The very first question is if you are actually using it, evaluating it, playing with it, curious about it… Might also be interesting knowing if recent movement of Damien Katz (leaving Apache CouchDB) might affect your decision.

Then if you use it from one of programming language or deploy your application inside CouchDB, or is it a HTML/JavaScript application that uses AJAX for interacting with CouchDB…

Third question is for people using it from Java and wondering about which API / library are you using…

Please, leave any comment or question that you would like to have included in this poll.