XPages setfield from view selection - xpages

I have a repeat control with buttons to select or deselect various docs - that works and I can identify each selected doc by the doc id. I have another button with the following SSJS. For the docs selected I want to set a field in the underlying doc. This script works if I do a simple doc.removePermanently(true)... but not if I try to manipulate a common field value for the selected docs. I'm getting an Error 500 HTTP Web Server: Command Not Handled Exception. What is the proper way to do this?
var docsForAction = sessionScope.get("myList");
var doc:NotesDocument;
for(i=0; i < docsForAction.length; i++){
doc = database.getDocumentByUNID(docsForAction[i]);
doc.setValue("Level","10");
}
docsForAction = [];
viewScope.put("myList", docsForAction);
context.reloadPage();

You're probably getting an error 500 because you don't have an error page defined. Go to Xsp Properties and tick "Display default error page". You will then get a more meaningful error page displayed.
The error you'll see is probable on the setValue() line, the message being "null". That's because setValue() is not a method of NotesDocument, it's a method of the dominoDocument datasource (the "NotesUIDocument" XPages equivalent, if you know LotusScript).
Casting the variable (adding ":NotesDocument") is good because it allows you to take advantage of the typeahead support in the SSJS editor or the source pane. That will give you a list of valid methods and the valid parameter types taken.

Related

EditBox fieldInput.getValue() and spaces

Here is the issue. I have an editBox which I am trying to retrieve the value from by server side JavaScript via the onClick event of a button named Add. It works until space is in the value, then it retrieves nothing. Code for the onClick events is as follows:
println("Button Clicked");
try{
var forkNumberInput:com.ibm.xsp.component.xp.XspInputText =
getComponent("forkNumberInput");
var forkNum = forkNumberInput.getValue();
viewScope.ForkNum = forkNum;
println(forkNum);
} catch(e){
println( Error in Add button: " + e.toString());
}
When a space is in the text the viewScope doesn't get populated and nothing is written to the server log not even "Button Clicked". No error is written to server log.
If "Button Clicked" is not printed, the likeliest cause is that there are validation errors. Add a Display Errors control to make sure that's not the case.
I second Paul's suggestion to add the Errors control. Besides that: Try to avoid to use getComponent() when all you want to do is grabbing the value. The idea of JSF, extending to XPages, is "data binding". Controls are bound to some data source for their value and you interact with those. getComponent() is meant to be used when you need to manipulate anything else.
So you would go and bind your control that scope variable viewScope.forkNum and you are done. Your button then grabs the value from there and does what it needs to do.
So in summary: Controls want to be bound. Data lives in models (not controls)
Thanks Paul - you led me to the right direction. It turned out that there was a validateConstraint under Properties > data > validators. I removed the validateConstraint and it started working. That's what happens when you copy design elements from one custom control to another.

OrchardCMS: How to access Content Menu Item boolean field in cshtml view

In orchard, I've added a boolean field called "IsDone" to the built in Content Menu Item content part via that Admin interface. I've then picked an item in Navigation and set the option to "yes" for the corresponding field i added.
In my custom theme, I've copied over MenuItem.cshtml.
How would I get the value of my custom "IsDone" field here?
I've tried something like
dynamic item = Model.ContentItem;
var myValue = item.MenuItem.IsDone.Value;
but I'm pretty sure my syntax is incorrect (because i get null binding errors at runtime).
thanks in advance!
First i suggest you use the shape alternate MenuItemLink-ContentMenuItem.cshtml instead of MenuItem.cshtml to target the content menu item directly.
Secondly, the field is attached to the ContentPart of the menu item. The following code retrieves the boolean field from this content part:
#using Orchard.ContentManagement;
#using System.Linq;
#{
Orchard.ContentManagement.ContentItem lContentItem = Model.Content.ContentItem;
var lBooleanField = lContentItem
.Parts
.Where(p => p.PartDefinition.Name == "ContentMenuItem") // *1
.SelectMany(p => p.Fields.Where(f => f.Name == "IsDone"))
.FirstOrDefault() as Orchard.Fields.Fields.BooleanField;
if (lBooleanField != null)
{
bool? v = lBooleanField.Value;
if (v.HasValue)
{
if (v.Value)
{
#("done")
}
else
{
#("not done")
}
}
else
{
#("not done")
}
}
}
*1
Sadly you cannot simply write lContentItem.As<Orchard.ContentManagement.ContentPart>() here as the first part in the part list is derived from this type, thus you would receive the wrong part.
While #ViRuSTriNiTy's answer is probably correct, it doesn't take advantage of the power of the dynamic objects that Orchard provides.
This is working for me but is a much shorter version:
#Model.Text
#{
bool? IsDone = Model.Content.ContentMenuItem.IsDone.Value;
var IsItDoneThough = (IsDone.HasValue ? IsDone.Value : false);
}
<p>Is it done? #IsItDoneThough</p>
You can see that in the first line I pull in the IsDone field using the dynamic nature of the Model.
For some reason (I'm sure there is a good one somewhere) the BooleanField uses a bool? as its backing value. This means that if you create the new menu item and just leave the checkbox blank it will be null when you query it. After you have saved it as checked it will be true and then if you go back and uncheck it then it will have the value false.
The second line that I've provided IsItDoneThough checks if it has a value yet. If it does then it uses that, otherwise it assumes it to be false.
Shape Alternate
#ViRuSTriNiTy's other advice, to change it to use the MenuItemLink-ContentMenuItem.cshtml instead of MenuItem.cshtml is also important.
The field doesn't exist on other menu items so it will crash if you try to access it. Just rename the .cshtml file to fix this.
Dynamic Model
Just to wrap this up with a little bit of insight as to how I got there (I'm still learning this as well) the way I figured it out is as follows:
.Content is a way of casting the current content item to dynamic, so you can use the dynamic advantages with the rest of line;
When you add the field in the admin panel it looks like it should be right there on the ContentItem, however it actually creates an invisible ContentPart to contain them and calls it whatever the ContentItem's type is.
So if you had added this field to a Page content type you would have used Model.Content.Page.IsDone.Value. If you had made a new content type called banana it would be Model.Content.Banana.IsDone.Value, etc.
Once you are inside the "invisible" part which holds the fields you can finally get at IsDone. This won't give you the actual value yet though. Each Field has its own properties which you can look up in the source code. the IsDone is actually a BooleanField and it exposes its data via the Value property.
Try doing a solution-wide search for : ContentField to see the classes for each of the fields you have available.
Hopefully this will have explained things clearly but I have actually written about using fields in a blog post and as part of my getting started with modules course over on the official docs (its way down in part 3 if you're curious).
Using built-in features instead of IsDone
This seems like a strange approach to do it this way. If you have a Content Item like a Page then you can just use the "Show on a menu" setting on the page.
Go to admin > content > open the page > down near the bottom you will find "Show on a menu":
This will automatically put it into your navigation and then you can move it around to where you want:
After it "IsDone" you can just go back and untick the "Show on a menu" option.
Setting up the alternative .cshtml
To clarify your comments about how to use the alternative, you need to
Copy the file you have at Orchard.Core/Shapes/Views/MenuItem.cshtml over to your theme's view folder so its /Views/MenuItem.cshtml
Rename the copy in your theme to MenuItem-ContentMenuItem.cshtml
Delete probably everything in it and paste in my sample at the start of this post. You don't want most of the original MenuItem.cshtml code in there as it is doing some special tricks to change itself into a different shape which isn't what you want.
Reset your original Orchard.Core/Shapes/Views/MenuItem.cshtml back to the factory default, grab it from the official Orchard repository
Understanding the view names
From your comments you asked about creating more specific views (known as alternates). You can use something call the Shape Tracer to view these. The name of them follows a certain pattern which makes them more and more specific.
You can learn about the alternates on the official docs site:
Accessing and Rendering Shapes
Alternates
To figure out what shape is being used and what alternates are available you can use the shape tracing module which is documented here:
Getting Started with Shape Tracing

How to find programmatically (SSJS) what item of the underlying doc the UI input control on Xpage is bound to?

The task is that I need to update the field of the underlying doc only given the id of the edit box or the combo box on the Xpage. All that has to happen before the page is actually saved. Cannot find any methods in UIComponent and subclasses that allow to find out the name of the actual doc item the current XSP input control is bound to. Plz help.
The following will get the Expression Language binding for a component with the id inputText1:
var inputText1:com.ibm.xsp.component.xp.XspInputText = getComponent("inputText1");
var valBinding:com.sun.faces.el.ValueBindingImpl = inputText1.getValueBinding("value");
return valBinding.getExpressionString();
This will return e.g. "#{document1.myField}". Using basic string parsing, you should be able to get what you want.
Like Oliver, I'd be interested to hear the use case. It's not something I've had the need to use.
As a bonus, try looking in the Local folder in Package Explorer at an XPage / Custom Control. You'll see all the getters / setters for components on your XPage, which will give you hints for what properties and methods are available. F3 and F4 are very useful for seeing all methods/properties and class hierarchy.

I would like to print selected documents from a view

Does anyone have a suggestion on how to print selected documents in an simple xPages view. I'm converting a legacy application. Which used the following Lotus script code to print. Thanks
Set db = session.CurrentDatabase
Set collection = db.UnprocessedDocuments
count = collection.count
If count = 0 Then
Goto errSelectDocs
End If
Stop
For i = 1 To count
'
Set note = collection.GetnthDocument (i)
Set Source2 = w.EditDocument( False, note )
Set Source3 = w.ComposeDocument("","","mRecensement imp")
Call Source3.print(1)
Call Source3.close
Call Source2.close
'----------------------------------
nextdocument:
Next
I'm going to answer here rather the following up in the comments of the Simon answer.
so ok. We're saying build a new page with a repeat control of the select documents. and the question asker is saying, I THINK, that it seems wrong to do:
doc:NotesDocument=database.getDocumentByID(rowData);
return doc.getItemValue("xxxx") for 30 + items
right. You don't want to do that. should work. But icky to do.
Probably what I would do is create a SSJS function to pass rowData into. In that function build an array. Load the document once... put all the items into the array and pass them back to the page with the repeat control.
Probably what you do then is have a panel and use either a dataContext or objectData that's bound to the panel. Inside the panel is your page and fields. Those fields just read from the dataContext or objectData. so you're only getting the document once. I guess you could even use just a scoped variable though I don't think there's an event to call code on each row. So you'd need to hack it into the first field maybe or something.
But that's what you want. I previously asked a question on StackOver flow about returning multiple parameters like this: How to pass variable parameters to an XPages SSJS function?
Maybe that's helpful.
Someone might come up with a better solution but one option is this.
First have your viewPanel on your first XPage. Select your documents and click a button. The code would do this.
var viewPanel = getComponent("viewPanel1");
sessionScope.documentIDs = viewPanel.getSelectedIds();
You then hand it off to another XPage which has a repeat control of the print structure for a document. It reads the document ID's and creates the page. Then just use the normal print command after loading.
window.print();

Inline Editing of View data used in a repeat

I have a repeat control using a view as the datasource with a custom control within the repeat. The custom control is made up of a panel with two tables. One table has computed fields with an Edit button and the other has editable fields with a Save and Cancel button. The Edit and Cancel buttons work as needed, but the Save button gives a NotesDocument.save() is null error. I have already narrowed the issue down to the error occurring on the edoc.save() line by commenting out all prior lines. I even tried to do an edoc.lock(), but got the same error.
var edoc:NotesDocument = database.getDocumentByUNID(viewScope.get('docid'));
edoc.replaceItemValue('Ext_1',viewScope.get('ext_1'));
edoc.replaceItemValue('DID',viewScope.get('did'));
edoc.replaceItemValue('Mobile',viewScope.get('mobile'));
try {
edoc.save();
} catch(e) {
print(e.toString());
}
The storage of a DocID in the viewScope and a repeat control doesn't seem right. You want to add a custom property to your custom control called DocID and then instead of
database.getDocumentByUNID(viewScope.get("docid"));
You do:
database.getDocumentByUNID(compositeData.DocID);
This was you can be sure that you get the document that was in that view for that row.
What you also might consider, instead of all the manual steps (the ones you commented out) have a panel with a DocumentDataSource and then simply bind your input fields to that one. Handover of id via custom property and "IgnoreRequestParameter = true
Then you simply do a rowDoc.save() (presuming you named the datasource rowDoc) and you don't need to recycle anything. Let us know how it goes.

Resources