I am trying to provide type-ahead functionality for a job number field. The pattern of the field is 8 followed by as many zeros as necessary to make the string they type a total of 10 digits. In other words, 8000001234 or 8001234567. In these examples, the users only want to type 1234 or 1234567 and have the type-ahead return the corresponding documents. Is this possible?
This can be done by using the parameter valueMarkup in xp:typeAhead.
In the suggestion response you add the value you wish to add to the field in the display:none section, the span of class informal is the part display in the suggestion list. You can modify/design the informal section with HTML code (f.e. include multiline informations, add images, etc.)
Here is a simple example:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:inputText id="inputText1" value="#{requestScope.TypeAhead}">
<xp:typeAhead mode="partial" minChars="1"
var="searchValue" valueMarkup="true">
<xp:this.valueList>
<![CDATA[#{javascript:
var directoryTypeahead = function (searchValue:string) {
/*** generate your matches ***/
var matches = {};
for( var i=10;i<20;i++){
matches[i] = { display: "80000" + i };
}
/*** return typeahead data ***/
var returnList = "<ul>";
for (var matchEntry in matches) {
var match = matches[matchEntry];
var matchDetails:string = [
"<li><div style=\"display:none;\">",
matchEntry,
"</div><span class=\"informal\"><strong>",
match.display,
"</span></li>"
].join("");
returnList += matchDetails;
}
returnList += "</ul>";
return returnList;
}
directoryTypeahead(searchValue)
}]]>
</xp:this.valueList>
</xp:typeAhead>
</xp:inputText>
You have to change the part between generate your matches to fit your requirements.
Roy - another options would be to roll your own typeAhead and not use the out of the box version
http://xomino.com/2012/05/01/jquery-in-xpages-8-tokeninput-autocomplete/
Using the Token Autocomplete you can control the search input and the display output - in this way you can display the whole 80000123 string and the 123 would be highlighted as the text that the user has input.
Possibly a partial answer - but I stumbled across a blog post by Rasmus Bauck a while back that explained a technique for handling the type-ahead calls with code of your own.
I didn't get around to trying it, but I saw your question and it jogged my memory.
http://devxpages.blogspot.com.au/2010/04/extending-xpages-type-ahead.html
Hope it helps,
Brendan
Related
I need to give an icon to each section in my entities. For example, I need to give an icon to General information, another one for interactions section.
Is there an idea about how could I do that? and how can I make a background color for each section please?
Thanks in advance,
There is no way to assign icons to sections. The best you could do would be to add a web resource to each of your sections and have them link to an image, but it doesn't really sound like that's what you're going for.
There are no supported ways to modify the form background color. If you don't care about remaining supported, though, you can use jQuery to do it. Put this function into your form script:
function changeSectionBackgroundColor(sectionId, color) {
parent.$("table.ms-crm-FormSection[name='"+sectionId+"']").css({"background-color": color});
}
and use it like this:
changeSectionBackgroundColor("General_Section_2", "red");
changeSectionBackgroundColor("General_Section_2", "#ababab");
You could try to do something like this to insert the Section images:
var stackoverflow = (function (Xrm)
{
var sectionBarClassName = '.ms-crm-Form-SectionBar'; // name of the CSS class given to the Section's Label's <td> element
function GetSection(tabName, sectionName)
{
var parentTab = Xrm.Page.ui.tabs.getByName(tabName); // get the tab
var section = parentTab.sections.getByName(sectionName); // get the section
return section;
}
function AddSectionImage(tabName, sectionName, imageUrl)
{
var section = GetSection(tabName, sectionName); // retrieve section using Xrm
var elSection = document.querySelector('table[name=' + section.getKey() + ']');
var elSectionHeader = elSection.querySelector('tr:first-child');
var elTitles = elSection.querySelectorAll(sectionBarClassName);
if (elTitles.length === 1) // we can assume that this section has a title
{
var elImg = document.createElement('img');
elImg.src = imageUrl;
elTitles[0].insertBefore(elImg, elTitles[0].firstChild);
}
}
return {
AddSectionImage : AddSectionImage
};
})(Xrm);
You then call this code and pass in the Name (not label) of the tab and section as well as the relative URL of the image you want displayed. Like so:
stackoverflow.AddSectionImage('tab_5', 'tab_5_section_1', '/imgs/Cancel_16.png');
I've only tested this code in CRM 2016 (online). And the image is a bit rough. You'll need to take care of the styling (inline) and the size yourself.
This is of course, unsupported by Microsoft :)
I have a XPage with design element. How can I get list of checked rows to post it to an agent?
<xe:dataView id="dataView1" columnTitles="true"
expandedDetail="true" var="dview1"
openDocAsReadonly="false" rows="15" showCheckbox="true"
showHeaderCheckbox="true">
Thank you!
For the client-side, you can use Dojo. The following CSJS script will return NoteIds for all selected rows:
dojo.query(".lotusFirstCell > input:checked").attr('value')
For the server side, you can grab the IDs of selected documents by:
var idList = getComponent("dataView1").getSelectedIds();
This will return a string array of NoteIDs. Then pass it to an in-memory document and call the agent.
var doc = database.createDocument();
doc.replaceItemValue("IDList", IDList);
var agent:NotesAgent=database.getAgent("SomeAgent");
agent.runWithDocumentContext(doc);
Perfect!
This is exactly what I need:
var IDs = dojo.query(".xspFirstCell > input:checked").attr('value');
I'm trying to delete selected doc from viewPanel1. The view is categorized ( can be > 1 category ) and is listing documents from 2 different datasources, let say: Cdoc and Pdoc. These docs. are linked by a common field.
my scenario: If the users select a Cdoc => the delete action will take place to the respective Cdoc but also for the all Pdoc being in the same category. If the user selects a Pdoc => delete just the Pdoc. Also, I would like to add some confirmation text with some information ( Value fields ) from the selected documents.
I tried the following
var viewPanel=getComponent("viewPanel1");
var docIDArray=viewPanel.getSelectedIds();
for(i=0;i < docIDArray.length;i++){
var docId=docIDArray[i];
var doc=database.getDocumentByID(docId);
var formName = (doc == null)? null : doc.getItemValueString("Form");
if( formName =="fmPersContact" ){
.....
} // in this case, it works OK.
else if ( formName =="fmCompanie" ){ // here if I selected > 1 Cdoc, it deletes just one Cdoc + the respective PDocs.
var doc:NotesDocument = null;
doc=database.getDocumentByID(docId);
var ky:java.util.Vector = new java.util.Vector();
ky.add(doc.getItemValueString("txt_NumeCompanie"));
... // delete method
}
Could you tell me what I did wrong and what am I missing in the above code? thanks for your time!
The first thing you want to do is confirm. Unlike Lotusscript, you cannot use a function in the middle of a script to open the confirm dialog and get the answer. To do this, I recommend using the confirm simple action before going into an execute script simple action.
<xp:button
value="delete"
id="button1"
>
<xp:eventHandler event="onclick" submit="true" refreshMode="complete">
<xp:this.action>
<xp:actionGroup>
<xp:confirm message="Are you certain?"></xp:confirm>
<xp:executeScript
script="#{javascript:doSomething();}"
>
</xp:executeScript>
</xp:actionGroup>
</xp:this.action></xp:eventHandler></xp:button>
EDIT
In the past, I have also used built my own dialogs with the extension library, filled the text in with SSJS and then called the doWhatever() or close() from the dialog itself. This is not the best solution as it requires an update from the server to get the string. The best solution would be, as Paul Withers says, to use CSJS to perform the confirmation. I have yet to do this though.
/EDIT
For your delete function, I recommend getting the document you want to delete, then tell whether it is a P- or C- doc, either by the form name or whatever mechanism you use, and then either delete the single document or by getting a documentcollection from the view by using getAllDocumentsBykey(), then iterating through them all, deleting them one by one.
var ky:java.util.Vector = new java.util.Vector();
ky.add("MainCat");
ky.add("subCat");
ky.add("subCat2");
var vw:NotesView = database.getView("vw_myView");
var docs:NotesDocumentCollection = vw.getAllDocumentsByKey(ky);
//... delete stuff...
//dont forget to recycle
Post Question Edit
I recommend the following to get the form name:
var getSelectedDoc = function(){
var vwPnl = getComponent("viewpanel");
var ids = vwPnl.getSelectedIds();
var id = null;
var doc:NotesDocument = null;
if(ids.length > 0){ //could use for loop var i = 0; i < ids.length;i++
id = ids[0]; //could pack all ids into java.util.ArrayList and return that list to work on further
//be warned that if the user selects a parent doc and those automatically deleted by it that you need a mechanism to check if the document was already deleted!
}
if(id != null){
doc = database.getDocumentByID(id);
}
return doc;
}
var doc = getSelectedDoc();
var formName = (doc == null)? null : doc.getItemValueString("form");
if(PDOC_FORM_NAME.equalsIgnoreCase(formName)){
deleteFunctionComplete(doc);
} else if (CDOC_FORM_NAME.equalsIgnoreCase(formName)){
deleteFunctionTwo(doc);
} else {
// uh-oh
}
this also allows you to have the document in case you want to delete it right away.
Edits for comments
If Cdoc should delete more than one document, then yes. You should be using the getAllDocumentsBykey keeping in mind that the view needs to be built for it. By that I mean if you have a view with one single category, there is no issue, just plug in the string and you are fine. If you have a view with three categories, you cannot feed a vector into the getalldocs function with only two values, it must be all three. So, you want to delete all for "mycomp" with the underlying pdocs "Greg" "Sally "Bob", just use alldocsbykey("mycomp"), if the view looks like:
mycomp
---Greg
---Sally
---Bob
but if the view looks like
Poland
---mycomp
------Greg
------Sally
------Bob
then the a vector with poland and mycomp must be used. "poland" does not get the correct documents. --just an fyi and pitfall that is sometimes had.
Edit after further question clarification
I prefer this loop style to remove docs
var doc_temp:NotesDocument = null;
var doc_toDelete:NotesDocument = null;
var coll_docs:NotesDocumentCollection = ...; //get document collection
var doc_nextDoc = coll_docs.getFirstDocument();
while(doc_nextDoc != null){
doc_temp = doc_nextDoc; //set document to delete
doc_nextDoc = coll_docs.getNextDocument(doc_nextDoc); // set next document before deletion
try{
doc_temp.remove(true);//lots of errors can happen here, such as ACL settings
} catch(e) {
//handle, or just break
} finally{
if(doc_temp != null) try{doc_temp.recycle()} catch(e){}// try to recycle, could also cause errors
doc_temp = null;// for the sense of completeness
}
}
Even further edit based on the question edits
of course you are only deleting one Pdoc, the way you have that set up, you are only ever returning one document. You could expand the getSelectedDoc() to put all selected documents into an java.util.ArrayList or something, and then use that arraylist to delete more than one at a time, but that could be dangerous depending on what you do because NotesDocuments are not serialisable. In that case, I recommend using the same code that you use for getSelected doc, use a for loop to get the document IDs, get the document, if the document is not null, then delete.
apropos getAllDocumentsByKey(with a vector)
The way this is currently set up, no Vector is necessary.
If you have a view with a category and sub category and you want to get all the documents in that sub category, then you must use a vector to get at it. If you include a simple string or a vector with only one value, then the documents in the sub category will not be returned. The vector can be thought of as "cat1", "subcat", "furtherSubCat"
Furthermore, there is no check here to see if the string returned from the document is empty. This should be done. There is also no check to see if the DocumentCollection is empty. This should also be done. My expectation is that there is an issue retrieving the collection based on above mentioned reasons.
I have a repeat control which displays a view. I now want to include a checkbox, so that I can perform actions on the selected documents. I have no problem adding the check box, but how can I check if the checkbox is checked then get to the associated document for that row?
Thoughts I had were:
Have the check box change event add or remove the document's UNID from a array scope variable. Then just perform actions on documents in that array.
Forget the checkbox and just popup a list box, to allow the user to select from that.
But is there an easier way?
For maximum flexibility, it's best to not bind our user interface components directly to data; rather, if we introduce a middle "data model" layer (typically, one that describes the real-world objects / people / processes the data is representative of, rather than thinking in "documents", which are ultimately just evidence that these real-world things exist), our UI code becomes very clean, easy to understand, and easy to maintain. It also makes it much easier to introduce features that are otherwise frustrating to implement when we continue to think in documents.
Suppose, for instance, that we use the Object Data Source from the Extension Library to create an arbitrary object (for example, let's call it pendingRequests) that we can later bind our repeat control to (instead of binding it directly to the view):
// Create an empty array to return at the end:
var results = [];
// Create a view navigator instance for iterating the view contents:
var pendingView = database.getView("pendingRequests");
var entryNavigator = pendingView.createViewNav();
var eachEntry = entryNavigator.getFirst();
while (eachEntry != null) {
// Add metadata about each entry to result array:
var metaData = eachEntry.getColumnValues();
results.push({
startDate: metaData.get(0).getDateOnly(),
endDate: metaData.get(1).getDateOnly(),
employeeName: metaData.get(2),
status: metaData.get(3),
unid: eachEntry.getUniversalID(),
selected: "0"
});
// In case any column values were Domino objects:
recycleAll(metaData);
// Cruise on to the next:
eachEntry = navigateToNext(entryNavigator, eachEntry);
}
// Final Domino handle cleanup:
recycleAll(entryNavigator, pendingView);
// Return our now populated array:
return results;
Before proceeding, I should point out that the above example includes two pieces of syntactical candy that aren't native to the platform: recycleAll() and navigateToNext(). Both of these are just utility functions for making the stupid recycle stuff easier to handle:
recycleAll
* More convenient recycling
*/
function recycleAll() {
for(var i = 0; i < arguments.length; i++) {
var eachObject = arguments[i];
// assume this is a collection
try {
var iterator = eachObject.iterator();
while (iterator.hasNext()) {
recycleAll(iterator.next());
}
} catch (collectionException) {
try {
eachObject.recycle();
} catch (recycleException) {
}
}
}
}
navigateToNext
/*
* Safe way to navigate view entries
*/
function navigateToNext(navigator, currentEntry) {
var nextEntry = null;
try {
nextEntry = navigator.getNext(currentEntry);
} catch (e) {
} finally {
recycleAll(currentEntry);
}
return nextEntry;
}
Okay, now back to the data model... specifically, this block:
var metaData = eachEntry.getColumnValues();
results.push({
startDate: metaData.get(0).getDateOnly(),
endDate: metaData.get(1).getDateOnly(),
employeeName: metaData.get(2),
status: metaData.get(3),
unid: eachEntry.getUniversalID(),
selected: "0"
});
So for each view entry, we create a very simple object that has all the pertinent info we want to allow the user to interact with, as well as two extra properties that are there for our own code's convenience: unid, which allows to get back to the document if we need to, and selected, which gives us a way to bind a checkbox to a property of this metadata object... which means the user can toggle its value via the checkbox.
So here's a basic example of how we might represent this data to the user:
<ul style="list-style-type: none;">
<xp:repeat var="vacationRequest" value="#{pendingRequests}">
<li style="margin-bottom:10px;">
<strong>
<xp:checkBox value="#{vacationRequest.selected}" text="#{vacationRequest.startDate} - #{vacationRequest.endDate}"
checkedValue="1" uncheckedValue="0" />
</strong>
<xp:text value="#{vacationRequest.employeeName} (#{vacationRequest.status})" tagName="div" />
</li>
</xp:repeat>
</ul>
Each checkbox in the repeat control is now bound directly to the selected property of the metadata object each "row" represents... which also has a unid property, so acting on the actual documents that correspond to this data model is simple:
for (var i = 0; i < pendingRequests.length; i++) {
var eachRequest = pendingRequests[i];
if (eachRequest.selected == "1") {
var requestDataSource = database.getDocumentByUNID(eachRequest.unid);
requestDataSource.replaceItemValue("status", "Approved");
if (requestDataSource.save()) {
// update in-memory metadata:
eachRequest.status = "Approved";
}
}
}
Since our data source is just an array of these metadata objects, we can just loop through each, ask whether the user has toggled the selected property of each, and, if so, get a handle on its corresponding document, modify one or more items, and save it. NOTE: because we're using a data source in this example, it won't reload the back end view data on every event. For performance reasons, this is a Very Good Thing (tm). But it does mean that we have to update the in-memory metadata object to match what we changed on the document (i.e. eachRequest.status = "Approved")... but it also means we can update only that, instead of having to scrap our entire data source and have it read everything back in from the view.
As a bonus, adding something like a "Select All" button is even simpler:
for (var i = 0; i < pendingRequests.length; i++) {
pendingRequests[i].selected = "1";
}
So, in summary, we have an in-memory data model where, in many cases, equivalent operations will execute more rapidly, but also allows us to write less code -- and more readable code -- to do fancier things.
If you want to play with this pattern live (and / or download all of the above source code in context), I've immortalized it here.
Bruce,
I did a Video on selecting documents from a repeat control. I did not use check boxes but used an Add and Remove button and then did some CSS to highlight the selected documents. I'm sure a checkbox could be used with basically the same code as in the add/remove buttons.
Basically I created a java.util.ArrayList in memory to hold the unids and then populated that array when a repeat row is clicked on. I computed the CSS for each row and if that UNID exists in the Array I change the background color to show it's "selected". I don't actually show any processing on the selected unids but since that array is in scoped memory you can pretty much do anything you want with it. Anyway the video demo is here:
http://notesin9.com/index.php/2011/04/01/notesin9-025-selecting-documents-from-a-repeat-control/
As always, Tim's answer is your best bet for long-term health, sanity, and code-maintainability.
There's also another route I've taken in the past, before I started using Java for all of my back-end logic. You can create a page-load-bound dataContext containing a HashMap and then bind each checkbox to that - it will fill in true or false for each key, so you can then loop through the map entries and find the ones that are true, which are the checked values.
I put together a quick example that pulls in the list from the names database to show what I mean:
<?xml version="1.0" encoding="UTF-8"?>
<xp:view xmlns:xp="http://www.ibm.com/xsp/core">
<xp:this.data>
<xp:dominoView var="names" databaseName="names.nsf" viewName="$NamesFieldLookup"/>
</xp:this.data>
<xp:this.dataContexts>
<xp:dataContext var="checkedNames" value="${javascript: new java.util.HashMap() }"/>
</xp:this.dataContexts>
<xp:div id="refresher">
<xp:repeat value="#{names}" var="name" rows="3">
<xp:this.facets>
<xp:pager xp:key="header" id="pager1" layout="Previous Group Next" />
</xp:this.facets>
<div>
<xp:checkBox value="#{checkedNames[name.$9]}">
<xp:eventHandler event="onclick" submit="true" refreshMode="partial" refreshId="refresher"/>
</xp:checkBox>
<xp:text value="#{name.$9}"/>
</div>
</xp:repeat>
<p><xp:text value="#{checkedNames}"/></p>
</xp:div>
</xp:view>
I am trying to develop a customized SharePoint 2010 web part for FAST search. I am using Microsoft.Office.Server.Search.Query.KeywordQuery something like this:
var FASTquery = new KeywordQuery(proxy)
{
ResultsProvider = SearchProvider.FASTSearch,
QueryText = queryText,
ResultTypes = ResultType.RelevantResults | ResultType.RefinementResults
};
FASTquery.SelectProperties.AddRange(
new string[] { "Title", /* ..., */ "HitHighlightedSummary" });
ResultTableCollection searchResults = FASTquery.Execute();
I go on to bind searchResults[ResultType.RelevantResults] to a Repeater control. I'm trying to get the "hit highlighted summary" to appear by calling FASTquery.HighlightStringValue(). The value I'm passing is the HitHighlightedSummary from searchResults. An example of what this looks like for a result when searching for "ear" is:
<ddd/>FALSE ); GetDlgItem(IDC_<c0>EAR</c0>_PAIN_STATIC)->EnableWindow<ddd/>FALSE ); GetDlgIte(IDC_<c0>EAR</c0>_PAIN_ABSENT_RADIO<ddd/>FALSE ); GetDlgItem(IDC_<c0>EAR</c0>_PAIN_MILD_RADIO<ddd/>
However, when called with a string like this, FASTquery.HighlightStringValue() is throwing a System.ServiceModel.FaultException with the message "Value does not fall within the expected range."
What is the correct way to convert this excerpt to HTML, or should I be calling HighlightStringValue() with some other value? The documentation is not particularly helpful.
I typically perform a manual conversion of the hit highlighted summary markup to HTML. You'll find a combination of two markers in the summary:
<c0> </c0> (Highlight)
<ddd/> (Ellipsis)
A manual transformation of the markup could be as simple as the following string replacement:
string hitHighilghtedSummary;
// ...
hitHighlightedSummary = hitHighlightedSummary.Replace("c0", "strong").Replace("<ddd/>", "…");