Enable a Custom Action button depending on current user - javascript

In essense I am trying to run a Javascript synchronized in order to make a query on a Sharepoint task list item to determine whether the current user is the same as the task asigned user. If 'true' this should enable the custom action button. But so far only being able to run the code asynchronized makes it fail, as the queries('Task Assigned To' & 'Current User') are not completed before the code returns the comparison (boolean) of the two users :-( Help anyone??

If your custom action is ribbon button after computing the value for EnabledScript cache it to some variable and call RefreshCommandUI(). This will cause second call to EnabledScript and here you can return cached value. Moreover you need to write your own state machine for that computing to avoid using expired non valid value. Real hard may appear if you have 2+ of such custom actions.

Related

Clear the storage persistent flag for a website

I'm using the navigator.storage.persist() API on Chrome, and have managed to get it set to true. But I would like to (at least for testing) be able to clear the setting putting back to false.
The API definition does not include a method or flag to clear as far as I can tell. See https://developer.mozilla.org/en-US/docs/Web/API/StorageManager and the living standard: https://storage.spec.whatwg.org/#storagemanager
However, for my purposes it would also be acceptable if there was a way from 'Site settings', the clear cache options, or even a custom page like the chrome://appcache-internals/ page for appcache.
If not, where does the flag get stored? i.e. what would I need to delete in the file system to reset things?
I has not been able to find a way to clear the flag for a specific website.
But for testing purposes (as you requested), the flag returned by the StorageManager.persisted() can be cleared by:
navigate to chrome://settings/?search=cooki
click on Clear browsing data
select Cookies and other site data in the popup and click Clear
data button
After performing the above steps, StorageManager.persisted() starts to return false.

Need to prompt user for text during NetSuite Workflow

Business logic: when an Approver rejects an expense report, an e-mail must be sent to the creator. This e-mail must contain the reason for rejection.
Existing setup: A multi-state workflow has already been set up, that sends the expense report through two separate approvals. Each Approver can approve or reject the workflow. Rejecting the workflow sends it back to the submission state for correction by the creator. My task is to acquire the rejection text and create the outgoing e-mail.
Obvious solutions rejected:
Send Email workflow action-- this WF action allows only boilerplate e-mails to be sent (with some parameterization). Nothing can be customized from the user's perspective.
Workflow Action Script-- this script context does not allow the use of JavaScript dialog presentations, such as window.confirm() or window.prompt(). There are popup parallels in the workflow action palette, but only for confirm() or alert()-- no prompt(). Unfortunately the technical requirements and restrictions for Workflow Action scripts are horribly documented, so this result was learned only after spending a few days researching and writing the script.
Add a tracking field on the expense report that must be filled in before the report can be Rejected. However, this requires unlocking the record, an issue for Audit concerns. It also must be made visible and hidden for appropriate states, and can be adjacent to only one set of action buttons.
The new state is not an end-state, so e-mail generation is not automatic as it is for end-states. We just want similar functionality.
The only other possiblity I see is to target a new page, such as a Suitelet. However, I only need a single string from the user. A Suitelet seems overkill, plus it makes the workflow more complicated to go back to the correct report.
Any insight or ideas that anyone might have would be most helpful.
Well, I've tried several other solutions and none of these seem to work:
Redirect (via nlapiSetRedirectURL() in a WFA script) on state Exit trigger to a Suitelet that takes the parameters passed from the workflow where the user enters the rejection text; then redirect to the expense report. This fails because the report state does not actually change.
Do the same thing, but from the Entry trigger of the new state. Requires some more detailed parameter handling but this also does not work. Apparently redirecting from any part of the UI experience cancels the workflow transition.
Setting the "User Interface" context on the workflow action also does not work; the redirection still kills the transition.
The nlapiTriggerWorkflow() function also does not seem to have an effect, even when the UI context is set on the action. No errors or debug text generated.
The user may just have to accept manual behaviors like, adding a note and sending a canned e-mail. This appears to be a major feature hole, either deliberate or not. Note that there is a Confirm and a Show Message action, but no Prompt. So why not? No details, just deal with it I guess.
Final solution:
A separate workflow state where a script runs. A new button on the workflow redirects to this new state.
A workflow action script in these special state(s) that has parameter settings that are changed depending on where they are in the workflow. This script redirects to the suitelet (next), which interrupts the workflow transition and keeps the item in the same state.
A suitelet that takes the user text in a textarea, and a non-submit action button. Don't want to use a submit button, because that reloads the same page, creating an extra step.
A client script that takes parameters from the suitelet button event, creates the e-mail, and redirects back to the original record (that is in the same workflow state as before).
Of course this is inelegant. The user must press a button to create the e-mail, and a separate button to transition to the correct state. It fulfills the user's needs, but it requires them to remember to press one button to create the e-mail reason text, and another button to actually reject the record.
The need for this convoluted solution is because of all the roadblocks in NetSuite design:
Can't prompt for text from a server-side WF action script. We can confirm() giving a Y/N (Ok / Cancel) answer, but somehow string returns are not allowed.
Can't complete a transition if a WF action script redirects to another page.
Suitelet submit buttons reload the same page, so we need a client script to do the final e-mail creation work.
Feature hole much?
There is a limitation on the workflow that it cannot accept input from the user. Hence, we need to go ahead with a customized solution for this scenario. I have implemented this in multiple NetSuite projects. Here is the solution which works
(1) Have a workflow action script which would call the suitelet. Please see script sample below for workflow action script
define(['N/record','N/runtime','N/redirect'],
function (record,runtime,redirect){
function callSuitelet(context)
{
try {
var currentRecord = context.newRecord;
var vendorId = currentRecord.id;
var vendorNumber = currentRecord.getValue('entityid');
redirect.toSuitelet({
scriptId: 'customscript_call_rejection_reason',
deploymentId: 'customdeploy_call_rejection_reason',
parameters: {'recid':vendorId,'vbTransactionNo':vendorNumber, trantype: context.newRecord.type}
});
}
catch (err) {
log.error("Error while calling Suitelet", err);
throw err;
}
}
return {
onAction: callSuitelet
};
});
(2) Have a suiteLet designed to capture the "Rejection Reason"
(a) Add a field on the suitelet form labelled as "Rejection Reason" (FieldType.TEXTAREA)
(b) Add a "Submit" button which will add the data to the transaction record
(c) Redirect the suitelet back to the transaction record once "submit" button is clicked
This should solve the problem stated above

Microsoft Dynamics CRM run script (or something similar) in background

I wrote a script that adds the values of field A and field B. It then writes the result in field C.
My problem now is that, as I'm using a script, it only runs if I the form is open. However, I need to run it whenever field A or field B changes, regardless whether the form is open or not. For example, if a workflow changes the value of field A in the background, I need the script to calculate the new value of field C in the background as well.
I know that scripts only run on forms. That's why I'm looking for an alternative for scripts. I am aware that I could normally solve this by using a workflow, but I can't access field A through the workflow (it's a calculated field).
Are there any other possibilities?
I find that for custom calculations the following approach is effective and doesn't require much effort to implement and mantain:
Create an ACTION (let's call it new_action) without any steps
Create a PLUGIN which does the math you want, register it to the new_action message
Identify all the Simple fields involved
Create a workflow for each entity where you found the fields, set it to run on Update of the Simple fields you identified in that entity
The workflows should all be the same and the only step would be EXECUTE ACTION -> new_action
The end result is this behavior:
Simple field involved in your math changes -> Workflow starts -> Action starts -> Plugin does the math
NOTE: I usually make the action Unbound and "hand-craft" the data received by both the action and the plugin, but I glossed over this aspect because the approach itself stays the same.
Use CRM plugin. It works on server side so it will run whenever fields values will change (user interface, workflow, system process, CRM API call, etc.).
Detailed information: https://msdn.microsoft.com/en-us/library/gg328263.aspx
1.Create an update message plugin with filtering attribute as A and B,
This will fire only when your form is updated and also only when attribute A and B has changed on that form.
2.write your logic in your plugin
3. choose async and sync depending on how frequent the changes are on A and B, ideally, I will use sync so that my value of C gets updated and be in sync all the time w.r.t A and B.

Clean design for centralized navigation?

Context
Single page / ajax web app
Basic code structure
LocationManager (responsible for updating the browser hash and switching the application location to a different tile)
Page/Tile Flow
Basic Info > Household Info > Vehicle Info > Purchase Options > Review Order > Enter Payment and Submit
Problem
When the user navigates from Purchase Options to Review Order, a long (5-8 second) service call is made to calculate order details. Upon the call's resolution, the callback is designed to navigate the user to Review Order page. The issue is, if the user clicks back during that time and goes back to Household Info, as soon as the call resolves, they will be "automatically" brought to Review Order. Very awkward user experience.
Limitations
Canceling the call is not an option. Need a solution to handle the navigation.
Current Proposed Implementation
Save "currentLocation" prior to making the calculateOrder call.
Pass the "currentLocation" in the callback to the setLocation method as intendedStartingPoint.
Inside setLocation method if(intendedStartingPoint === Locationmanager.currentLocation) {//Navigate}
To sum it up, if the user changes the location while the call is in progress, upon the call's resolution, we won't navigate since the user doesn't expect to be navigated to Review Order at that point.
This works, right?
The Catch
We have many places in the app where setLocation is called within a callback for a long-running call. This means that I will have to update all the setLocation calls with a new parameter - intendedStartingPoint. While it makes sense to me, it does seem like it has potential to get a bit cluttered.
Any ideas on how to clean it up and centralize it?
So, right now a user can click the Calculate button on a Purchase Options page. You then display some kind of a loading indicator (hopefully)
and send an asynchronous request to a server with setLocation('ReviewOrder') attached in a continuation. There is quite a number of places in the application where you use this pattern.
The problem of unexpected (from a user point of view) redirects is there because with this approach server data retrieval and UI navigation are coupled. A solution that comes to mind is to decouple them and remove setLocation calls
from all long-running request continuations. It can work the following way.
When the user clicks the Calculate button, you start an asynchronous request and at the same time immediately navigate to the Review Order page (this is important from a UX perspective since users now clearly understand that the Calculate button navigates to Review Order). On the Review Order page, display a loading indicator saying something like 'please wait, about 10 seconds remaining...' When a request completes, hide the loading indicator and show the data.
This way your users will have a consistent UX knowing that whenever they click a button in your application the same thing happens (they navigate to a view), and there are no surprising automagical redirects.
Given that you can't prevent the user from navigating among the tiles, notifying her about the calculation delay won't solve the whole problem. You can tell the user the estimated time to completion, you can display a progress bar, and you can take her immediately to the Review Order tile to wait for the results, but if she navigates away from the tile, you're left with your original problem.
If the user chose to navigate away after all of that information, she must have made a conscious decision to interrupt the proceedings. It would be bad UX to transport her back to Review Order. What now?
You propose, quite reasonably, that the callback function sent with calculateOrder should pass an intendedStartingPoint parameter to setLocation. You worry that this would require you to modify every call to setLocation to accommodate the new parameter. Never fear, JavaScript offers a neat way to solve this dilemma.
You can add a new parameter to setLocation without modifying the existing calls. This merely requires that intendedStartingPoint be the last argument in setLocation's argument list. Then your new version of setLocation can check the value of intendedStartingPoint to see if it's undefined.
If intendedStartingPoint is undefined, you know that setLocation is receiving one of the old calls, the ones that don't pass intendedStartingPoint. In these cases you ignore intendedStartingPoint and proceed as before. Otherwise, you compare intendedStartingPoint to the current location and proceed according to the result of the comparison.
An even better approach would be to make the new parameter not intendedStartingPoint, but an object called options that contains intendedStartingPoint as one of its attributes. This allows you to pass further optional values to setLocation if the need arises in the future.
The new behavior of setLocation is quite simple. Before setting a new location, you check whether intendedStartingPoint is equal to the current location. If it is, you don't have to do anything because the user is already where she's intended to be. But if the intendedStartingPoint is different from the current location, the user has navigated away, so you do something like this:
if (LocationManager.currentLocation !== options.intendedStartingPoint) {
// Tell the user that the calculation has finished.
// Ask her if she wants to return to Review Order now.
}
First thing, calculate order details via asynchronous call and show/simulate a progress bar to the end-user via javascript.
The second thing: do not enforce ReviewOrder tile opening in your service callback function. As the service completes it's calculation, your callback function checks the current tile, and if it is not ReviewOrder tile, then it stores the calculated information in the Session or Local Storage.
As user navigates ReviewOrder tile, compare order details which came from the user with the stored order details (via hashing function, for example).
If hashcodes of user order details and stored order details are the same, then show saved order information, otherwise call the service again.
Important note: to prevent order forging, consider the following way:
Upon calculating order details on the server, generate unique order id, that will be returned to the user. Then store the calculated order details along with this id in the server database. If user did not change order details, your script will post only this order id to the server as a sign, that order has been accepted. Then read your database and process the order by this id.
If order was not completed then employ a scheduled task, that cleans up your database from non-completed orders (for example - orders, calculated 24 hours ago, but still not completed).
First of all, if the user is able to go back and change any entered information on previous pages, it is a must to invalidate any pending service calls. If a service call based upon outdated information returns, it must be discarded.
This means, if(intendedStartingPoint === Locationmanager.currentLocation) {//Navigate} is not sufficient. You have to do something like if(intendedStartingPoint === Locationmanager.currentLocation && /* no information altered in the meantime*/) {//Navigate}.
Now for your design question: It's a little hard to construct this without any concrete code, but you could do the following:
Provide means to register and manage long-running calls in LocationManager
Long-running calls should then always be registered with the LocationManager
LocationManager should assure that at most one long-running call is active at a moment
If any location change occurs, all (or the one active) long-running call must be invalidated
Call-back of a long-running call should check if it not has been invalidated, and only navigate if this is the case. LocationManager could do this in a unified manner for all call-backs.
New long-running calls could replace/invalidate an already running call or be rejected, as you like.
I hope this makes sense in your concrete situation.

pipwerks JS SCORM API wrapper sets completed module to incomplete on re-entry

Main thrust of question:
When a user re-enters a module which they had already completed and passed, the LMS reflects the course as not having been passed by unchecking the second checkbox in the course menu. It seems that the pipwerks SCORM API wrapper for JS cannot get a satisfactory status of the course from the LMS and it is automatically setting back the status to incomplete. How can I fix this?
Background and Environment
We've created an HTML/CSS/JS course which is packaged as a multi-SCO PIF. Each SCO within contains a series of slides and an assessment. When a user has gone through all the slides and taken the assessment, I set cmi.completion_status to completed. If they passed the assessment, I set cmi.success_status to passed and if not I set it to failed.
The below code snippet shows use of my custom SCORM session and assessment scoring code to do this:
if (scorm_session && scoring_info.isComplete()) {
scorm_session.setParam('cmi.score.min', 0);
scorm_session.setParam('cmi.score.max', 100);
scorm_session.setParam('cmi.score.raw', scoring_info.getScore());
scorm_session.setParam('cmi.success_status', scoring_info.isPass() ? 'passed' : 'failed');
scorm_session.setParam('cmi.completion_status', 'completed');
}
This works correctly--I see the AJAX request go out with the proper statuses in the data. Further, I see in the LMS course menu that the first checkbox for that module gets checked for completion, and if passed I see the second one get checked as well.
When the user leaves that module to go to another, those checkboxes stay checked correctly. If the user completely exits the course and later comes back, those boxes are still checked. If, however, the user re-enters a module he has passed by clicking the title in the course menu, the "passed" checkbox gets unchecked. So if user wants to go back and look at something in a module he has already completed and passed, he is given the visual indication that he is no longer considered to have passed the course.
It appears that this status change is being triggered by the pipwerks SCORM API wrapper for JS, but it's due to info it gets back from the LMS on module entry. I see in the console that, on entry, the API asks the LMS for the course status (the API wrapper runs scorm.status("get");). If it receives not attempted or unknown it calls scorm.status("set", "incomplete").
I see in the console that the request for status goes out and unknown is returned--even when I have explicitly set it to completed and passed. I then immediately see a transmission setting the status to incomplete as the code I described says it will.
This is happening both on http://cloud.scorm.com as well as a Moodle installation we have in place.
By spec, if a SCORM SCO is completed, then subsequently relaunched, the second launch is to be treated as a new attempt. This cannot be controlled by the course, this can only be overridden in the LMS's course handling and/or launch options. Not all LMSs provide this option, but I've seen it in Moodle (SCORM 1.2), and I imagine SCORM Cloud would provide the option, too.
Regarding the wrapper's handling of the completion status, the wrapper sets incomplete only if the course status is not attempted or unknown. If the LMS returns any other value, the wrapper will leave it alone.
If you feel the wrapper's completion status handling is causing a problem, you can disable it by setting this flag before initializing the course:
scorm.handleCompletionStatus = false;

Categories

Resources