I have right now a problem.
I created a View in my MySQL instance to get the result of multiples joins.
Then in the appmaker i got that view as a Google Cloud SQL View.
I drop this as table and made a query script to apply filters to the results (filters are made by the value of 3 dropdowns).
But now i have a problem: Sort does not work! I click in any header of the table and the arrow (sort indicator) appears, but sort does not work.
Someone have a solution for this?
query script:
var status = query.parameters.Status;
var operation = query.parameters.Operation;
var local = query.parameters.Local;
var concat = query.parameters.Concat;
var query = app.models.DRIVERS_LIST.newQuery();
switch(status) {
case 'Activos':
query.filters.DoprEndDate._equals = null;
query.sorting.Name._ascending();
break;
case 'Inactivos':
query.filters.DoprEndDate._notEquals = null;
//query.sorting.Name._ascending();
break;
case 'Todos':
query.clearFilters();
//query.sorting.Name._ascending();
break;
default:
query.filters.DoprEndDate._equals = null;
query.sorting.Name._ascending();
}
if (operation !== null) {
query.filters.Operation._equals = operation;
//query.sorting.Name._ascending();
}
if (local !== null) {
query.filters.Local._equals = local;
//query.sorting.Name._ascending();
}
if (concat !== null) {
query.filters.concatAll._contains = concat;
//query.sorting.Name._ascending();
}
return query.run();
When user clicks table header App Maker sets sorting to the query and passes it to the server side. In your server script you have some code, that can potentially reset sorting settings provided by user. If you want to sort table on header click, then I would recommend to remove all sorting-related code from your server script.
Related
A simple testing setup: department: employee, 1:M and a search form that allows filtering on Emploee FirstName =, lastname =, email contains, age >=, join date <= and related department =.
A search form with widgets bound to parameters of a cloud SQL datasource query script.
A Submit button on the search form which opens up a query results page with a table bound to the cloud SQL query script datasource.
query script
var params = query.parameters;
return getEmployeeRecords_(
params.param_FirstName,
params.param_LastName,
params.param_Email,
params.param_StartDate,
params.param_Age,
params.param_Department
);
and
function getEmployeeRecords_( firstName, lastName, email, startDate, age,
department) {
var ds = app.models.Employee.newQuery();
if ( firstName !== null ) {
ds.filters.FirstName._equals = firstName;
}
if ( lastName !== null ) {
ds.filters.LastName._equals = lastName;
}
if ( email !== null) {
ds.filters.Email._contains = email;
}
if ( startDate !== null) {
ds.filters.StartDate._greaterThanOrEquals = startDate;
}
if ( age !== null) {
ds.filters.Age._lessThanOrEquals = parseInt(age, 10);
}
if ( department !== null) {
ds.filters.Department.Department._equals = department;
}
var records = ds.run();
// intention is to store this value for future use
var recs = records.length;
return records;
}
On the results page for the query script datasource paging is just broken. A query that correctly returns 8 records where the query page size is set to 5 allows me to get the pager to go to page 1000 if I wished, but the datasource always stays on the first page of records. With page size set to e.g., 100 the correct result set is clearly displayed.
In fact everything I do with this sort of query has paging issues. If I insert this code
var ds = app.models.Employee.newQuery();
//ds.filters.FirstName._equals = firstName;
//ds.filters.LastName._equals = lastName;
//ds.filters.Email._contains = '.com';
//ds.filters.StartDate._greaterThanOrEquals = startDate;
ds.filters.Age._lessThanOrEquals = 40;
//ds.filters.Department.Department._equals = department;
ds.sorting.Age._ascending();
var records = ds.run();
return records;
directly into the datasource query script I still have similar paging issues.
If I use a query builder script such as
(
FirstName =? :param_FirstName and
LastName =? :param_LastName and
Email contains? :param_Email and
StartDate >=? :param_Startdate and
Age <=? :param_Age and
Department.Department =? :param_Department
)
and bindings such as
#datasources.Search_Query_Builder.query.parameters.param_FirstName
this works without issue. The same with direct filtering, where we use bindings such as
#datasources.Employee.query.filters.FirstName._equals
Anyone any ideas in terms of what is wrong with this stuff. We need query scripts for more controle, e.g., the ability to retrieve a count of records and where you have to filter for a condition where you restrict data, e.g. a logged in user is related to a client which in turn is related to a property and the property value is restricted according to client.
... Just looking at a real application under development and the use of a query script within the datasource query script editor, no parameters, no binding, just this code:-
var ds = app.models.Incident.newQuery();
ds.filters.Id._greaterThanOrEquals = 200;
ds.filters.Id._lessThanOrEquals = 300;
var records = ds.run();
return records;
and a page size set to 20 and again the paging is up the creek, never moves beyond the first page of records despite the page number incrementing.
I have some suggestions how to address this issue, although it is still unclear what exactly is causing the paging issue and whether or not my suggestions will fix the underlying issue. However, in my own application environment I have several instances where I use a standard SQL model and I apply filters to a datasource from that model and then have a concurrent calculated model (datasource) that returns the total count of records that meet the filters applied to my other datasource. Here we go:
Create a new datasource under your Employee model, leave it on the default 'Query Builder' type, adjust the query page size to your preference, but preferably to something that you know will return more than one page of records when querying the datasource. Uncheck the 'automatically load data' property unless you want to load all records when first going to your page where you set your filters. Do not enter anything in the query builder.
For the calculated datasource that you called Employee_RecordCount add your parameters, FirstName_equals, LastName_equals, Email_contains, StartDate_greaterequals, Age_lessequals, and Departments_equals, if you have not already. In this calculated model you should have a field called RecordCount. In the query script section of this datasource you should put your function as return getEmployeeRecords_(query).
In your server script section where your getEmployeeRecords function is the code should be as follows:
function getEmployeeRecords_(query) {
var params = query.parameters;
var ds = app.models.Employee.newQuery();
if (params.FirstName_equals !== null ) {
ds.filters.FirstName._equals = params.FirstName_equals;
}
if (params.LastName_equals !== null ) {
ds.filters.LastName._equals = params.LastName_equals;
}
if (params.Email_contains !== null) {
ds.filters.Email._contains = params.Email_contains;
}
if (params.StartDate_greaterequals !== null) {
ds.filters.StartDate._greaterThanOrEquals = params.StartDate_greaterequals;
}
if (params.Age_lessequals !== null) {
ds.filters.Age._lessThanOrEquals = parseInt(params.Age_lessequals, 10);
}
if (params.Department_equals !== null) {
ds.filters.Department.Department._equals = params.Department_equals;
}
var records = ds.run();
// update calculated model with record count
var calculatedModelRecord = app.models.Employee_RecordCount.newRecord();
calculatedModelRecord.RecordCount = records.length;
return [calculatedModelRecord];
}
Now go to your search page, create a new panel or form set it to the same new datasource that you created. Make sure you have all your appropriate fields and change the binding of these fields to:
#datasource.query.filters.firstName._equals
#datasource.query.filters.lastName._equals
#datasource.query.filters.email._contains
#datasource.query.filters.StartDate._greaterThanOrEquals
#datasource.query.filters.Age._lessThanOrEquals
#datasource.query.fitlers.Department.Department._equals
The button that initiates your search should have the following code:
var ds = widget.datasource;
ds.load(function() {
app.showPage(app.pages.YourSearchResultPage);
}
var calculatedDs = app.datasources.Employee_RecordCount;
var props = calculatedDs.properties;
props.FirstName_equals = ds.query.filters.firstName._equals;
props.LastName_equals = ds.query.filters.lastName._equals;
props.Email_contains = ds.query.filters.email._contains;
props.StartDate_greaterequals = ds.query.filters.StartDate._greaterThanOrEquals;
props.Age_lessequals = ds.query.filters.Age._lessThanOrEquals;
props.Department_equals = ds.query.filters.Department.Department._equals;
calculatedDs.load();
Now go to your search result page and make sure the you have the following elements:
A panel that the datasource is set to Employee_RecordCount. Inside
this panel create a label and set the binding to
#datasource.item.RecordCount.
A table that has the datasource set
to the same datasource as created in the first step. Make sure your
table has 'pagination' turned on.
That should be all, and this works in my application. It is a pain to set up, but I'm afraid it is the only workaround to have a total count of records. I should note that I have never had any paging issues either.
I'm making an app with an advanced search feature which can help users filter data from dropdowns and textboxes (Dropdown to choose column and clause, Textbox for entering search parameter) like this one:
Advanced Search page sample:
I tried to bind the Column's name dropdown to #datasource.query.parameters.Parameter and changed the Query part of the datasource like this:
Datasource's Query Script and Parameters:
However, I keep getting errors like:
Parameter 'Column' is used in 'where' clause but not defined in property 'parameters'
Could you please tell me how can I resolve this problem?
You literally have to construct your 'Where' clause in this case and then set the parameter of the where clause equal to your parameters. So lets say your first set of parameters is Column1: Name, Query1: contains, Parameter1: John, then your datasource needs to have the following parameters Column1, Query1, and Parameter1 and your bindings on your dropdown1, dropdown2, and textbox1 should be:
#datasource.query.parameters.Column1
#datasource.query.parameters.Query1
#datasource.query.parameters.Parameter1
respectively.
Then your query script needs to be as follows:
if (query.parameters.Field1 === null || query.parameters.Query1 === null) {
throw new app.ManagedError('Cannot complete query without Parameters!');
}
switch (app.metadata.models.MaintenanceManagement.fields[query.parameters.Field1].type) {
case 'Number':
query.parameters.Parameter1 = Number(query.parameters.Parameter1);
break;
case 'Date':
query.parameters.Parameter1 = new Date(query.parameters.Parameter1);
break;
case 'Boolean':
if (query.parameters.Parameter1 === 'True' || query.parameters.Parameter1 === 'true') {
query.parameters.Parameter1 = true;
} else {
query.parameters.Parameter1 = false;
}
break;
default:
query.parameters.Parameter1 = query.parameters.Parameter1;
}
query.where = query.parameters.Column1 + " " + query.parameters.Query1 + "? :Parameter1";
return query.run();
So your where statement essentially becomes a string that reads 'Name contains? :Parameter1' (i.e. John) that then becomes your query. Hope this makes sense, feel free to ask follow up questions.
I'm sure I'm doing something wrong... but every time I query on a calculated datasource, I get the error "cannot handle returning cyclic object."
Here's the gist:
I have a calculated model that fetches a user's google contacts and places the full name field into a table on the UI. The goal is to have a separate text box that can be used to search the full name field and then repopulate the table on the same page with the results of the search, similar to how google contacts search behavior works. The on value change event of the text box sends the textbox value to this server script:
function searchContacts (sq) {
var ds = app.models.Contacts.newQuery();
ds.filters.FullName._contains = sq;
var results = ds.run();
return results;
}
But every time I get the cyclic object error when the values are returned from that function. The error actually fires when the query run command (ds.run) is executed.
I've tried querying the datasource as well, but I've read somewhere that you can't query the datasource of a calculated model because it doesn't exist, so you have to query the model.
Any help would be much appreciated.
From your question it is not 100% clear, what you are trying to do. In case you are actually using Calculated Model, then your Server Script Query should look like this:
var sq = query.parameters.SearchQuery;
var contactsQuery = app.models.Contacts.newQuery();
contactsQuery.filters.FullName._contains = sq;
var contacts = ds.run();
var results = contacts.map(function(contact) {
var calcRecord = app.MyCalcModel.newRecord();
calcRecord.Name = contact.FullName;
return calcRecord;
});
return results;
Note, that you cannot return objects of arbitrary type from Server Script Query, only of type of this particular Calculated Model.
But from some parts of your description and error text if feels like you are trying to load records with async serever call using google.scritp.run. In this case you cannot return App Maker records(App Script doesn't allow this) and you need to map them to simple JSON objects.
I don't think I was super-clear on my original post.
I have a calculated model that is all of the user's contacts from Google Contacts (full name, email, mobile, etc...) On the UI I have a list widget that's populated with all of the Full Name fields and above the list widget a text input that's used to search the list widget. So the search text box's on input change event sends a request to query the Full Names, similar to how Google Contact's search feature works.
Screen Shot
It appears that App Maker doesn't let you query calculated models, so I have this workaround - unless someone comes up with something better:
This is the onInputChange handler for the search text box:
sq = app.pages.SelectClient.descendants.TextBox1.value;
app.datasources.SearchContacts.query.parameters.Name = sq;
app.datasources.SearchContacts.load();
This is the Server Script Code (thanks to #Pavel Shkleinik for the heads up):
var sq = query.parameters.Name;
if (sq !== null) {
return getContactsbyName(sq);
} else {
return getContacts();
}
And the server code with no query:
function getContacts() {
var results = [];
var contacts = ContactsApp.getContacts();
contacts.forEach(function(item) {
var contact = app.models.Contacts.newRecord();
contact.FullName = item.getFullName();
var emails = item.getEmails(ContactsApp.Field.WORK_EMAIL);
if (emails.length > 0) {
contact.PrimaryEmail = emails[0].getAddress();
}
contact.LastName = item.getFamilyName();
contact.FirstName = item.getGivenName();
var phones = item.getPhones(ContactsApp.Field.MOBILE_PHONE);
if (phones.length > 0) {
contact.Mobile = phones[0].getPhoneNumber();
}
var addresses = item.getAddresses(ContactsApp.Field.WORK_ADDRESS);
if (addresses.length > 0) {
contact.Address = addresses[0].getAddress();
}
results.push(contact);
results.sort();
});
return results;
}
And with the query:
function getContactsbyName(sq) {
var results = [];
var contacts = ContactsApp.getContactsByName(sq);
contacts.forEach(function(item) {
var contact = app.models.Contacts.newRecord();
contact.FullName = item.getFullName();
var emails = item.getEmails(ContactsApp.Field.WORK_EMAIL);
if (emails.length > 0) {
contact.PrimaryEmail = emails[0].getAddress();
}
contact.LastName = item.getFamilyName();
contact.FirstName = item.getGivenName();
var phones = item.getPhones(ContactsApp.Field.MOBILE_PHONE);
if (phones.length > 0) {
contact.Mobile = phones[0].getPhoneNumber();
}
var addresses = item.getAddresses(ContactsApp.Field.WORK_ADDRESS);
if (addresses.length > 0) {
contact.Address = addresses[0].getAddress();
}
results.push(contact);
results.sort();
});
return results;
}
This way, the list populates with all of the names when there's no search query present, and then re-populates with the search query results as needed.
The only issue is that the call to the Google Contacts App to populate the Calculated Model is sometimes very slow.
Given this code:
var Container = CRM.GetBlock("Container");
var CustomCommunicationDetailBox = CRM.GetBlock("CustomCommunicationDetailBox");
Container.AddBlock(CustomCommunicationDetailBox);
if(!Defined(Request.Form)){
CRM.Mode=Edit;
}else{
CRM.Mode=Save;
}
CRM.AddContent(Container.Execute());
var sHTML=CRM.GetPageNoFrameset();
Response.Write(sHTML);
Im calling this .asp page with this parameters but does not seems to work
popupscreeens.asp?SID=33185868154102&Key0=1&Key1=68&Key2=82&J=syncromurano%2Ftabs%2FCompany%2FCalendarioCitas%2Fcalendariocitas.asp&T=Company&Capt=Calendario%2Bcitas&CLk=T&PopupWin=Y&Key6=1443Act=512
Note the Key6=Comm_Id and Act=512??? which i believe it is when editing?
How can i achieve to fill the screen's field with entity dada?
In this case it is a communication entity
In order to populate a custom screen with data, you need to pass the data to the screen.
First, you need to get the Id value. In this case, we're getting it from the URL:
var CommId = Request.QueryString("Key6") + '';
We're going to put a few other checks in though. These are mainly to handle scenarios that have come up in different versions or from different user actions.
// check we have a value and get the Id from context if we don't
if(CommId == 'undefined'){
CommId = CRM.GetContextInfo("Communication","comm_communicationid");
}
// if CommId is still undefined, set it to zero to check later
// otherwise, make sure the URL only contains one CommId
if(CommId == 'undefined'){
CommId = 0;
} else if(CommId.indexOf(",") > -1){
CommId = CommId.substr(0,CommId.indexOf(","));
}
Certain user actions can make the URL hold multiple Ids in the same attribute. In these cases, those Ids are separated by commas. So, if the Id is not defined, we check if there is a comma in it. If there is, we take the 1st Id.
After we have the Id, we need to load the record. At this point, you should have already checked you have a valid id (E.g. not zero) and put some error handling in. In some pages you may want to display an error, in others you may want to create a new, blank record. This gets the record:
var CommRecord = CRM.FindRecord("communication","comm_communicationid = " + CommId);
After that, you need to apply the record to the screen. Using your example above:
CustomCommunicationDetailBox.ArgObj = CommRecord;
Adding all that to your script, you get:
var CommId = Request.QueryString("Key6") + '';
// check we have a value and get the Id from context if we don't
if(CommId == 'undefined'){
CommId = CRM.GetContextInfo("Communication","comm_communicationid");
}
// if CommId is still undefined, set it to zero to check later
// otherwise, make sure the URL only contains one CommId
if(CommId == 'undefined'){
CommId = 0;
} else if(CommId.indexOf(",") > -1){
CommId = CommId.substr(0,CommId.indexOf(","));
}
// add some error checking here
// get the communication record
var CommRecord = CRM.FindRecord("communication","comm_communicationid = " + CommId);
// get the container and the detail box
var Container = CRM.GetBlock("Container");
var CustomCommunicationDetailBox = CRM.GetBlock("CustomCommunicationDetailBox");
// apply the communication record to the detail box
CustomCommunicationDetailBox.ArgObj = CommRecord;
// add the box to the container
Container.AddBlock(CustomCommunicationDetailBox);
// set the moder
if(!Defined(Request.Form)){
CRM.Mode=Edit;
} else {
CRM.Mode=Save;
}
// output
CRM.AddContent(Container.Execute());
var sHTML=CRM.GetPageNoFrameset();
Response.Write(sHTML);
However, we would advise putting in more error/exception handling. If the user is saving the record, you will also need to add a redirect in after the page is written.
Six Ticks Support
I have a page that need to run a query against a large dataset very often. To ease the burden on the database, I've set up a cache that will refresh itself every 5 minutes.
The logic is:
When a call is made, check if there is data in cache, if it is, run the queryu on the cache. If not, start a task of fetching from all rows from database while running a query on my repository to get out just the data needed for that call. When all rows is fetched, put it in the cache so it can be accessed on the next call. The problem is that I sometimes get a: "Message = "There is already an open DataReader associated with this Command which must be closed first." I guess this is because it runs two queries to the same repository at the same time (one for all rows and one for the query). I've got MARS enabled in my connections string.
My code
public IQueryable<TrackDto> TrackDtos([FromUri] int[] Ids)
{
if (HttpContext.Current.Cache["Tracks"] != null && ((IQueryable<TrackDto>)HttpContext.Current.Cache["Tracks"]).Any())
{
var trackDtos = Ids.Length > 0
? ((IQueryable<TrackDto>)HttpContext.Current.Cache["Tracks"]).Where(trackDto => Ids.Contains(trackDto.Id).AsQueryable()
: ((IQueryable<TrackDto>)HttpContext.Current.Cache["Tracks"]).AsQueryable();
return trackDtos;
}
else
{
UpdateTrackDtoCache(DateTime.Today);
var trackDtos = Ids.Length > 0
? WebRepository.TrackDtos.Where(trackDto => trackDto.Date == DateTime.Today && Ids.Contains(trackDto.Id)).AsQueryable()
: WebRepository.TrackDtos.Where(trackDto => trackDto.Date == DateTime.Today).AsQueryable().AsQueryable();
return trackDtos;
}
}
private IQueryable<TrackDto> MapTrackDtosFromDb(DateTime date)
{
return WebRepository.TrackDtos.Where(tdto => tdto.Date == date.Date);
}
private void UpdateTrackDtoCache(DateTime date)
{
if (CacheIsUpdating)
return;
CacheIsUpdating = true;
var task = Task.Factory.StartNew(
state =>
{
var context = (HttpContext)state;
context.Cache.Insert("Tracks", MapTrackDtosFromDb(date), null, Cache.NoAbsoluteExpiration,
new TimeSpan(0, 5, 0));
CacheIsUpdating = false;
},
HttpContext.Current);
}
I believe you are running DML or DDL sql queries using the same active connection. And MARS does not allow that. You can execute multiple select statements or bulk insert but if you run multiple update, delete statements or your sql execution will throw this kind of errors. Even if you run an update sql query while running a select statement on the same command you will get this error. For more info read this
http://msdn.microsoft.com/en-us/library/h32h3abf(v=vs.110).aspx