Does anyone know how to sort the contact data that phonegap liberates from iOS to javascript. The order at the moment is nothing to do with alphabetical sorting. I want to sort on last name.
Here is my contact code:
function init_contacts() {
var fields = [ "name","phoneNumbers"];
navigator.service.contacts.find(fields, contactSuccess, contactError, '');
}
function contactSuccess(contacts) {
for (n = 0; n < contacts.length; n++) {
if (contacts[n].phoneNumbers) {
for (m = 0; m < contacts[n].phoneNumbers.length; m++) {
addToMyContacts(contacts[n].name.formatted, contacts[n].phoneNumbers[m].value);
console.log('Found ' + contacts[n].name.formatted + ' ' + contacts[n].phoneNumbers[m].value);
}
}
}
$("#my_contacts").listview("refresh");
};
function contactError() {
navigator.notification.alerter('contactError!');
};
You can do this sort by hand in Javascript.
var cSort = function(a, b) {
var aName = a.lastName + ' ' + a.firstName;
var bName = b.lastName + ' ' + b.firstName;
return aName < bName ? -1 : (aName == bName ? 0 : 1);
};
function contactSuccess(contacts) {
contacts = contacts.sort(cSort);
...
};
For more fun and cleaner code you may consider using lodash
contacts = _.sortBy(contacts, ['last_name', 'first_name']);
sorting in js
function sortByitemName(a, b) {
var x = a.displayName.toLowerCase();
var y = b.displayName.toLowerCase();
return ((x < y) ? -1 : ((x > y) ? 1 : 0));
}
function onSuccess(contacts) {
contacts.sort(sortByitemName);
}
I am using this method, which is far more efficient (compared to the accepted answer)
var cSort=function(a,b){
var an=a.name.formatted.toUpperCase();
var bn=b.name.formatted.toUpperCase();
return (an<bn)?-1:(an==bn)?0:1;
};
function contactSuccess(contacts) {
contacts = contacts.sort(cSort);
...
};
Contact.name.formatted is more consistent across platforms
All names starting with the same letter will be grouped together irregardless of the case. (you can also try it to see).
Related
I have below procedure in Document DB. It executes fine from DocumentDb script explorer but the result it returns is partial. I have more than 250 documents satisfying its given where clause which I checked in query explorer. But when I run procedure from script explorer count(defined in procedure) is always 100.
Below is my procedure -
function getInvoice(pageNo, numberOfRecords, member, searchText, customerGroupId,ResellerId) {
var collectionReseller = getContext().getCollection();
var filterquery ;
var count=0, invoiceAmountTotal=0, referalCommissionTotal=0, developerCommissionTotal=0;
var customerIdString='';
var InvoiceList = [];
var whereClause;
if (customerGroupId != "") {
filterquery = 'SELECT c.id from c where c.Type="Customer" and c.CustomerGroupID="' + customerGroupId + '"';
var isAccepted = collectionReseller.queryDocuments(
collectionReseller.getSelfLink(), filterquery,
function (err, documents, responseOptions) {
var docCount = documents.length;
documents.forEach(function (doc) {
docCount--;
if (docCount > 0)
customerIdString = customerIdString + '"' + doc.id + '", '
else
customerIdString = customerIdString + '"' + doc.id + '" '
})
whereClause = 'where r.Type="Invoice" and r.CustomerID IN (' + customerIdString + ')'
var filterquery1 = 'SELECT * FROM root r ';
if (member.length > 0) {
member.forEach(function (val, i) {
whereClause = whereClause + ' and contains(r.' + member[i] + ',"' + searchText[i] + '")';
});
}
isAccepted = collectionReseller.queryDocuments(
collectionReseller.getSelfLink(), filterquery1 + whereClause,
function (err, invoiceDoc) {
var qr = filterquery1 + whereClause;
count = invoiceDoc.length;
invoiceDoc.forEach(function (doc) {
invoiceAmountTotal = parseFloat(invoiceAmountTotal) + parseFloat(doc.InvoiceAmount);
referalCommissionTotal = parseFloat(referalCommissionTotal) + parseFloat(doc.ReferralCommission);
developerCommissionTotal= parseFloat(developerCommissionTotal) + parseFloat(doc.DeveloperCommission);
InvoiceList.push(doc);
});
InvoiceList.sort(SortByID);
InvoiceList = InvoiceList.slice(pageNo * numberOfRecords, pageNo * numberOfRecords + numberOfRecords);
// Check the feed and if empty, set the body to 'no docs found',
// else take 1st element from feed
getContext().getResponse().setBody(JSON.stringify({ InvoiceList, count, invoiceAmountTotal, referalCommissionTotal, developerCommissionTotal }));
});
});
}
else
{
whereClause = ' where r.Type = "Invoice" and r.ResellerID = "'+ ResellerId + '"';
filterquery = 'SELECT * FROM root r ';
if(member.length > 0) {
member.forEach(function (val, i) {
whereClause = whereClause + ' and contains(r.' + member[i] + ',"' + searchText[i] + '")';
});
}
filterquery = filterquery + whereClause;
var isAccepted = collectionReseller.queryDocuments(
collectionReseller.getSelfLink(), filterquery,
function (err, documents, responseOptions) {
if (err) throw err;
invoiceDoc = documents;
count =invoiceDoc.length;
invoiceDoc.forEach(function (doc) {
InvoiceList.push(doc);
invoiceAmountTotal = parseFloat(invoiceAmountTotal) + parseFloat(doc.InvoiceAmount);
referalCommissionTotal = parseFloat(referalCommissionTotal) + parseFloat(doc.ReferralCommission);
developerCommissionTotal= parseFloat(developerCommissionTotal) + parseFloat(doc.DeveloperCommission);
});
InvoiceList.sort(SortByID);
InvoiceList = InvoiceList.slice(pageNo * numberOfRecords, pageNo * numberOfRecords + numberOfRecords);
// Check the feed and if empty, set the body to 'no docs found',
// else take 1st element from feed
getContext().getResponse().setBody(JSON.stringify({ InvoiceList, count, invoiceAmountTotal, referalCommissionTotal, developerCommissionTotal }));
});
}
function SortByID(a, b) {
var aName = a.UpdatedOn.toLowerCase();
var bName = b.UpdatedOn.toLowerCase();
return ((aName < bName) ? -1 : ((aName > bName) ? 1 : 0));
}
if (!isAccepted) throw new Error('The query was not accepted by the server.');
}
Any help will be highly appreciated..
If you want to get all 250 back in one shot, you need to populate the options parameter for queryDocuments() with a pageSize field. It's an optional third parameter for that function call. Without it, this server-side API will default to 100.
You can also set pageSize to -1 to get you all documents. However, for server-side stored procedures, I recommend against this. Rather, you need to handle paging using the continuation token. If you want it to be really robust you also need to deal with premature shutdown of the stored procedure.
I have a LineChart with two simple lines, I have to have the possibility to hide the tooltips (render a line not selectable) for a particular series. Is it possible to achieve it with some apis provided?
I'm trying to develop my particular behaviour:
onmouseover: function (d, node){
if (d.id=="Requested")
{
__ what here?
}
}
I solved by adding the if statement in the content generator.
tooltip:
{
contents: function (d, defaultTitleFormat, defaultValueFormat, color)
{
var $$ = this, config = $$.config, CLASS = $$.CLASS,
titleFormat = config.tooltip_format_title || defaultTitleFormat,
nameFormat = config.tooltip_format_name || function (name) { return name; },
valueFormat = config.tooltip_format_value || defaultValueFormat,
text, i, title, value, name, bgcolor;
// You can access all of data like this:
var count=0;
for (i = 0; i < d.length; i++)
{
**if (d[i].id=="Requested")** {
if (! (d[i] && (d[i].value || d[i].value === 0))) { continue; }
// ADD
if (! text)
{
var formats=d3.time.format('%Y%m%d');
var date= new Date(formats.parse(scene[d[i].index].date));
title = date.getDate()+"/"+date.getMonth()+1+"/"+date.getFullYear();
text = "<table class='" + CLASS.tooltip + "'>" + (title || title === 0 ? "<tr><th colspan='2'>" + title + "</th></tr>" : "");
}
}//if requested
}
I am working on an extended Textarea like http://podio.github.com/jquery-mentions-input/
There you can see a transparent Textarea with an element in background simulating the highlighting.
You can see the problem there also: type some long text like "iiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiiii " (attention to space at the end)
and then type "#ke" and choose first contact.
You will see that the background breaks different than the text in the textarea.
I figured out that this is not because different sizes!
Any ideas how to avoid that?
P.S.: I dont want to you contentediable.
For testing i used chrome (test with points!) and firefox.
I think this technic is also used often for auto-calculating a textarea-hight and they must have the same problems?!
I found a different solution myself: count line-breaks manually.
I modified and improved the line-break-adder from this thread: finding "line-breaks" in textarea that is word-wrapping ARABIC text
The big difference: this function only retrieves the breaked value without applying the breaks cause it used a temporary element copy.
I think it could help someone else!
function getApplyLineBreaks(strTextAreaId)
{
var strRawValue = $('#' + strTextAreaId).val();
var measureClone = $('#' + strTextAreaId).clone();
measureClone.attr('id', 'value_break_mess_clone');
measureClone.val('');
measureClone.css('overflow', 'hidden');
measureClone.removeAttr('onchange').removeAttr('onclick').removeAttr('onkeydown').removeAttr('onkeyup').removeAttr('onblur').removeAttr('onfocus');
measureClone.height(10);
measureClone.insertAfter('#' + strTextAreaId);
var lastScrollWidth = measureClone[0].scrollWidth;
var lastScrollHeight = measureClone[0].scrollHeight;
var lastWrappingIndex = -1;
var tolerancePixels = 5; //sollte kleiner als font-size
var addedSpace = false;
var debug_c = 0;
for (var i = 0; i < strRawValue.length; i++)
{
var curChar = strRawValue.charAt(i);
if (curChar == ' ' || curChar == '-' || curChar == '+')
lastWrappingIndex = i;
measureClone.val(measureClone.val() + curChar);
addedSpace = false;
if (i != strRawValue.length - 1 && strRawValue.charAt(i + 1) != "\n")
{
measureClone.val(measureClone.val() + ' '); //this is only 90% zero-width breaker unnoticed
addedSpace = true;
}
if (((measureClone[0].scrollWidth - tolerancePixels) > lastScrollWidth) || ((measureClone[0].scrollHeight - tolerancePixels) > lastScrollHeight))
{
if (addedSpace)
measureClone.val(measureClone.val().substr(0, measureClone.val().length - 1));
var buffer = "";
if (lastWrappingIndex >= 0)
{
for (var j = lastWrappingIndex + 1; j < i; j++)
buffer += strRawValue.charAt(j);
lastWrappingIndex = -1;
}
buffer += curChar;
measureClone.val(measureClone.val().substr(0, measureClone.val().length - buffer.length));
if (curChar == "\n")
{
if (i == strRawValue.length - 1)
measureClone.val(measureClone.val() + buffer + "\n");
else
measureClone.val(measureClone.val() + buffer);
}
else
{
measureClone.val(measureClone.val() + "\n" + buffer);
}
lastScrollHeight = measureClone[0].scrollHeight;
}
else if (addedSpace)
{
measureClone.val(measureClone.val().substr(0, measureClone.val().length - 1));
}
}
var returnText = measureClone.val();
measureClone.remove();
return returnText;
}
Only thing: its slow on long texts. Ideas for optimization are welcome.
I'm reading email headers (in Node.js, for those keeping score) and they are VARY varied. E-mail addresses in the to field look like:
"Jake Smart" <jake#smart.com>, jack#smart.com, "Development, Business" <bizdev#smart.com>
and a variety of other formats. Is there any way to parse all of this out?
Here's my first stab:
Run a split() on - to break up the different people into an array
For each item, see if there's a < or ".
If there's a <, then parse out the email
If there's a ", then parse out the name
For the name, if there's a ,, then split to get Last, First names.
If I first do a split on the ,, then the Development, Business will cause a split error. Spaces are also inconsistent. Plus, there may be more e-mail address formats that come through in headers that I haven't seen before. Is there any way (or maybe an awesome Node.js library) that will do all of this for me?
There's a npm module for this - mimelib (or mimelib-noiconv if you are on windows or don't want to compile node-iconv)
npm install mimelib-noiconv
And the usage would be:
var mimelib = require("mimelib-noiconv");
var addressStr = 'jack#smart.com, "Development, Business" <bizdev#smart.com>';
var addresses = mimelib.parseAddresses(addressStr);
console.log(addresses);
// [{ address: 'jack#smart.com', name: '' },
// { address: 'bizdev#smart.com', name: 'Development, Business' }]
The actual formatting for that is pretty complicated, but here is a regex that works. I can't promise it always will work though. https://www.rfc-editor.org/rfc/rfc2822#page-15
const str = "...";
const pat = /(?:"([^"]+)")? ?<?(.*?#[^>,]+)>?,? ?/g;
let m;
while (m = pat.exec(str)) {
const name = m[1];
const mail = m[2];
// Do whatever you need.
}
I'd try and do it all in one iteration (performance). Just threw it together (limited testing):
var header = "\"Jake Smart\" <jake#smart.com>, jack#smart.com, \"Development, Business\" <bizdev#smart.com>";
alert (header);
var info = [];
var current = [];
var state = -1;
var temp = "";
for (var i = 0; i < header.length + 1; i++) {
var c = header[i];
if (state == 0) {
if (c == "\"") {
current.push(temp);
temp = "";
state = -1;
} else {
temp += c;
}
} else if (state == 1) {
if (c == ">") {
current.push(temp);
info.push (current);
current = [];
temp = "";
state = -1;
} else {
temp += c;
}
} else {
if (c == "<"){
state = 1;
} else if (c == "\"") {
state = 0;
}
}
}
alert ("INFO: \n" + info);
For something complete, you should port this to JS: http://cpansearch.perl.org/src/RJBS/Email-Address-1.895/lib/Email/Address.pm
It gives you all the parts you need. The tricky bit is just the set of regexps at the start.
How can I compute a MD5 or SHA1 hash of text in a specific cell and set it to another cell in Google Spreadsheet?
Is there a formula like =ComputeMD5(A1) or =ComputeSHA1(A1)?
Or is it possible to write custom formula for this? How?
Open Tools > Script Editor then paste the following code:
function MD5 (input) {
var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, input);
var txtHash = '';
for (i = 0; i < rawHash.length; i++) {
var hashVal = rawHash[i];
if (hashVal < 0) {
hashVal += 256;
}
if (hashVal.toString(16).length == 1) {
txtHash += '0';
}
txtHash += hashVal.toString(16);
}
return txtHash;
}
Save the script after that and then use the MD5() function in your spreadsheet while referencing a cell.
This script is based on Utilities.computeDigest() function.
Thanks to gabhubert for the code.
This is the SHA1 version of that code (very simple change)
function GetSHA1(input) {
var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.SHA_1, input);
var txtHash = '';
for (j = 0; j <rawHash.length; j++) {
var hashVal = rawHash[j];
if (hashVal < 0)
hashVal += 256;
if (hashVal.toString(16).length == 1)
txtHash += "0";
txtHash += hashVal.toString(16);
}
return txtHash;
}
Ok, got it,
Need to create custom function as explained in
http://code.google.com/googleapps/appsscript/articles/custom_function.html
And then use the apis as explained in
http://code.google.com/googleapps/appsscript/service_utilities.html
I need to handtype the complete function name so that I can see the result in the cell.
Following is the sample of the code that gave base 64 encoded hash of the text
function getBase64EncodedMD5(text)
{
return Utilities.base64Encode( Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, text));
}
The difference between this solution and the others is:
It fixes an issue some of the above solution have with offsetting the output of Utilities.computeDigest (it offsets by 128 instead of 256)
It fixes an issue that causes some other solutions to produce the same hash for different inputs by calling JSON.stringify() on input before passing it to Utilities.computeDigest()
function MD5(input) {
var result = "";
var byteArray = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, JSON.stringify(input));
for (i=0; i < byteArray.length; i++) {
result += (byteArray[i] + 128).toString(16) + "-";
}
result = result.substring(result, result.length - 1); // remove trailing dash
return result;
}
to get hashes for a range of cells, add this next to gabhubert's function:
function RangeGetMD5Hash(input) {
if (input.map) { // Test whether input is an array.
return input.map(GetMD5Hash); // Recurse over array if so.
} else {
return GetMD5Hash(input)
}
}
and use it in cell this way:
=RangeGetMD5Hash(A5:X25)
It returns range of same dimensions as source one, values will spread down and right from cell with formulae.
It's universal single-value-function to range-func conversion method (ref), and it's way faster than separate formuleas for each cell; in this form, it also works for single cell, so maybe it's worth to rewrite source function this way.
Based on #gabhubert but using array operations to get the hexadecimal representation
function sha(str){
return Utilities
.computeDigest(Utilities.DigestAlgorithm.SHA_1, str) // string to digested array of integers
.map(function(val) {return val<0? val+256 : val}) // correct the offset
.map(function(val) {return ("00" + val.toString(16)).slice(-2)}) // add padding and enconde
.join(''); // join in a single string
}
Using #gabhubert answer, you could do this, if you want to get the results from a whole row. From the script editor.
function GetMD5Hash(value) {
var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, value);
var txtHash = '';
for (j = 0; j <rawHash.length; j++) {
var hashVal = rawHash[j];
if (hashVal < 0)
hashVal += 256;
if (hashVal.toString(16).length == 1)
txtHash += "0";
txtHash += hashVal.toString(16);
}
return txtHash;
}
function straightToText() {
var ss = SpreadsheetApp.getActiveSpreadsheet().getSheets();
var r = 1;
var n_rows = 9999;
var n_cols = 1;
var column = 1;
var sheet = ss[0].getRange(r, column, n_rows, ncols).getValues(); // get first sheet, a1:a9999
var results = [];
for (var i = 0; i < sheet.length; i++) {
var hashmd5= GetMD5Hash(sheet[i][0]);
results.push(hashmd5);
}
var dest_col = 3;
for (var j = 0; j < results.length; j++) {
var row = j+1;
ss[0].getRange(row, dest_col).setValue(results[j]); // write output to c1:c9999 as text
}
}
And then, from the Run menu, just run the function straightToText() so you can get your result, and elude the too many calls to a function error.
I was looking for an option that would provide a shorter result. What do you think about this? It only returns 4 characters. The unfortunate part is that it uses i's and o's which can be confused for L's and 0's respectively; with the right font and in caps it wouldn't matter much.
function getShortMD5Hash(input) {
var rawHash = Utilities.computeDigest(Utilities.DigestAlgorithm.MD5, input);
var txtHash = '';
for (j = 0; j < 16; j += 8) {
hashVal = (rawHash[j] + rawHash[j+1] + rawHash[j+2] + rawHash[j+3]) ^ (rawHash[j+4] + rawHash[j+5] + rawHash[j+6] + rawHash[j+7])
if (hashVal < 0)
hashVal += 1024;
if (hashVal.toString(36).length == 1)
txtHash += "0";
txtHash += hashVal.toString(36);
}
return txtHash.toUpperCase();
}
I needed to get a hash across a range of cells, so I run it like this:
function RangeSHA256(input)
{
return Array.isArray(input) ?
input.map(row => row.map(cell => SHA256(cell))) :
SHA256(input);
}