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();
Related
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
There is a viewPanel having a column with showCheckbox="true".
Is it possible to restrict the users to select only one row/document ( and not multiple rows/docs. ) listed by the viewPanel?
Not in a View Panel. The View Panel is designed to offer a quick, simple approach with restricted functionality.
An alternative (possibly better) approach may be to have another column with a link or image that triggers whatever functionality you need. That will allow users to trigger functionality with a single click rather than two. The View Panel allows you to place controls in columns instead of just mapping to a column in the underlying view.
Alternatively, you could add a checkbox manually to a column, map to a scoped variable, and check/uncheck programmatically.
Paul's probably right on. My alternative for you is to use a repeat control. You can make it look however you want. Including a view control.
I have an example of this in this NotesIn9 show: http://notesin9.com/index.php/2011/07/11/notesin9-ee-009-using-java-hashmaps-and-treemaps-with-xpages/
Now in my example I did multiple values. But if instead of a HashMap or ArrayList if you kept your "selected" document id in a single value field like just a scoped variable.. then you'd get what you want. One document at a time.
I agree with Paul Stephans (also upvoted his answer because I think it would be the Nest solution) but if you insist on adding such a functionality to your viewPanel you can do this by adding a client side script to prevent the user from selecting more then one element:
First add the styleClass="rowCB" to your checkbox row and insert this code to your xpage:
<xp:scriptBlock>
<xp:this.value><![CDATA[dojo.ready(function(){
dojo.query('.rowCB>input').connect("onclick", function(evt){
var target = evt.target.id;
if(!window.crrCheckedElement){
window.crrCheckedElement = evt.target.id;
}else if(window.crrCheckedElement != target){
alert("You can select only one item!");
evt.target.checked = false;
}else if(window.crrCheckedElement == target){
window.crrCheckedElement = "";
}
})
});]]></xp:this.value>
</xp:scriptBlock>
Maby the code needs some improvement but this should be your way to go.
though maybe not the best solution, here is one possibility.
function deselectOtherDocs(viewName, currentDocId) {
var viewPanel:com.ibm.xsp.component.xp.XspViewPanel = getComponent(viewName);
var selectedIds = viewPanel.getSelectedIds();
for(var i=0; i<selectedIds.length; ++i){
if(selectedIds[i]!=currentDocId){viewPanel._xspSetIdUnchecked(selectedIds[i])}
return;
}
fire this off when a doc is checked and pass the view control name and the current doc's unid.
pardon any typos. I am writing from my phone.
edit: if you don't have to use a view control, then David Leedy's suggestion is the way to go. store the selected unid in a scope variable and let that determine which repeat row's box is selected. you might also consider using a radio button instead of a checkbox, since the former is understood as a single selector by users.
I have a tabbed panel containing different sections of a form. In one section, users are given the ability to add a child document to the currently open document. In a second section, they are given a listbox, where the options are dynamically generated (via #DbLookup) by looking at a view of all the child documents, filtered by the current document's ID. This functionality all works correctly, however, there is a problem with the dynamic listbox options.
When a user adds a new child document, then switches to the next tab, the listbox is not updated with this new document. If they save/re-edit the main document or refresh the page, it makes no different, the user must load another XPage before going back to the original in order for the listbox to update. I have tried to resolve this by doing full updates of the page, using session.evaluate and "NoCache" in the #DBLookup call, or calling database.getView("My view").refresh(), but without luck.
There is also a similar problem where I have a repeat control which uses a view of child documents (again filtered by main document ID) as a datasource. When a user adds a child document using a button, it partially refreshes the repeat - but doesn't show the new child document until the page is refreshed again (or you leave the page and return).
Is there something crucial I am missing with regards to the JSF lifecycle/the way that view data is cached?
As first measure I would add another formula item to the listbox which simply returns the current time (#Now() should work). That way you can check if the listbox options are refreshed on the update in the first place.
If the options are refreshed fine it's indeed clear that the #DbLookup does some caching, although I'm not aware of any default caching logic.
At least for a test I would change the code to use a NotesView object instead of the #DbLookup, something like this:
var nview = database.getView("someview");
var nc = nview.getAllEntriesByKey(currentDocument.getDocument().getUniversalID(), true);
var a = [];
var ve = nc.getFirstEntry();
while (ve) {
a.push(ve.getColumnValues().elementAt(0)); // value of first column
ve = nc.getNextEntry(ve);
}
return a;
(I wrote the code from memory, there may be syntax errors).
Since the code only works with view entries it should be equally fast than the #DbLookup. And you could even do a nview.refresh() if needed.
I have a repeat control for a domino view which displays the results from a search field.
As you type more characters into the search field the number of items in the list is reduced. If/When the the list only contains a single item I would like to open item automatically, without having to click the link.
Any ideas are appreciated.
Edit: after some very interesting responses, here are some screenshots
I have 3 elements on the page, a searchbar, a repeat control and a form:
When I start typing in the search bar, the repeat is refreshed with every keystroke:
the list is reduced, typing the next character ...
again the list is reduced, only 2 left, typing again....
Only one left, now it would be time to open the document in the form ..... without clicking the link.
I've tried several events on the page, but it seems that I could not find the one that will allow me to "select" the document and display the data in the form.
It seems that it's not as simple as I thought
Since you want to open the link automatically I don't know if I would try to base it on the getRowCount() of the repeat itself. You don't want to even get that far right? you just want to go to the single document.
I would put a function in beforePageLoad event maybe. Not totally sure which event but I'd try that first. Use SSJS and do a lookup that would basically return a collection of what the repeat would show. If the collection count = 1 then get your destination from that entry and do your redirection from there.
That what I would try at least. Interesting scenario!
Now that I see the screenshots this might be easier then you think and I have already implemented something similar on an internal application that I have built. It does rely on the fact that each entry in the list is 100% unique.
First of all you will need to bind the search field to a scoped variable and the onchange/onkeypress event will need to perform a partial refresh of a panel that contains both the list and the document portion of the page.
For the list the link on each item should set the value of the same scoped variable used in the search box and clicking the link should be set to run a partial refresh of the document area.
For the document area you will need two panels, the first panel will only display if there is no matching document and the second panel will only display if there is a matching document, you can do this in the rendered section by writing some ssjs that grabs a handle to the db/view and does a dblookup and returns either true or false if the document exists depending on panel your dealing with.
With this setup, when somebody clicks a link or fills out the searchbox the scoped variable will contain a value, the document panels will then check to see if this is a unique value in the view in the db and update themselves to either display the 'no document' panel or the 'document' panel accordingly.
You could add a evaluation script to the entry of your repeat control which checks the size of your repeat control using the method getRowCount() from the component. If this is 1 you could execute a context.redirectToPage("yourpage.xsp?id=yourid",true) this forces the current page to send a redirect request back to the browser and therefore redirects you to the correct page.
All you need to know is which xpage you need to open and which parameters you should use. But these could be retrieved from the content you are iterating over.
In my Lotus Notes application (classic, not using XPages), the user won't be able to edit documents directly.
Instead, modifications will happen dialog-based and triggered through actions on the form.
For example, I have an action invoking code similar to the following:
Dim ws As New NotesUIWorkspace
Dim result As String
Dim document As NotesDocument
Dim options(1 To 6) As String
'... - Fill options
Set document = ws.CurrentDocument.Document
result = ws.Prompt(PROMPT_OKCANCELCOMBO, "New Value", "Please choose the new value", document.Foo(0), options)
If result <> "" Then
document.Foo = result
Call document.ComputeWithForm(False, True)
Call document.Save(True, False)
End If
This updates the value of Foo to the value the user chose in the dialog.
However, this new value is not shown to the user - the Form does not seem to be refreshed.
Reopening and closing the form does show the new value; it definitely gets updated.
The nearest I could get was the following code (inside the if-block):
ws.CurrentDocument.EditMode = True
document.Foo = result
Call ws.CurrentDocument.Save()
ws.CurrentDocument.EditMode = False
Nevertheless, this solution seems a bit suboptimal to me as I have to enter edit mode.
How can I refresh the form using Notes Script to reflect the change of the field without having to enter edit-mode?
Methods like ws.CurrentDocument.Refresh either don't show any effect or raise errors as they can't be used outside edit-mode.
Many thanks in advance for your ideas, tips and solutions!
I found another solution avoiding entering edit-mode:
As the documentation to Reload states:
Modifications made to the back-end document outside the current
editing session (for example, by an agent or another user) do not
appear until the document is closed and reopened. You can close and
reopen a front-end document with NotesUIDocument.Close(True) and
NotesUIWorkspace.EditDocument.
Thus, the document can be "refreshed" (with a bit of flickering) using:
Dim ws As New NotesUIWorkspace
Dim document As NotesDocument
Set document = ws.CurrentDocument.Document
'...
Call ws.Currentdocument.Close(True)
Call ws.Editdocument(False, document)
Your answer is the best one. Going into edit mode and either setting the field and saving or simply refreshing the page after you've already set the field is the right idea. It's unfortunate but necessary.
The user will need edit rights to the document of course which presents another problem. How do you prevent the user from typing control E or clicking a smart icon to edit the document?
For that you can set a Global variable that controls access to editing the document. When you're opening your dialog you can set that global variable to true and then set it back to false after you've made your changes. In the query mode changed event set continue to the value of that global variable.
If your user has editor rights in that document, you are not able to stop him to bypass any script action in your form and simply edit it.
Better way to let user edit only particular fields is Controlled Access Section.
Lets say you have a table. You want to show the next row to add an order item or something:
#setfield("NumOrders";NumOrders+1);
#Command( [ViewRefreshFields] )
You need a hidden field NumOrders, then refresh will work.
#Command( [ViewRefreshFields] )
It's that easy!