In my workflow model I have an association to cm:person type, in share configuration I'm using the authority.ftl template to display it, how can I limit the available users to select from down to the members of one group ?
There s a few changes that I needed to do in order to achieve this:
You should pass a parameter to your authority.ftl in your share modules config file using a new control param:
<config evaluator="node-type" condition="my:customtype">
<forms>
<form>
<appearance>
<field id="my:personproperty">
<control template="/org/alfresco/components/form/controls/authority.ftl">
<control-param name="filterString">mygroupname</control-param>
</control>
In alfresco\web-extension\site-webscripts\org\alfresco\components\form\controls\authority.ftl, pass this param to the js picker:
picker.setOptions(
{
itemType: "${field.endpointType}",
<#if field.control.params.filterString??>
filterString: "${field.control.params.filterString}",
</#if>
multipleSelectMode: ${field.endpointMany?string},
itemFamily: "authority"
});
In alfresco\extension\templates\webscripts\org\alfresco\repository\forms\pickerchildren.get.js read and use the parameter to query in only the given group:
argsFilterString = args['filterString']
...
if (argsSelectableType == "cm:person")
{
findUsers(argsSearchTerm, argsFilterString, maxResults, results);
}
...
function findUsers(searchTerm, filterString, maxResults, results){
var paging = utils.createPaging(maxResults, -1);
var searchResults = groups.searchUsers(searchTerm, paging, "lastName");
var groupmembers = null;
if (filterString != null){
var group = groups.getGroup(filterString);
var groupmembers = group.getAllUsers();
}
// create person object for each result
for each(var user in searchResults)
{
if (logger.isLoggingEnabled())
logger.log("found user = " + user.userName);
var add=true;
if (groupmembers != null ){
var ismember = false;
for each (var p in groupmembers){
if (""+p.userName == ""+user.userName){//need to add +"" cause they are java strings!
ismember = true;
}
}
if (!ismember){
logger.log(user.userName+" is no member of group "+filterString);
add=false;
}
}
if(add){
// add to results
results.push(
{
item: createPersonResult(user.person),
selectable: true
});
}
}
}
You can create a new control based on that authority.ftl and authority-finder.js, in which you modify the webscript call to a limited number of users to be returned !
Note :
If the webscript doesnot support maxItems parameter (or any other parameter to limit the number of results returned), you can always create your brand new webscript that supports that feature and then point your new control at it.
Related
I'm working on a requirement where I have a datasource named 'emailSearchResults' where I search for email messages metadata and load the results in the datasource.
The fields in the datasource are not relevant, however I set the datasource to have 50 records per page as per the below screenshot:
The script I used to load the datasource is shown in the query field, that call the following script:
function getMessageDetails(userId, msgID)
{
var messageDetails = [];
var messageData;
var msgID_,subject_,from_,date_;
messageData=Gmail.Users.Messages.get(userId,msgID,{format:"metadata", metadataHeaders:["Message-ID", "Subject", "From", "Date"]});
console.log(messageData.payload.headers);
//console.log(msgID);
//console.log(messageData.payload.headers[3].value);
date_="<na>";
from_="<na>";
subject_="<na>";
msgID_="<na>";
for (var counter =0;counter<4;counter++)
{
if (messageData.payload.headers[counter].name=="Message-ID")
{
msgID_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="Subject")
{
subject_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="From")
{
from_=messageData.payload.headers[counter].value;
}
if (messageData.payload.headers[counter].name=="Date")
{
date_=messageData.payload.headers[counter].value;
}
}
messageDetails.push(date_);
messageDetails.push(from_);
messageDetails.push(subject_);
messageDetails.push(msgID_);
return messageDetails;
}
function searchMessages(userId,condition)
{
//
// first we build the conditions
// we can make it fixed
// or we can make it dynamic
var searchResult;
var deleteResult;
var currentMessage;
var results = [];
var pageToken;
var params = {};
var _stat;
var options = {
includeSpamTrash: "true",
pageToken: pageToken
};
var msgRecord = [];
do
{
searchResult=Gmail.Users.Messages.list(userId,options);
for (var i = 0; i < searchResult.messages.length; i++)
{
var record=app.models.emailSearchResults.newRecord();
msgRecord=getMessageDetails(userId,searchResult.messages[i].id);
record.msgMainID=searchResult.messages[i].id;
record.msgID=msgRecord[3];
record.subject=msgRecord[2];
record.senderAddress=msgRecord[1];
record.msgDate=msgRecord[0];
/*console.log(searchResult.messages[i].id);
console.log(msgRecord[3]);
console.log(msgRecord[2]);
console.log(msgRecord[1]);
console.log(msgRecord[0]);
return;*/
results.push(record);
msgRecord=null;
}
if (searchResult.nextPageToken) {
options.pageToken = searchResult.nextPageToken;
}
} while (searchResult.pageToken);
searchResult=null;
return results;
}
On the main page I put a table and linked it to the datasource, and I enabled pagination on the table, so I get the pager buttons at the bottom of the table as below:
When I execute the app and the datasource is filled, I see the first page results in a correct way, however when I want to move to the next page, I click the next page button and once the loading is complete I find out that I still see the same results from the first page on the table.
I am not familiar with how to make the table show the results of the second page then the third page, and I am going in circles on this...
Hope the explanation is clear and addresses the issue..
I would really appreciate any help on this!
Regards
Currently pagination isn't working as expected with calculated datasources. You can, however, build your own. There are several changes you'll need to make to accomplish this. First you'll want to refactor your searchMessages function to something like this:
function searchMessages(userId, pageToken){
var results = [];
var options = {
includeSpamTrash: "true",
pageToken: pageToken,
maxResults: 50
};
var searchResult = Gmail.Users.Messages.list(userId, options);
for (var i = 0; i < searchResult.messages.length; i++){
var record = app.models.emailSearchResults.newRecord();
var msgRecord = getMessageDetails(userId,searchResult.messages[i].id);
record.msgMainID = searchResult.messages[i].id;
record.msgID = msgRecord[3];
record.subject = msgRecord[2];
record.senderAddress = msgRecord[1];
record.msgDate = msgRecord[0];
results.push(record);
}
return {records: results, nextPageToken: searchResult.nextPageToken};
}
Then you'll want to change your datasource query. You'll need to add a number parameter called page.
var cache = CacheService.getUserCache();
var page = query.parameters.page || 1;
var pageToken;
if(page > 1){
pageToken = cache.get('pageToken' + page.toString());
}
var results = searchMessages('me', pageToken);
var nextPage = (page + 1).toString();
cache.put('pageToken' + nextPage, results.nextPageToken);
return results.records;
You'll need to modify the pagination widget's various attributes. Here are the previous/next click functions:
Previous:
widget.datasource.query.pageIndex--;
widget.datasource.query.parameters.page = widget.datasource.query.pageIndex;
widget.datasource.load();
Next:
widget.datasource.query.pageIndex++;
widget.datasource.query.parameters.page = widget.datasource.query.pageIndex;
widget.datasource.load();
You should be able to take it from there.
As I would like to create documents by merging the entries in a list into a Google Docs template. I have therefore integrated the DocumentMerge method from my previous question into a printButton in a list widget.
Clicking on the printButton should produce a document that merges the contents of the current row into the document template. But when I click on the printButton the method fails due to a circular reference. How can I fix that? The print method goes like this ...
function printReview(widget) {
var review = app.models.Review.getRecord(widget.datasource.item._key);
var templateId = 'templateId';
var filename = 'Review for ...' + new Date();
var copyFile = DriveApp.getFileById(templateId).makeCopy(filename);
var copyDoc = DocumentApp.openById(copyFile.getId());
var copyBody = copyDoc.getBody();
var fields = app.metadata.models.Review.fields;
for (var i in fields) {
var text = '$$' + fields[i].name + '$$';
var data = review[fields[i].name];
copyBody.replaceText(text, data);
}
copyDoc.saveAndClose();
}
As Morfinismo noticed you are getting the error because you are trying to pass complex object from client to server and serializer fails to handle it. In order to fix that you need to adjust your code:
// onClick button's event handler (client script)
function onPrintClick(button) {
var reviewKey = button.datasource.item._key;
google.script.run
.withSuccessHandler(function() { /* TODO */ })
.withFailureHandler(function() { /* TODO */ })
.printReview(reviewKey);
}
// server script
function printReview(reviewKey) {
var review = app.models.Review.getRecord(reviewKey);
...
}
I am trying to update both the state and value of an attribute for an entity by writing plugin for CRM 2013. I did try to set the state of an activity using setStateRequest but I am not sure if we can update attribute value as well. I've registered the plugin on Merge message to change the state of an activity. How can I update an attribute value along with state change? Here is my code so far for state ch
protected void ExecutePreCaseMerge(LocalPluginContext localContext)
{
if (localContext == null)
{
throw new ArgumentNullException("localContext");
}
// TODO: Implement your custom Plug-in business logic.
IPluginExecutionContext context = localContext.PluginExecutionContext;
IOrganizationService service = localContext.OrganizationService;
//The InputParameters collection contains all the data passed in the message request.
if (context.InputParameters.Contains("SubordinateId") &&
context.InputParameters["SubordinateId"] is Guid)
{
try
{
Guid subordinateId = (Guid)context.InputParameters["SubordinateId"];
var fetch = #"<fetch version='1.0' output-format='xml-platform' mapping='logical' distinct='false'>
<entity name='task'>
<attribute name='new_issuephase' />
<filter type='and'>
<filter type='and'>
<condition attribute='regardingobjectid' operator='eq' uitype='incident' value='" + subordinateId + #"' />
<condition attribute='statecode' operator='eq' value='0' />
</filter>
</filter>
</entity>
</fetch>";
EntityCollection ec = service.RetrieveMultiple(new FetchExpression(fetch));
if (ec.Entities.Count > 0)
{
// Create an ExecuteMultipleRequest object.
ExecuteMultipleRequest requestWithResults = new ExecuteMultipleRequest()
{
// Assign settings that define execution behavior: continue on error, return responses.
Settings = new ExecuteMultipleSettings()
{
ContinueOnError = false,
ReturnResponses = true
},
// Create an empty organization request collection.
Requests = new OrganizationRequestCollection()
};
foreach (var item in ec.Entities)
{
SetStateRequest setStateRequest = new SetStateRequest();
setStateRequest.EntityMoniker = new EntityReference("task", item.Id);
setStateRequest.State = new OptionSetValue(2);
setStateRequest.Status = new OptionSetValue(6);
requestWithResults.Requests.Add(setStateRequest);
}
ExecuteMultipleResponse responseWithResults =
(ExecuteMultipleResponse)service.Execute(requestWithResults);
}
}
ange.
Thanks for any help!
You must do the update as a separate request but you can execute it together with the SetStateRequests inside the same ExecuteMultipleRequest:
foreach (var item in ec.Entities)
{
SetStateRequest setStateRequest = new SetStateRequest();
setStateRequest.EntityMoniker = new EntityReference("task", item.Id);
setStateRequest.State = new OptionSetValue(2);
setStateRequest.Status = new OptionSetValue(6);
requestWithResults.Requests.Add(setStateRequest);
//New Code
item.Attributes["attributetobeupdated"] = "Updated Value";
UpdateRequest request = new UpdateRequest() { Target = item };
requestWithResults.Requests.Add(request);
}
ExecuteMultipleResponse responseWithResults =
(ExecuteMultipleResponse)service.Execute(requestWithResults);
Note that if any of the requests fail the ExecuteMultipleRequest will not throw an error but will return the details of the individual failures within the ExecuteMultipleResponse. It seems that a common reason for this operation failing is that by default SQL server queries are configured to time out after 30 seconds. More info here:
http://manyrootsofallevilrants.blogspot.in/2012/09/ms-crm-2011-timeout-settings-and-limits.html
I've had several cases where I had a page with several query parameters - most recently a search results page - and needed to create a link to the same page with one or more query parameters changed in the URL. This seems like such a common use case that I feel as though there must be some simple built-in way of doing it.
Right now, I'm using a function I wrote which takes in a dictionary of parameters and values and merges them with the params and values from Request.QueryString. Parameters given with a null value are removed. It works, but I'm open to simpler methods.
Minor improvements I'd suggest:
//...
{
UriBuilder ub = new UriBuilder(Request.Url);
//...
ub.Query = string.Join("&", parameters.Select(kv => string.Format("{0}={1}", Server.UrlEncode(kv.Key), Server.UrlEncode(kv.Value))));
return ub.ToString();
}
Edit
Actually the return value should also be a Uri type but I didn't want to introduce any breaking changes.
The function I'm using now:
public string ThisPageWithParams(IDictionary<string, string> newParameters)
{
string url = Request.Url.AbsolutePath + "?";
var parameters = new Dictionary<string, string>();
foreach (string k in Request.QueryString)
{
parameters[k] = Request.QueryString[k];
}
foreach (var kv in newParameters)
{
if (newParameters[kv.Key] == null)
{
parameters.Remove(kv.Key);
}
else
{
parameters[kv.Key] = kv.Value;
}
}
url += string.Join("&", parameters.Select(kv => Server.UrlEncode(kv.Key) + "=" + Server.UrlEncode(kv.Value)));
return url;
}
The following query is working successfully.
var tabs = (
from r in db.TabMasters
orderby r.colID
select new { r.colID, r.FirstName, r.LastName })
.Skip(rows * (page - 1)).Take(rows);
Now I want to return JsonResult as like
var jsonData = new
{
total = (int)Math.Ceiling((float)totalRecords / (float)rows),
page = page,
records = totalRecords,
rows = (from r in tabs
select new { id = r.colID, cell = new string[] { r.FirstName, r.LastName } }).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
But it will gives me an error like:
The array type 'System.String[]' cannot be initialized in a query result. Consider using 'System.Collections.Generic.List`1[System.String]' instead.
What should I do to get expected result?
I suspect that it's as simple as pushing the last part into an in-process query using AsEnumerable():
var jsonData = new
{
total = (int)Math.Ceiling((float)totalRecords / (float)rows),
page = page,
records = totalRecords,
rows = (from r in tabs.AsEnumerable()
select new { id = r.colID,
cell = new[] { r.FirstName, r.LastName } }
).ToArray()
};
return Json(jsonData, JsonRequestBehavior.AllowGet);
You may want to pull that query out of the anonymous type initializer, for clarity:
var rows = tabs.AsEnumerable()
.Select(r => new { id = r.colID,
cell = new[] { r.FirstName, r.LastName })
.ToArray();
var jsonData = new {
total = (int)Math.Ceiling((float)totalRecords / (float)rows),
page,
records = totalRecords,
rows
};
It's because it's adding to the LINQ query that is your tabs IQueryable. That is then trying to turn the LINQ expression into a SQL query and the provider doesn't support projecting arrays.
You can either change the assignment of the tabs variable's LINQ expression to use ToList to materialize the DB results right then and there, or you can add .AsEnumerable() to the LINQ expression assigned to the rows field of the anonymous type that is your JsonResult. AsEnumerable will demote the IQueryable to an IEnumerable which will prevent your second LINQ query from trying to be added to the DB query and just make it a LINQ-to-objects call like it needs to be.