C# variable value only update once when called in Javascript - javascript

I've look an found only a similar post and didn't really answer my question or maybe they did but I didn't understand. The post I read was this one: Why does this javascript variable I'm creating via C# only get updated once?
I'm using a paged gridview and every time it's object data source runs the SelectCountMethod, I use the returning value on javascript. But, I've noticed that even thought that same returned value changes, on the server side. On javascript this value doesn't update.
My program is quite long but I'll use a basic example and hopefully some of you will understand it.
Client side:
function SomeFuntion()
{
alert("<%=num%>");
}
Server side:
//Global variable
Public Static int num = 0;
Public int SelectCountMethod()
{
var num = SomeMethod(); //Returns int
return num;
}
For example, on the server side num returns 60 and then this value updates to 7. On the server side num equals 7 but on the client side it's still 60. Is there a way to update the client sides value?
I apologies for my poor typing skills, English is not my native language.
Some examples might be helpful and thanks in advance.
I noticed that it doesn't mater where I update this variable(on selectCount method or any other method), on the client side doesn't update.

Taking a look at your client-side code, the "<%=num%>" is actually run on the server. If you examined the source in your browser, what you'll see is:
function SomeFuntion()
{
alert("60");
}
As you can see--there is no variable to update. In order to see that "60" change to "7", you'd have to refresh the client to pick up the new value that the server has for "num".

You could modify your JS method like this
var myMsg = <%=num%>;
function SomeFuntion()
{
alert(myMsg);
}
and in the codebehind
public int num = 60;
public int SelectCountMethod()
{
num = SomeMethod(); //Returns int
ScriptManager.RegisterStartupScript(this,
this.GetType(),
"Funct",
"myMsg = " + num + ";",
true);
return num;
}
So every time your method SelectCountMethod() is called, your JS variable myMsg get a new value because of the line ScriptManager.RegisterStartupScript

Without your server side code, we won't be able to find the ideal solution. However, you could potentially do the following:
Use a Hidden Field to store the value.
Use a global variable for the page, to pass the value to global variable in your JavaScript.
The easiest would be the Hidden Field, the reason is you can easily modify the data on both Client and Server without any real issues. You'll want to ensure that you do not modify the state to often.
<input type="hidden" id="hdTimer" runat="server" />
Then you can do your JavaScript, such as:
$('#Example').on('change', function () {
$('#hdTimer').val('60');
});
Now throughout the Client code you'll be able to modify the field with no issues, but when you need to submit a form, for a PostBack. You can use the field server side:
var content = hdTimer.value;
As I noted though, excessive cross manipulation may cause an issue at some point. Depending on the complexity. Your other approach would be the Global.
// Server Side
var example = 60;
protected void Page_Load(object sender, EventArgs e)
{
}
So this Global will hold the value, but when you reinitialize the value at Page Load it will hold be able to push an updated value to your JavaScript:
//Client Side:
var example = '<%= example %>';
The key though, will be to ensure you properly reinitialize the value.
When you do a PostBack your page is reinitialized, which can modify values on you if you aren't aware. This is incredibly important.

Use an HttpHandler and jQuery. Google is your friend, there are several examples on SO as well.

I found a possible solution, inefficient, but it will have to do. I have a search textbox that every time the search button is clicked, updates the grid view with the retrieved data from a data base. When the Onclick is called it binds the data source with the gridView. What I did was call the SelectCountMethod again right below the binding and used the same parameters I had stored on the Object data source as paramaters for the selectCountMethod. Then the amount returned by the selectCount I stored it on a hiddenField and that's it.
//Global variables
string _param1 = string.Empty,
_param2 = string.Empty;
//On click method for search btn
protected void OnSearch(object sender, EventArgs e)
{
gv.DataBind();
someHiddenField = SelectCountMethod(param1, param2);
}
protected void OnSelecting(object sender, ObjectDataSourceSelectingEventArgs e)
{
try
{
e.InputParameters["Param1"] = param1;
_param1 = param1
e.InputParameters["Param2"] = param2;
_param2 = param2;
}
catch (Exception ex)
{
cvServerError.IsValid = false;
cvServerError.ErrorMessage = ex.Message;
}
}

Related

struts2 optiontransferselect retrieve and display value from database

In the jsp page, I have a <s:optiontransferselect> for swap values between left side and right side and a submit button to save.
<s:optiontransferselect
allowUpDownOnLeft="false"
allowUpDownOnRight="false"
allowSelectAll="false"
allowAddAllToLeft="false"
allowAddAllToRight="false"
addToRightLabel="Go to right"
addToLeftLabel="Go to left"
leftTitle="Left side values"
headerKey="0"
name="option"
list= "optionList"
rightTitle="Right side values"
doubleHeaderKey="0"
doubleList="selectedOptionList"
doubleName="selectOption"
doubleId="selectedValues"
>
</s:optiontransferselect>
<s:submit />
I run the program, it actually can save the value from the right side. However it does not show the saved values there.
I am thinking about using javascript and use onchange event in <s:optiontransferselect> to achieve this
<script>
function show(){
const list = document.getElementById("selectedValues");
for (var i = 0; i < list.options.length; i++) {
//seems something not correct in this part but I am not sure how solve in this way
list.options[i].selected = true;
}
return true;
}
</script>
<s:optiontransferselect
allowUpDownOnLeft="false"
allowUpDownOnRight="false"
allowSelectAll="false"
allowAddAllToLeft="false"
allowAddAllToRight="false"
addToRightLabel="Go to right"
addToLeftLabel="Go to left"
leftTitle="Left side values"
headerKey="0"
name="option"
list= "optionList"
rightTitle="Right side values"
doubleHeaderKey="0"
doubleList="selectedOptionList"
doubleName="selectOption"
doubleId="selectedValues"
onchange ="show()" <!-- onchange seems not work here -->
>
</s:optiontransferselect>
When I run the program, the right side still cannot show the saved value.
More information about the optiontransferselect in case it is useful.
I have an action class for it.
public class OptionTransferSelectAction extends ActionSupport {
private List<String> optionList;
private List<String> selectedOptionList;
//private String option;
private List<String> option;
private List<String> selectOption;
public OptionTransferSelectAction (){
optionList=new ArrayList<String>();
//display on the left side for selection
optionList.add("Section A");
optionList.add("Section B");
optionList.add("Section C");
optionList.add("Section D");
optionList.add("Section E");
optionList.add("Section F");
optionList.add("Section G");
optionList.add("Section H");
selectedOptionList=new ArrayList<String>();
//display on the right side
//pretend it does not have any values in the first time (does not
//trigger the save yet)
}
public List<String> getOptionList() {
return optionList;
}
public void setOptionList(List<String> optionList) {
this.optionList = optionList;
}
public List<String> getSelectedOptionList() {
return selectedOptionList;
}
public void setSelectedOptionList(List<String> selectedOptionList) {
this.selectedOptionList = selectedOptionList;
}
/*
public String getOption() {
return option;
}
public void setOption(String option) {
this.option = option;
}
*/
public List<String> getOption() {
return option;
}
public void setOption(List<String> option) {
this.option = option;
}
public List<String> getSelectOption() {
return selectOption;
}
public void setSelectOption(List<String> selectOption) {
this.selectOption = selectOption;
}
}
update
I change option from String to List<String> with the proper getter and setter. I run the code, the optiontransferselect still cannot show the saved values.
Which part I did wrong? Would someone let me know please? Thank you.
update
I created a new jsp for example, success.jsp. If I selected some values to selectOption and click the submit button. the jsp file can display the values that I just submitted. I can see the saved values in the database. However the optiontransferselect still cannot show the saved values.
The success.jsp has one code which can display the value I have just submitted.
Selected Value(s) : <s:property value="selectOption"/>
In the database, I can see the saved values, so I would like to know how to let the optiontransferselect show the saved values?
another update
I try to use a button to call javascript function to show selectedValues before submit.
<script>
function testshow() {
var value = document.getElementById("selectedValues");
for(var i=0;i<value.options.length; i++) {
alert(value.options[i].innerHTML);
}
return true;
}
</script>
//button to call the function
<s:submit onclick="return testshow()"/>
I run the program, when I click the button, it can show the selected values before submit, so I still don't understand why the optiontransferselect cannot get saved selected values.
I think there are two things you need to know in order to solve your issue:
The lifetime of a Struts action.
How the optiontransferselect actually works.
Lifetime of a Struts Action
I start with that, because that might already solve your issue.
👉 A new instance of a Struts action is created for each request.
That means for you when connect the attributes list (left side) and doubleList (right side) to fields in a class derived from ActionSupport - like in the example OptionTransferSelectAction above - you must ensure, that you initialize it with meaningful values, I mean values that reflect the current state you want to show to the user. In the worst case you have to fetch the values from the database with each request.
Alternatively you can inject a bean into the action with a dedicated lifetime (e.g. session scoped). I might line that out in a separate post.
(BTW I would be thankful if someone else could provide further information, if the lifetime of a Struts action can be changed directly).
How optiontransferselect works
This tag wraps up two select tags (or more specifically updownselect tags). I will line out the function of it first, because it makes us better understand optiontransferselect. (Sidenote: Contrary to JSF, Struts clearly distinguishes between attributes for get and set operations.)
To connect the select tag with the underlying data, these are the three relevant attributes:
list - This is the list of all available values (=get).
value - This is the list of values initially selected (=get).
name - The name of the property (field) of the action where the selected values will be supplied on form submit (=set).
So the only property your action will recieve is the one connected with name. The select tag will NOT...
...post back the complete list of values (the value of list) to the action.
...change the values of list and value in the underlying Java class / action.
And the same is true for optiontransferselect. Which makes the semantics quite weird, because some changes (namely which values show up left and which show up right) will never be post back to the Struts action, you only can get hold of the selected values of both sides.
The major difference is that you have those three attributes now twice:
list --> list for the left and doubleList for the right side.
value --> value for the left and doubleValue for the right side.
name --> name for the left and doubleName for the right side.
The question now is how to come by that you will only get the selected values? I would suggest to automatically select them right before submission:
<s:submit value="Save" onclick="selectAllOptions(document.getElementById('idLeft'));selectAllOptions(document.getElementById('idRight'));"/>
Where idLeft corresponds to the value of id in the optiontransferselect and idRight corresponds to the value of doubleId. With that change you would get the complete list of left and right values in name and doubleName respectively.
The code is exactly the same as for the button you would get with allowSelectAll="true" and the JavaScript method selectAllOptions is provided by Struts via the optiontransferselect.js, regardless of allowSelectAll was true or false.

Custom ASP.Net Server Controls: How to call javascript function in parent page

I have built a number of custom server controls and routinely call javascript functions that are embedded into my controls. But I haven't been able to figure out how to create a property that the user of the control can add their own function to and their javascript function in the page containing the control will be called. I need to do something like OnClientClick for an asp:Button control.
Edit 1:
Thanks to the answer by #h2015 I have made progress on this.
In my control I create a public property for the user's script,
public string EditAddressScript
{
set
{
string scriptName = "EditAddressScript";
Type csType = this.GetType();
ClientScriptManager csm = Page.ClientScript;
string scriptText = string.Format("function EditAddress() {{{0}}}", value);
csm.RegisterClientScriptBlock(csType, scriptName, scriptText, true);
}
}
Then in the RenderContents section of my control I check whether a value has been specified for EditAddressScript and create a button if it has.
output.Write("<input id='btnEdit' type='button' value='Edit Address' style='font-size: 6pt;' onclick='EditAddress();' />");
The page that uses this control creates it dynamically so I do something like this,
ECFControls.DefendantAddressReview defendantAddress = new ECFControls.DefendantAddressReview();
divControls.Controls.Add(defendantAddress);
defendantAddress.ClientEditAddressScript = "alert('Hello, World');";
This works, but is far from optimal.
I would like to define the script in the aspx page and then just give my control the name of the script, rather than setting the value of EditAddressScript to the actual javascript code.
In the RenderContents section of the control I have the name of the script hardcoded I would like that to be dynamic.
Edit 2:
Okay, it looks like I was making this far more complicated than necessary. All that is needed is to create a script in the parent aspx page and set the event in the control to that script.
So this is what I have now
Add script to aspx page.
function EditAddress() {
alert("Hello, World");
}
Create a property in the control to hold the value (_EditAddressScript is a private string variable in the control)
public string EditAddressScript
{
set
{
_EditAddressScript = value;
}
}
Then in the RenderContents section of the control I check whether a value has been specified for EditAddressScript and create a button if it has.
if(_EditAddressScript.Trim() != "")
output.Write(string.Format("<input id='btnEdit' type='button' value='Edit' style='font-size: 6pt;' onclick='{0}' />", _EditAddressScript));
Create the control like this,
ECFControls.DefendantAddressReview defendantAddress = new ECFControls.DefendantAddressReview();
divControls.Controls.Add(defendantAddress);
defendantAddress.EditAddressScript = "EditAddress()";
If the control were created declaratively I could do this,
<ECF:DefendantAddressReview ID="defendantAddressReview" runat="server" EditAddressScript="EditAddress()" />
Is there a better approach?
Thanks.
this.Page.ClientScript.Register...
https://msdn.microsoft.com/en-us/library/ms178207.aspx
I explored two possible solutions in the edits to my original question. The second edit achieves the desired result. I wanted to have an optional property for a custom ASP.Net server control that would specify the name of a javascript function that the user of the control could place in the same page as the control. Here's how I did it.
Add a public string property to the server control that defines the name of your javascript function. (_CustomJavascript is a private string variable in the control)
public string CustomJavascript
{
set
{
_CustomJavascript = value;
}
}
Then in the RenderContents section of the control check whether a value has been specified for _CustomJavascript and add the code to call that javascript if it has been. In my case I was creating a composite control and wanted an edit button to display that would call the user's javascript function.
if(_CustomJavascript.Trim() != "")
output.Write(string.Format("<input id='btnEdit' type='button' value='Edit' style='font-size: 6pt;' onclick='{0}' />", _CustomJavascript));
All the user has to do is write their function.
function EditAddress() {
alert("Hello, World");
}
And give the name of their function to the CustomJavascript property when declaring the control.
<cc:AddressReview ID="addressReview" runat="server" CustomJavascript="EditAddress()" />

How to change value in array from asp.net to javascript

I am loading the value of string myvalue (global string) in array arr in javascript by
function hello()
{
alert("hi");
var arr=[<% myvalue %>];
alert(arr);
}
protected void ListBox1_SelectedIndexChanged(object sender, EventArgs e)
{
myvalue="1234";
Page.ClientScript.RegisterStartupScript(GetType(), "whatiskey", "hello();", true);
}
and updating myvalue on listbox1.item select and calling method which updates value of arr, but javascript arr does not load the new value
you have to put double quotes and write it like:
var arr=["<%=myvalue %>"];
or more better way :
var arr= new Array();
arr.push("<%=myvalue %>");
Register ListBox1 as partial post back element could also be one of the reason.
are you able debug, that ListBox1_SelectedIndexChanged is being called.
You need to something like below to stop this from multiple time registered, this can one of the reason not to call hello(). Use F12 to investigate the rendered HTML.
// Check to see if the client script is already registered.
if (!Page.ClientScript.IsClientScriptBlockRegistered(cstype, csname2))
{
Page.ClientScript.RegisterStartupScript(GetType(), "aNewKey", "hello();", true);
}

Invoking a JavaScript function from oncomplete handler of p:remoteCommand - simulating the same using some JavaScript code

Caution : Although this question covers long textual information with a mess of Java code snippets, it is merely targeted to JavaScript/jQuery and a bit of PrimeFaces stuff (just <p:remoteCommand>) as mentioned in the introductory part in the beginning.
I am receiving a JSON message from WebSockets (Java EE 7 / JSR 356 WebSocket API) as follows.
if (window.WebSocket) {
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event) {
jsonMsg=event.data;
var json = JSON.parse(jsonMsg);
var msg=json["jsonMessage"];
if (window[msg]) {
window[msg](); //It is literally interpreted as a function - updateModel();
}
};
}
In the above code, event.data contains a JSON string {"jsonMessage":"updateModel"}. Thus, msg will contain a string value which is updateModel.
In the following segment of code,
if (window[msg]) {
window[msg](); //It is literally interpreted as a JavaScript function - updateModel();
}
window[msg](); causes a JavaScript function associated with a <p:remoteCommand> to be invoked (which in turn invokes an actionListener="#{bean.remoteAction}" associated with the <p:remoteCommand>).
<p:remoteCommand name="updateModel"
actionListener="#{bean.remoteAction}"
oncomplete="notifyAll()"
process="#this"
update="#none"/>
update="#none" is not necessarily needed.
After receiving this message, I need to notify all the associated clients about this update. I use the following JavaScript function to do so which is associated with the oncomplete handler of the above <p:remoteCommand>.
var jsonMsg;
function notifyAll() {
if(jsonMsg) {
sendMessage(jsonMsg);
}
}
Notice that the variable jsonMsg is already assigned a value in the first snippet - it is a global variable. sendMessage() is another JavaScript function that actually sends a notification about this update to all the associated clients through WebSockets which is not needed in this question.
This works well but is there a way to do some magic in the following condition
if (window[msg]) {
window[msg]();
//Do something to call notifyAll() on oncomplete of remote command.
}
so that the notifyAll() function can be invoked through some JavaScript code directly (which is currently attached to oncomplete of <p:remoteCommand> and the expected JavaScript code (or even something else) should simulate this oncomplete) basically eliminating the need to depend upon a global JavaScript variable (jsonMSg)?
Edit : The problem I am trying to solve (it may be considered to be additional information).
When an admin for example, makes some changes (by means of DML operations) to a JPA entity named Category, entity listeners are triggered which in turn causes a CDI event to be raised as follows.
#ApplicationScoped
public class CategoryListener {
#PostPersist
#PostUpdate
#PostRemove
public void onChange(Category category) throws NamingException {
BeanManager beanManager = (BeanManager) InitialContext.doLookup("java:comp/BeanManager");
beanManager.fireEvent(new CategoryChangeEvent(category));
}
}
Needless to say that the entity Category is designated with the annotation #EntityListeners(CategoryListener.class).
Just one side note (completely off topic) : Getting an instance of BeanManager through a JNDI look-up as done in the preceding code snippet is temporary. The GlassFish Server 4.1 having the Weld version 2.2.2 final fails to inject the CDI event javax.enterprise.event.Event<T> which is supposed to be injected as follows.
#Inject
private Event<CategoryChangeEvent> event;
And then, the event can be fired as follows with reference to the relevant code snippet above.
event.fire(new CategoryChangeEvent(category));
This event is observed in the web project as follows.
#ApplicationScoped
public class RealTimeUpdate {
public void onCategoryChange(#Observes CategoryChangeEvent event) {
AdminPush.sendAll("updateModel");
}
}
Where an admin uses his own end-point as follows (AdminPush.sendAll("updateModel"); is invoked manually therein).
#ServerEndpoint(value = "/AdminPush", configurator = ServletAwareConfig.class)
public final class AdminPush {
private static final Set<Session> sessions = new LinkedHashSet<Session>();
#OnOpen
public void onOpen(Session session, EndpointConfig config) {
if (Boolean.valueOf((String) config.getUserProperties().get("isAdmin"))) {
sessions.add(session);
}
}
#OnClose
public void onClose(Session session) {
sessions.remove(session);
}
private static JsonObject createJsonMessage(String message) {
return JsonProvider.provider().createObjectBuilder().add("jsonMessage", message).build();
}
public static void sendAll(String text) {
synchronized (sessions) {
String message = createJsonMessage(text).toString();
for (Session session : sessions) {
if (session.isOpen()) {
session.getAsyncRemote().sendText(message);
}
}
}
}
}
Here only an admin is allowed to use this end-point. All other users are prevented from creating a WebSocket session using a conditional check in the onOpen() method.
session.getAsyncRemote().sendText(message); inside the foreach loop sends a notification (in the form of a JSON message) to the admin about these changes made in the entity Category.
As shown in the first code snippet, window[msg](); invokes an action method (through a <p:remoteCommand> as shown earlier) associated with an application scoped bean - actionListener="#{realTimeMenuManagedBean.remoteAction}".
#Named
#ApplicationScoped
public class RealTimeMenuManagedBean {
#Inject
private ParentMenuBeanLocal service;
private List<Category> category;
private final Map<Long, List<SubCategory>> categoryMap = new LinkedHashMap<Long, List<SubCategory>>();
// Other lists and maps as and when required for a dynamic CSS menu.
public RealTimeMenuManagedBean() {}
#PostConstruct
private void init() {
populate();
}
private void populate() {
categoryMap.clear();
category = service.getCategoryList();
for (Category c : category) {
Long catId = c.getCatId();
categoryMap.put(catId, service.getSubCategoryList(catId));
}
}
// This method is invoked through the above-mentioned <p:remoteCommand>.
public void remoteAction() {
populate();
}
// Necessary accessor methods only.
}
All other users/clients (who are on a different panel - other than the admin panel) should only be notified when actionListener="#{realTimeMenuManagedBean.remoteAction}" finishes in its entirely - must not happen before the action method finishes - should be notified through the oncomplate event handler of <p:remoteCommand>. This is the reason why two different end-points have been taken.
Those other users use their own end-point:
#ServerEndpoint("/Push")
public final class Push {
private static final Set<Session> sessions = new LinkedHashSet<Session>();
#OnOpen
public void onOpen(Session session) {
sessions.add(session);
}
#OnClose
public void onClose(Session session) {
sessions.remove(session);
}
#OnMessage
public void onMessage(String text) {
synchronized (sessions) {
for (Session session : sessions) {
if (session.isOpen()) {
session.getAsyncRemote().sendText(text);
}
}
}
}
}
The method annotated with #OnMessage comes to play, when a message is sent through oncomplete of <p:remoteCommand> as shown above.
Those clients use the following JavaScript code to just fetch the new values from the above-mentioned application scoped bean (the bean was already queried adequately by the admin from the database. Thus, there is no need to ridiculously query it again by each and every individual client separately (other than the admin). Hence, it is an application scoped bean).
if (window.WebSocket) {
var ws = new WebSocket("wss://localhost:8181/ContextPath/Push");
ws.onmessage = function (event) {
var json = JSON.parse(event.data);
var msg = json["jsonMessage"];
if (window[msg]) {
window[msg]();
}
};
$(window).on('beforeunload', function () {
ws.close();
});
}
In conjunction with the following <p:remoteCommand>.
<p:remoteCommand name="updateModel"
process="#this"
update="parentMenu"/>
Where parentMenu - the component to be updated by this <p:remoteCommand> is an id of a container JSF component <h:panelGroup> which contains a plain CSS menu with a bunch of <ui:repeat>s.
Hope this makes the scenario clearer.
Update :
This question has been answered precisely here based on <p:remoteCommand> (As to the concrete question, the sole question was to remove a dependency upon a global JavaScript variable as stated in the introductory part of this question).
I don't think I understood every aspect of your problem, but anyway I try to help a bit. Note that I do not know PrimeFaces, so all I did was reading the docs.
What I understand is, that you try to get rid of the global variable. But I am afraid, I do not think this is possible.
The problem here is, that PrimeFaces does not allow you to pass something transparently from your invocation of the remote call further to the oncomplete call (except you pass it to a Java code of the Bean and then back to the UI, and this usually is not what you want).
However, I hope, you can come very close to it.
Part 1, JS returns early
Please also note that there probably is some misconception about Java and JavaScript.
Java is multithreaded and runs several commands in parallel, while JavaScript is singlethreaded and usually never waits for something to complete. Doing things asychronously is mandatory to get a responsive Web-UI.
Hence your remoteCommand invocation (seen from the JS side) will (usually, async case) return long before the oncomplete handler will be invoked. That means, if window[msg]() returns, you are not finished with the remoteCommand yet.
So what you want to manage with following code
if (window[msg]) {
window[msg]();
//Do something to call notifyAll() on oncomplete of remote command.
dosomethinghere();
}
will fail. dosomethinghere() will not be invoked when the remoteCommand returned (as JS does not want to wait for some event, which might never happen). This means, dosomethinghere() will be invoked when the Ajax-request was just opened to the remote (to the Java application).
To run something after the Ajax call finished, this must be done in the oncomplete routine (or onsuccess). This is why it's there.
Part 2, validate msg
Please note something different about window[msg](). This can be considered a bit dangerous if you cannot trust the pushed message completely. window[msg]() essentially runs any function named with the contents of the variable msg. For example if msg happen to be close then window.close() will be run, which probably is not what you want.
You should make sure, msg is one expected word, and decline all other words. Example code for this:
var validmsg = { updateModel:1, rc:1 }
[..]
if (validmsg[msg] && window[msg])
window[msg]();
Part 3: How to handle multiple JSON messages in parallel
The global variable has some drawback. There is only one. If you happen to receive another JSON message on the WebSocket while the previous message still is processing in the remoteCommand, this will overwrite the previous message. So the notifyAll() will see the newer message twice, the old one is lost.
A classical race condition. What you must do is, to create something like a registry to register all the messages, and then pass some value to notifyAll() to tell, which of the registered messages shall be processed.
With only a little change, you can either parallely (here) or serially (Part 4) process the messages.
First, create a counter to be able to distinguish the messages. Also an object to store all the messages. And we declare all valid messages we expect (see Part 2):
var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }
Now add a message each time we receive one:
if (window.WebSocket) {
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event) {
var jsonMsg = event.data;
var json = JSON.parse(jsonMsg);
var msg=json["jsonMessage"];
if (validmsg[msg] && window[msg]) {
var nr = ++jsonMsgNr;
jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };
To be able to pass the nr to NotifyAll() an additional parameter needs to be passed to the Bean. Let's call it msgNr:
// Following might look a bit different on older PrimeFaces
window[msg]([{name:'msgNr', value:nr}]);
}
}
}
Perhaps have a look into https://stackoverflow.com/a/7221579/490291 for more on passing values this way.
The remoteAction bean now gets an additional parameter msgNr passed, which must be passed back via Ajax.
Unfortunately I have no idea (sorry) how this looks in Java. So make sure, your answer to the AjaxCall copies the msgNr out again.
Also, as the documentation is quiet about this subject, I am not sure how the parameters are passed back to the oncomplete handler. According to the JavaScript debugger, notifyAll() gets 3 parameters: xhdr, payload, and pfArgs. Unfortunately I was not able to setup a test case to find out how things look like.
Hence the function looks a bit like (bear with me, please):
function notifyAll(x, data, pfArgs) {
var nr = ???; // find out how to extract msgNr from data
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
jsonMessages[nr] = null; // free memory
sendMessage(jsonMsg);
dosomething(json);
}
If you split this into two functions, then you can invoke the notifyAll() from other parts in your application:
function notifyAll(x, data, unk) {
var nr = ???; // find out how to extract msgNr from data
realNotifyAll(nr);
}
function realNotifyAll(nr) {
if (!(nr in jsonMessages)) return;
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
delete jsonMessages[nr]; // free memory
sendMessage(jsonMsg);
dosomething(json);
}
Some things here are a bit redundant. For example you perhaps do not need the json element in jsonMessages or want to parse the json again to spare some memory in case the json is very big. However the code is meant not to be optimal but to be easy to adjust to your needs.
Part 4: serialize requests
Now to the changes to serialize things. That's quite easy by adding some semaphore. Semaphores in JavaScript are just variables. This is because there is only one global thread.
var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }
var jsonMsgNrLast = 0; // ADDED
if (window.WebSocket) {
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event) {
var jsonMsg = event.data;
var json = JSON.parse(jsonMsg);
var msg=json["jsonMessage"];
if (validmsg[msg] && window[msg]) {
var nr = ++jsonMsgNr;
jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };
if (!jsonMsgNrLast) { // ADDED
jsonMsgNrLast = nr; // ADDED
window[msg]([{name:'msgNr', value:nr}]);
}
}
}
}
function realNotifyAll(nr) {
if (!(nr in jsonMessages)) return;
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
delete jsonMessages[nr]; // free memory
sendMessage(jsonMsg);
dosomething(json);
// Following ADDED
nr++;
jsonMsgNrLast = 0;
if (nr in jsonMessages)
{
jsonMsgNrLast = nr;
window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
}
}
Note: jsonMsgNrLast could be just a flag (true/false). However having the current processed number in a variable perhaps can help somewhere else.
Having said that, there is a starvation problem in case something fails in sendMessage or dosomething. So perhaps you can interleave it a bit:
function realNotifyAll(nr) {
if (!(nr in jsonMessages)) return;
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
delete jsonMessages[nr]; // free memory
nr++;
jsonMsgNrLast = 0;
if (nr in jsonMessages)
{
jsonMsgNrLast = nr;
// Be sure you are async here!
window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
}
// Moved, but now must not rely on jsonMsgNrLast:
sendMessage(jsonMsg);
dosomething(json);
}
This way the AJAX request is already send out while sendMessage is running. If now dosomething has a JavaScript error or similar, the messages are still processed correctly.
Please note: All this was typed in without any tests. There might be syntax errors or worse. Sorry, I tried my best. If you find a bug, edit is your friend.
Part 5: Direct Invocation from JS
Now, with all this in place and a serialized Run, you can always invoke the previous notifyAll() using realNotifyAll(jsonMsgNrLast). Or you can display the jsonMessages in a list and choose any arbitrary number.
By skipping the call to window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]); (and above window[msg]([{name:'msgNr', value:nr}]);) you also can halt the Bean processing and run it on-demand using the usual JQuery callbacks. For this create a function and change the code a bit again:
var jsonMsgNr = 0;
var jsonMessages = {};
var validmsg = { updateModel:1 }
var jsonMsgNrLast = 0;
var autoRun = true; // ADDED, set false control through GUI
if (window.WebSocket) {
var ws = new WebSocket("wss://localhost:8181/ContextPath/AdminPush");
ws.onmessage = function (event) {
var jsonMsg = event.data;
var json = JSON.parse(jsonMsg);
if (validmsg[msg] && window[msg]) {
var nr = ++jsonMsgNr;
jsonMessages[nr] = { jsonMsg:jsonMsg, json:json };
updateGuiPushList(nr, 1);
if (autoRun && !jsonMsgNrLast) {
runRemote(nr);
}
}
}
}
function realNotifyAll(nr) {
if (!(nr in jsonMessages)) return;
var jsonMsg = jsonMessages[nr].jsonMsg;
var json = jsonMessages[nr].json;
delete jsonMessages[nr]; // free memory
updateGuiPushList(nr, 0);
jsonMsgNrLast = 0;
if (autoRun)
runRemote(nr+1);
// Moved, but now must not rely on jsonMsgNrLast:
sendMessage(jsonMsg);
dosomething(json);
}
function runRemote(nr) {
if (nr==jsonMsgNrLast) return;
if (nr in jsonMessages)
{
if (jsonMsgNrLast) { alert("Whoopsie! Please wait until processing finished"); return; }
jsonMsgNrLast = nr;
updateGuiPushList(nr, 2);
// Be sure you are async here!
window[jsonMessages[nr].json.msg]([{name:'msgNr', value:nr}]);
}
}
Now you can start the processing with runRemote(nr) and invoke the completion function with realNotifyAll(nr).
The function updateGuiPushList(nr, state) with state=0:finished 1=added 2=running is the callback to your GUI code which updates the on-screen list of waiting pushes to process. Set autoRun=false to stop automatic processing and autoRun=true for automatic processing.
Note: After setting autoRun from false to true you need to trigger runRemote once with the lowest nr, of course.

How Breeze's WebApi Controller works with UTC DateTime?

I'm having a problem in the settings of Breeze's JsonMediaTypeFormatter.
What I would do is that the date of json sent and received by WebAPI
always work in UTC.
According to this document, it would be possible by setting the property DateTimeZoneHandling to DateTimeZoneHandling.Utc for the JsonSerializerSettings
However that did not work.
Investigating this source code, I realized that what might be influencing this behavior was the hack that was done for this other issue.
 
By removing all this code bellow, everything works ok.
//jsonSerializerSettings.Converters.Add(new IsoDateTimeConverter
//{
// DateTimeFormat = "yyyy-MM-dd\\THH:mm:ss.fffK"
//});
How can I handle this situation without having to remove the Hack?
EDIT 1
My first attempt to set was as follows:
var jsonFormatter = Breeze.WebApi.JsonFormatter.Create();
jsonFormatter.SerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
jsonFormatter.SupportedEncodings.Add(new UTF8Encoding(false, true));
GlobalConfiguration.Configuration.Formatters.Insert(
0, jsonFormatter);
But this did not work, the returned date was not in UTC.
EDIT 2
First, I've updated the Breeze lib to 0.80.3 version.
In my App_Start folder I have this BreezeWebApiConfig.cs file:
[assembly: WebActivator.PreApplicationStartMethod(
typeof(Partner.App_Start.BreezeWebApiConfig), "RegisterBreezePreStart")]
namespace Partner.App_Start
{
public static class BreezeWebApiConfig
{
public static void RegisterBreezePreStart()
{
GlobalConfiguration.Configuration.Routes.MapHttpRoute(
name: "BreezeApi",
routeTemplate: "api/{controller}/{action}"
);
var jsonFormatter = Breeze.WebApi.JsonFormatter.Create();
jsonFormatter.SupportedMediaTypes.Add(new MediaTypeHeaderValue("application/json"));
jsonFormatter.SupportedEncodings.Add(new UTF8Encoding(false, true));
GlobalConfiguration.Configuration.Formatters.Insert(
0, jsonFormatter);
// Apply query parameters, expressed as OData URI query strings,
// to results of Web API controller methods that return IQueryable<T>
GlobalConfiguration.Configuration.Filters.Add(
new Breeze.WebApi.ODataActionFilter());
}
}
}
Second, I've created a CustomBreezeConfig.cs class (with the code described below by Jay) in a folder that I called BreezeConfig, but this new attempt did not work.
Regards,
Bernardo Pacheco
As of breeze v 0.80.3, we've added the capability to customize the json serializer settings that breeze uses for both queries and saves. It involves adding a server side class that is a subclass of the new Breeze.WebApi.BreezeConfig class. This subclass will look something like:
public class CustomBreezeConfig : Breeze.WebApi.BreezeConfig {
/// <summary>
/// Overriden to create a specialized JsonSerializer implementation that uses UTC date time zone handling.
/// </summary>
protected override JsonSerializerSettings CreateJsonSerializerSettings() {
var baseSettings = base.CreateJsonSerializerSettings();
baseSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
return baseSettings;
}
}
Any instance of a subclass of Breeze.WebApi.BreezeConfig that appears in the server side project will now be automatically discovered and used to customize breeze's configuration.
Please let us know if this helps ( or doesn't ).
Please try breeze v 0.80.5 along with the corresponding release notes. Hopefully, 'time's should now roundtrip properly.
When you say adding DateTimeZoneHandling didn't work, how did you try setting it?
You might try just adding this line immediately above the 'Converters.Add' call (from above) in the source (without removing the 'hack'), and let me know if it works.
jsonSerializerSettings.DateTimeZoneHandling = DateTimeZoneHandling.Utc;
I agree that it's still clumsy because it means that you have to modify the breeze source. So if it does work, we will try to come up with some way to allow you to set this from outside the formatter. Please let us know.
I solved the utc problem with this hack, which still smells.
in app.vm.run.js
app.vm.run = (function ($, ko, dataservice, router) {
var currentRunId = ko.observable(),
// run will be an entity
run = ko.observable(),
...
save = function () {
this.run().lastUpdate(makeDatetimeUTC(moment().toDate()));
this.run().runStart(makeDatetimeUTC(this.run().runStart()));
this.run().runEnd(makeDatetimeUTC(this.run().runEnd()));
dataservice.saveChanges();
// the test r === run() succeeds because the local run is a
// ko.observable which is bound to the run in the cache
var r = dataservice.getRunById(currentRunId());
},
...
})($, ko, app.dataservice, app.router);
in myScripts.js
// Here is a real pain in the neck.
// For some reason, when the entity is saved, it shows up on the server as UTC datetime
// instead of local. Moment parses everything as local by default, so the formatDate function
// used to get a display value needs to be converted to utc before it is returned to the server.
//
// This function takes the value of the dependentObservable in the entity
// and converts it to a string which can be stored back into the entity before sending
// it to the server.
//
// The reason I need to do that is so that it displays properly after the save.
// The date seems to be handled properly by the server.
var makeDatetimeUTC = function(localDatetime) {
var datestring = formatDate(localDatetime);
var utc = moment.utc(datestring);
return formatDate(utc);
};
var formatDate = function(dateToFormat) {
if (dateToFormat === null ||dateToFormat === undefined || dateToFormat.length === 0)
return "";
// intermediate variable is not needed, but is good for debugging
var formattedDate = moment(dateToFormat).format('MM/DD/YYYY hh:mm A');
return formattedDate;
},
formatObservableDate = function(observable) {
if (ko.isObservable(observable))
return observable(formatDate(observable()));
else
throw new Error("formatObservableDate expected a ko.Observable ");
};

Categories