App Maker Document approval template : How can I Add Default Approvers - google-app-maker

I use document approval template, and I want to define default approver and stages.
I have tried to change the custom value associates to the userpicker widget in EditRequest Page to define a default Approver by changing the location from onValueChange to onAttach. I set default value for mail's approvers.
PageEditRequest/userPickerWidget:
Function associates to the custom value of userPickerWidget:
But I don't know how can I associate a new stage to an another approver...
I tried a lot of things that failed
Have you any ideas?
I want to have this type of results without any client interaction:
Desired result:

The earlier answer points to solve the problem where user are adding stage manually. If you want all stages and all approvers list to be added automatically, follow the below steps.
Open Edit Request page, in that page you can find event onAttach, this event will trigger when page is being loaded and data has not loaded yet. DMS Template has already provided a method called startLoading() to this Event.
Locate startLoading() method in Client Script named EditRequestPage_Request. This method is calling loadEditRequestPage() method internally. Locate loadEditRequestPage() method.
This method is adding a default stage (i.e. Stage 1) to the approval workflow. We need to perform our operations here for Automatically add approvers.
Locate the line requestDs.relations.WorkflowStages.createItem in the code, this line is Adding a Stage to the workflow. So we need to add this line multiple times to Add multiple stages. In my below code I've show cased for 2 stages.
Code for adding 2 stages and 1 approver at each stage.
if (requestDs.item.WorkflowStages.length === 0) {
requestDs.relations.WorkflowStages.createItem(function() {
var createDatasource = requestDs.relations.WorkflowStages.relations.Approvers.modes.create;
var draft = createDatasource.item;
draft.Email = 'darpan.sanghavi#abc.com';
draft.Name = 'Darpan Sanghavi';
createDatasource.createItem(function(createdRecord) { });
});
requestDs.relations.WorkflowStages.createItem(function() {
var createDatasource = requestDs.relations.WorkflowStages.relations.Approvers.modes.create;
var draft = createDatasource.item;
draft.Email = 'darpan.sanghavi#xyz.com';
draft.Name = 'Darn Alarm';
createDatasource.createItem(function(createdRecord) { });
app.closeDialog();
});
}
In the above code I've added lines inside requestDs.relations.WorkflowStages.createItem call, this call is creating a stage, inside a stage I've added Predefined Approver by Creating New Approver Data source.
This code still can be changed for incorporating changes like User's Thumbnail and some other changes, but this will help you get going. Add/Change code as per need.

Answer to your question :
how can I associate a new stage to an another approver
Whenever you are clicking on + ADD STAGE button, you can add your predefined approvers in method createStage. You can do so by adding Approver in request.WorkflowStages.
Try doing this. If it does not work let me know. I will try to provide you some more code.

The code from Darpan do add 2 stages and 1 approver at each stage automatically, however if you can see from the screenshot below, both Stage1 and Stage2 is under Current approval status. Which mean Stage2 approver can approve first before the Stage1 approver approve it yet. This is not correct is it?

Related

JavaFX Observable where the value can be swapped

I am writing a small experimental desktop application. Basically it has the options to display a list of recipes and detailed information about a single recipe.
To accomplish that i implemented a class called RecipeContext that stores a ObservableList<Recipe> that i bind to a TableView, so the view gets automatically updated if i add to, or remove from the collection.
I want something similar for the single recipe, an Observable where i simply have to change the contained recipe and have the view automatically update to display the new recipe information.
To make this a bit more clear, i want something like this:
SingleObservable<Recipe> detailedRecipe = new SingleObservable<>(new Recipe("A"));
detailedInformationController.bindRecipeObservable(detailedRecipe);
// Recipe A is displayed
detailedRecipe.set(new Recipe("B"));
// View is notified about the change and displays Recipe B
Is there a class that does that?
SimpleObjectProperty will do what you want.
SimpleObjectProperty<Recipe> detailedRecipe = new SimpleObjectProperty<>(new Recipe("A"));
...
detailedRecipe.set(new Recipe("B"));
http://docs.oracle.com/javase/8/javafx/api/javafx/beans/property/SimpleObjectProperty.html

Call child form programmatically with parameter / filter

I'm creating a customization where on a click of a button, I need to allocate a charge for a particular purchase order / invoice journal.
From the front end, I would accomplish this by following the purchase order life-cycle and invoicing it. I would then go under the invoice tab of the PO, click Invoice Journals -> Charges -> Adjustment . This will open up my desired form where I will select a charges code, charges value, currency and category, and then I will click 'Ok' and have the system take care of the rest of the process.
Form name: MarkupAllocation_VendInvoiceTrans
Parent form Name: VendInvoiceJournal
You can see that the child form gets called with a few parameters such as the invoice number, there obviously needs to be that link. If I go into the AOT under forms, I right click and open up VendInvoiceJournal, but I wouldn't be able to open up MarkupAllocation_VendInvoiceTrans because it requires parameters.
Objective:
A: To open MarkupAllocation_VendInvoiceTrans through code where I manually pass those parameters to link to the parent table. I would provide the invoice number and such. The objective is to skip opening the parent table and manually going into the adjustments. I want to open that form directly and have it link to whichever record I specify.
B: I need to be able to pass a _ChargesValue parameter and have that be pre-populated for me. I don't know if this is possible, so I wanted to ask and confer. Ideally, I should be able to click a button on my custom form, and have MarkupAllocation_VendInvoiceTrans form directly open for a specified invoice, with pre-populated values on the line.
I know I should be tackling this problem one step at a time, so step A is priority number one.
I can open up the parent form with relative ease like so, but I cannot do the same for the child form. Obviously the same time of approach won't work, as I need to specify the relationship of the parent table before I open it.
private void allocateMarkup()
{
Object formRun;
Args args = new Args();
VendInvoiceJour jourTable;
;
select * from jourTable where jourTable.PurchId == 'PO000001191';
args.name(formstr(VendInvoiceJournal));
args.record(jourTable);
formRun = ClassFactory.formRunClass(args);
formRun.init();
formRun.run();
formRun.wait();
}
How would I be able to do so?
(Side note, I realize this whole form calling could be avoided if do all the transactions programmatically instead of letting the out of the box functionality handle it, but the markup and allocation logic is a beast of it's own and to me seems much more complicated than doing this. If someone has done it this manual way, any help on that would be greatly appreciated as well)
If I read your post right, you just want to open the Charges>Adjustment for a certain invoice. Here is one simple method:
MarkupAdjustment markupAdjustment = new MarkupAdjustment();
markupAdjustment.vendInvoiceJour(VendInvoiceJour::findFromPurchId('PO 120079'));
markupAdjustment.run();

Update Gridx with JsonStore, on event?

I am new to Web UI, Dojo, Java etc. If you are referring any advance topic please give some reading reference. It will help.
Context:
I have Gridx design using JsonStore, which takes a target + id for URL. With fixed "id" Grid loads well.
I have Dynamic Tree. It is working fine with lazy-loading etc.
Objective:
Based on click (or dblclick) event for a given node in Tree, I have to load Gridx with data. Hence, if tree node "id":7 is clicked, then JsonStore: /target/7 should be fetched and get loaded in Gridx.
Issues:
As you can guess, at start there is no valid "id" property to fill in JsonStore. In click event handler of tree, I will get this value, upon a user click. Hence, can't call gridx.startup() in "ready". Though I have "placed" the widget in "ready".
Hence, I have following snippet to handle,
<
// this function is called from tree event handler
function LatestTabGridxLoad( id ) {
console.log( "ID %s fetched.", id );
LatestTabGridxStore.idProperty = id;
LatestTabGridx.startup();
LatestTabGridx.body.refresh();
}
ready(function()
{
TabLatestAssetTree.startup();
LatestTabGridx.placeAt( "ReportMiddleTabLatestCenterContainer" );
}
Now, trouble is, at first time loading, JsonStore GET fired with /target/ alone, without any "id" without any user click. Hence, server responds with 405. Subsequently, when user clicks, again same HTTP GET without "id" results in HTTP 405. I am not able to somehow feed "id" to the GET URL. I have checked JSON, it is in perfect shape, as it works in standalone table, that is declarative(ly) defined.
Please suggest me ways to link a TREE node through its "id" to Gridx. Also suggest, if approach I am taking is right way to solve this problem.
Sorry, misunderstanding of the question. I thought it was about gridx tree but it is about regular gridx controlled by another widget.
The problem is in this line:
console.log( "ID %s fetched.", id );
LatestTabGridxStore.idProperty = id;
'idProperty' is the name of the JSON attribute which stores the ID. This will not change with each selection. If it is not set, JsonStore will assume it to be 'id'. What you intended to do was to modify the target property of the store to include the ID. This can done directly and will look something like the following (details are application specific)
LatestTabGridxStore.target = (someURL) + '/' + id;
After that, the content of gridx needs to be replaced using the new store setting. There are few ways to do it, the simplest being destroying the current gridx instance and replacing it with another one which uses the altered store.

How to add default custom action to dashlet's title bar in Alfresco Share

Since some days ago I'm struggling to find out the best way to add a custom action in every dashlet's title bar by default.
At this stage, I know the actions are set up as a widget in every dashlet's webscript. For example, in docsummary.get.js:
var dashletTitleBarActions = {
id : "DashletTitleBarActions",
name : "Alfresco.widget.DashletTitleBarActions",
useMessages : false,
options : {
actions: [
{
cssClass: "help",
bubbleOnClick:
{
message: msg.get("dashlet.help")
},
tooltip: msg.get("dashlet.help.tooltip")
}
]
}
};
model.widgets = [docSummary, dashletResizer, dashletTitleBarActions];
Then, the widget is instanciated in when the page is rendered.
I have figured out the next approaches:
Augment the "actions" array of every widget instance with the action I need in a custom JS snippet, every time the page is rendered. I have performed some tests without success using techniques like this: http://acidmartin.wordpress.com/2012/03/19/getting-instance-names-of-a-javascript-object/
Modify the prototype of Alfresco.widget.DashletTitleBarActions by adding my custom action. I believe it doesn't work either as the "actions" object is always overridden when the widget is instantiated, as you can see in the code above pasted.
Create an extension module for every dashlet's webscript which adds the custom action for every Alfresco.widget.DashletTitleBarActions widget definition, similarly as Dave Drapper explains in his post http://blogs.alfresco.com/wp/ddraper/2012/05/22/customizing-share-javascript-widget-instantiation-part-1/
Get every dashlet div container and add the action required directly manipulating the DOM once the page is ready. It should work but is something I consider slightly dirty and inconsistent, hence I would like to avoid it.
Could anyone imagine a better and feasible solution??
Let's start with adding an action to a single existing dashlet. As you suggest in (3) you can define an extensibility module to change the behaviour of the dashlet by intercepting and modifying its model.
Creating an extensibility module is well covered on the blog article and follow-up posts, but the trick here is to provide a controller JavaScript extension which locates the DashletTitleBarActions widget and adds your action to it, e.g.
if (model.widgets)
{
for (var i = 0; i < model.widgets.length; i++)
{
var widget = model.widgets[i];
if (widget.id == "DashletTitleBarActions")
{
widget.actions.push({...})
}
}
}
What you put in the object literal depends on how your action is implemented. If you require some client-side behaviour (rather than say, a static link) then you will also need to bind that in using a CustomEvent - see the RSS Feed dashlet org/alfresco/components/dashlets/rssfeed.get.html.ftl for an example.
The down-side of an extensibility module is that you will need to define an explicit extension JS file for each dashlet. You could easily put the code above in a central file and then include it in each dashlet extension where it is needed, e.g.
<import resource="classpath:alfresco/site-webscripts/org/myco/utils/dashlet.utils.js">
I would take the road number 2 here.
The definition of this widget is in share.js file ({share.context}/js/share.js). For Alfresco 4.2, DashletTitleBarActions is defined at around line 1700. In the onReady handler of the widget, there's a loop that processes the actions.
// Reverse the order of the arrays so that the first entry is furthest to the left...
this.options.actions.reverse();
// Iterate through the array of actions creating a node for each one...
for (var i = 0; i < this.options.actions.length; i++)
{
As you can see, it reverses the order of actions parameter, and then adds starts to loop the actions. So depending if you want your action to be the first or last, you could probably edit this file and add your custom action:
myAction = {
"cssClass": "customCSS"
, "tooltip": this.msg("slingshot.messages.generic.tooltip")
, "eventOnClick": ...
...
}
this.options.actions.push(myAction);
// now move on with the rest of the loop
for (...)
Of course, that requires overwriting shares' own js file, and not extending it. If you can't do that, you would then have to include a custom JS file on each page where share.js is also included and make sure it's executed after share.js but before any of the widgets are ready, and overwrite the onReady method of the widget itself so that it does this.

copy/paste of Page doesn't start workflow in same SG, starts workflow in other SG

I was perplexed by a user turning up this sort of behavior recently. Assume all SGs have workflow set.
Copy Page X in SG A, paste in same SG A. Page Y is created but no workflow is initiated.
Copy Page X in SG A, paste in other SG B. Page Z is created and workflow is initiated.
Now, the first scenario I confirmed with Nuno when he was on site. We agreed with business folks that we can live with that behavior. However, I'm curious why the second scenario happens as-is. If #1 doesn't kick off workflow, why should #2? Can anyone explain why? Is there any way I could programmatically cause scenario #1 to start the workflow (as the business would like that to happen)? or, worst case disable it from #2. It should be consistent.
But in practical scenario, I would really question the need for workflow to kick off as soon as you copy the page in the same structure group.
When you copy a page in the same structure group, it will create a page with title Copy of original page name and the CMS User will obviously wants/needs to edit the page with proper page name. As soon as they save the page workflow kicks in. Also, CMS user may want to change some component presentations on the page as well.
When you copy this on a different structure group, you are creating a page with a name in other structure group, so the workflow kicks in since it is not same copy in same structure group.
I would consider this as a nice feature :) :).
However as Nick suggested I would recommend submitting a support ticket as well.
If you want to consider a programmatic approach for your scenario # 1, you could implement the EventSystem to force the workflow kick off by simply saving the page again with same user who created it. You need to capture the CopyEvent .
Sample EventSystem stub if you want to go this route (Not Tested) :
private void Subscribe()
{
EventSystem.Subscribe<Page, CopyEventArgs>(PostCopyActivity, EventPhases.Processed);
}
private static void PostCopyActivity(Page page, CopyEventArgs args, EventPhases phase)
{
// do your logic to save page..
string sourceId = args.CopiedObject.OrganizationalItem.Id;
string destinationId = args.Destination.ToString();
if (sourceId.Equals(destinationId))
{
// copying to the same location .. so now get the User Session and Update the page to force workflow
}
}
Hope this information helps.
I went ahead and tried this on my system, and it does not start a workflow in any case (copy/pasting to same structure group or different structure group).
Do you have an event triggered on Paste?

Resources