SharePoint field required on task form - sharepoint-2013

I have a task form (OOB) where user needs to enter comments only when clicks on rejected button.
I wrote following script but this is firing even when clicked on approved button.
$('input[value=Rejected]').click(function() {
PreSaveAction();
});
});
function PreSaveAction()
{
var commentsBox = getFieldControl('ApprovalComments','Text');
var comments=$("textarea[title='ApprovalComments']").val();
if(comments.length==0)
{
var errorHtml='';
errorHtml = '<span class="ms-formvalidation"><span role="alert">Please enter Comments<br></span> </span>';
commentsBox.after(errorHtml);
return false;
}
return false;
}
function getFieldControl(fieldName,fieldType)
{
var control = $('[id^=' + fieldName + '_][id$=' + fieldType + 'Field]');
return control;
}
Thanks in advance

You can actually achieve it easily. Below is the jquery code sample:
function PreSaveAction()
{
var currentPage = decodeURIComponent(window.location.href.replace(/\+/g, " "));
if (currentPage.contains("Lists/WorkflowTasks/EditForm.aspx"))
{
var commentsTextArea=$("textarea[title='ApprovalComments']");
if(commentsTextArea.val().trim()=="")
{
var errorHtml='';
errorHtml = '<span class="ms-formvalidation"><span role="alert">Please enter Comments<br></span></span>';
commentsTextArea.after(errorHtml);
return false;
}
else
{
return true;
}
}else
{
return true;
}
}
String.prototype.contains = function (it) {
return this.indexOf(it) != -1;
};
Explanation:
PreSaveAction method will be triggered in all the New and Edit Forms in SP2010/SP2013.
Hence there is no need of Button onClick methods for client side validation on these forms.
In the above code, when you click of Save/Approved/Rejected buttons in your page, PreSaveAction will be fired.
We will be checking if current page is of Task Forms (WorkflowTasks is the name of the task List , please update this accordingly), else return true.
If the ApprovalComments is empty then error message will be shown and return false.
Else, return true.
Also notice the helper method String.prototype.contains.

Related

StateHasChanged() does not reload page

Issue:
As mentioned in Title, StateHasChanged does not re-render the page
Objective:
I want to Refresh the page when a button is clicked
Current Code
<button #onclick="CreatePlayer">Create User</button>
#functions {
string username;
[CascadingParameter]
Task<AuthenticationState> authenticationStateTask { get; set; }
async Task CreatePlayer()
{
var authState = await authenticationStateTask;
var user = authState.User;
var player = await PlayerData.GetByEmail(user.Identity.Name);
if (player == null)
{
player = new Player()
{
Email = user.Identity.Name,
UserName = username
};
await PlayerData.Create(player);
}
await Task.Delay(50);
StateHasChanged();
}
}
Just for the record, I add my comment in an answer :
StateHasChanged just inform the component that something changes in is state, that doesn't rerender it. The component choose by itself if it has to rerender or not. You can override ShouldRender to force the component to rerender on state changed.
#code {
bool _forceRerender;
async Task CreatePlayer()
{
var authState = await authenticationStateTask;
var user = authState.User;
var player = await PlayerData.GetByEmail(user.Identity.Name);
if (player == null)
{
player = new Player()
{
Email = user.Identity.Name,
UserName = username
};
await PlayerData.Create(player);
}
_forceRerender = true;
StateHasChanged();
}
protected override bool ShouldRender()
{
if (_forceRerender)
{
_forceRerender = false;
return true;
}
return base.ShouldRender();
}
}
On the one hand, you tell the compiler that she should create an event handler for the click event, named CreatePlayer: #onclick="CreatePlayer . This attribute compiler directive, behind the scenes, creates an EventCallback<Task> handler for you, the implication of which is that you do not need to use StateHasChanged in your code at all, as this method ( StateHasChanged ) is automatically called after UI events take place.
On the other hand, you tell the compiler that the type of the button should be set to "submit". This is wrong of course... You can't have it both. Setting the type attribute to "submit", normally submit form data to the server, but In Blazor it is prevented to work that way by code in the JavaScript portion of Blazor. Do you want to submit a form data to the server ? Always recall Blazor is an SPA Application. No submit ?
Your code should be:
<button #onclick="CreatePlayer" >Create User</button>
Just for the records, ordinarily you should inject the AuthenticationStateProvider object into your components, like this:
#inject AuthenticationStateProvider AuthenticationStateProvider
and then retrieve the AuthenticationState object. This is how your code may be rewritten:
var authState = await AuthenticationStateProvider.GetAuthenticationStateAsync();
var user = authState.User;

Changing workflow state in Sitecore

I'm having an issue changing the workflow state for an item programmatically. The state isn't being changed no matter what I do to the field. I've tried using (new SecurityDisabler()){} and putting the item in editing mode then changing the field manually. I've noticed that the item itself has the Lock set to <r />, could this be causing an issue?
Here is some sample code of what I've tried to do:
[HttpPost]
[MultipleButton(Name = "action", Argument = "Submit")]
public ActionResult Submit(LoI model)
{
if (model.Submitted || !model.Signed)
{
return Redirect("/Profile/LoI");
}
ModifyCandidateInfo(model, true);
Session["message"] = Translate.Text("loi-submitted-message");
Session["messageClass"] = "success";
return Redirect("/Profile/LoI");
}
private static void ModifyCandidateInfo(LoI model, bool isSubmission)
{
using (new SecurityDisabler())
{
var candidateFolder = CBUtility.GetCandidateFolder();
var loi= candidateFolder.GetChildren().SingleOrDefault(loi => loi.TemplateID == LoITemplateId);
if (loi == null) return;
loi.Editing.BeginEdit();
EditFields(loi, model);
EditChildren(loi, model);
//Send emails upon submission
if (isSubmission)
{
loi.ExecuteCommand("Submit",
loi.Name + " submitted for " + model.CandidateName);
using (new SecurityDisabler())
{
loi.Editing.BeginEdit();
loi.Fields["__Workflow state"].Value = "{F352B651-341B-4CCF-89FE-BD77F5E4D540}";
loi.Editing.EndEdit();
}
}
loi.Editing.EndEdit();
}
}
I initalized the item's workflow with the following function:
public static void InitializeWorkflow(Item item, ID workflowId)
{
item.Editing.BeginEdit();
var workflow =
item.Database.WorkflowProvider.GetWorkflow(workflowId.ToString());
workflow.Start(item);
item.Editing.EndEdit();
}
The item starts at the default drafting state and executed a "Submit" command that fires off emails. Through the Sitecore UI if I hit submit it'll go to the next workflow state but not programmatically when I fire off the ExecuteCommand function. Below you'll find the ExecuteCommand function.
public static WorkflowResult ExecuteCommand(this Item item, string commandName, string comment)
{
using (new SecurityDisabler())
{
var workflow = item.Database.WorkflowProvider.GetWorkflow(item);
if (workflow == null)
{
return new WorkflowResult(false, "No workflow assigned to item");
}
var command = workflow.GetCommands(item[FieldIDs.WorkflowState])
.FirstOrDefault(c => c.DisplayName == commandName);
return command == null
? new WorkflowResult(false, "Workflow command not found")
: workflow.Execute(command.CommandID, item, comment, false);
}
}
The command fires off fine and the emails are sent but I can't figure out why the state won't change. Could someone provide me with other suggestions or a solution?
Am I reading the workflow state id correctly? I'm using the item ID for the workflow state.
I think your code is really similar to my implementation. This is my code's background.
All items have the same workflow named "WF" and it has three workflow states (Working, Awaiting Approval, and Approved). One page-item having "WF" has some rendering items and those datasource items. Suppose a content editor is ready to submit and approve the item with its related items. By hitting the "Submit" and "Approval" button in the page, all page-item's related items have the same workflow state as the page-item's one.
Most code are from Marek Musielak and this code is perfectly working in my side.
public class UpdateWorkflowState
{
// List all controls in page item
public RenderingReference[] GetListOfSublayouts(string itemId, Item targetItem)
{
RenderingReference[] renderings = null;
if (Sitecore.Data.ID.IsID(itemId))
{
renderings = targetItem.Visualization.GetRenderings(Sitecore.Context.Device, true);
}
return renderings;
}
// Return all datasource defined on one item
public IEnumerable<string> GetDatasourceValue(WorkflowPipelineArgs args, Item targetItem)
{
List<string> uniqueDatasourceValues = new List<string>();
Sitecore.Layouts.RenderingReference[] renderings = GetListOfSublayouts(targetItem.ID.ToString(), targetItem);
LayoutField layoutField = new LayoutField(targetItem.Fields[Sitecore.FieldIDs.FinalLayoutField]);
LayoutDefinition layoutDefinition = LayoutDefinition.Parse(layoutField.Value);
DeviceDefinition deviceDefinition = layoutDefinition.GetDevice(Sitecore.Context.Device.ID.ToString());
foreach (var rendering in renderings)
{
if (!uniqueDatasourceValues.Contains(rendering.Settings.DataSource))
uniqueDatasourceValues.Add(rendering.Settings.DataSource);
}
return uniqueDatasourceValues;
}
// Check workflow state and update state
public WorkflowResult ChangeWorkflowState(Item item, ID workflowStateId)
{
using (new EditContext(item))
{
item[FieldIDs.WorkflowState] = workflowStateId.ToString();
}
Sitecore.Layouts.RenderingReference[] renderings = GetListOfSublayouts(item.ID.ToString(), item);
return new WorkflowResult(true, "OK", workflowStateId);
}
// Verify workflow state and update workflow state
public WorkflowResult ChangeWorkflowState(Item item, string workflowStateName)
{
IWorkflow workflow = item.Database.WorkflowProvider.GetWorkflow(item);
if (workflow == null)
{
return new WorkflowResult(false, "No workflow assigned to item");
}
WorkflowState newState = workflow.GetStates().FirstOrDefault(state => state.DisplayName == workflowStateName);
if (newState == null)
{
return new WorkflowResult(false, "Cannot find workflow state " + workflowStateName);
}
unlockItem(newState, item);
return ChangeWorkflowState(item, ID.Parse(newState.StateID));
}
// Unlock the item when it is on FinalState
public void unlockItem(WorkflowState newState, Item item)
{
if (newState.FinalState && item.Locking.IsLocked())
{
using (new EditContext(item, false, false))
{
item["__lock"] = "<r />";
}
}
}
}

Sitecore experience editor button triggering .aspx pop up page

Is it possible to make sitecore experience editor ribbon button trigger .aspx page in pop up window? It was possible before speak-ui was introduced by assigning a command to the button's click field.
There is a lot of tutorials describing how to use XML controls (e.g. http://jockstothecore.com/sitecore-8-ribbon-button-transfiguration/) but I can't find any information about triggering .aspx page.
My command looks like this:
<command name="item:showDashboard" type="Sitecore.Starterkit.customcode.Reports, MyProject" />
In the tutorial you posted I will just modify the code snippets to reflect what you need to do. (Considering you have done everything else). In the part Spell Two
In the command class you should do something like this (if you need to wait for postback):
public override void Execute(CommandContext context)
{
Assert.ArgumentNotNull((object) context, "context");
Context.ClientPage.Start((object) this, "Run", context.Parameters);
}
protected static void Run(ClientPipelineArgs args)
{
Assert.ArgumentNotNull((object) args, "args");
SheerResponse.ShowModalDialog(new UrlString("/YOURURL.aspx").ToString(), true);
args.WaitForPostBack();
}
If you just want to show something :
public override void Execute(CommandContext context)
{
Assert.ArgumentNotNull((object)context, "context");
if (context.Items.Length != 1)
return;
Item obj = context.Items[0];
UrlString urlString = new UrlString("/YOURURL.aspx");
urlString["fo"] = obj.ID.ToString();
urlString["la"] = obj.Language.ToString();
urlString["vs"] = obj.Version.ToString();
string str = "location=0,menubar=0,status=0,toolbar=0,resizable=1,getBestDialogSize:true";
SheerResponse.Eval("scForm.showModalDialog('" + (object)urlString + "', 'SitecoreWebEditEditor', '" + str + "');");
}
For the javascript:
define(["sitecore"], function (Sitecore) {
Sitecore.Commands.ScoreLanguageTools = {
canExecute: function (context) {
return true; // we will get back to this one
},
execute: function (context) {
var id = context.currentContext.itemId;
var lang = context.currentContext.language;
var ver = context.currentContext.version;
var path = "/YOURURL.aspx?id=" + id + "&lang=" + lang + "&ver=" + ver;
var features = "dialogHeight: 600px;dialogWidth: 500px;";
Sitecore.ExperienceEditor.Dialogs.showModalDialog(
path, '', features, null,
function (result) {
if (result) {
window.top.location.reload();
}
}
);
}
};
});

Simple boolean conditional from AJAX (ember.js)

I'm trying to do something which must be really simple to accomplish in Ember.
I want to show a button in my template based on the boolean state of a property:
{{#if canFavoriteTag}}
{{d-button action="favoriteTag" label="tagging.favorite" icon="star-o" class="admin-tag favorite-tag"}}
{{else}}
{{d-button action="unFavoriteTag" label="tagging.unfavorite" icon="star-o" class="admin-tag favorite-tag tag-unfavorite"}}
{{/if}}
I have created a property called canFavoriteTag with a function which I want to return true or false to the template based on whether the user can favorite the tag or not:
export default Ember.Controller.extend(BulkTopicSelection, {
canFavoriteTag: function() {
const self = this;
var ticker = this.get('tag.id');
console.log('checking can fav stock:' + ticker);
Discourse.ajax("/stock/get_users_favorite_stocks", {
type: "GET",
}).then(function(data) {
var favable = true;
for (var i = data.stock.length - 1; i >= 0; i--) {
var stock = jQuery.parseJSON(data.stock[i]);
if(ticker.toLowerCase() == stock.symbol.toLowerCase()) { console.log(ticker + ' is a favorite stock: ' + stock.symbol.toLowerCase()); favable = false; }
}
console.log(favable);
return favable;
});
}.property('canFavoriteTag') <-- unsure about this?
...
When the page loads, the wrong button shows (always the "false" one).. I see in the console that the favable variable gets set to false when the ajax call completes, but the button never changes. How do I get it to show the right button based on the function? Do I need to use a promise? If so, how?

How to add forreign key fields like it happens in django admin site?

Actually am trying to add a foreign key field in my form like how it happens in django admin site . When you click on the green " + " button it opens up a new pop up window where you add the respective field .
My models are like :
class DealType(models.Model):
label = models.CharField(max_length = 100)
def __unicode__(self):
return self.label
class Deal(models.Model):
label = models.ForeignKey(DealType, blank = True, null = True)
.
.
.
And i want to add DealType while i fill up my DealForm .
I think you have to create a seperate view to create a DealType.
In your DealForm you add a link to open that view.
...
I took a look at an admin page from a project of mine.
html
<img src="/static/admin/img/admin/icon_addlink.gif" width="10" height="10" alt="Add Another"/>
Javascript
taken from
<script type="text/javascript" src="/static/admin/js/admin/RelatedObjectLookups.js"> </script>
function showAddAnotherPopup(triggeringLink) {
var name = triggeringLink.id.replace(/^add_/, '');
name = id_to_windowname(name);
href = triggeringLink.href
if (href.indexOf('?') == -1) {
href += '?_popup=1';
} else {
href += '&_popup=1';
}
var win = window.open(href, name, 'height=500,width=800,resizable=yes,scrollbars=yes');
win.focus();
return false;
}
This opens a new window with the view with the add form.
This view should add the DealType and then close the window using the following function also found in the same javascript file
function dismissAddAnotherPopup(win, newId, newRepr) {
// newId and newRepr are expected to have previously been escaped by
// django.utils.html.escape.
newId = html_unescape(newId);
newRepr = html_unescape(newRepr);
var name = windowname_to_id(win.name);
var elem = document.getElementById(name);
if (elem) {
if (elem.nodeName == 'SELECT') {
var o = new Option(newRepr, newId);
elem.options[elem.options.length] = o;
o.selected = true;
} else if (elem.nodeName == 'INPUT') {
if (elem.className.indexOf('vManyToManyRawIdAdminField') != -1 && elem.value) {
elem.value += ',' + newId;
} else {
elem.value = newId;
}
}
} else {
var toId = name + "_to";
elem = document.getElementById(toId);
var o = new Option(newRepr, newId);
SelectBox.add_to_cache(toId, o);
SelectBox.redisplay(toId);
}
win.close();
}
This is just backtracked from the admin panel but it should get you started.
...
Found a guide that walks you through the process here which probably explains alot better. (havn't read it)