jqGrid json data grid not displayed - json

I have a jqGrid with below definintion
function loadSearchGrid(jsonData){
jQuery("#searchGrid").jqGrid({
datatype: 'jsonstring',
width: w ,
datastr: jsonData,
colNames:['Hidden Location ID','Hidden Contact ID','Hidden Customer ID','ID','Company','Service Address', 'State', 'LCON First','LCON Last','LCON Phone','LCON Email','LCON External ID','Letters','Notes','Attachments'],
colModel:[
{name: "hiddenLocationID",index:'hiddenLocationID', sortable:false, search:false, width:110, hidden:true},
{name: "hiddenContactID",index:'hiddenContactID', sortable:false, search:false, width:110, hidden:true},
{name: "hiddenCustomerID",index:'hiddenCustomerID', sortable:false, search:false, width:110, hidden:true},
{name: "ID",index:'ID', sortable:true, search:false, width:10},
{name:'company',index:'company', width:40,search:true, sorttype:"int"},
{name:'serviceAddress',index:'serviceAddress',search:true, width:40},
{name:'state',index:'state',search:true, width:40},
{name:'lconFirst',index:'lconFirst',search:true, width:40, align:"center"},
{name:'lconLast',index:'lconLast',search:true, width:40, align:"center"},
{name:'lconPhone',index:'lconPhone',search:true, width:40,align:"center"},
{name:'lconEmail',index:'lconEmail',search:true, width:40,align:"center",formatter:emailFormatter},
{name:'lconExternalId',index:'lconExternalId',search:true, width:40,align:"center",formatter:lconExternalIdFormatter },
{name:'letters',index:'letters', width:40,search:false,align:"center",formatter:letterFormatter},
{name:'notes',index:'notes', width:40,search:false,align:"center",formatter:noteFormatter},
{name:'attachments',index:'attachments', width:40,search:false, sortable:false,formatter:attachmentFormatter}
],
loadComplete: function ()
{
var totalRecords = jQuery("#searchGrid").getGridParam("records");
/*if (totalRecords==0)
{
$("#searchDiv").addClass("tblSection marT10 nodataFound");
$("#searchDiv").html("No Results were found matching your criteria");
}*/
//$('#searchGrid').jqGrid('filterToolbar',{searchOnEnter:false , defaultSearch:"cn"});
},
onSelectRow: function(rowid, iRow, iCol, e)
{
var company = $('#searchGrid').jqGrid('getCell', rowid, 'company');
var serviceAddress = $('#searchGrid').jqGrid('getCell', rowid, 'serviceAddress');
var locationId = $('#searchGrid').jqGrid('getCell', rowid, 'hiddenLocationID');
var contactId = $('#searchGrid').jqGrid('getCell', rowid, 'hiddenContactID');
var customerId = $('#searchGrid').jqGrid('getCell', rowid, 'hiddenCustomerID');
$('#customerId').val(customerId);
$('#contactId').val(contactId);
$('#locationId').val(locationId);
//disbaled top buttons
$("#exportToExcel").removeAttr("disabled");
$("#editContact").removeAttr("disabled");
$("#deleteContact").removeAttr("disabled");
$("#newLcon").removeAttr("disabled");
$("#newLocation").removeAttr("disabled");
},
rowNum:10,
rowList:[10,20,30],
pager: '#searchPager',
paging:true,
height: 400,
sortname: 'company',
viewrecords: true,
sortorder: "desc",
gridview: true,
jsonReader : {
root: "rows",
page: "page",
total: "total",
records: "records",
id:"ID",
repeatitems: false
},
loadonce: false,
ignoreCase: true
});
console.log("msg is ", jsonData);
$('#searchGrid').jqGrid('filterToolbar',{searchOnEnter:false , defaultSearch:"cn"});}
I get the json data in the following format from Struts2 Action:
Also my jsonData is as below
{"total":"18","page":"1","lastRecord":"18","cspUserReqFormUrl":"http://ebiz.sbc.com/cpct/cpct_user_request_form.cfm","records":"18","popCount":"5","rows":[{"lconEmail":"111#111.com","hiddenCustomerID":"119289","state":"Texas","lconPhone":"7133868166","lconLast":"Rapp","hiddenContactID":"95696","lconExternalId":"11#111.com|Remove","hiddenLocationID":"97459","company":"AT T","ID":"1","attachments":"0.0","lconFirst":"Loren","notes":"1.0","letters":"0.0","serviceAddress":"11830 WEBB CHAPEL RD, DALLAS, Texas,75234"},{"lconEmail":"111#111.COM","hiddenCustomerID":"37302","state":"Texas","lconPhone":"9727151699","lconLast":"FLOYD","hiddenContactID":"62753","lconExternalId":"|Add","hiddenLocationID":"63956","company":"Prudential","ID":"2","attachments":"0.0","lconFirst":"MARK","notes":"1.0","letters":"0.0","serviceAddress":"5001 SPRING VALLEY RD, FARMERS BRANCH, Texas,75244"},{"lconEmail":"111#111.COM","hiddenCustomerID":"111313","state":"Texas","lconPhone":"9724372888","lconLast":"SAVARIAN","hiddenContactID":"85208","lconExternalId":"|Add","hiddenLocationID":"86708","company":"ATX-First Community Bank N.A.","ID":"3","attachments":"0.0","lconFirst":"SHELLY","notes":"1.0","letters":"0.0","serviceAddress":"1755 N. COLLINS BLVD, RICHARDSON, Texas,75080"},{"lconEmail":"111#111.COM","hiddenCustomerID":"111711","state":"Texas","lconPhone":"2672628167","lconLast":"COREY","hiddenContactID":"82922","lconExternalId":"|Add","hiddenLocationID":"84302","company":"AT&T-VIEWPOINT BANK","ID":"4","attachments":"0.0","lconFirst":"JAIME","notes":"1.0","letters":"0.0","serviceAddress":"1001 E. CAMPBELL RD, RICHARDSON, Texas,75081"},{"lconEmail":"111#111.COM","hiddenCustomerID":"11025","state":"Texas","lconPhone":"8475079311","lconLast":"CALLAGHAN","hiddenContactID":"63333","lconExternalId":"|Add","hiddenLocationID":"64534","company":"Apple","ID":"5","attachments":"0.0","lconFirst":"BILL","notes":"1.0","letters":"0.0","serviceAddress":"8787 N. CENTRAL EXPWY, DALLAS, Texas,75225"},{"lconEmail":"111#111.COM","hiddenCustomerID":"111290","state":"Texas","lconPhone":"2147018465","lconLast":"KIM","hiddenContactID":"82552","lconExternalId":"|Add","hiddenLocationID":"83932","company":"QCC DBA CENTURYLINK QCC-SAMSUNG SDS AMERICA INC","ID":"6","attachments":"0.0","lconFirst":"TONY","notes":"1.0","letters":"0.0","serviceAddress":"1301 E LOOKOUT DR, RICHARDSON, Texas,75082"},{"lconEmail":"111#111.COM","hiddenCustomerID":"113889","state":"Texas","lconPhone":"2142779863","lconLast":"DAO","hiddenContactID":"85110","lconExternalId":"|Add","hiddenLocationID":"86594","company":"ATX-VIETFACE TV HOUSTON","ID":"7","attachments":"0.0","lconFirst":"HUNG","notes":"1.0","letters":"0.0","serviceAddress":"1301 E. ARAPAHO RD, RICHARDSON, Texas,75081"},{"lconEmail":"111#111.COM","hiddenCustomerID":"108914","state":"Texas","lconPhone":"9725810403","lconLast":"REAVES","hiddenContactID":"80035","lconExternalId":"|Add","hiddenLocationID":"81398","company":"QCC DBA CENTURYLINK QCC-PILGRIMS PRIDE","ID":"8","attachments":"0.0","lconFirst":"STEPHAN","notes":"1.0","letters":"0.0","serviceAddress":"1780 JAY ELL DR, RICHARDSON, Texas,75081"},{"lconEmail":"111#111.COM","hiddenCustomerID":"91749","state":"Texas","lconPhone":"2543156520","lconLast":"BLACK","hiddenContactID":"62652","lconExternalId":"|Add","hiddenLocationID":"63853","company":"SMITHFIELD","ID":"9","attachments":"0.0","lconFirst":"JASON","notes":"1.0","letters":"0.0","serviceAddress":"17201 WATERVIEW PKWY, DALLAS, Texas,75252"},{"lconEmail":"111#111.COM","hiddenCustomerID":"103403","state":"Texas","lconPhone":"9726640727","lconLast":"HAWORTH","hiddenContactID":"74404","lconExternalId":"|Add","hiddenLocationID":"75724","company":"ATX-AWARD SOLUTIONS INC","ID":"10","attachments":"0.0","lconFirst":"BOB","notes":"1.0","letters":"0.0","serviceAddress":"2100 LAKESIDE BLVD, RICHARDSON, Texas,75082"},{"lconEmail":"111#111.COM","hiddenCustomerID":"115783","state":"Texas","lconPhone":"9727293352","lconLast":"SELLERS","hiddenContactID":"86985","lconExternalId":"|Add","hiddenLocationID":"88505","company":"QCC DBA CENTURYLINK QCC-WAL MART","ID":"11","attachments":"0.0","lconFirst":"GLEN","notes":"1.0","letters":"0.0","serviceAddress":"400 INTERNATIONAL PKWY, RICHARDSON, Texas,75081"},{"lconEmail":"111#111.COM","hiddenCustomerID":"21501","state":"Texas","lconPhone":"9727929393","lconLast":"MARTINEZ","hiddenContactID":"86997","lconExternalId":"|Add","hiddenLocationID":"88518","company":"ATX-HILTON","ID":"12","attachments":"0.0","lconFirst":"TERESA","notes":"1.0","letters":"0.0","serviceAddress":"1001 W PRESIDENT GEORGE BUSH HWY, RICHARDSON, Texas,75080"},{"lconEmail":"111#111.com","hiddenCustomerID":"44762","state":"New
But my grid is not loaded with data. I get a blank grid with only headers.
Can anyone guide me through it. I have searched other questions. And tried all that mentioned in those questions(or maybe I have missed something major).
Also i have to implement server side paging. So using jsonString as datatype wont help I guess. As it then acts as local after loading. Correct me if I am wrong.
Any guidance for writing the java paging implementation would also help.

Instead of using datatype: 'jsonstring' use datatype: 'json',

Related

SQL json_arrayarg with json_object producing duplicate values

This SQL query gives me same values repeatedly:
select json_object('id',basics.resume_id,
'work',JSON_ARRAYAGG(json_object('name', work.name, 'location', work.location,
'description', work.description, 'position', work.position, 'url', work.url,
'startDate', work.startDate, 'endDate', work.endDate, 'summary', work.summary,
'highlights', work.highlights, 'keywords', work.keywords)
),
'awards', JSON_ARRAYAGG(JSON_OBJECT(
'title', awards.title
)
)) from basics
left join work on basics.resume_id = work.resume_id
left join awards on basics.resume_id = awards.resume_id where basics.resume_id = 1
The value I get for it is:
{
"id":1,
"work":[
{
"url":"http://piedpiper.example.com",
"name":"Pied Piper3",
"endDate":"2014-12-01",
"summary":"Pied Piper is a multi-platform technology based on a proprietary universal compression algorithm that has consistently fielded high Weisman Scores™ that are not merely competitive, but approach the theoretical limit of lossless compression.",
"keywords":"Javascript, React",
"location":"Palo Alto, CA",
"position":"CEO/President",
"startDate":"2013-12-01",
"highlights":"Build an algorithm for artist to detect if their music was violating copy right infringement laws, Successfully won Techcrunch Disrupt, Optimized an algorithm that holds the current world record for Weisman Scores",
"description":"Awesome compression company"
},
{
"url":" bnvc ",
"name":"Pied Piper",
"endDate":"vb nsncd",
"summary":"bcbbv",
"keywords":"nbdcnbsvd",
"location":"nbdcnb",
"position":"m m",
"startDate":" vbn vb",
"highlights":"jhsfcf ",
"description":"mbvm"
}
],
"awards":[
{
"title":"Digital Compression Pioneer Award"
},
{
"title":"Digital Compression Pioneer Award"
}
]}
I have added two rows of data in work table for resume_id = 1, but only 1 row of data in awards table for resume_id = 1

Filter a json data by another array in underscore.js

I have a search field and I want to add some complex functionality using underscore.js.
Sometimes users search for a whole "sentence" like "Samsung galaxy A20s ultra". I want to filter JSON data using any of the words in the search string and sort by results that contain more of the words.
Sample data:
var phones = [
{name: "Samsung A10s", id: 845},
{name: "Samsung galaxy", id: 839},
{name: "Nokia 7", id: 814},
{name: "Samsung S20s ultra", id: 514},
{name: "Apple iphone ultra", id: 159},
{name: "LG S20", id: 854}];
What is the best way to do it in underscore?
In this answer, I'll be building a function searchByRelevance that takes two arguments:
a JSON array of phones with name and id properties, and
a search string,
and which returns a new JSON array, with only the phones of which the name has at least one word in common with the search string, sorted such that the phones with the most common words come first.
Let's first identify all the subtasks and how you could implement them with Underscore. Once we've done that, we can compose them into the searchByRelevance function. In the end, I'll also spend some words on how we might determine what is "best".
Subtasks
Split a string into words
You don't need Underscore for this. Strings have a builtin split method:
"Samsung galaxy A20s ultra".split(' ')
// [ 'Samsung', 'galaxy', 'A20s', 'ultra' ]
However, if you have a whole array of strings and you want to split them all, so you get an array of arrays, you can do so using _.invoke:
_.invoke([
'Samsung A10s',
'Samsung galaxy',
'Nokia 7',
'Samsung S20s ultra',
'Apple iphone ultra',
'LG S20'
], 'split', ' ')
// [ [ 'Samsung', 'A10s' ],
// [ 'Samsung', 'galaxy' ],
// [ 'Nokia', '7' ],
// [ 'Samsung', 'S20s', 'ultra' ],
// [ 'Apple', 'iphone', 'ultra' ],
// [ 'LG', 'S20' ] ]
Find the words that two arrays have in common
If you have two arrays of words,
var words1 = [ 'Samsung', 'galaxy', 'A20s', 'ultra' ],
words2 = [ 'Apple', 'iphone', 'ultra' ];
then you can get a new array with just the words they have in common using _.intersection:
_.intersection(words1, words2) // [ 'ultra' ]
Count the number of words in an array
This is again something you don't need Underscore for:
[ 'Samsung', 'A10s' ].length // 2
But if you have multiple arrays of words, you can get the word counts for all of them using _.map:
_.map([
[ 'Samsung', 'A10s' ],
[ 'Samsung', 'galaxy' ],
[ 'Nokia', '7' ],
[ 'Samsung', 'S20s', 'ultra' ],
[ 'Apple', 'iphone', 'ultra' ],
[ 'LG', 'S20' ]
], 'length')
// [ 2, 2, 2, 3, 3, 2 ]
Sort an array by some criterion
_.sortBy does this. For example, the phones data by id:
_.sortBy(phones, 'id')
// [ { name: 'Apple iphone ultra', id: 159 },
// { name: 'Samsung S20s ultra', id: 514 },
// { name: 'Nokia 7', id: 814 },
// { name: 'Samsung galaxy', id: 839 },
// { name: 'Samsung A10s', id: 845 },
// { name: 'LG S20', id: 854 } ]
To sort descending instead of ascending, you can first sort ascending and then reverse the result using the builtin reverse method:
_.sortBy(phones, 'id').reverse()
// [ { name: 'LG S20', id: 854 },
// { name: 'Samsung A10s', id: 845 },
// { name: 'Samsung galaxy', id: 839 },
// { name: 'Nokia 7', id: 814 },
// { name: 'Samsung S20s ultra', id: 514 },
// { name: 'Apple iphone ultra', id: 159 } ]
You can also pass a criterion function. The function receives the current item and it can do anything, as long as it returns a string or number to use as the rank of the current item. For example, this sorts the phones by the last letter of the name (using _.last):
_.sortBy(phones, function(phone) { return _.last(phone.name); })
// [ { name: 'LG S20', id: 854 },
// { name: 'Nokia 7', id: 814 },
// { name: 'Samsung S20s ultra', id: 514 },
// { name: 'Apple iphone ultra', id: 159 },
// { name: 'Samsung A10s', id: 845 },
// { name: 'Samsung galaxy', id: 839 } ]
Group the elements of an array by some criterion
Instead of sorting directly, we might also first only group the items by a criterion. Here's grouping the phones by the first letter of the name, using _.groupBy and _.first:
_.groupBy(phones, function(phone) { return _.first(phone.name); })
// { S: [ { name: 'Samsung A10s', id: 845 },
// { name: 'Samsung galaxy', id: 839 },
// { name: 'Samsung S20s ultra', id: 514 } ],
// N: [ { name: 'Nokia 7', id: 814 } ],
// A: [ { name: 'Apple iphone ultra', id: 159 } ],
// L: [ { name: 'LG S20', id: 854 } ] }
We have seen that we can pass keys to sort or group by, or a function that returns something to use as a criterion. There is a third option which we can use here instead of the function above:
_.groupBy(phones, ['name', 0])
// { S: [ { name: 'Samsung A10s', id: 845 },
// { name: 'Samsung galaxy', id: 839 },
// { name: 'Samsung S20s ultra', id: 514 } ],
// N: [ { name: 'Nokia 7', id: 814 } ],
// A: [ { name: 'Apple iphone ultra', id: 159 } ],
// L: [ { name: 'LG S20', id: 854 } ] }
Getting the keys of an object
This is what _.keys is for:
_.keys({name: "Samsung A10s", id: 845}) // [ 'name', 'id' ]
You can also do this with the standard Object.keys. _.keys works in old environments where Object.keys doesn't. Otherwise, they are interchangeable.
Turn an array of things into other things
We have previously seen the use of _.map to get the lengths of multiple arrays of words. In general, it takes an array or object and something that you want to be done with each element of that array or object, and it will return an array with the results:
_.map(phones, 'id')
// [ 845, 839, 814, 514, 159, 854 ]
_.map(phones, ['name', 0])
// [ 'S', 'S', 'N', 'S', 'A', 'L' ]
_.map(phones, function(phone) { return _.last(phone.name); })
// [ 's', 'y', '7', 'a', 'a', '0' ]
Note the similarity with _.sortBy and _.groupBy. This is a general pattern in Underscore: you have a collection of something and you want to do something with each element, in order to arrive at some sort of result. The thing you want to do with each element is called the "iteratee". Underscore has a function that ensures you can use the same iteratee shorthands in all functions that work with an iteratee: _.iteratee.
Sometimes you may want to do something with each element of a collection and combine the results in a way that is different from what _.map, _.sortBy and the other Underscore functions already do. In this case, you can use _.reduce, the most general function of them all. For example, here's how we can create a mixture of the names of the phones, by taking the first letter of the name of the first phone, the second letter of the name of the second phone, and so forth:
_.reduce(phones, function(memo, phone, index) {
return memo + phone.name[index];
}, '')
// 'Sakse0'
The function that we pass to _.reduce is invoked for each phone. memo is the result that we've built so far. The result of the function is used as the new memo for the next phone that we process. In this way, we build our string one phone at a time. The last argument to _.reduce, '' in this case, sets the initial value of memo so we have something to start with.
Concatenate multiple arrays into a single one
For this we have _.flatten:
_.flatten([
[ 'Samsung', 'A10s' ],
[ 'Samsung', 'galaxy' ],
[ 'Nokia', '7' ],
[ 'Samsung', 'S20s', 'ultra' ],
[ 'Apple', 'iphone', 'ultra' ],
[ 'LG', 'S20' ]
])
// [ 'Samsung', 'A10s', 'Samsung', 'galaxy', 'Nokia', '7',
// 'Samsung', 'S20s', 'ultra', 'Apple', 'iphone', 'ultra',
// 'LG', 'S20' ]
Putting it all together
We have an array of phones and a search string, we want to somehow compare each of those phones to the search string, and finally we want to combine the results of that so we get the phones by relevance. Let's start with the middle part.
Does "each of those phones" ring a bell? We are creating an iteratee! We want it to take a phone as its argument, and we want it to return the number of words that its name has in common with the search string. This function will do that:
function relevance(phone) {
return _.intersection(phone.name.split(' '), searchTerms).length;
}
This assumes that there is a searchTerms variable defined outside of the relevance function. It has to be an array with the words in the search string. We'll deal with this in a moment; let's address how to combine our results first.
While there are many ways possible, I think the following is quite elegant. I start with grouping the phones by relevance,
_.groupBy(phones, relevance)
but I want to omit the group of phones that have zero words in common with the search string:
var groups = _.omit(_.groupBy(phones, relevance), '0');
Note that I'm omitting the string key '0', not the number key 0, because the result of _.groupBy is an object, and the keys of an object are always strings.
Now we need to order the remaining groups by the number of matching words. We know the number of matching words for each group by taking the keys of our groups,
_.keys(groups)
and we can sort these ascending first, but we must take care to cast them back to numbers, so that we will sort 2 before 10 (numerical comparison) instead of '10' before '2' (lexicographical comparison):
_.sortBy(_.keys(groups), Number)
then we can reverse this in order to arrive at the final order of our groups.
var tiers = _.sortBy(_.keys(groups), Number).reverse();
Now we just need to transform this sorted array of keys into an array with the actual groups of phones. To do this, we can use _.map and _.propertyOf:
_.map(tiers, _.propertyOf(groups))
Finally, we only need to flatten this into one big array, in order to have our search results by relevance.
_.flatten(_.map(tiers, _.propertyOf(groups)))
Let's wrap all of this up into our searchByRelevance function. Remember that we still needed to define searchTerms outside of our relevance iteratee:
function searchByRelevance(phones, searchString) {
var searchTerms = searchString.split(' ');
function relevance(phone) {
return _.intersection(phone.name.split(' '), searchTerms).length;
}
var groups = _.omit(_.groupBy(phones, relevance), '0');
var tiers = _.sortBy(_.keys(groups), Number).reverse();
return _.flatten(_.map(tiers, _.propertyOf(groups)));
}
Now put it to the test!
searchByRelevance(phones, 'Samsung galaxy A20s ultra')
// [ { name: 'Samsung galaxy', id: 839 },
// { name: 'Samsung S20s ultra', id: 514 },
// { name: 'Samsung A10s', id: 845 },
// { name: 'Apple iphone ultra', id: 159 } ]
What is "best"?
If you measure "goodness" by the number of lines of code, then less code is generally better. We implemented searchByRelevance above in just eight lines of code, so that seems pretty good.
It is, however, a bit dense. The number of lines increases, but the readability improves a bit, if we use chaining:
function searchByRelevance(phones, searchString) {
var searchTerms = searchString.split(' ');
function relevance(phone) {
return _.intersection(phone.name.split(' '), searchTerms).length;
}
var groups = _.chain(phones)
.groupBy(relevance)
.omit('0');
return groups.keys()
.sortBy(Number)
.reverse()
.map(_.propertyOf(groups.value()))
.flatten()
.value();
}
Yet another dimension of "goodness" is performance. Could searchByRelevance be faster? To get a sense of this, we usually take the smallest and most frequent operation, and we calculate how often we'll be executing that operation for a given size of input.
The main thing we'll be doing a lot in searchByRelevance, is comparing words. This is not the smallest operation, because comparing words consists of comparing letters, but because words in English tend to be short, we can pretend for now that comparing two words is our smallest and most executed operation. This makes the calculations a bit easier.
For each phone, we will be comparing each word in its name with each word in our search string. If we have 100 phones, and the average phone name has 3 words, and the search string has 5 words, then we will be making 100 * 3 * 5 = 1500 word comparisons.
Computers are fast, so 1500 is nothing. Generally, if the number of times you execute your smallest step remains under 100000 (100k), you probably won't even notice a delay unless that smallest step is very expensive.
However, the number of word comparisons will grow quite explosively with larger inputs. If we have 20000 (20k) phones, 5 words in the average name and a search string of 10 words, we are already making a million word comparisons. That could mean staring at your screen for a few seconds before the results come in.
Can we write a variant of searchByRelevance that can search 20k phones with long names in an eyeblink? Yes, and in fact we can probably also do a million or more! I won't go into the details line by line, but we can get much better speed by using appropriate lookup structures:
// lookup table by word in the name
function createIndex(phones) {
return _.reduce(phones, function(lookup, phone) {
_.each(phone.name.split(' '), function(word) {
var matchingPhones = (lookup[word] || []);
matchingPhones.push(phone.id);
lookup[word] = matchingPhones;
});
return lookup;
}, {});
}
// search using lookup tables
function searchByRelevance(phonesById, idsByWord, searchString) {
var groups = _.chain(searchString.split(' '))
.map(_.propertyOf(idsByWord))
.compact()
.flatten()
.countBy()
.pairs()
.groupBy('1');
return groups.keys()
.sortBy(Number)
.reverse()
.map(_.propertyOf(groups.value()))
.flatten(true) // only one level of flattening
.map('0')
.map(_.propertyOf(phonesById))
.value();
}
To use this, we create the lookup tables once, then reuse them for each search. We need to recreate the lookup tables only if the JSON data of phones change.
var phonesById = _.indexBy(phones);
var idsByWord = createIndex(phones);
searchByRelevance(phonesById, idsByWord, 'Samsung galaxy A20s ultra')
// [ { name: 'Samsung galaxy', id: 839 },
// { name: 'Samsung S20s ultra', id: 514 },
// { name: 'Samsung A10s', id: 845 },
// { name: 'Apple iphone ultra', id: 159 } ]
searchByRelevance(phonesById, idsByWord, 'Apple')
// [ { name: 'Apple iphone ultra', id: 159 } ]
To appreciate how much faster this is, let's count the smallest operations again. In createIndex, the smallest most frequent operation is storing an association between a word and the id of a phone. We do this once for each phone, for each word in its name. In searchByRelevance, the smallest most frequent operation is incrementing the relevance of a given phone in the countBy step. We do this once for each word in the search string, for each phone that matches that word.
We can estimate the number of matching phones for a given search string if we make some reasonable assumptions. The most frequent words in the phone names are probably the brands, such as "Samsung" and "Apple". Since there are at least ten brands, we can assume that the number of phones that match a given search term is generally less than 10% of the total number of phones. So the time it takes to execute one search is the number of words in the search string, times the number of phones, times 10% (i.e., divided by 10).
So if we have 100 phones with on average 3 words in the name, then indexing takes 100 * 3 = 300 times storing an association in the idsByWord lookup table. Performing a search with 5 words in the search string takes only 5 * 100 * 10% = 50 relevance increments. This is already much faster than the 1500 word comparisons we needed without lookup tables, although the human behind the computer will not notice the difference in this case.
The speed advantage of the approach with the lookup table further increases with larger inputs:
┌───────────────────┬───────┬────────┬───────┐
│ Problem size │ Small │ Medium │ Large │
├───────────────────┼───────┼────────┼───────┤
│ phones │ 100 │ 20k │ 1M │
│ words per name │ 3 │ 5 │ 8 │
│ search terms │ 5 │ 10 │ 15 │
├───────────────────┼───────┼────────┼───────┤
│ w/o lookup tables │ │ │ │
│ word comparisons │ 1500 │ 1M │ 120M │
├───────────────────┼───────┼────────┼───────┤
│ w/ lookup tables │ │ │ │
│ associations │ 300 │ 100k │ 8M │
│ increments │ 50 │ 20k │ 1.5M │
└───────────────────┴───────┴────────┴───────┘
This is, in fact, still underestimating the speed advantage, since the percentage of phones that match a given search term is likely to drop as the number of phones increases.
Lookup tables make searching much faster. But is it better? As I said before, for small problem sizes, the speed difference will not be noticable. A disadvantage of the lookup tables is that this requires more code, which makes it a bit harder understand, as well as taking more effort to maintain. It also requires a lookup table as large as the number of associations, which means we will be using much more additional memory than before.
To conclude, what is "best" always depends on a tradeoff between different constraints, such as code size, speed and memory usage. It is up to you to decide how you want to weigh these constraints relative to each other.

How can I describe this JSON object in swagger parameters?

I've looked at a few other related questions and I still can't seem to find what I'm looking for. This is an example JSON payload being sent to an API I'm writing:
{
"publishType": "Permitable",
"electricalPanelCapacity": 0.0,
"roofConstruction": "Standard/Pitched",
"roofType": "Composition Shingle",
"systemConstraint": "None",
"addedCapacity": 0.0,
"isElectricalUpgradeRequired": false,
"cadCompletedBy": "94039",
"cadCompletedDate": "2017-02-01T02:18:15Z",
"totalSunhourDeficit": 5.0,
"designedSavings": 5.0,
"isDesignedWithinTolerance": "N/A",
"energyProduction": {
"january": 322.40753170051255,
"february": 480.61501312589826,
"march": 695.35215022905118,
"april": 664.506907341219,
"may": 877.53769491124172,
"june": 785.56924358327,
"july": 782.64347308783363,
"august": 760.1123565793057,
"september": 574.67050827435878,
"october": 524.53797441350321,
"november": 324.31132291046379,
"december": 280.46921069200033
},
"roofSections": [{
"name": "North East Roof 4",
"roofType": "Composition Shingle",
"azimuth": 55.678664773137086,
"tilt": 15.0,
"solmetricEstimate": 510.42831656979456,
"shadingLoss": 14.0,
"systemRating": 580.0,
"sunHours": 0.88004882167205956,
"moduleCount": 2,
"modules": [{
"moduleRating": 290.0,
"isovaPartNumber": "CDS-MON-007070",
"partCount": 2
}]
}, {
"name": "South West Roof 3",
"roofType": "Composition Shingle",
"azimuth": 235.67866481720722,
"tilt": 38.0,
"solmetricEstimate": 3649.1643776261653,
"shadingLoss": 59.0,
"systemRating": 5220.0,
"sunHours": 0.69907363556056812,
"moduleCount": 18,
"modules": [{
"moduleRating": 290.0,
"isovaPartNumber": "CDS-MON-007070",
"partCount": 18
}]
}, {
"name": "South East Roof",
"roofType": "Composition Shingle",
"azimuth": 145.67866477313709,
"tilt": 19.0,
"solmetricEstimate": 2913.1406926526984,
"shadingLoss": 31.0,
"systemRating": 2900.0,
"sunHours": 1.0045312733285168,
"moduleCount": 10,
"modules": [{
"moduleRating": 290.0,
"isovaPartNumber": "CDS-MON-007070",
"partCount": 10
}]
}],
"SystemConfiguration": {
"inverters": [{
"isovaPartNumber": "ENP-INV-007182",
"partCount": 30
}]
}
}
Describing all the beginning parameters was easy.
/post/new-cad/{serviceNumber}:
post:
summary: Publish a new CAD record.
description: Creates a new CAD record under the provided service number and returns the name of the new CAD record, the unique SF ID, and the deep link URL for Salesforce.
parameters:
- name: serviceNumber
in: path
description: The service number for the solar project you're interested in publishing to.
required: true
type: string
- name: publishType
in: formData
description: The type of CAD record to publish (Proposal, Permitable, or AsBuilt).
required: true
type: string
- name: electricalPanelCapacity
in: formData
required: true
type: number
format: double
- name: roofConstruction
in: formData
description: New, Flat Roof, Open Beam, Standard/Pitched
required: true
type: string
- name: roofType
in: formData
description: Composition Shingle, Membrane (Rubber, TPO, PVC, EPDM), Metal - Corrugated (S-Curve), Metal - Standing Seam, Metal - Trapezoidal, Multi Roof Type, Rolled Comp, Silicone, Tar & Gravel, Tile - Flat, Tile - S-Curve, or Tile - W-Curve
type: string
- name: systemConstraint
in: formData
description: Usage, None, Roof, Electrical, Shading, or 10kW Max
required: true
type: string
- name: addedCapacity
in: formData
required: true
type: number
format: double
- name: isElectricalUpgradeRequired
in: formData
type: boolean
- name: cadCompletedBy
in: formData
description: Employee ID of record author.
type: number
required: true
- name: cadCompletedDate
in: formData
description: The date the CAD record was completed.
type: string
format: date
required: true
- name: totalSunhourDeficit
in: formData
type: number
format: double
- name: designedSavings
in: formData
type: number
format: double
- name: isDesignedWithinTolerance
in: formData
type: string
description: Yes, No, or N/A
And yields the expected result in Swagger-UI:
But now I'm struggling with the last parts of the example JSON payload above. I'm unsure how to express the energyProduction key which is an object with a key for each month of the year. I'm also unsure how to describe roofSections which is an array of objects and systemConfiguration which is an object with a property inverters whose value is an array of objects.
I'm going over the swagger documentation quite a bit but I'm still pretty confused and hoping maybe someone here can explain things a little better to me.
I figured it out. Turns out formData is not what I should have been using for my parameters. Instead I needed to use body and define the structure of the JSON that would populate the body. Here is the completed design file using a body parameter with an object schema and describes all the nested objects and arrays as well.
/new-cad/{serviceNumber}:
post:
summary: Publish a new CAD record.
description: Creates a new CAD record under the provided service number and returns the name of the new CAD record, the unique SF ID, and the deep link URL for Salesforce.
parameters:
- name: serviceNumber
in: path
description: The service number for the solar project you're interested in publishing to.
required: true
type: string
- name: cadData
in: body
description: A JSON payload containing the data required to publish a new CAD record.
required: true
schema:
type: object
properties:
publishType:
type: string
default: "Proposal"
enum: ["Proposal","Permitable","AsBuilt"]
electricalPanelCapacity:
type: number
roofConstruction:
type: string
default: "New"
enum: ["New","Flat Roof","Open Beam","Standard/Pitched"]
roofType:
type: string
enum: ["Composition Shingle","Membrane (Rubber, TPO, PVC, EPDM)","Metal - Corrugated (S-Curve)","Metal - Standing Seam","Metal - Trapezoidal","Multi Roof Type","Rolled Comp","Silicone","Tar & Gravel","Tile - Flat","Tile - S-Curve","Tile - W-Curve"]
systemConstraint:
type: string
default: "None"
enum: ["None","Usage","Roof","Electrical","Shading","10kW Max"]
addedCapacity:
type: number
default: 0
isElectricalUpgradeRequired:
type: boolean
cadCompletedBy:
type: string
cadCompletedDate:
type: string
totalSunhourDeficit:
type: number
designedSavings:
type: number
isDesignedWithinTolerance:
type: string
default: "N/A"
enum: ["N/A","Yes","No"]
energyProduction:
type: object
properties:
january:
type: number
february:
type: number
march:
type: number
april:
type: number
may:
type: number
june:
type: number
july:
type: number
august:
type: number
september:
type: number
october:
type: number
november:
type: number
december:
type: number
roofSections:
type: array
items:
type: object
properties:
name:
type: string
roofType:
type: string
enum: ["Composition Shingle","Membrane (Rubber, TPO, PVC, EPDM)","Metal - Corrugated (S-Curve)","Metal - Standing Seam","Metal - Trapezoidal","Multi Roof Type","Rolled Comp","Silicone","Tar & Gravel","Tile - Flat","Tile - S-Curve","Tile - W-Curve"]
azimuth:
type: number
tilt:
type: number
solmetricEstimate:
type: number
shadingLoss:
type: number
systemRating:
type: number
sunHours:
type: number
moduleCount:
type: integer
modules:
type: array
items:
type: object
properties:
moduleRating:
type: number
isovaPartNumber:
type: string
partCount:
type: integer
systemConfiguration:
type: object
properties:
inverters:
type: array
items:
type: object
properties:
isovaPartNumber:
type: string
partCount:
type: integer
tags:
- NEW-CAD
responses:
200:
description: CAD record created successfully.
schema:
type: object
properties:
cadName:
type: string
sfId:
type: string
sfUrl:
type: string
examples:
cadName: some name
sfId: a1o4c0000000GGAQA2
sfUrl: http://some-url-to-nowhere.com
204:
description: No project could be found for the given service number.
500:
description: Unexpected error. Most likely while communicating with Salesforce.
schema:
type: string
So now I can still get the serviceNumber from the path while everything else comes in the request body. One thing to keep in mind here is that you cannot use all the same Swagger Data Types. For example I tried to use double for one of the properties and Swagger complained that it couldn't parse type double. I was very confused until I finally found the section of the docs describing the difference between formData parameters and a body parameter (of which you can only have one, because it describes the entire request body). Basically you can only use data types that are supported by the JSON schema.
Swagger-UI now shows a single textarea instead of multiple input fields for each parameter. Not as pretty but it works great. You can click the "Example Value" box on the right and it places a predefined JSON template in the textarea for you so you can just fill in the values.
If you are just learning Swagger like I am I hope this helps!

Parsing JSON data from Yelp API

I'm new to programming and am trying to parse some data returned from Yelp's API. From this data, how could I return something like just the phone number (display_phone) and address? Thank you
Result for business "little-miss-bbq-phoenix-2" found:
{ u'categories': [[u'Barbeque', u'bbq']],
u'display_phone': u'+1-602-437-1177',
u'id': u'little-miss-bbq-phoenix-2',
u'image_url': u'http://s3-media2.fl.yelpcdn.com/bphoto/4Rcm0IIbRhdo-4Z4KPvuXQ/ms.jpg',
u'is_claimed': True,
u'is_closed': False,
u'location': { u'address': [u'4301 E University Dr'],
u'city': u'Phoenix',
u'coordinate': { u'latitude': 33.421587,
u'longitude': -111.989088},
u'country_code': u'US',
u'display_address': [ u'4301 E University Dr',
u'Phoenix, AZ 85034'],
u'geo_accuracy': 9.5,
u'postal_code': u'85034',
u'state_code': u'AZ'},
u'mobile_url': u'http://m.yelp.com/biz/little-miss-bbq-phoenix-2',
u'name': u'Little Miss BBQ',
u'phone': u'6024371177',
u'rating': 5.0,
u'rating_img_url': u'http://s3-media1.fl.yelpcdn.com/assets/2/www/img/f1def11e4e79/ico/stars/v1/stars_5.png',
u'rating_img_url_large': u'http://s3-media3.fl.yelpcdn.com/assets/2/www/img/22affc4e6c38/ico/stars/v1/stars_large_5.png',
u'rating_img_url_small': u'http://s3-media1.fl.yelpcdn.com/assets/2/www/img/c7623205d5cd/ico/stars/v1/stars_small_5.png',
u'review_count': 403,
u'reviews': [ { u'excerpt': u"I saw that this place had almost 400 reviews and that they have a perfect 5 star rating. It sounded too good to be true BUT it's worth every star and...",
u'id': u'-9poa0ycpVnOveVlqbYE9Q',
u'rating': 5,
u'rating_image_large_url': u'http://s3-media3.fl.yelpcdn.com/assets/2/www/img/22affc4e6c38/ico/stars/v1/stars_large_5.png',
u'rating_image_small_url': u'http://s3-media1.fl.yelpcdn.com/assets/2/www/img/c7623205d5cd/ico/stars/v1/stars_small_5.png',
u'rating_image_url': u'http://s3-media1.fl.yelpcdn.com/assets/2/www/img/f1def11e4e79/ico/stars/v1/stars_5.png',
u'time_created': 1431095420,
u'user': { u'id': u'd43iQ50HjWIl4vN4rBgoVQ',
u'image_url': u'http://s3-media4.fl.yelpcdn.com/photo/sn17KBbRjXOEELnjSir1tg/ms.jpg',
u'name': u'Jason J.'}}],
u'snippet_image_url': u'http://s3-media4.fl.yelpcdn.com/photo/sn17KBbRjXOEELnjSir1tg/ms.jpg',
u'snippet_text': u"I saw that this place had almost 400 reviews and that they have a perfect 5 star rating. It sounded too good to be true BUT it's worth every star and...",
u'url': u'http://www.yelp.com/biz/little-miss-bbq-phoenix-2'}

jqGrid - Pagination not working properly

As you can see in this image
I have 13 records on my DB but the pager says it has only 1 page (with 10 rows), which is not correct.
Relevant part of the code from my .js
function cria(){
$("#grid").jqGrid({
datatype: 'json',
url: 'json.jsp',
jsonReader: {repeatitems: false},
pager: '#paginado',
rowNum: 10,
rowList: [10,20,30],
emptyrecords: "Não há registros.",
recordtext: "Registros {0} - {1} de {2}",
pgtext: "Página {0} de {1}",
colNames:['Código','Descrição'],
colModel:[
{name:'codigo', width:80, sorttype:"int", sortable: true, editable: false},
{name:'descricao', width:120, sortable: true, editable: true, editrules:{required:true}}
],
viewrecords: true,
editurl:"dadosGrid.jsp?edit=true",
caption: "Grupos",
hiddengrid: true
});
$("#grid").jqGrid('navGrid','#paginado',{},
{edit:true,url:"Adm?aux=edit",closeAfterEdit:true,reloadAfterSubmit:true},
{add:true,url:"Adm?aux=add",closeAfterAdd:true,reloadAfterSubmit:true},
{del:false},
{search:true},
{refresh:true});
};
Relevant part of the code from my .jsp
String json = "[";
for (UserAux user : users ){
json += "{";
json += "\"codigo\":\""+user.getCod()+"\",";
json += "\"descricao\":\""+user.getDescricao()+"\",";
json += "},";
}
json = json.substring(0,json.length()-1);
json += "]";
out.println(json);
%>
Default options of jqGrid means that you implements server side paging. If you want to returns all data at once from the server (which would be good choice if you have 13 records) you should just add loadonce: true option.
Additionally I would recommend you to add gridview: true, autoencode: true and height: "auto" option to your jqGrid. Moreover you should remove edit:true, del:false, search:true and refresh:true which you use inside of options navGrid because you use there on the wrong place. If you want to specify the options you should specify properties of the second parameter (which is {} in your code).